Fix Jellyfin playlist integration and tracklist matching for single-CD albums
- hint_extractor: add _normalize_vertical_tracklist() to handle bare-number/ title/duration format (Tufaranka-style tracklists) - hint_extractor: fix level-1 tracklist match — allow disc_num=None (single-CD) by assuming disc=1; previously no tracklist title was ever applied to single- CD tracks because the guard required disc_num to be set - music_enricher: register module in sys.modules before exec_module() so @dataclass definitions in jellyfin_playlist_generator work correctly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
776c977573
commit
701b05a75d
2 changed files with 55 additions and 4 deletions
|
|
@ -138,7 +138,56 @@ def _read_tags(path: Path) -> Tuple[Dict[str, str], Optional[float]]:
|
|||
return {}, None
|
||||
|
||||
|
||||
_STANDALONE_NUM_RE = re.compile(r"^\d{1,3}$")
|
||||
_DURATION_ONLY_RE = re.compile(r"^\d{1,2}:\d{2}$")
|
||||
|
||||
|
||||
def _normalize_vertical_tracklist(text: str) -> Optional[str]:
|
||||
"""
|
||||
Erkennt 'vertikales' Format:
|
||||
1
|
||||
Katka dovádí
|
||||
3:22
|
||||
2
|
||||
Záludná
|
||||
→ konvertiert zu '1. Katka dovádí 3:22\\n2. Záludná ...'
|
||||
"""
|
||||
non_empty = [l.strip() for l in text.splitlines() if l.strip()]
|
||||
# Mindestens 3 Standalone-Zahlen als Heuristik
|
||||
num_lines = sum(1 for l in non_empty if _STANDALONE_NUM_RE.match(l))
|
||||
if num_lines < 3:
|
||||
return None
|
||||
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(non_empty):
|
||||
line = non_empty[i]
|
||||
if _STANDALONE_NUM_RE.match(line) and i + 1 < len(non_empty):
|
||||
title_candidate = non_empty[i + 1]
|
||||
# Nächste Zeile darf selbst keine Zahl und keine Dauer sein
|
||||
if not _STANDALONE_NUM_RE.match(title_candidate) and not _DURATION_ONLY_RE.match(title_candidate):
|
||||
duration = ""
|
||||
skip = 2
|
||||
if i + 2 < len(non_empty) and _DURATION_ONLY_RE.match(non_empty[i + 2]):
|
||||
duration = non_empty[i + 2]
|
||||
skip = 3
|
||||
entry = f"{line}. {title_candidate}"
|
||||
if duration:
|
||||
entry += f" {duration}"
|
||||
result.append(entry)
|
||||
i += skip
|
||||
continue
|
||||
i += 1
|
||||
|
||||
return "\n".join(result) if len(result) >= 3 else None
|
||||
|
||||
|
||||
def _parse_tracklist(text: str) -> List[Dict[str, str]]:
|
||||
# Vertikales Format normalisieren bevor das reguläre Parsing läuft
|
||||
normalized = _normalize_vertical_tracklist(text)
|
||||
if normalized:
|
||||
text = normalized
|
||||
|
||||
tracks: List[Dict[str, str]] = []
|
||||
current_disc = 1
|
||||
|
||||
|
|
@ -522,13 +571,14 @@ def extract_hints(scan: AlbumScan, use_ocr: bool = True) -> AlbumHints:
|
|||
if parsed_tracklist:
|
||||
matched_tl: Optional[Dict[str, str]] = None
|
||||
|
||||
# 1. Exakt per Tracknummer + Disc (nur wenn beides aus Tag/Dateiname bekannt)
|
||||
if track_num and disc_num:
|
||||
# 1. Exakt per Tracknummer + Disc (disc_num=None → Single-CD, assume 1)
|
||||
if track_num:
|
||||
assumed_disc = disc_num if disc_num else 1
|
||||
for tl_entry in parsed_tracklist:
|
||||
tl_track = tl_entry.get("track")
|
||||
tl_disc = tl_entry.get("disc", "1")
|
||||
tl_disc = int(tl_entry.get("disc", "1"))
|
||||
if (tl_track and int(tl_track) == track_num
|
||||
and int(tl_disc) == disc_num):
|
||||
and tl_disc == assumed_disc):
|
||||
matched_tl = tl_entry
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ def _run_jellyfin_generator(album_dir: Path, generator_path: Path) -> None:
|
|||
try:
|
||||
spec = importlib.util.spec_from_file_location("jellyfin_pg", generator_path)
|
||||
mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type]
|
||||
sys.modules["jellyfin_pg"] = mod # muss vor exec_module stehen (für @dataclass)
|
||||
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
||||
|
||||
media_files = mod.collect_media_recursive(album_dir)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue