Lint-Fixes, process-Disc-Validierung und Forgejo-CI

- ruff: Import-Sortierung, unused imports, Zeilenlängen behoben
- cli.py: _check_disc_counts_or_exit() extrahiert; auch process-Befehl
  prüft jetzt Disc-Anzahlen vor dem Umbenennen
- .forgejo/workflows/ci.yml: ruff + pytest auf push/PR gegen main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-18 00:51:14 +01:00
commit 70c096cde4
6 changed files with 73 additions and 40 deletions

27
.forgejo/workflows/ci.yml Normal file
View file

@ -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

View file

@ -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)

View file

@ -2,8 +2,6 @@
from pathlib import Path
import pytest
from musiksammlung.cover import find_cover

View file

@ -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)

View file

@ -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)
]

View file

@ -2,8 +2,6 @@
from pathlib import Path
import pytest
from musiksammlung.config import AudioFormat
from musiksammlung.ripper import (
RipperConfig,