Up to 3 retries with 2–6 s random wait between attempts, as MusicBrainz
occasionally returns no results on the first try. retries parameter is
configurable (default: 3).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After all file operations (rename, tag, cover, playlist), apply now
renames the album root directory to match album.json metadata:
- input_dir = CD1/CD2 etc.: parent directory is renamed automatically
e.g. Kärntner_Doppelsextett/ → Du_Berührst_Mi_20_Jahre_Kärntner_Doppelsextett/
- input_dir = album root: a hint with the mv command is printed instead
(avoids renaming an actively used path)
- Existing directory with target name: warning, no rename
Also: _sanitize_filename() in organizer.py made public (sanitize_filename),
used consistently across organizer, playlist and cli modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Catch ValueError (barcode not found) and httpx.HTTPError (network error)
in _scan_to_album and print a user-friendly message with hint instead of
a raw Python traceback. Also remove unused `call` import in test_ripper.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New module musicbrainz.py: lookup_by_barcode() via EAN-13/UPC-12,
two-step API (barcode search → release detail with recordings),
respects 1 req/s rate limit with User-Agent header
- cli.py: scan command gets --barcode option as highest-priority mode
(no images needed); _scan_to_album() dispatches to MusicBrainz first
- ripper.py: interactive_rip() prompts for optional EAN after album name;
MusicBrainz data (incl. year) takes priority over CDDB for album.json;
album_root.mkdir() added so JSON can be written even when MB changes dir
- tests: test_musicbrainz.py (16 tests), test_ripper.py +6 barcode tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- tagger.py: embed_cover() unterstützt jetzt .opus (Vorbis-Comment
METADATA_BLOCK_PICTURE) und .m4a (MP4Cover); imports ergänzt
- test_tagger.py: 2 neue Tests für Opus/M4A; minimale Audio-Fixtures
als base64-Konstanten (176 B Opus, 856 B M4A)
- test_cover.py: TestPrepareCover (5 Tests) und TestCopyCovers (6 Tests)
für prepare_cover() und copy_covers()
- test_ocr.py: 13 Tests für run_ocr(), _detect_and_fix_rotation()
und ocr_images(); Tesseract via subprocess.run gemockt
144 Tests, 0 Fehler
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- tests/test_tagger.py: 20 Tests für tag_file, tag_album,
_scale_cover_for_embed, embed_cover (FLAC + MP3), embed_album_cover
- tests/test_cli.py: 14 Tests für apply (in-place, disc-mismatch,
dry-run, playlist, multi-disc), check und scan (via Mock)
- tagger.py: embed_cover für MP3 fängt ID3NoHeaderError ab und
erstellt einen neuen ID3-Tag wenn keiner vorhanden ist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
cover: find_cover() sucht frontcover.jpg/.png und backcover.jpg/.png;
copy_covers() speichert als frontcover.jpg / backcover.jpg
tagger: embed_album_cover() bettet Frontcover in alle Tracks ein
cli: apply und process rufen embed_album_cover() nach copy_covers() auf
tests: TestFindCover mit 7 Tests (jpg, png, Symlink, Priorität, Negativ)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
organizer: Separator vor Titel angleichen (war: 01_Titel_-_K., neu: 01_-_Titel_-_K.)
playlist: Glob-Pattern und Fallback auf neues Schema angepasst
tests: Assertions aktualisiert
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Track model: add optional artist field (None = fall back to album artist)
- organizer: append _-_<artist> to each filename
- tagger: use track.artist over album.artist for the 'artist' tag
- playlist: widen glob pattern to match new _-_<artist> suffix
- tests: update assertions + add test for track-artist override
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove tests/ directory from version control (added to .gitignore)
- Add .idea/ to .gitignore
- Ripper: CDDB lookup, non-interactive mode, English UI, file renaming
- Config: abcde format mapping, per-format quality options
- CLI: English help texts, new --no-cddb / --pipes / --parallel / --quality options
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Tesseract OCR fails on rotated/low-contrast CD back covers.
New vision_llm module sends images directly to qwen3-vl via
Ollama chat API, bypassing OCR entirely. Robust JSON extraction
handles thinking tags, markdown blocks, and empty responses.
CLI scan/process commands gain --vision flag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>