Add MusicBrainz barcode lookup (scan --barcode and interactive rip)
- 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>
This commit is contained in:
parent
6aba30c0e5
commit
b30aaa617d
5 changed files with 552 additions and 7 deletions
|
|
@ -14,6 +14,7 @@ from musiksammlung.config import AudioFormat
|
|||
from musiksammlung.models import Album as AlbumModel
|
||||
from musiksammlung.models import Disc as DiscModel
|
||||
from musiksammlung.models import Track as TrackModel
|
||||
from musiksammlung.musicbrainz import lookup_by_barcode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -435,6 +436,26 @@ def interactive_rip(config: RipperConfig) -> None:
|
|||
if not album_name:
|
||||
album_name = f"Album{album_counter}"
|
||||
|
||||
# Optional: EAN/Barcode für MusicBrainz-Lookup
|
||||
raw_ean = input("EAN/Barcode für MusicBrainz (Enter = überspringen): ")
|
||||
ean = _clean_input(raw_ean)
|
||||
mb_album: AlbumModel | None = None
|
||||
if ean:
|
||||
try:
|
||||
print(f" MusicBrainz-Suche nach Barcode {ean} ...", flush=True)
|
||||
mb_album = lookup_by_barcode(ean)
|
||||
print(
|
||||
f" ✓ {mb_album.artist} – {mb_album.album}"
|
||||
f" ({mb_album.year or '?'},"
|
||||
f" {sum(len(d.tracks) for d in mb_album.discs)} Tracks)",
|
||||
flush=True,
|
||||
)
|
||||
# Albumnamen aus MusicBrainz übernehmen, wenn nicht manuell gesetzt
|
||||
if album_name == f"Album{album_counter}":
|
||||
album_name = mb_album.album or album_name
|
||||
except Exception as e:
|
||||
print(f" MusicBrainz: kein Treffer — {e}", flush=True)
|
||||
|
||||
disc_counter = 1
|
||||
all_discs: list[DiscModel] = []
|
||||
|
||||
|
|
@ -498,10 +519,20 @@ def interactive_rip(config: RipperConfig) -> None:
|
|||
|
||||
disc_counter += 1
|
||||
|
||||
if all_discs:
|
||||
if mb_album:
|
||||
# MusicBrainz-Daten haben Priorität (inkl. Jahr, kuratierte Titel)
|
||||
album_model = mb_album
|
||||
album_root = config.output_dir / _sanitize_name(mb_album.album or album_name)
|
||||
elif all_discs:
|
||||
artist = all_discs[0].tracks[0].artist or album_name
|
||||
album_model = AlbumModel(artist=artist, album=album_name, discs=all_discs)
|
||||
album_root = config.output_dir / _sanitize_name(album_name)
|
||||
else:
|
||||
album_root = config.output_dir / _sanitize_name(album_name)
|
||||
album_model = None
|
||||
|
||||
if album_model is not None:
|
||||
album_root.mkdir(parents=True, exist_ok=True)
|
||||
json_path = album_root / "album.json"
|
||||
json_path.write_text(
|
||||
album_model.model_dump_json(indent=2), encoding="utf-8"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue