mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2025-10-23 15:11:08 +00:00
fix(tags): gracefully handle missing TMDB/Simkl API keys
Simkl now requires a client_id from https://simkl.com/settings/developer/
This commit is contained in:
@@ -89,6 +89,7 @@ class Config:
|
|||||||
self.tag_group_name: bool = kwargs.get("tag_group_name", True)
|
self.tag_group_name: bool = kwargs.get("tag_group_name", True)
|
||||||
self.tag_imdb_tmdb: bool = kwargs.get("tag_imdb_tmdb", True)
|
self.tag_imdb_tmdb: bool = kwargs.get("tag_imdb_tmdb", True)
|
||||||
self.tmdb_api_key: str = kwargs.get("tmdb_api_key") or ""
|
self.tmdb_api_key: str = kwargs.get("tmdb_api_key") or ""
|
||||||
|
self.simkl_client_id: str = kwargs.get("simkl_client_id") or ""
|
||||||
self.decrypt_labs_api_key: str = kwargs.get("decrypt_labs_api_key") or ""
|
self.decrypt_labs_api_key: str = kwargs.get("decrypt_labs_api_key") or ""
|
||||||
self.update_checks: bool = kwargs.get("update_checks", True)
|
self.update_checks: bool = kwargs.get("update_checks", True)
|
||||||
self.update_check_interval: int = kwargs.get("update_check_interval", 24)
|
self.update_check_interval: int = kwargs.get("update_check_interval", 24)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ def _api_key() -> Optional[str]:
|
|||||||
return config.tmdb_api_key or os.getenv("TMDB_API_KEY")
|
return config.tmdb_api_key or os.getenv("TMDB_API_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
def _simkl_client_id() -> Optional[str]:
|
||||||
|
return config.simkl_client_id or os.getenv("SIMKL_CLIENT_ID")
|
||||||
|
|
||||||
|
|
||||||
def _clean(s: str) -> str:
|
def _clean(s: str) -> str:
|
||||||
return STRIP_RE.sub("", s).lower()
|
return STRIP_RE.sub("", s).lower()
|
||||||
|
|
||||||
@@ -63,9 +67,14 @@ def fuzzy_match(a: str, b: str, threshold: float = 0.8) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def search_simkl(title: str, year: Optional[int], kind: str) -> Tuple[Optional[dict], Optional[str], Optional[int]]:
|
def search_simkl(title: str, year: Optional[int], kind: str) -> Tuple[Optional[dict], Optional[str], Optional[int]]:
|
||||||
"""Search Simkl API for show information by filename (no auth required)."""
|
"""Search Simkl API for show information by filename."""
|
||||||
log.debug("Searching Simkl for %r (%s, %s)", title, kind, year)
|
log.debug("Searching Simkl for %r (%s, %s)", title, kind, year)
|
||||||
|
|
||||||
|
client_id = _simkl_client_id()
|
||||||
|
if not client_id:
|
||||||
|
log.debug("No SIMKL client ID configured; skipping SIMKL search")
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
# Construct appropriate filename based on type
|
# Construct appropriate filename based on type
|
||||||
filename = f"{title}"
|
filename = f"{title}"
|
||||||
if year:
|
if year:
|
||||||
@@ -78,7 +87,8 @@ def search_simkl(title: str, year: Optional[int], kind: str) -> Tuple[Optional[d
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
session = _get_session()
|
session = _get_session()
|
||||||
resp = session.post("https://api.simkl.com/search/file", json={"file": filename}, timeout=30)
|
headers = {"simkl-api-key": client_id}
|
||||||
|
resp = session.post("https://api.simkl.com/search/file", json={"file": filename}, headers=headers, timeout=30)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
log.debug("Simkl API response received")
|
log.debug("Simkl API response received")
|
||||||
@@ -338,73 +348,97 @@ def tag_file(path: Path, title: Title, tmdb_id: Optional[int] | None = None) ->
|
|||||||
return
|
return
|
||||||
|
|
||||||
if config.tag_imdb_tmdb:
|
if config.tag_imdb_tmdb:
|
||||||
# If tmdb_id is provided (via --tmdb), skip Simkl and use TMDB directly
|
# Check if we have any API keys available for metadata lookup
|
||||||
if tmdb_id is not None:
|
|
||||||
log.debug("Using provided TMDB ID %s for tags", tmdb_id)
|
|
||||||
else:
|
|
||||||
# Try Simkl first for automatic lookup
|
|
||||||
simkl_data, simkl_title, simkl_tmdb_id = search_simkl(name, year, kind)
|
|
||||||
|
|
||||||
if simkl_data and simkl_title and fuzzy_match(simkl_title, name):
|
|
||||||
log.debug("Using Simkl data for tags")
|
|
||||||
if simkl_tmdb_id:
|
|
||||||
tmdb_id = simkl_tmdb_id
|
|
||||||
|
|
||||||
# Handle TV show data from Simkl
|
|
||||||
if simkl_data.get("type") == "episode" and "show" in simkl_data:
|
|
||||||
show_ids = simkl_data.get("show", {}).get("ids", {})
|
|
||||||
if show_ids.get("imdb"):
|
|
||||||
standard_tags["IMDB"] = show_ids["imdb"]
|
|
||||||
if show_ids.get("tvdb"):
|
|
||||||
standard_tags["TVDB2"] = f"series/{show_ids['tvdb']}"
|
|
||||||
if show_ids.get("tmdbtv"):
|
|
||||||
standard_tags["TMDB"] = f"tv/{show_ids['tmdbtv']}"
|
|
||||||
|
|
||||||
# Handle movie data from Simkl
|
|
||||||
elif simkl_data.get("type") == "movie" and "movie" in simkl_data:
|
|
||||||
movie_ids = simkl_data.get("movie", {}).get("ids", {})
|
|
||||||
if movie_ids.get("imdb"):
|
|
||||||
standard_tags["IMDB"] = movie_ids["imdb"]
|
|
||||||
if movie_ids.get("tvdb"):
|
|
||||||
standard_tags["TVDB2"] = f"movies/{movie_ids['tvdb']}"
|
|
||||||
if movie_ids.get("tmdb"):
|
|
||||||
standard_tags["TMDB"] = f"movie/{movie_ids['tmdb']}"
|
|
||||||
|
|
||||||
# Use TMDB API for additional metadata (either from provided ID or Simkl lookup)
|
|
||||||
api_key = _api_key()
|
api_key = _api_key()
|
||||||
if not api_key:
|
simkl_client = _simkl_client_id()
|
||||||
log.debug("No TMDB API key set; applying basic tags only")
|
|
||||||
_apply_tags(path, custom_tags)
|
|
||||||
return
|
|
||||||
|
|
||||||
tmdb_title: Optional[str] = None
|
if not api_key and not simkl_client:
|
||||||
if tmdb_id is None:
|
log.debug("No TMDB API key or Simkl client ID configured; skipping IMDB/TMDB tag lookup")
|
||||||
tmdb_id, tmdb_title = search_tmdb(name, year, kind)
|
|
||||||
log.debug("TMDB search result: %r (ID %s)", tmdb_title, tmdb_id)
|
|
||||||
if not tmdb_id or not tmdb_title or not fuzzy_match(tmdb_title, name):
|
|
||||||
log.debug("TMDB search did not match; skipping external ID lookup")
|
|
||||||
_apply_tags(path, custom_tags)
|
|
||||||
return
|
|
||||||
|
|
||||||
prefix = "movie" if kind == "movie" else "tv"
|
|
||||||
standard_tags["TMDB"] = f"{prefix}/{tmdb_id}"
|
|
||||||
try:
|
|
||||||
ids = external_ids(tmdb_id, kind)
|
|
||||||
except requests.RequestException as exc:
|
|
||||||
log.debug("Failed to fetch external IDs: %s", exc)
|
|
||||||
ids = {}
|
|
||||||
else:
|
else:
|
||||||
log.debug("External IDs found: %s", ids)
|
# If tmdb_id is provided (via --tmdb), skip Simkl and use TMDB directly
|
||||||
|
if tmdb_id is not None:
|
||||||
imdb_id = ids.get("imdb_id")
|
log.debug("Using provided TMDB ID %s for tags", tmdb_id)
|
||||||
if imdb_id:
|
|
||||||
standard_tags["IMDB"] = imdb_id
|
|
||||||
tvdb_id = ids.get("tvdb_id")
|
|
||||||
if tvdb_id:
|
|
||||||
if kind == "movie":
|
|
||||||
standard_tags["TVDB2"] = f"movies/{tvdb_id}"
|
|
||||||
else:
|
else:
|
||||||
standard_tags["TVDB2"] = f"series/{tvdb_id}"
|
# Try Simkl first for automatic lookup (only if client ID is available)
|
||||||
|
if simkl_client:
|
||||||
|
simkl_data, simkl_title, simkl_tmdb_id = search_simkl(name, year, kind)
|
||||||
|
|
||||||
|
if simkl_data and simkl_title and fuzzy_match(simkl_title, name):
|
||||||
|
log.debug("Using Simkl data for tags")
|
||||||
|
if simkl_tmdb_id:
|
||||||
|
tmdb_id = simkl_tmdb_id
|
||||||
|
|
||||||
|
# Handle TV show data from Simkl
|
||||||
|
if simkl_data.get("type") == "episode" and "show" in simkl_data:
|
||||||
|
show_ids = simkl_data.get("show", {}).get("ids", {})
|
||||||
|
if show_ids.get("imdb"):
|
||||||
|
standard_tags["IMDB"] = show_ids["imdb"]
|
||||||
|
if show_ids.get("tvdb"):
|
||||||
|
standard_tags["TVDB2"] = f"series/{show_ids['tvdb']}"
|
||||||
|
if show_ids.get("tmdbtv"):
|
||||||
|
standard_tags["TMDB"] = f"tv/{show_ids['tmdbtv']}"
|
||||||
|
|
||||||
|
# Handle movie data from Simkl
|
||||||
|
elif simkl_data.get("type") == "movie" and "movie" in simkl_data:
|
||||||
|
movie_ids = simkl_data.get("movie", {}).get("ids", {})
|
||||||
|
if movie_ids.get("imdb"):
|
||||||
|
standard_tags["IMDB"] = movie_ids["imdb"]
|
||||||
|
if movie_ids.get("tvdb"):
|
||||||
|
standard_tags["TVDB2"] = f"movies/{movie_ids['tvdb']}"
|
||||||
|
if movie_ids.get("tmdb"):
|
||||||
|
standard_tags["TMDB"] = f"movie/{movie_ids['tmdb']}"
|
||||||
|
|
||||||
|
# Use TMDB API for additional metadata (either from provided ID or Simkl lookup)
|
||||||
|
if api_key:
|
||||||
|
tmdb_title: Optional[str] = None
|
||||||
|
if tmdb_id is None:
|
||||||
|
tmdb_id, tmdb_title = search_tmdb(name, year, kind)
|
||||||
|
log.debug("TMDB search result: %r (ID %s)", tmdb_title, tmdb_id)
|
||||||
|
if not tmdb_id or not tmdb_title or not fuzzy_match(tmdb_title, name):
|
||||||
|
log.debug("TMDB search did not match; skipping external ID lookup")
|
||||||
|
else:
|
||||||
|
prefix = "movie" if kind == "movie" else "tv"
|
||||||
|
standard_tags["TMDB"] = f"{prefix}/{tmdb_id}"
|
||||||
|
try:
|
||||||
|
ids = external_ids(tmdb_id, kind)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
log.debug("Failed to fetch external IDs: %s", exc)
|
||||||
|
ids = {}
|
||||||
|
else:
|
||||||
|
log.debug("External IDs found: %s", ids)
|
||||||
|
|
||||||
|
imdb_id = ids.get("imdb_id")
|
||||||
|
if imdb_id:
|
||||||
|
standard_tags["IMDB"] = imdb_id
|
||||||
|
tvdb_id = ids.get("tvdb_id")
|
||||||
|
if tvdb_id:
|
||||||
|
if kind == "movie":
|
||||||
|
standard_tags["TVDB2"] = f"movies/{tvdb_id}"
|
||||||
|
else:
|
||||||
|
standard_tags["TVDB2"] = f"series/{tvdb_id}"
|
||||||
|
elif tmdb_id is not None:
|
||||||
|
# tmdb_id was provided or found via Simkl
|
||||||
|
prefix = "movie" if kind == "movie" else "tv"
|
||||||
|
standard_tags["TMDB"] = f"{prefix}/{tmdb_id}"
|
||||||
|
try:
|
||||||
|
ids = external_ids(tmdb_id, kind)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
log.debug("Failed to fetch external IDs: %s", exc)
|
||||||
|
ids = {}
|
||||||
|
else:
|
||||||
|
log.debug("External IDs found: %s", ids)
|
||||||
|
|
||||||
|
imdb_id = ids.get("imdb_id")
|
||||||
|
if imdb_id:
|
||||||
|
standard_tags["IMDB"] = imdb_id
|
||||||
|
tvdb_id = ids.get("tvdb_id")
|
||||||
|
if tvdb_id:
|
||||||
|
if kind == "movie":
|
||||||
|
standard_tags["TVDB2"] = f"movies/{tvdb_id}"
|
||||||
|
else:
|
||||||
|
standard_tags["TVDB2"] = f"series/{tvdb_id}"
|
||||||
|
else:
|
||||||
|
log.debug("No TMDB API key configured; skipping TMDB external ID lookup")
|
||||||
|
|
||||||
merged_tags = {
|
merged_tags = {
|
||||||
**custom_tags,
|
**custom_tags,
|
||||||
|
|||||||
@@ -336,6 +336,10 @@ filenames:
|
|||||||
# API key for The Movie Database (TMDB)
|
# API key for The Movie Database (TMDB)
|
||||||
tmdb_api_key: ""
|
tmdb_api_key: ""
|
||||||
|
|
||||||
|
# Client ID for SIMKL API (optional, improves metadata matching)
|
||||||
|
# Get your free client ID at: https://simkl.com/settings/developer/
|
||||||
|
simkl_client_id: ""
|
||||||
|
|
||||||
# conversion_method:
|
# conversion_method:
|
||||||
# - auto (default): Smart routing - subby for WebVTT/SAMI, standard for others
|
# - auto (default): Smart routing - subby for WebVTT/SAMI, standard for others
|
||||||
# - subby: Always use subby with advanced processing
|
# - subby: Always use subby with advanced processing
|
||||||
|
|||||||
Reference in New Issue
Block a user