Add project skeleton: CLI pipeline for CD digitization
Modular Python package with Typer CLI (scan/apply/process commands), Pydantic data models, OCR via Tesseract, LLM-based tracklist parsing, mutagen audio tagging, M3U playlist generation, and cover processing. Includes 8 passing tests and ruff lint config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
225f6b3dbf
commit
3e073250ca
17 changed files with 1027 additions and 0 deletions
57
src/musiksammlung/playlist.py
Normal file
57
src/musiksammlung/playlist.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""M3U-Playlist-Generierung für Jellyfin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from musiksammlung.models import Album
|
||||
from musiksammlung.organizer import _sanitize_filename
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_playlist(album: Album, album_dir: Path) -> Path:
|
||||
"""Erzeugt eine M3U-Playlist für das gesamte Album.
|
||||
|
||||
Die Playlist liegt im Album-Root und referenziert alle Tracks
|
||||
über relative Pfade (CD1/01 Titel.flac, CD2/01 Titel.flac, ...).
|
||||
|
||||
Returns:
|
||||
Pfad zur erzeugten Playlist-Datei.
|
||||
"""
|
||||
playlist_name = _sanitize_filename(album.album) + ".m3u"
|
||||
playlist_path = album_dir / playlist_name
|
||||
multi_disc = len(album.discs) > 1
|
||||
|
||||
lines = ["#EXTM3U"]
|
||||
|
||||
for disc in album.discs:
|
||||
if multi_disc:
|
||||
disc_prefix = f"CD{disc.disc_number}/"
|
||||
else:
|
||||
disc_prefix = ""
|
||||
|
||||
for track in disc.tracks:
|
||||
safe_title = _sanitize_filename(track.title)
|
||||
# Audiodatei im Zielverzeichnis finden
|
||||
pattern = f"{track.track_number:02d} {safe_title}.*"
|
||||
if multi_disc:
|
||||
search_dir = album_dir / f"CD{disc.disc_number}"
|
||||
else:
|
||||
search_dir = album_dir
|
||||
|
||||
matches = list(search_dir.glob(pattern))
|
||||
if matches:
|
||||
filename = matches[0].name
|
||||
else:
|
||||
# Fallback: generischer Name mit .flac
|
||||
filename = f"{track.track_number:02d} {safe_title}.flac"
|
||||
logger.warning("Datei nicht gefunden, Fallback: %s", filename)
|
||||
|
||||
lines.append(f"#EXTINF:0,{track.title}")
|
||||
lines.append(f"{disc_prefix}{filename}")
|
||||
|
||||
playlist_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
logger.info("Playlist erstellt: %s", playlist_path)
|
||||
return playlist_path
|
||||
Loading…
Add table
Add a link
Reference in a new issue