Compare commits

..
4 changed files with 5 additions and 87 deletions

View file

@ -13,7 +13,7 @@ from musiksammlung.cover import copy_covers
from musiksammlung.llm_parser import parse_tracklist
from musiksammlung.models import Album
from musiksammlung.ocr import ocr_images
from musiksammlung.organizer import apply_mapping, build_mapping, check_disc_counts
from musiksammlung.organizer import apply_mapping, build_mapping
from musiksammlung.playlist import generate_playlist
from musiksammlung.ripper import RipperConfig, interactive_rip
from musiksammlung.tagger import tag_album
@ -90,7 +90,7 @@ def scan(
),
languages: str = typer.Option("deu+eng", "--lang", "-l", help="OCR-Sprachen"),
backend: str = typer.Option("ollama", "--backend", "-b", help="LLM-Backend"),
model: str = typer.Option("gemma3:12b", "--model", "-m", help="Text-LLM-Modell"),
model: str = typer.Option("llama3", "--model", "-m", help="Text-LLM-Modell"),
base_url: str = typer.Option(
"http://localhost:11434", "--url", help="LLM-API-URL"
),
@ -146,36 +146,6 @@ def apply(
raw = json.loads(album_json.read_text(encoding="utf-8"))
album = Album.model_validate(raw)
# Prüfe Track-Anzahl pro Disc
checks = check_disc_counts(album, input_dir)
problems = [c for c in checks if not c.ok]
if problems:
typer.echo(
"\nFEHLER: Track-Diskrepanz zwischen gerippten Dateien und album.json:\n",
err=True,
)
for c in checks:
status = "OK" if c.ok else "!!"
typer.echo(
f" [{status}] Disc {c.disc_number}: "
f"{c.audio_file_count} Datei(en), {c.json_track_count} JSON-Track(s)",
err=True,
)
if c.surplus_files:
typer.echo(
f" → {c.surplus_files} Track(s) fehlen im JSON "
f"(Tracks {c.json_track_count + 1}–{c.audio_file_count} eintragen)",
err=True,
)
elif c.surplus_json:
typer.echo(
f" → {c.surplus_json} JSON-Eintrag/Einträge ohne Audiodatei "
f"(Tracks {c.audio_file_count + 1}–{c.json_track_count} prüfen)",
err=True,
)
typer.echo(f"\nBitte {album_json} korrigieren und erneut aufrufen.", err=True)
raise typer.Exit(1)
mapping = build_mapping(album, input_dir, output_dir)
typer.echo(f"Mapping: {len(mapping)} Dateien")
for src, dst in mapping.items():
@ -292,7 +262,7 @@ def process(
),
languages: str = typer.Option("deu+eng", "--lang", "-l"),
backend: str = typer.Option("ollama", "--backend", "-b"),
model: str = typer.Option("gemma3:12b", "--model", "-m"),
model: str = typer.Option("llama3", "--model", "-m"),
base_url: str = typer.Option("http://localhost:11434", "--url"),
dry_run: bool = typer.Option(False, "--dry-run"),
) -> None:

View file

@ -47,7 +47,7 @@ def _call_ollama(text: str, model: str, base_url: str) -> str:
],
"stream": False,
},
timeout=300.0,
timeout=120.0,
)
response.raise_for_status()
return response.json()["message"]["content"]
@ -71,7 +71,7 @@ def _call_openai_compatible(
{"role": "user", "content": text},
],
},
timeout=300.0,
timeout=120.0,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]

View file

@ -5,7 +5,6 @@ from __future__ import annotations
import logging
import re
import shutil
from dataclasses import dataclass
from pathlib import Path
from musiksammlung.config import AUDIO_EXTENSIONS
@ -14,29 +13,6 @@ from musiksammlung.models import Album
logger = logging.getLogger(__name__)
@dataclass
class DiscCheck:
"""Ergebnis der Track-Zählung für eine einzelne Disc."""
disc_number: int
audio_file_count: int
json_track_count: int
@property
def ok(self) -> bool:
return self.audio_file_count == self.json_track_count
@property
def surplus_files(self) -> int:
"""Dateien ohne JSON-Eintrag (Tracks fehlen im JSON)."""
return max(0, self.audio_file_count - self.json_track_count)
@property
def surplus_json(self) -> int:
"""JSON-Einträge ohne Datei (Dateien fehlen im Verzeichnis)."""
return max(0, self.json_track_count - self.audio_file_count)
def _sanitize_filename(name: str) -> str:
"""Entfernt problematische Zeichen aus Dateinamen."""
return re.sub(r'[<>:"/\\|?*]', "_", name).strip()
@ -52,31 +28,6 @@ def discover_audio_files(directory: Path) -> list[Path]:
return sorted(files, key=extract_number)
def check_disc_counts(album: Album, input_dir: Path) -> list[DiscCheck]:
"""Vergleicht Dateianzahl und JSON-Track-Anzahl pro Disc.
Args:
album: Validiertes Album-Modell
input_dir: Verzeichnis mit gerippten Dateien (enthält CD1/, CD2/, ... bei Multi-Disc)
Returns:
Liste von DiscCheck-Objekten — auch für korrekte Discs (ok=True).
"""
multi_disc = len(album.discs) > 1
results: list[DiscCheck] = []
for disc in album.discs:
source_dir = input_dir / f"CD{disc.disc_number}" if multi_disc else input_dir
file_count = len(discover_audio_files(source_dir)) if source_dir.exists() else 0
results.append(DiscCheck(
disc_number=disc.disc_number,
audio_file_count=file_count,
json_track_count=len(disc.tracks),
))
return results
def build_mapping(
album: Album,
input_dir: Path,

View file

@ -28,9 +28,6 @@ WICHTIG:
- Wenn "CD 1", "CD 2", "Disc 1" etc. sichtbar sind, erstelle mehrere Einträge in "discs".
- Ohne Disc-Angabe: eine Disc mit disc_number=1.
- Lasse Zeitangaben (z.B. "3:12") und Interpretenangaben pro Track weg.
- MEHRSPALTIGE LAYOUTS: CD-Rückseiten haben oft 2, 3 oder 4 Spalten nebeneinander.
Lies ALLE Spalten vollständig von oben nach unten, bevor du zur nächsten Spalte gehst.
Überspringen oder Auslassen von Spalten ist ein häufiger Fehler — lies jede Spalte komplett.
Antworte NUR mit dem JSON, ohne Erklärung. Beispiel: