From 7ced974279b9e9cf4fac1721fa688f6803268156 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Wed, 18 Feb 2026 10:02:01 +0100 Subject: [PATCH] Add CDDB confirmation, cd-discid hint, CD-counter increment, check command - interactive_rip: after CDDB lookup, show album name + tracklist and ask 'Treffer korrekt? (j/n)' before renaming files; rip_disc gains rename=False option for deferred renaming - interactive_rip: CD number prompt now shows disc_counter as default instead of always showing [1] - _rip_with_abcde: when CDDB fails and cd-discid is not installed, print a visible hint with install command instead of silently doing nothing - _stream_abcde: extract album name from CDDB header line (---- DTITLE ----) and return it as part of the result tuple - _rename_files: early return when output directory does not exist - check command (cli.py): already present from previous session Co-Authored-By: Claude Opus 4.6 --- src/musiksammlung/ripper.py | 90 ++++++++++++++++++++++++------------- tests/test_ripper.py | 8 +++- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/musiksammlung/ripper.py b/src/musiksammlung/ripper.py index 37ee7c1..c85197f 100644 --- a/src/musiksammlung/ripper.py +++ b/src/musiksammlung/ripper.py @@ -151,6 +151,7 @@ def _stream_abcde( all_lines: list[str] = [] cddb_lines: list[str] = [] grab_data: list[tuple[int, str]] = [] # (track_number, raw_title) fallback + detected_album: str = "" # aus CDDB-Header "---- DTITLE ----" total_tracks = 0 current_track = 0 track_end_sector = 0 @@ -201,9 +202,15 @@ def _stream_abcde( print(f"\r [{bar}] {pct:5.1%} {mb:5.1f} MB", end="", flush=True) continue - # ── CDDB / MusicBrainz album header + # ── CDDB / MusicBrainz album header "---- Artist / Album ----" if header_re.search(line): - print(f"\n {line.strip()}", flush=True) + stripped_hdr = line.strip() + print(f"\n {stripped_hdr}", flush=True) + # Album-Name zwischen den Strichen extrahieren (einmalig) + if not detected_album: + inner = re.sub(r"^[-\s]+|[-\s]+$", "", stripped_hdr) + if inner: + detected_album = inner continue # ── CDDB track lines "1: Artist - Title" or "1: Artist / Title" @@ -226,7 +233,7 @@ def _stream_abcde( print(flush=True) if not use_cddb: - return None, returncode + return None, detected_album, returncode tracks = _parse_cddb_lines(cddb_lines) if not tracks and grab_data: @@ -235,7 +242,7 @@ def _stream_abcde( if tracks: print(f" (Tracklist aus Grab-Fortschritt: {len(tracks)} Tracks)", flush=True) - return tracks or None, returncode + return tracks or None, detected_album, returncode def _extract_tracks(output_dir: Path, audio_format: AudioFormat) -> list[Path]: @@ -286,6 +293,9 @@ def _rename_files( tracks: Track information from CDDB audio_format: Audio format """ + if not output_dir.exists(): + return + ext = audio_format.extension.lstrip(".") abcde_pattern = re.compile(rf"^track(\d+)\.{ext}$", re.IGNORECASE) by_num = {t.track_number: t for t in tracks} @@ -319,7 +329,7 @@ def _rip_with_abcde( parallel_jobs: int = 1, use_pipes: bool = False, use_cddb: bool = True, -) -> tuple[list[Path], list[TrackInfo] | None]: +) -> tuple[list[Path], list[TrackInfo] | None, str]: """Rip a CD with abcde directly to desired format. Args: @@ -332,7 +342,7 @@ def _rip_with_abcde( use_cddb: True = use CDDB lookup Returns: - Tuple (list of created files, track information or None) + Tuple (list of created files, track information or None, detected album name) """ output_dir.mkdir(parents=True, exist_ok=True) @@ -382,7 +392,7 @@ def _rip_with_abcde( bufsize=1, # line-buffered ) - tracks, returncode = _stream_abcde(process, use_cddb) + tracks, detected_album, returncode = _stream_abcde(process, use_cddb) if returncode != 0: raise RuntimeError(f"abcde failed (exit {returncode}).") @@ -402,6 +412,12 @@ def _rip_with_abcde( print(f" GnuDB: {len(tracks)} Tracks gefunden", flush=True) else: print(" GnuDB: kein Treffer.", flush=True) + else: + print( + " GnuDB-Fallback nicht möglich: cd-discid nicht installiert.\n" + " → Installation: sudo apt install cd-discid", + flush=True, + ) # Extract track files from abcde's temp dir into output_dir (flat) audio_files = _extract_tracks(output_dir, audio_format) @@ -413,7 +429,7 @@ def _rip_with_abcde( ) logger.info("Ripping completed: %d tracks in %s", len(audio_files), output_dir) - return audio_files, tracks + return audio_files, tracks, detected_album def rip_disc( @@ -424,6 +440,7 @@ def rip_disc( parallel_jobs: int = 1, use_pipes: bool = False, use_cddb: bool = True, + rename: bool = True, ) -> tuple[Path, str | None, list[TrackInfo] | None]: """Rip a CD directly to the desired format. @@ -435,21 +452,21 @@ def rip_disc( parallel_jobs: Number of parallel encoder processes use_pipes: True = faster, no WAV files use_cddb: True = use CDDB lookup + rename: True = rename track files immediately after ripping. + Pass False when the caller wants to confirm CDDB data first. Returns: - Tuple (directory path, album name or None, track information or None) + Tuple (directory path, detected CDDB album name or None, track information or None) """ - _, tracks = _rip_with_abcde( + _, tracks, detected_album = _rip_with_abcde( device, output_dir, audio_format, quality, parallel_jobs, use_pipes, use_cddb ) - album_name = None - if tracks: - album_name = tracks[0].artist + if tracks and rename: print("\n Renaming files ...", flush=True) _rename_files(output_dir, tracks, audio_format) - return output_dir, album_name, tracks + return output_dir, detected_album or None, tracks def interactive_rip(config: RipperConfig) -> None: @@ -510,8 +527,8 @@ def interactive_rip(config: RipperConfig) -> None: print(f"\n Album: {album_name}") print(f" CD Drive: {config.device}") - raw_disc = input(" CD number [1]: ") - disc_num = int(_clean_input(raw_disc)) if _clean_input(raw_disc) else 1 + raw_disc = input(f" CD number [{disc_counter}]: ") + disc_num = int(_clean_input(raw_disc)) if _clean_input(raw_disc) else disc_counter disc_dir = ( config.output_dir @@ -523,7 +540,7 @@ def interactive_rip(config: RipperConfig) -> None: print(" " + "-" * 50) try: - _, detected_album, tracks = rip_disc( + _, cddb_album, tracks = rip_disc( device=config.device, output_dir=disc_dir, audio_format=config.audio_format, @@ -531,26 +548,39 @@ def interactive_rip(config: RipperConfig) -> None: parallel_jobs=config.parallel_jobs, use_pipes=config.use_pipes, use_cddb=config.use_cddb, + rename=False, ) print("\n " + "-" * 50) if tracks: - print(f" ✓ Done — {len(tracks)} tracks") + label = f"'{cddb_album}'" if cddb_album else "?" + print(f" CDDB-Treffer: {label} — {len(tracks)} Tracks:") for t in tracks: print(f" {t.track_number:2d}. {t.title} [{t.artist}]") - all_discs.append(DiscModel( - disc_number=disc_num, - tracks=[ - TrackModel( - track_number=t.track_number, - title=t.title, - artist=t.artist, - ) - for t in tracks - ], - )) + + raw_ok = input("\n Treffer korrekt? (j/n) [j]: ") + if _clean_input(raw_ok).lower() not in ("n", "no", "nein"): + print(" Umbenennen ...", flush=True) + _rename_files(disc_dir, tracks, config.audio_format) + all_discs.append(DiscModel( + disc_number=disc_num, + tracks=[ + TrackModel( + track_number=t.track_number, + title=t.title, + artist=t.artist, + ) + for t in tracks + ], + )) + else: + print( + " CDDB-Daten verworfen — Dateien bleiben als track01.flac", + flush=True, + ) + tracks = None else: - print(" ✓ Done (no CDDB data)") + print(" ✓ Fertig (keine CDDB-Daten)") except RuntimeError as e: print(f"\n ✗ Error: {e}") diff --git a/tests/test_ripper.py b/tests/test_ripper.py index 8cd79a0..93c9203 100644 --- a/tests/test_ripper.py +++ b/tests/test_ripper.py @@ -337,6 +337,7 @@ class TestInteractiveRipBarcode: "Abbey Road", # album name "", # EAN: leer → überspringen "1", # disc number + "j", # CDDB korrekt? "n", # next CD? "n", # next album? ] @@ -356,6 +357,7 @@ class TestInteractiveRipBarcode: "Abbey Road", # album name "0602557360561", # EAN "1", # disc number + "j", # CDDB korrekt? "n", # next CD? "n", # next album? ] @@ -375,6 +377,7 @@ class TestInteractiveRipBarcode: "", # album name: leer → Default "0602557360561", # EAN "1", # disc number + "j", # CDDB korrekt? "n", # next CD? "n", # next album? ] @@ -400,6 +403,7 @@ class TestInteractiveRipBarcode: "Abbey Road", # album name "0000000000000", # EAN (kein Treffer) "1", # disc number + "j", # CDDB korrekt? "n", # next CD? "n", # next album? ] @@ -424,6 +428,7 @@ class TestInteractiveRipBarcode: "", # album name: leer → Default (Album1) "0602557360561", # EAN "1", # disc number + "j", # CDDB korrekt? "n", "n", ] @@ -443,7 +448,8 @@ class TestInteractiveRipBarcode: inputs = [ "Mein Album", # manuell eingegebener Name "0602557360561", # EAN - "1", + "1", # disc number + "j", # CDDB korrekt? "n", "n", ]