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:
parent
fc045cd74e
commit
6531a19c7a
2 changed files with 40 additions and 5 deletions
|
|
@ -213,6 +213,8 @@ def auto_video_process_wishlist(
|
||||||
|
|
||||||
grabbed = [0]
|
grabbed = [0]
|
||||||
searched = [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)
|
total = len(todo)
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
|
@ -224,25 +226,45 @@ def auto_video_process_wishlist(
|
||||||
if media_type == 'episode':
|
if media_type == 'episode':
|
||||||
name = "%s S%02dE%02d" % (name, int(it.get('season_number') or 0),
|
name = "%s S%02dE%02d" % (name, int(it.get('season_number') or 0),
|
||||||
int(it.get('episode_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:
|
with lock:
|
||||||
searched[0] += 1
|
searched[0] += 1
|
||||||
if ok:
|
if ok:
|
||||||
grabbed[0] += 1
|
grabbed[0] += 1
|
||||||
|
elif not cands:
|
||||||
|
noresults[0] += 1
|
||||||
|
else:
|
||||||
|
rejected[0] += 1
|
||||||
deps.update_progress(
|
deps.update_progress(
|
||||||
automation_id, phase='Searching + grabbing…',
|
automation_id, phase='Searching + grabbing…',
|
||||||
progress=10 + int(85 * searched[0] / max(total, 1)),
|
progress=10 + int(85 * searched[0] / max(total, 1)),
|
||||||
log_line=("Grabbed '%s'" % name) if ok else ("No acceptable release for '%s'" % name),
|
log_line=msg, log_type=lt)
|
||||||
log_type='success' if ok else 'info')
|
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=concurrency) as ex:
|
with ThreadPoolExecutor(max_workers=concurrency) as ex:
|
||||||
list(ex.map(_one, todo))
|
list(ex.map(_one, todo))
|
||||||
|
|
||||||
done = ('Grabbed %d %s(s) of %d searched' % (grabbed[0], label, searched[0])) if grabbed[0] \
|
# Headline with the WHY breakdown: it's the difference between "the source has
|
||||||
else ('Searched %d %s(s) — no acceptable releases found' % (searched[0], label))
|
# 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',
|
deps.update_progress(automation_id, status='finished', progress=100, phase='Complete',
|
||||||
log_line=done, log_type='success' if grabbed[0] else 'info')
|
log_line=done, log_type='success' if grabbed[0] else 'info')
|
||||||
return {'status': 'completed', 'searched': searched[0], 'grabbed': grabbed[0],
|
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
|
except Exception as e: # noqa: BLE001
|
||||||
deps.update_progress(automation_id, status='error', phase='Error', log_line=str(e), log_type='error')
|
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}
|
return {'status': 'error', 'error': str(e), '_manages_own_progress': True}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,19 @@ def test_no_acceptable_release_grabs_nothing():
|
||||||
assert res["searched"] == 1 and res["grabbed"] == 0 and enq == []
|
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():
|
def test_missing_library_folder_is_a_quiet_skip():
|
||||||
res, enq, _, deps = _run([{"tmdb_id": 1, "title": "A"}], root="")
|
res, enq, _, deps = _run([{"tmdb_id": 1, "title": "A"}], root="")
|
||||||
assert res["status"] == "completed" and res.get("skipped") == "no_folder"
|
assert res["status"] == "completed" and res.get("skipped") == "no_folder"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue