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 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-18 10:02:01 +01:00
commit 7ced974279
2 changed files with 67 additions and 31 deletions

View file

@ -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,13 +548,20 @@ 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}]")
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=[
@ -550,7 +574,13 @@ def interactive_rip(config: RipperConfig) -> None:
],
))
else:
print(" ✓ Done (no CDDB data)")
print(
" CDDB-Daten verworfen — Dateien bleiben als track01.flac",
flush=True,
)
tracks = None
else:
print(" ✓ Fertig (keine CDDB-Daten)")
except RuntimeError as e:
print(f"\n ✗ Error: {e}")

View file

@ -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",
]