OverviewFilesCommitsIssuesDocsReleasesPeers

kodi-jacktook commits / eb0475a

Find the id where the tmdb-only catalogs actually keep it (1.16.2)
1.16.0 claimed a title with only a tmdb id would now get subtitles. Playing one on the Pi, it did not -- the log said "No IMDb or TMDB ID found", and the payload said why: "ids": {"tmdb_id": "", "tvdb_id": "", "imdb_id": "", "original_id": "fxl:639988"} Three things at once. The keys are PRESENT and EMPTY, so a check for a missing key sails straight past them. The only real id is under `original_id`. And it wears a catalog prefix the endpoint does not understand. Which means the case that failed was precisely the case the change existed to fix: those catalogs are the ones with no imdb id. The feature worked everywhere it was not needed. A prefix is stripped only when what follows is a bare number -- `fxl:639988` and `tmdb:639988` are a tmdb id wearing a hat, `kitsu:anime-42` is not and must not be cut in half. `tt...` passes through untouched. The unit tests did not catch this and were not going to. They built `ids` the shape I believed it had. The payload that disproved it came from decoding a real row off the Pi, which is the only place that shape actually exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
eb0475a5202f2299f3443170cce746766f598995
by plankatron <fleeky@gmail.com> · 2026-09-05T01:17:53.000Z

Changed files (3) +107 -2

plugin.video.jacktook/addon.xml +1 -1
……@@ -1,5 +1,5 @@
11<?xml version="1.0" encoding="utf-8"?>
2 <addon id="plugin.video.jacktook" name="Jacktook" version="1.16.1" provider-name="Sam-Max">
2 <addon id="plugin.video.jacktook" name="Jacktook" version="1.16.2" provider-name="Sam-Max">
33 <requires>
44 <import addon="xbmc.python" version="3.00.0"/>
55 <import addon="script.module.requests" version="2.25.1"/>
plugin.video.jacktook/lib/clients/subtitle/submanager.py +38 -1
……@@ -32,6 +32,39 @@ class KodiJsonRpcClient:
3232 return response.get("result", {})
3333
3434
35
36def resolve_subtitle_ident(ids):
37 """The id to ask Wyzie for, out of whatever shape `ids` arrived in.
38
39 Measured on the Pi playing a flixlab-catalog title, the payload was:
40
41 {"tmdb_id": "", "tvdb_id": "", "imdb_id": "", "original_id": "fxl:639988"}
42
43 Three things in one: the keys are PRESENT and EMPTY (so a None check sails
44 past them), the only real id is under `original_id`, and it carries a
45 catalog prefix the endpoint does not understand. Those catalogs are exactly
46 the ones with no imdb id, i.e. the ones this was meant to rescue.
47
48 A prefix is stripped only when what follows is a bare number -- `fxl:639988`
49 and `tmdb:639988` are a tmdb id wearing a hat, while `kitsu:anime-42` is not
50 and must not be cut in half. `tt...` ids pass through untouched; the
51 endpoint takes those directly.
52 """
53 ids = ids or {}
54 for key in ("imdb_id", "tmdb_id"):
55 value = str(ids.get(key) or "").strip()
56 if value:
57 return value
58
59 original = str(ids.get("original_id") or "").strip()
60 if not original:
61 return None
62 prefix, sep, rest = original.partition(":")
63 if sep and rest.isdigit():
64 return rest
65 return original
66
67
3568class SubtitleManager(KodiJsonRpcClient):
3669 def __init__(self, data: Any, notification: Any):
3770 self.data = data
……@@ -88,7 +121,11 @@ class SubtitleManager(KodiJsonRpcClient):
88121 # them (verified 2026-09-04: id=1396 and id=tt0903747 return
89122 # byte-identical bodies), so a title from the tmdb-only catalogs is no
90123 # longer refused outright the way the imdb-keyed path had to refuse it.
91 ident = imdb_id or ids.get("tmdb_id")
124 ident = imdb_id or resolve_subtitle_ident(ids)
125
126 # "" is what these keys actually hold when unset, so normalise before
127 # anything downstream treats an empty string as an id.
128 imdb_id = imdb_id or (str(ids.get("imdb_id") or "").strip() or None)
92129
93130 if not ident:
94131 kodilog("No IMDb or TMDB ID found for the current video, skipping subtitles")
plugin.video.jacktook/tests/unit/test_wyzie_subtitles.py +68 -0
……@@ -336,3 +336,71 @@ def test_the_player_runs_subtitles_when_only_wyzie_is_enabled():
336336 assert body is not None, "handle_subtitles not found in player.py"
337337 assert "wyzie_subtitle_enabled" in body.group(0), \
338338 "handle_subtitles never checks the wyzie setting"
339
340
341class TestIdentFromOriginalId:
342 """The catalogs that most need this carry no tmdb_id or imdb_id at all.
343
344 Measured on the Pi, playing No Other Choice from the flixlab catalog, the
345 playback payload was:
346
347 "ids": {"tmdb_id": "", "tvdb_id": "", "imdb_id": "",
348 "original_id": "fxl:639988"}
349
350 -- empty strings, not missing keys, with the only real id under
351 `original_id` behind a catalog prefix. So the fetch bailed with "no_imdb"
352 for exactly the titles the tmdb-or-imdb endpoint was supposed to rescue.
353 """
354
355 def test_an_fxl_original_id_yields_the_bare_tmdb_id(self, tmp_path):
356 m = _manager({"title": "No Other Choice", "mode": "movies",
357 "ids": {"tmdb_id": "", "imdb_id": "",
358 "original_id": "fxl:639988"}})
359 m.wyzie_client.get_subtitles = MagicMock(return_value=[])
360 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
361 m.fetch_subtitles(folder_path=str(tmp_path))
362 assert m.wyzie_client.get_subtitles.call_args[0][1] == "639988"
363
364 def test_a_tmdb_prefixed_original_id_is_also_stripped(self, tmp_path):
365 m = _manager({"title": "X", "mode": "movies",
366 "ids": {"original_id": "tmdb:1396"}})
367 m.wyzie_client.get_subtitles = MagicMock(return_value=[])
368 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
369 m.fetch_subtitles(folder_path=str(tmp_path))
370 assert m.wyzie_client.get_subtitles.call_args[0][1] == "1396"
371
372 def test_an_imdb_original_id_is_left_intact(self, tmp_path):
373 """tt-ids are what the endpoint wants; stripping is only for prefixes."""
374 m = _manager({"title": "X", "mode": "movies",
375 "ids": {"original_id": "tt0903747"}})
376 m.wyzie_client.get_subtitles = MagicMock(return_value=[])
377 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
378 m.fetch_subtitles(folder_path=str(tmp_path))
379 assert m.wyzie_client.get_subtitles.call_args[0][1] == "tt0903747"
380
381 def test_a_real_imdb_id_still_wins_over_original_id(self, tmp_path):
382 m = _manager({"title": "X", "mode": "movies",
383 "ids": {"imdb_id": "tt31967670", "original_id": "fxl:639988"}})
384 m.wyzie_client.get_subtitles = MagicMock(return_value=[])
385 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
386 m.fetch_subtitles(folder_path=str(tmp_path))
387 assert m.wyzie_client.get_subtitles.call_args[0][1] == "tt31967670"
388
389 def test_empty_strings_are_treated_as_absent_not_as_ids(self, tmp_path):
390 """The keys are PRESENT and empty, so `.get(...)` returns "" and any
391 check that only tests for None would sail straight past it."""
392 m = _manager({"title": "X", "mode": "movies",
393 "ids": {"tmdb_id": "", "imdb_id": "", "original_id": ""}})
394 m.wyzie_client.get_subtitles = MagicMock()
395 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
396 assert m.fetch_subtitles(folder_path=str(tmp_path)) is None
397 assert m.last_fetch_status == "no_imdb"
398 m.wyzie_client.get_subtitles.assert_not_called()
399
400 def test_a_prefixed_non_numeric_id_is_not_mangled(self, tmp_path):
401 m = _manager({"title": "X", "mode": "movies",
402 "ids": {"original_id": "kitsu:anime-42"}})
403 m.wyzie_client.get_subtitles = MagicMock(return_value=[])
404 with patch("lib.clients.subtitle.submanager.get_setting", _settings()):
405 m.fetch_subtitles(folder_path=str(tmp_path))
406 assert m.wyzie_client.get_subtitles.call_args[0][1] == "kitsu:anime-42"