Spaces:
Running
Running
Hermes Agent commited on
Commit ·
5ff93ff
1
Parent(s): 780f7a9
fix(metadata): purge unwanted Ms.Rose & Mr.Cane and resolve Obsession to official 2026 Curry Barker release
Browse files- apex/apex_stream/tmdb_meta.py +15 -0
- apex/combined-entrypoint.sh +2 -1
- apex/repair_jellyfin_libraries.py +4 -1
- apex/tests/test_tmdb_meta.py +35 -1
- apex/tg_streamer.py +49 -16
apex/apex_stream/tmdb_meta.py
CHANGED
|
@@ -641,6 +641,21 @@ def best_tmdb_match(results: List[Dict], query: str, media_type: str,
|
|
| 641 |
elif not is_indian and lang == "en" and hit_lang not in {"en", ""}:
|
| 642 |
score -= 0.35
|
| 643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
if score > best_score:
|
| 645 |
best, best_score = r, score
|
| 646 |
return best if best_score >= threshold else None
|
|
|
|
| 641 |
elif not is_indian and lang == "en" and hit_lang not in {"en", ""}:
|
| 642 |
score -= 0.35
|
| 643 |
|
| 644 |
+
# Boost real distributed films with popularity/votes/artwork over zero-vote placeholders
|
| 645 |
+
try:
|
| 646 |
+
pop = float(r.get("popularity") or 0.0)
|
| 647 |
+
votes = int(r.get("vote_count") or 0)
|
| 648 |
+
if pop >= 5.0 or votes >= 10:
|
| 649 |
+
score += 0.20
|
| 650 |
+
if r.get("poster_path"):
|
| 651 |
+
score += 0.10
|
| 652 |
+
if r.get("backdrop_path") or r.get("overview"):
|
| 653 |
+
score += 0.05
|
| 654 |
+
if not r.get("poster_path") and votes == 0:
|
| 655 |
+
score -= 0.15
|
| 656 |
+
except (ValueError, TypeError):
|
| 657 |
+
pass
|
| 658 |
+
|
| 659 |
if score > best_score:
|
| 660 |
best, best_score = r, score
|
| 661 |
return best if best_score >= threshold else None
|
apex/combined-entrypoint.sh
CHANGED
|
@@ -56,7 +56,8 @@ restore_jellyfin_local() {
|
|
| 56 |
|
| 57 |
# Permanently delete TV Shows/English and blacklisted demo episodes from media paths
|
| 58 |
rm -rf "/data/jellyfin/media/TV Shows/English" "/opt/jellyfin-local/media/TV Shows/English" 2>/dev/null || true
|
| 59 |
-
find /data/jellyfin/media -iname "*mot*test*servicing*" -exec rm -rf {} + 2>/dev/null || true
|
|
|
|
| 60 |
|
| 61 |
# Purge old low-res Jellyfin image cache so newly fetched HD/4K artwork is displayed immediately
|
| 62 |
rm -rf "$LOCAL_ROOT/cache/images"/* /data/jellyfin/cache/images/* 2>/dev/null || true
|
|
|
|
| 56 |
|
| 57 |
# Permanently delete TV Shows/English and blacklisted demo episodes from media paths
|
| 58 |
rm -rf "/data/jellyfin/media/TV Shows/English" "/opt/jellyfin-local/media/TV Shows/English" 2>/dev/null || true
|
| 59 |
+
find /data/jellyfin/media /data/movies /data/tvshows -iname "*mot*test*servicing*" -exec rm -rf {} + 2>/dev/null || true
|
| 60 |
+
find /data/jellyfin/media /data/movies /data/tvshows -iname "*rose*cane*" -exec rm -rf {} + 2>/dev/null || true
|
| 61 |
|
| 62 |
# Purge old low-res Jellyfin image cache so newly fetched HD/4K artwork is displayed immediately
|
| 63 |
rm -rf "$LOCAL_ROOT/cache/images"/* /data/jellyfin/cache/images/* 2>/dev/null || true
|
apex/repair_jellyfin_libraries.py
CHANGED
|
@@ -100,11 +100,14 @@ def repair_database(db_path: str) -> bool:
|
|
| 100 |
print(f"[REPAIR] Healed '{name}' ({row_id}): Path -> '{views_path}', Locations -> {locs}")
|
| 101 |
repaired += 1
|
| 102 |
|
| 103 |
-
# Permanently purge blacklisted demo items and invalid TV Shows/English items
|
| 104 |
cur.execute(
|
| 105 |
"DELETE FROM BaseItems WHERE Path LIKE '%/TV Shows/English%' "
|
| 106 |
"OR Path LIKE '%Mot test servicing%' "
|
| 107 |
"OR Name LIKE '%Mot test servicing%' "
|
|
|
|
|
|
|
|
|
|
| 108 |
"OR (Name = 'English' AND type LIKE '%Series%')"
|
| 109 |
)
|
| 110 |
purged = cur.rowcount
|
|
|
|
| 100 |
print(f"[REPAIR] Healed '{name}' ({row_id}): Path -> '{views_path}', Locations -> {locs}")
|
| 101 |
repaired += 1
|
| 102 |
|
| 103 |
+
# Permanently purge blacklisted demo items, Ms.Rose & Mr.Cane, and invalid TV Shows/English items
|
| 104 |
cur.execute(
|
| 105 |
"DELETE FROM BaseItems WHERE Path LIKE '%/TV Shows/English%' "
|
| 106 |
"OR Path LIKE '%Mot test servicing%' "
|
| 107 |
"OR Name LIKE '%Mot test servicing%' "
|
| 108 |
+
"OR Path LIKE '%Rose%Cane%' "
|
| 109 |
+
"OR Name LIKE '%Rose%Cane%' "
|
| 110 |
+
"OR Name LIKE '%Ms.Rose%' "
|
| 111 |
"OR (Name = 'English' AND type LIKE '%Series%')"
|
| 112 |
)
|
| 113 |
purged = cur.rowcount
|
apex/tests/test_tmdb_meta.py
CHANGED
|
@@ -534,4 +534,38 @@ class TestNfoRepair:
|
|
| 534 |
assert backdrop.exists()
|
| 535 |
|
| 536 |
with Image.open(str(backdrop)) as out_img:
|
| 537 |
-
assert out_img.size == (1920, 1080)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
assert backdrop.exists()
|
| 535 |
|
| 536 |
with Image.open(str(backdrop)) as out_img:
|
| 537 |
+
assert out_img.size == (1920, 1080)
|
| 538 |
+
|
| 539 |
+
def test_ms_rose_mr_cane_blacklisted(self):
|
| 540 |
+
from tg_streamer import is_blacklisted
|
| 541 |
+
assert is_blacklisted("Ms.Rose & Mr.Cane") is True
|
| 542 |
+
assert is_blacklisted("Ms Rose & Mr Cane 2026") is True
|
| 543 |
+
assert is_blacklisted("Ms. Rose & Mr. Cane (2026).strm") is True
|
| 544 |
+
assert is_blacklisted("Mot test servicing") is True
|
| 545 |
+
assert is_blacklisted("Obsession (2025).strm") is False
|
| 546 |
+
|
| 547 |
+
def test_popular_movie_preferred_within_one_year_gap(self):
|
| 548 |
+
candidates = [
|
| 549 |
+
{
|
| 550 |
+
"id": 999999,
|
| 551 |
+
"title": "Obsession",
|
| 552 |
+
"release_date": "2025-01-01",
|
| 553 |
+
"popularity": 0.5,
|
| 554 |
+
"vote_count": 0,
|
| 555 |
+
"poster_path": None,
|
| 556 |
+
"backdrop_path": None,
|
| 557 |
+
},
|
| 558 |
+
{
|
| 559 |
+
"id": 1339713,
|
| 560 |
+
"title": "Obsession",
|
| 561 |
+
"release_date": "2026-05-15",
|
| 562 |
+
"popularity": 65.0,
|
| 563 |
+
"vote_count": 450,
|
| 564 |
+
"poster_path": "/obsession_poster.jpg",
|
| 565 |
+
"backdrop_path": "/obsession_backdrop.jpg",
|
| 566 |
+
"overview": "A man uses a mystical charm to make his crush fall in love.",
|
| 567 |
+
}
|
| 568 |
+
]
|
| 569 |
+
match = best_tmdb_match(candidates, "obsession", "movie", year=2025)
|
| 570 |
+
assert match is not None
|
| 571 |
+
assert match["id"] == 1339713
|
apex/tg_streamer.py
CHANGED
|
@@ -19,6 +19,7 @@ from typing import Optional
|
|
| 19 |
|
| 20 |
_BLACKLIST_PATTERNS = [
|
| 21 |
re.compile(r"mot\s*test\s*servicing", re.IGNORECASE),
|
|
|
|
| 22 |
]
|
| 23 |
|
| 24 |
|
|
@@ -1064,23 +1065,31 @@ async def _tmdb_search(media_type, query, year=None, language=None):
|
|
| 1064 |
params = {"query": query or "", "include_adult": "false"}
|
| 1065 |
if language:
|
| 1066 |
params["language"] = language
|
|
|
|
|
|
|
| 1067 |
if year:
|
|
|
|
| 1068 |
if media_type == "tv":
|
| 1069 |
-
|
| 1070 |
else:
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
|
| 1075 |
-
#
|
| 1076 |
-
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
-
|
| 1080 |
-
|
| 1081 |
-
|
| 1082 |
-
|
| 1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
|
| 1085 |
|
| 1086 |
async def _tmdb_find(external_id):
|
|
@@ -1365,6 +1374,21 @@ async def _enrich_media_metadata(entry):
|
|
| 1365 |
f"[TMDB-LANG] '{entry.get('title')}' matched via language '{lang_code}'"
|
| 1366 |
)
|
| 1367 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1368 |
# AI-tier-2 fallback: rule parsing + TMDB all missed; ask the LLM to
|
| 1369 |
# resolve the canonical title (it knows real-world films and handles ANY
|
| 1370 |
# release junk), then retry TMDB — single-type first, then multi-search —
|
|
@@ -3869,6 +3893,12 @@ async def _reconcile_and_upgrade_library():
|
|
| 3869 |
logger.info(f"[PURGE] Removed misplaced Patriot 2000 directory: {dn}")
|
| 3870 |
except OSError:
|
| 3871 |
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3872 |
|
| 3873 |
# Step 2: Remove blacklisted items from FILE_ID_CACHE
|
| 3874 |
keys_to_purge = []
|
|
@@ -3955,12 +3985,15 @@ async def _reconcile_and_upgrade_library():
|
|
| 3955 |
pass
|
| 3956 |
|
| 3957 |
suspicious = False
|
| 3958 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3959 |
for foreign_pfx in ("a vingança", "vingança", "el ", "le ", "die ", "der "):
|
| 3960 |
if foreign_pfx in tmdb_title or foreign_pfx in nfo_text:
|
| 3961 |
suspicious = True
|
| 3962 |
break
|
| 3963 |
-
raw_name_lower = (data.get("file_name") or data.get("title") or "").lower()
|
| 3964 |
if not suspicious and "patriot" in raw_name_lower:
|
| 3965 |
if data.get("tmdb_id") == 2108 or "mel gibson" in nfo_text or (tmdb_year and tmdb_year <= 2010):
|
| 3966 |
suspicious = True
|
|
|
|
| 19 |
|
| 20 |
_BLACKLIST_PATTERNS = [
|
| 21 |
re.compile(r"mot\s*test\s*servicing", re.IGNORECASE),
|
| 22 |
+
re.compile(r"ms\.?\s*rose\s*&?\s*mr\.?\s*cane", re.IGNORECASE),
|
| 23 |
]
|
| 24 |
|
| 25 |
|
|
|
|
| 1065 |
params = {"query": query or "", "include_adult": "false"}
|
| 1066 |
if language:
|
| 1067 |
params["language"] = language
|
| 1068 |
+
|
| 1069 |
+
results = []
|
| 1070 |
if year:
|
| 1071 |
+
yparams = dict(params)
|
| 1072 |
if media_type == "tv":
|
| 1073 |
+
yparams["first_air_date_year"] = int(year)
|
| 1074 |
else:
|
| 1075 |
+
yparams["year"] = int(year)
|
| 1076 |
+
data = await _tmdb_get(f"/search/{media_type}", yparams)
|
| 1077 |
+
results = (data.get("results") or []) if data else []
|
| 1078 |
+
|
| 1079 |
+
# Also query without strict year filter so releases where festival premiere year
|
| 1080 |
+
# vs theatrical release year differ by 1 year (e.g. Obsession 2025 festival vs 2026 theatrical)
|
| 1081 |
+
# are included in candidate hits for best_tmdb_match to rank.
|
| 1082 |
+
data_all = await _tmdb_get(f"/search/{media_type}", params)
|
| 1083 |
+
all_results = (data_all.get("results") or []) if data_all else []
|
| 1084 |
+
|
| 1085 |
+
seen = set()
|
| 1086 |
+
merged = []
|
| 1087 |
+
for r in results + all_results:
|
| 1088 |
+
rid = r.get("id")
|
| 1089 |
+
if rid and rid not in seen:
|
| 1090 |
+
seen.add(rid)
|
| 1091 |
+
merged.append(r)
|
| 1092 |
+
return merged
|
| 1093 |
|
| 1094 |
|
| 1095 |
async def _tmdb_find(external_id):
|
|
|
|
| 1374 |
f"[TMDB-LANG] '{entry.get('title')}' matched via language '{lang_code}'"
|
| 1375 |
)
|
| 1376 |
break
|
| 1377 |
+
|
| 1378 |
+
# Specific resolution for known modern blockbuster titles with festival vs theatrical date skew:
|
| 1379 |
+
# "Obsession" (2025 PSA release -> 2026 theatrical release, TMDB ID 1339713 by Curry Barker)
|
| 1380 |
+
raw_name_lower = (entry.get("file_name") or entry.get("raw_name") or entry.get("title") or "").lower()
|
| 1381 |
+
if "obsession" in raw_name_lower and entry.get("year") in (2025, 2026, None):
|
| 1382 |
+
if not hit or hit.get("id") != 1339713:
|
| 1383 |
+
try:
|
| 1384 |
+
obsession_hit = await _tmdb_get("/movie/1339713", {})
|
| 1385 |
+
if obsession_hit and obsession_hit.get("id") == 1339713:
|
| 1386 |
+
hit = obsession_hit
|
| 1387 |
+
used_query = "Obsession"
|
| 1388 |
+
logger.info("[TMDB] Resolved Obsession directly to TMDB ID 1339713 (Curry Barker)")
|
| 1389 |
+
except Exception as _exc:
|
| 1390 |
+
pass
|
| 1391 |
+
|
| 1392 |
# AI-tier-2 fallback: rule parsing + TMDB all missed; ask the LLM to
|
| 1393 |
# resolve the canonical title (it knows real-world films and handles ANY
|
| 1394 |
# release junk), then retry TMDB — single-type first, then multi-search —
|
|
|
|
| 3893 |
logger.info(f"[PURGE] Removed misplaced Patriot 2000 directory: {dn}")
|
| 3894 |
except OSError:
|
| 3895 |
pass
|
| 3896 |
+
if "rose" in dn.lower() and "cane" in dn.lower():
|
| 3897 |
+
try:
|
| 3898 |
+
shutil.rmtree(os.path.join(dirpath, dn), ignore_errors=True)
|
| 3899 |
+
logger.info(f"[PURGE] Removed unwanted Ms.Rose & Mr.Cane directory: {dn}")
|
| 3900 |
+
except OSError:
|
| 3901 |
+
pass
|
| 3902 |
|
| 3903 |
# Step 2: Remove blacklisted items from FILE_ID_CACHE
|
| 3904 |
keys_to_purge = []
|
|
|
|
| 3985 |
pass
|
| 3986 |
|
| 3987 |
suspicious = False
|
| 3988 |
+
raw_name_lower = (data.get("file_name") or data.get("title") or "").lower()
|
| 3989 |
+
if not suspicious and "obsession" in raw_name_lower:
|
| 3990 |
+
if data.get("tmdb_id") != 1339713:
|
| 3991 |
+
suspicious = True
|
| 3992 |
+
if not suspicious and lang in ("tamil", "malayalam", "telugu", "hindi", "kannada"):
|
| 3993 |
for foreign_pfx in ("a vingança", "vingança", "el ", "le ", "die ", "der "):
|
| 3994 |
if foreign_pfx in tmdb_title or foreign_pfx in nfo_text:
|
| 3995 |
suspicious = True
|
| 3996 |
break
|
|
|
|
| 3997 |
if not suspicious and "patriot" in raw_name_lower:
|
| 3998 |
if data.get("tmdb_id") == 2108 or "mel gibson" in nfo_text or (tmdb_year and tmdb_year <= 2010):
|
| 3999 |
suspicious = True
|