Spaces:
Running
Running
Hermes Agent commited on
Commit ·
6231a0a
1
Parent(s): 4a485e9
fix(streamer): resolve Jellyfin items moved by TMDB organuize
Browse filesrefresh_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.
- 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
(
|
| 1723 |
-
|
|
|
|
|
|
|
| 1724 |
None,
|
| 1725 |
)
|
| 1726 |
if not item_id:
|
| 1727 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 = {
|