Hermes Agent commited on
Commit
6231a0a
·
1 Parent(s): 4a485e9

fix(streamer): resolve Jellyfin items moved by TMDB organuize

Browse files

refresh_jellyfin_item_by_path now polls with a longer window (12x5s),
matches the pre-move path as an alias, falls back to .strm basename
matching, and nudges a library scan every few attempts so items that
were moved to their language folder get indexed before refresh.

Files changed (1) hide show
  1. apex/tg_streamer.py +49 -7
apex/tg_streamer.py CHANGED
@@ -1184,7 +1184,12 @@ async def _enrich_media_metadata(entry):
1184
  logger.info(f"[AI-FALLBACK] Published canonical title '{ai_title}' (no TMDB match)")
1185
  try:
1186
  if entry.get("strm_path"):
1187
- asyncio.create_task(refresh_jellyfin_item_by_path(entry.get("strm_path")))
 
 
 
 
 
1188
  else:
1189
  asyncio.create_task(request_jellyfin_scan())
1190
  except Exception as scan_exc:
@@ -1226,6 +1231,9 @@ async def _enrich_media_metadata(entry):
1226
  os.makedirs(new_target_dir, exist_ok=True)
1227
  os.replace(old_strm_path, new_strm_path)
1228
  entry["strm_path"] = new_strm_path
 
 
 
1229
  logger.info(
1230
  f"[ORGANIZE] Moved '{entry.get('title')}' to {tmdb_language} folder"
1231
  )
@@ -1315,7 +1323,12 @@ async def _enrich_media_metadata(entry):
1315
  # enrichment never waits on Jellyfin.
1316
  try:
1317
  if entry.get("strm_path"):
1318
- asyncio.create_task(refresh_jellyfin_item_by_path(entry.get("strm_path")))
 
 
 
 
 
1319
  else:
1320
  asyncio.create_task(request_jellyfin_scan())
1321
  except Exception as scan_exc:
@@ -1682,13 +1695,19 @@ async def trigger_jellyfin_scan():
1682
  logger.warning(f"Could not trigger Jellyfin library refresh after retries: {e}")
1683
 
1684
 
1685
- async def refresh_jellyfin_item_by_path(strm_path, retries=8, delay=4.0):
1686
  """Locate the Jellyfin item for a given `.strm` path and trigger a full
1687
  metadata refresh so the NFO/poster written by TMDB enrichment are re-read.
1688
 
1689
  A plain /Library/Refresh does NOT re-identify an already-indexed item (its
1690
  name is stable), so Jellyfin keeps the provisional folder-based identity.
1691
  This forces an item-level FullRefresh which consumes the local .nfo.
 
 
 
 
 
 
1692
  """
1693
  if not strm_path:
1694
  return False
@@ -1696,7 +1715,11 @@ async def refresh_jellyfin_item_by_path(strm_path, retries=8, delay=4.0):
1696
  if not headers:
1697
  return False
1698
  api_key = _read_jellyfin_api_key()
1699
- norm = os.path.normpath(strm_path)
 
 
 
 
1700
  for attempt in range(retries):
1701
  try:
1702
  connector = aiohttp.TCPConnector(family=socket.AF_INET)
@@ -1718,13 +1741,32 @@ async def refresh_jellyfin_item_by_path(strm_path, retries=8, delay=4.0):
1718
  if response.status != 200:
1719
  continue
1720
  payload = await response.json()
 
1721
  item_id = next(
1722
- (it.get("Id") for it in (payload.get("Items") or [])
1723
- if os.path.normpath(str(it.get("Path") or "")) == norm),
 
 
1724
  None,
1725
  )
1726
  if not item_id:
1727
- # Item may not have been indexed by the ingest scan yet.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1728
  await asyncio.sleep(delay)
1729
  continue
1730
  refresh_params = {
 
1184
  logger.info(f"[AI-FALLBACK] Published canonical title '{ai_title}' (no TMDB match)")
1185
  try:
1186
  if entry.get("strm_path"):
1187
+ asyncio.create_task(
1188
+ refresh_jellyfin_item_by_path(
1189
+ entry.get("strm_path"),
1190
+ alt_paths=([entry["_prev_strm_path"]] if entry.get("_prev_strm_path") else []),
1191
+ )
1192
+ )
1193
  else:
1194
  asyncio.create_task(request_jellyfin_scan())
1195
  except Exception as scan_exc:
 
1231
  os.makedirs(new_target_dir, exist_ok=True)
1232
  os.replace(old_strm_path, new_strm_path)
1233
  entry["strm_path"] = new_strm_path
1234
+ # Remember the pre-move path so the post-enrichment item refresh
1235
+ # can still locate the Jellyfin item while it is re-indexed.
1236
+ entry["_prev_strm_path"] = old_strm_path
1237
  logger.info(
1238
  f"[ORGANIZE] Moved '{entry.get('title')}' to {tmdb_language} folder"
1239
  )
 
1323
  # enrichment never waits on Jellyfin.
1324
  try:
1325
  if entry.get("strm_path"):
1326
+ asyncio.create_task(
1327
+ refresh_jellyfin_item_by_path(
1328
+ entry.get("strm_path"),
1329
+ alt_paths=([entry["_prev_strm_path"]] if entry.get("_prev_strm_path") else []),
1330
+ )
1331
+ )
1332
  else:
1333
  asyncio.create_task(request_jellyfin_scan())
1334
  except Exception as scan_exc:
 
1695
  logger.warning(f"Could not trigger Jellyfin library refresh after retries: {e}")
1696
 
1697
 
1698
+ async def refresh_jellyfin_item_by_path(strm_path, alt_paths=None, retries=12, delay=5.0):
1699
  """Locate the Jellyfin item for a given `.strm` path and trigger a full
1700
  metadata refresh so the NFO/poster written by TMDB enrichment are re-read.
1701
 
1702
  A plain /Library/Refresh does NOT re-identify an already-indexed item (its
1703
  name is stable), so Jellyfin keeps the provisional folder-based identity.
1704
  This forces an item-level FullRefresh which consumes the local .nfo.
1705
+
1706
+ The item may not be indexed yet (the .strm can be created and even moved to
1707
+ its language folder seconds before this runs), so we poll. Matching is by
1708
+ exact path, by any `alt_paths` (e.g. the pre-move path), and finally by the
1709
+ .strm basename so a Jellyfin-side folder move still resolves. Every few
1710
+ attempts a library scan is nudged so a freshly moved file gets indexed.
1711
  """
1712
  if not strm_path:
1713
  return False
 
1715
  if not headers:
1716
  return False
1717
  api_key = _read_jellyfin_api_key()
1718
+ base = os.path.basename(strm_path).lower()
1719
+ paths = [strm_path]
1720
+ if alt_paths:
1721
+ paths.extend(alt_paths)
1722
+ norm_paths = {os.path.normpath(p).lower() for p in paths if p}
1723
  for attempt in range(retries):
1724
  try:
1725
  connector = aiohttp.TCPConnector(family=socket.AF_INET)
 
1741
  if response.status != 200:
1742
  continue
1743
  payload = await response.json()
1744
+ items = payload.get("Items") or []
1745
  item_id = next(
1746
+ (
1747
+ it.get("Id") for it in items
1748
+ if os.path.normpath(str(it.get("Path") or "")).lower() in norm_paths
1749
+ ),
1750
  None,
1751
  )
1752
  if not item_id:
1753
+ # Fallback: the item may live under a different folder after
1754
+ # TMDB language organize moved the .strm; match by basename.
1755
+ item_id = next(
1756
+ (
1757
+ it.get("Id") for it in items
1758
+ if os.path.basename(str(it.get("Path") or "")).lower() == base
1759
+ ),
1760
+ None,
1761
+ )
1762
+ if not item_id:
1763
+ # Every few attempts, nudge a library scan so a freshly
1764
+ # created/moved .strm is indexed before the next lookup.
1765
+ if attempt % 4 == 0:
1766
+ try:
1767
+ await request_jellyfin_scan()
1768
+ except Exception:
1769
+ pass
1770
  await asyncio.sleep(delay)
1771
  continue
1772
  refresh_params = {