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:
parent
2f80cb2693
commit
7ced974279
2 changed files with 67 additions and 31 deletions
|
|
@ -151,6 +151,7 @@ def _stream_abcde(
|
||||||
all_lines: list[str] = []
|
all_lines: list[str] = []
|
||||||
cddb_lines: list[str] = []
|
cddb_lines: list[str] = []
|
||||||
grab_data: list[tuple[int, str]] = [] # (track_number, raw_title) fallback
|
grab_data: list[tuple[int, str]] = [] # (track_number, raw_title) fallback
|
||||||
|
detected_album: str = "" # aus CDDB-Header "---- DTITLE ----"
|
||||||
total_tracks = 0
|
total_tracks = 0
|
||||||
current_track = 0
|
current_track = 0
|
||||||
track_end_sector = 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)
|
print(f"\r [{bar}] {pct:5.1%} {mb:5.1f} MB", end="", flush=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# ── CDDB / MusicBrainz album header
|
# ── CDDB / MusicBrainz album header "---- Artist / Album ----"
|
||||||
if header_re.search(line):
|
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
|
continue
|
||||||
|
|
||||||
# ── CDDB track lines "1: Artist - Title" or "1: Artist / Title"
|
# ── CDDB track lines "1: Artist - Title" or "1: Artist / Title"
|
||||||
|
|
@ -226,7 +233,7 @@ def _stream_abcde(
|
||||||
print(flush=True)
|
print(flush=True)
|
||||||
|
|
||||||
if not use_cddb:
|
if not use_cddb:
|
||||||
return None, returncode
|
return None, detected_album, returncode
|
||||||
|
|
||||||
tracks = _parse_cddb_lines(cddb_lines)
|
tracks = _parse_cddb_lines(cddb_lines)
|
||||||
if not tracks and grab_data:
|
if not tracks and grab_data:
|
||||||
|
|
@ -235,7 +242,7 @@ def _stream_abcde(
|
||||||
if tracks:
|
if tracks:
|
||||||
print(f" (Tracklist aus Grab-Fortschritt: {len(tracks)} Tracks)", flush=True)
|
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]:
|
def _extract_tracks(output_dir: Path, audio_format: AudioFormat) -> list[Path]:
|
||||||
|
|
@ -286,6 +293,9 @@ def _rename_files(
|
||||||
tracks: Track information from CDDB
|
tracks: Track information from CDDB
|
||||||
audio_format: Audio format
|
audio_format: Audio format
|
||||||
"""
|
"""
|
||||||
|
if not output_dir.exists():
|
||||||
|
return
|
||||||
|
|
||||||
ext = audio_format.extension.lstrip(".")
|
ext = audio_format.extension.lstrip(".")
|
||||||
abcde_pattern = re.compile(rf"^track(\d+)\.{ext}$", re.IGNORECASE)
|
abcde_pattern = re.compile(rf"^track(\d+)\.{ext}$", re.IGNORECASE)
|
||||||
by_num = {t.track_number: t for t in tracks}
|
by_num = {t.track_number: t for t in tracks}
|
||||||
|
|
@ -319,7 +329,7 @@ def _rip_with_abcde(
|
||||||
parallel_jobs: int = 1,
|
parallel_jobs: int = 1,
|
||||||
use_pipes: bool = False,
|
use_pipes: bool = False,
|
||||||
use_cddb: bool = True,
|
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.
|
"""Rip a CD with abcde directly to desired format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -332,7 +342,7 @@ def _rip_with_abcde(
|
||||||
use_cddb: True = use CDDB lookup
|
use_cddb: True = use CDDB lookup
|
||||||
|
|
||||||
Returns:
|
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)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
@ -382,7 +392,7 @@ def _rip_with_abcde(
|
||||||
bufsize=1, # line-buffered
|
bufsize=1, # line-buffered
|
||||||
)
|
)
|
||||||
|
|
||||||
tracks, returncode = _stream_abcde(process, use_cddb)
|
tracks, detected_album, returncode = _stream_abcde(process, use_cddb)
|
||||||
|
|
||||||
if returncode != 0:
|
if returncode != 0:
|
||||||
raise RuntimeError(f"abcde failed (exit {returncode}).")
|
raise RuntimeError(f"abcde failed (exit {returncode}).")
|
||||||
|
|
@ -402,6 +412,12 @@ def _rip_with_abcde(
|
||||||
print(f" GnuDB: {len(tracks)} Tracks gefunden", flush=True)
|
print(f" GnuDB: {len(tracks)} Tracks gefunden", flush=True)
|
||||||
else:
|
else:
|
||||||
print(" GnuDB: kein Treffer.", flush=True)
|
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)
|
# Extract track files from abcde's temp dir into output_dir (flat)
|
||||||
audio_files = _extract_tracks(output_dir, audio_format)
|
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)
|
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(
|
def rip_disc(
|
||||||
|
|
@ -424,6 +440,7 @@ def rip_disc(
|
||||||
parallel_jobs: int = 1,
|
parallel_jobs: int = 1,
|
||||||
use_pipes: bool = False,
|
use_pipes: bool = False,
|
||||||
use_cddb: bool = True,
|
use_cddb: bool = True,
|
||||||
|
rename: bool = True,
|
||||||
) -> tuple[Path, str | None, list[TrackInfo] | None]:
|
) -> tuple[Path, str | None, list[TrackInfo] | None]:
|
||||||
"""Rip a CD directly to the desired format.
|
"""Rip a CD directly to the desired format.
|
||||||
|
|
||||||
|
|
@ -435,21 +452,21 @@ def rip_disc(
|
||||||
parallel_jobs: Number of parallel encoder processes
|
parallel_jobs: Number of parallel encoder processes
|
||||||
use_pipes: True = faster, no WAV files
|
use_pipes: True = faster, no WAV files
|
||||||
use_cddb: True = use CDDB lookup
|
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:
|
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
|
device, output_dir, audio_format, quality, parallel_jobs, use_pipes, use_cddb
|
||||||
)
|
)
|
||||||
|
|
||||||
album_name = None
|
if tracks and rename:
|
||||||
if tracks:
|
|
||||||
album_name = tracks[0].artist
|
|
||||||
print("\n Renaming files ...", flush=True)
|
print("\n Renaming files ...", flush=True)
|
||||||
_rename_files(output_dir, tracks, audio_format)
|
_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:
|
def interactive_rip(config: RipperConfig) -> None:
|
||||||
|
|
@ -510,8 +527,8 @@ def interactive_rip(config: RipperConfig) -> None:
|
||||||
print(f"\n Album: {album_name}")
|
print(f"\n Album: {album_name}")
|
||||||
print(f" CD Drive: {config.device}")
|
print(f" CD Drive: {config.device}")
|
||||||
|
|
||||||
raw_disc = input(" CD number [1]: ")
|
raw_disc = input(f" CD number [{disc_counter}]: ")
|
||||||
disc_num = int(_clean_input(raw_disc)) if _clean_input(raw_disc) else 1
|
disc_num = int(_clean_input(raw_disc)) if _clean_input(raw_disc) else disc_counter
|
||||||
|
|
||||||
disc_dir = (
|
disc_dir = (
|
||||||
config.output_dir
|
config.output_dir
|
||||||
|
|
@ -523,7 +540,7 @@ def interactive_rip(config: RipperConfig) -> None:
|
||||||
print(" " + "-" * 50)
|
print(" " + "-" * 50)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_, detected_album, tracks = rip_disc(
|
_, cddb_album, tracks = rip_disc(
|
||||||
device=config.device,
|
device=config.device,
|
||||||
output_dir=disc_dir,
|
output_dir=disc_dir,
|
||||||
audio_format=config.audio_format,
|
audio_format=config.audio_format,
|
||||||
|
|
@ -531,13 +548,20 @@ def interactive_rip(config: RipperConfig) -> None:
|
||||||
parallel_jobs=config.parallel_jobs,
|
parallel_jobs=config.parallel_jobs,
|
||||||
use_pipes=config.use_pipes,
|
use_pipes=config.use_pipes,
|
||||||
use_cddb=config.use_cddb,
|
use_cddb=config.use_cddb,
|
||||||
|
rename=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
print("\n " + "-" * 50)
|
print("\n " + "-" * 50)
|
||||||
if tracks:
|
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:
|
for t in tracks:
|
||||||
print(f" {t.track_number:2d}. {t.title} [{t.artist}]")
|
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(
|
all_discs.append(DiscModel(
|
||||||
disc_number=disc_num,
|
disc_number=disc_num,
|
||||||
tracks=[
|
tracks=[
|
||||||
|
|
@ -550,7 +574,13 @@ def interactive_rip(config: RipperConfig) -> None:
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
else:
|
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:
|
except RuntimeError as e:
|
||||||
print(f"\n ✗ Error: {e}")
|
print(f"\n ✗ Error: {e}")
|
||||||
|
|
|
||||||
|
|
@ -337,6 +337,7 @@ class TestInteractiveRipBarcode:
|
||||||
"Abbey Road", # album name
|
"Abbey Road", # album name
|
||||||
"", # EAN: leer → überspringen
|
"", # EAN: leer → überspringen
|
||||||
"1", # disc number
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n", # next CD?
|
"n", # next CD?
|
||||||
"n", # next album?
|
"n", # next album?
|
||||||
]
|
]
|
||||||
|
|
@ -356,6 +357,7 @@ class TestInteractiveRipBarcode:
|
||||||
"Abbey Road", # album name
|
"Abbey Road", # album name
|
||||||
"0602557360561", # EAN
|
"0602557360561", # EAN
|
||||||
"1", # disc number
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n", # next CD?
|
"n", # next CD?
|
||||||
"n", # next album?
|
"n", # next album?
|
||||||
]
|
]
|
||||||
|
|
@ -375,6 +377,7 @@ class TestInteractiveRipBarcode:
|
||||||
"", # album name: leer → Default
|
"", # album name: leer → Default
|
||||||
"0602557360561", # EAN
|
"0602557360561", # EAN
|
||||||
"1", # disc number
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n", # next CD?
|
"n", # next CD?
|
||||||
"n", # next album?
|
"n", # next album?
|
||||||
]
|
]
|
||||||
|
|
@ -400,6 +403,7 @@ class TestInteractiveRipBarcode:
|
||||||
"Abbey Road", # album name
|
"Abbey Road", # album name
|
||||||
"0000000000000", # EAN (kein Treffer)
|
"0000000000000", # EAN (kein Treffer)
|
||||||
"1", # disc number
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n", # next CD?
|
"n", # next CD?
|
||||||
"n", # next album?
|
"n", # next album?
|
||||||
]
|
]
|
||||||
|
|
@ -424,6 +428,7 @@ class TestInteractiveRipBarcode:
|
||||||
"", # album name: leer → Default (Album1)
|
"", # album name: leer → Default (Album1)
|
||||||
"0602557360561", # EAN
|
"0602557360561", # EAN
|
||||||
"1", # disc number
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n",
|
"n",
|
||||||
"n",
|
"n",
|
||||||
]
|
]
|
||||||
|
|
@ -443,7 +448,8 @@ class TestInteractiveRipBarcode:
|
||||||
inputs = [
|
inputs = [
|
||||||
"Mein Album", # manuell eingegebener Name
|
"Mein Album", # manuell eingegebener Name
|
||||||
"0602557360561", # EAN
|
"0602557360561", # EAN
|
||||||
"1",
|
"1", # disc number
|
||||||
|
"j", # CDDB korrekt?
|
||||||
"n",
|
"n",
|
||||||
"n",
|
"n",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue