Add edit dialogs, library organizer, and TMDb pool migration
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""One-off migration of an existing pool to the Jellyfin naming convention.
|
||||
|
||||
Older pools carry files like ``Title.ext`` / ``Title (rok).ext`` with **no TMDb
|
||||
id**. This matches each such movie against TMDb by title + year and proposes a
|
||||
``tmdbid`` (and the missing year, which TMDb supplies), so the file can be renamed
|
||||
to ``Title (rok) [tmdbid].ext`` and Jellyfin can match it deterministically.
|
||||
|
||||
The classification is deliberately **conservative**: only an exact
|
||||
(diacritics-insensitive) title match with a matching year and a single candidate
|
||||
is ``auto``; everything else is ``ambiguous`` for the user to confirm.
|
||||
"""
|
||||
import re
|
||||
import unicodedata
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatchProposal:
|
||||
"""One movie's proposed TMDb match."""
|
||||
|
||||
file: object # core.file.File
|
||||
current_name: str
|
||||
title: str
|
||||
year: Optional[str]
|
||||
status: str # "auto" | "ambiguous" | "no_match" | "error"
|
||||
chosen: object = None # tmdb.TmdbResult preselected for the user, or None
|
||||
candidates: list = field(default_factory=list) # list[tmdb.TmdbResult]
|
||||
error: str = ""
|
||||
|
||||
|
||||
def normalize(text: str) -> str:
|
||||
"""Fold a title for comparison: strip diacritics/punctuation, lowercase."""
|
||||
text = unicodedata.normalize("NFD", text or "")
|
||||
text = "".join(c for c in text if not unicodedata.combining(c))
|
||||
text = re.sub(r"[^0-9a-zA-Z ]+", " ", text.lower())
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _year_close(a: Optional[str], b: Optional[str]) -> bool:
|
||||
if not (a and b and str(a).isdigit() and str(b).isdigit()):
|
||||
return False
|
||||
return abs(int(a) - int(b)) <= 1
|
||||
|
||||
|
||||
def classify(title: str, year: Optional[str], candidates: list) -> tuple[str, object]:
|
||||
"""Return ``(status, chosen)`` for a title/year against TMDb candidates.
|
||||
|
||||
``auto`` requires an exact normalized-title match, a year within ±1, and that
|
||||
exactly one candidate qualifies. Otherwise ``ambiguous`` (top hit preselected)
|
||||
or ``no_match`` when there are no candidates.
|
||||
"""
|
||||
if not candidates:
|
||||
return "no_match", None
|
||||
nt = normalize(title)
|
||||
exact_year = [
|
||||
c for c in candidates
|
||||
if normalize(c.title) == nt and _year_close(c.year, year)
|
||||
]
|
||||
if len(exact_year) == 1:
|
||||
return "auto", exact_year[0]
|
||||
return "ambiguous", candidates[0]
|
||||
Reference in New Issue
Block a user