video wishlist processor: log WHY nothing grabbed (no results vs quality reject)

'no acceptable release' was ambiguous — couldn't tell if slskd returned nothing
or returned hits the quality profile rejected. now each item logs 'No search
results for X' vs 'N result(s) for X, none accepted — <reason>' (reason from the
top hit), and the summary tallies '… · N had no results, M rejected on quality'.
makes the slskd-finds-nothing-for-video situation legible.
This commit is contained in:
BoulderBadgeDad 2026-06-26 08:44:40 -07:00
parent fc045cd74e
commit 6531a19c7a
2 changed files with 40 additions and 5 deletions

View file

@ -213,6 +213,8 @@ def auto_video_process_wishlist(
grabbed = [0]
searched = [0]
noresults = [0] # search came back empty (the source had nothing)
rejected = [0] # source had hits, but none passed the quality profile
total = len(todo)
lock = threading.Lock()
@ -224,25 +226,45 @@ def auto_video_process_wishlist(
if media_type == 'episode':
name = "%s S%02dE%02d" % (name, int(it.get('season_number') or 0),
int(it.get('episode_number') or 0))
# distinguish "source returned nothing" from "returned hits but all rejected"
# (the rejected case carries the reason on the top-ranked hit) so the log is useful.
if ok:
msg, lt = "Grabbed '%s'" % name, 'success'
elif not cands:
msg, lt = "No search results for '%s'" % name, 'info'
else:
why = (cands[0].get('rejected') or 'none met your quality profile')
msg, lt = "%d result(s) for '%s', none accepted — %s" % (len(cands), name, why), 'info'
with lock:
searched[0] += 1
if ok:
grabbed[0] += 1
elif not cands:
noresults[0] += 1
else:
rejected[0] += 1
deps.update_progress(
automation_id, phase='Searching + grabbing…',
progress=10 + int(85 * searched[0] / max(total, 1)),
log_line=("Grabbed '%s'" % name) if ok else ("No acceptable release for '%s'" % name),
log_type='success' if ok else 'info')
log_line=msg, log_type=lt)
with ThreadPoolExecutor(max_workers=concurrency) as ex:
list(ex.map(_one, todo))
done = ('Grabbed %d %s(s) of %d searched' % (grabbed[0], label, searched[0])) if grabbed[0] \
else ('Searched %d %s(s) — no acceptable releases found' % (searched[0], label))
# Headline with the WHY breakdown: it's the difference between "the source has
# nothing" (noresults) and "it has stuff but your quality profile rejects it" (rejected).
tail = []
if noresults[0]:
tail.append('%d had no results' % noresults[0])
if rejected[0]:
tail.append('%d rejected on quality' % rejected[0])
breakdown = (' · ' + ', '.join(tail)) if tail else ''
done = ('Grabbed %d %s(s) of %d searched%s' % (grabbed[0], label, searched[0], breakdown)) if grabbed[0] \
else ('Searched %d %s(s), grabbed 0%s' % (searched[0], label, breakdown))
deps.update_progress(automation_id, status='finished', progress=100, phase='Complete',
log_line=done, log_type='success' if grabbed[0] else 'info')
return {'status': 'completed', 'searched': searched[0], 'grabbed': grabbed[0],
'_manages_own_progress': True}
'noresults': noresults[0], 'rejected': rejected[0], '_manages_own_progress': True}
except Exception as e: # noqa: BLE001
deps.update_progress(automation_id, status='error', phase='Error', log_line=str(e), log_type='error')
return {'status': 'error', 'error': str(e), '_manages_own_progress': True}

View file

@ -123,6 +123,19 @@ def test_no_acceptable_release_grabs_nothing():
assert res["searched"] == 1 and res["grabbed"] == 0 and enq == []
def test_breakdown_distinguishes_no_results_from_quality_rejection():
# the diagnostic: "the source had nothing" vs "had hits but quality rejected them"
items = [{"tmdb_id": 1, "title": "A"}, {"tmdb_id": 2, "title": "B"}]
res, enq, _, deps = _run(items, searches={
("movie", "1"): [], # source had nothing
("movie", "2"): [dict(_cand("junk", accepted=False), rejected="Unknown / unsupported quality")],
})
assert res["grabbed"] == 0 and res["noresults"] == 1 and res["rejected"] == 1
logs = " ".join(p.get("log_line") or "" for p in deps.progress)
assert "No search results for 'A'" in logs
assert "none accepted — Unknown / unsupported quality" in logs # reason surfaced
def test_missing_library_folder_is_a_quiet_skip():
res, enq, _, deps = _run([{"tmdb_id": 1, "title": "A"}], root="")
assert res["status"] == "completed" and res.get("skipped") == "no_folder"