diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..e94dc99 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install -e ".[dev]" + + - name: Lint (ruff) + run: ruff check src/ tests/ + + - name: Tests (pytest) + run: pytest tests/ -v --tb=short diff --git a/src/musiksammlung/cli.py b/src/musiksammlung/cli.py index f124643..1978e7c 100644 --- a/src/musiksammlung/cli.py +++ b/src/musiksammlung/cli.py @@ -7,8 +7,9 @@ import logging from pathlib import Path import typer +from mutagen import File as MutagenFile -from musiksammlung.config import AudioFormat +from musiksammlung.config import AUDIO_EXTENSIONS, AudioFormat from musiksammlung.cover import copy_covers, find_cover from musiksammlung.llm_parser import parse_tracklist from musiksammlung.models import Album @@ -16,9 +17,6 @@ from musiksammlung.ocr import ocr_images from musiksammlung.organizer import apply_mapping, build_mapping, check_disc_counts from musiksammlung.playlist import generate_playlist from musiksammlung.ripper import RipperConfig, interactive_rip -from mutagen import File as MutagenFile - -from musiksammlung.config import AUDIO_EXTENSIONS from musiksammlung.tagger import embed_album_cover, tag_album from musiksammlung.vision_llm import parse_image @@ -62,6 +60,39 @@ def _scan_to_album( ) +def _check_disc_counts_or_exit(album: Album, input_dir: Path, album_json: Path) -> None: + """Prüft Disc-Zählungen und beendet das Programm bei Abweichungen.""" + checks = check_disc_counts(album, input_dir) + problems = [c for c in checks if not c.ok] + if not problems: + return + 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) + + def _print_album_summary(album: Album) -> None: """Gibt eine kompakte Album-Zusammenfassung aus.""" typer.echo(f" Artist: {album.artist}") @@ -167,34 +198,7 @@ def apply( raise typer.Exit(1) # 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) + _check_disc_counts_or_exit(album, input_dir, album_json) mapping = build_mapping(album, input_dir, output_dir, in_place=in_place) typer.echo(f"Mapping: {len(mapping)} Dateien") @@ -346,6 +350,9 @@ def process( json_path = input_dir / "album.json" json_path.write_text(album.model_dump_json(indent=2), encoding="utf-8") + # Prüfe Track-Anzahl pro Disc + _check_disc_counts_or_exit(album, input_dir, json_path) + # 2. Dateien organisieren typer.echo("Schritt 2/4: Dateien organisieren...") mapping = build_mapping(album, input_dir, output_dir) diff --git a/tests/test_cover.py b/tests/test_cover.py index d94ffae..70341df 100644 --- a/tests/test_cover.py +++ b/tests/test_cover.py @@ -2,8 +2,6 @@ from pathlib import Path -import pytest - from musiksammlung.cover import find_cover diff --git a/tests/test_organizer.py b/tests/test_organizer.py index 3543659..5ac1f45 100644 --- a/tests/test_organizer.py +++ b/tests/test_organizer.py @@ -2,8 +2,6 @@ from pathlib import Path -import pytest - from musiksammlung.models import Album, Disc, Track from musiksammlung.organizer import ( _sanitize_filename, @@ -122,7 +120,10 @@ class TestCheckDiscCounts: album="B", discs=[ Disc(disc_number=1, tracks=[Track(track_number=1, title="T1")]), - Disc(disc_number=2, tracks=[Track(track_number=1, title="T2"), Track(track_number=2, title="T3")]), + Disc( + disc_number=2, + tracks=[Track(track_number=1, title="T2"), Track(track_number=2, title="T3")], + ), ], ) results = check_disc_counts(album, tmp_path) diff --git a/tests/test_playlist.py b/tests/test_playlist.py index 6113fd8..1626266 100644 --- a/tests/test_playlist.py +++ b/tests/test_playlist.py @@ -10,7 +10,9 @@ def _make_album(n_tracks: int = 2, n_discs: int = 1) -> Album: discs = [ Disc( disc_number=d, - tracks=[Track(track_number=i, title=f"Disc{d} Song {i}") for i in range(1, n_tracks + 1)], + tracks=[ + Track(track_number=i, title=f"Disc{d} Song {i}") for i in range(1, n_tracks + 1) + ], ) for d in range(1, n_discs + 1) ] diff --git a/tests/test_ripper.py b/tests/test_ripper.py index 592f931..9874255 100644 --- a/tests/test_ripper.py +++ b/tests/test_ripper.py @@ -2,8 +2,6 @@ from pathlib import Path -import pytest - from musiksammlung.config import AudioFormat from musiksammlung.ripper import ( RipperConfig,