Fix file extraction: don't use abcde move, extract from temp dir ourselves
abcde's move action + OUTPUTFORMAT config failed because shell variables
like ${TRACKNUM} are evaluated immediately when the config is sourced.
Instead: skip move, search abcde's internal temp dir (abcde.XXXX/trackNN.flac)
and move files flat into output_dir ourselves.
- Replace _get_audio_files/_write_abcde_config with _extract_tracks()
- _rename_files() now matches track01.flac pattern (abcde naming)
- Fallback rename to 01.flac etc. when no CDDB data available
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
096be97ba8
commit
f2d3684956
1 changed files with 44 additions and 56 deletions
|
|
@ -102,51 +102,37 @@ def _parse_cddb_response(output: str) -> list[TrackInfo]:
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
def _get_audio_files(output_dir: Path, audio_format: AudioFormat) -> list[Path]:
|
def _extract_tracks(output_dir: Path, audio_format: AudioFormat) -> list[Path]:
|
||||||
"""Find all audio files in directory recursively (case-insensitive).
|
"""Find abcde track files recursively and move them flat into output_dir.
|
||||||
|
|
||||||
|
abcde stores encoded files inside its temp dir as:
|
||||||
|
output_dir/abcde.XXXX/track01.flac
|
||||||
|
output_dir/abcde.XXXX/track02.flac ...
|
||||||
|
|
||||||
|
This function moves them to:
|
||||||
|
output_dir/track01.flac
|
||||||
|
output_dir/track02.flac ...
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_dir: Target directory
|
output_dir: Directory to search and target for flat layout
|
||||||
audio_format: Audio format
|
audio_format: Audio format
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Sorted list of found files
|
Sorted list of moved files in output_dir
|
||||||
"""
|
"""
|
||||||
# Regex pattern for case-insensitive search
|
|
||||||
ext = audio_format.extension.lstrip(".")
|
ext = audio_format.extension.lstrip(".")
|
||||||
pattern = re.compile(rf".*\.{ext}$", re.IGNORECASE)
|
# abcde names files trackNN.ext (with -p: track01, track02, ...)
|
||||||
|
pattern = re.compile(rf"^track(\d+)\.{ext}$", re.IGNORECASE)
|
||||||
|
|
||||||
audio_files = []
|
moved = []
|
||||||
# rglob: search recursively so abcde subdirs are also covered
|
for file in sorted(output_dir.rglob("*")):
|
||||||
for file in output_dir.rglob("*"):
|
|
||||||
if file.is_file() and pattern.match(file.name):
|
if file.is_file() and pattern.match(file.name):
|
||||||
audio_files.append(file)
|
dest = output_dir / file.name
|
||||||
|
if file != dest:
|
||||||
|
file.rename(dest)
|
||||||
|
moved.append(dest)
|
||||||
|
|
||||||
return sorted(audio_files)
|
return moved
|
||||||
|
|
||||||
|
|
||||||
def _write_abcde_config(output_dir: Path) -> Path:
|
|
||||||
"""Write a temporary abcde config file.
|
|
||||||
|
|
||||||
Sets OUTPUTDIR to output_dir and uses a flat filename format
|
|
||||||
(track number only) so we can rename files ourselves afterward.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
output_dir: Directory where encoded files should be placed
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Path to the config file
|
|
||||||
"""
|
|
||||||
config = f"""\
|
|
||||||
OUTPUTDIR="{output_dir}"
|
|
||||||
OUTPUTFORMAT="${{TRACKNUM}}"
|
|
||||||
VAOUTPUTFORMAT="${{TRACKNUM}}"
|
|
||||||
ONETRACKOUTPUTFORMAT="${{TRACKNUM}}"
|
|
||||||
PLAYLISTFORMAT=""
|
|
||||||
"""
|
|
||||||
config_path = output_dir / ".abcde.conf"
|
|
||||||
config_path.write_text(config, encoding="utf-8")
|
|
||||||
return config_path
|
|
||||||
|
|
||||||
|
|
||||||
def _rename_files(
|
def _rename_files(
|
||||||
|
|
@ -154,26 +140,26 @@ def _rename_files(
|
||||||
tracks: list[TrackInfo],
|
tracks: list[TrackInfo],
|
||||||
audio_format: AudioFormat,
|
audio_format: AudioFormat,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Rename files according to naming scheme.
|
"""Rename track files according to naming scheme.
|
||||||
|
|
||||||
Format: <two-digit track_number>_-_title_-_artist.extension
|
Expected input: track01.flac, track02.flac, ...
|
||||||
|
Output: 01_-_title_-_artist.flac, 02_-_title_-_artist.flac, ...
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_dir: Directory with files
|
output_dir: Directory with files
|
||||||
tracks: Track information
|
tracks: Track information from CDDB
|
||||||
audio_format: Audio format
|
audio_format: Audio format
|
||||||
"""
|
"""
|
||||||
audio_files = _get_audio_files(output_dir, audio_format)
|
ext = audio_format.extension.lstrip(".")
|
||||||
|
# Matches track01.flac, track02.flac, ... (abcde naming)
|
||||||
|
abcde_pattern = re.compile(rf"^track(\d+)\.{ext}$", re.IGNORECASE)
|
||||||
|
|
||||||
# Pattern for abcde filenames: 01, 02, ..., 10, 11, ...
|
audio_files = sorted(output_dir.glob(f"track*.{ext}"))
|
||||||
abcde_pattern = re.compile(r"^(\d+)\.")
|
|
||||||
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
# Find matching file
|
|
||||||
for file in audio_files:
|
for file in audio_files:
|
||||||
match = abcde_pattern.match(file.name)
|
match = abcde_pattern.match(file.name)
|
||||||
if match and int(match.group(1)) == track.track_number:
|
if match and int(match.group(1)) == track.track_number:
|
||||||
# New name: <two-digit track_number>_-_title_-_artist.extension
|
|
||||||
track_num_padded = f"{track.track_number:02d}"
|
track_num_padded = f"{track.track_number:02d}"
|
||||||
artist_clean = _sanitize_name(track.artist)
|
artist_clean = _sanitize_name(track.artist)
|
||||||
title_clean = _sanitize_name(track.title)
|
title_clean = _sanitize_name(track.title)
|
||||||
|
|
@ -181,14 +167,22 @@ def _rename_files(
|
||||||
f"{track_num_padded}_-_{title_clean}_-_"
|
f"{track_num_padded}_-_{title_clean}_-_"
|
||||||
f"{artist_clean}{audio_format.extension}"
|
f"{artist_clean}{audio_format.extension}"
|
||||||
)
|
)
|
||||||
|
|
||||||
new_path = output_dir / new_name
|
new_path = output_dir / new_name
|
||||||
|
|
||||||
if file != new_path:
|
if file != new_path:
|
||||||
logger.info("Renaming: %s -> %s", file.name, new_name)
|
logger.info("Renaming: %s -> %s", file.name, new_name)
|
||||||
file.rename(new_path)
|
file.rename(new_path)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Rename remaining track files without CDDB info (fallback: 01.flac, ...)
|
||||||
|
for file in sorted(output_dir.glob(f"track*.{ext}")):
|
||||||
|
match = abcde_pattern.match(file.name)
|
||||||
|
if match:
|
||||||
|
num = int(match.group(1))
|
||||||
|
new_path = output_dir / f"{num:02d}{audio_format.extension}"
|
||||||
|
if file != new_path:
|
||||||
|
logger.info("Renaming (no CDDB): %s -> %s", file.name, new_path.name)
|
||||||
|
file.rename(new_path)
|
||||||
|
|
||||||
|
|
||||||
def _rip_with_abcde(
|
def _rip_with_abcde(
|
||||||
device: str,
|
device: str,
|
||||||
|
|
@ -215,12 +209,8 @@ def _rip_with_abcde(
|
||||||
"""
|
"""
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Write abcde config: controls OUTPUTDIR and flat filename format
|
|
||||||
config_path = _write_abcde_config(output_dir)
|
|
||||||
|
|
||||||
# abcde options:
|
# abcde options:
|
||||||
# -c config: use our config (OUTPUTDIR, OUTPUTFORMAT)
|
# -a actions: cddb+read+encode+tag (no 'move' — we extract files ourselves)
|
||||||
# -a actions: cddb+read+encode+tag+move, or read+encode+move
|
|
||||||
# -p: pad track numbers with zeros
|
# -p: pad track numbers with zeros
|
||||||
# -o format: output format
|
# -o format: output format
|
||||||
# -d device: CD drive
|
# -d device: CD drive
|
||||||
|
|
@ -228,7 +218,6 @@ def _rip_with_abcde(
|
||||||
# -N: non-interactive (no prompts, auto-select first CDDB match)
|
# -N: non-interactive (no prompts, auto-select first CDDB match)
|
||||||
cmd = [
|
cmd = [
|
||||||
"abcde",
|
"abcde",
|
||||||
"-c", str(config_path),
|
|
||||||
"-p",
|
"-p",
|
||||||
"-o", audio_format.get_abcde_format(),
|
"-o", audio_format.get_abcde_format(),
|
||||||
"-d", device,
|
"-d", device,
|
||||||
|
|
@ -236,11 +225,10 @@ def _rip_with_abcde(
|
||||||
"-N",
|
"-N",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Actions — move is required so files land in OUTPUTDIR
|
|
||||||
if use_cddb:
|
if use_cddb:
|
||||||
cmd.extend(["-a", "cddb,read,encode,tag,move"])
|
cmd.extend(["-a", "cddb,read,encode,tag"])
|
||||||
else:
|
else:
|
||||||
cmd.extend(["-a", "read,encode,move"])
|
cmd.extend(["-a", "read,encode"])
|
||||||
|
|
||||||
# Parallel encodes
|
# Parallel encodes
|
||||||
if parallel_jobs > 1:
|
if parallel_jobs > 1:
|
||||||
|
|
@ -290,8 +278,8 @@ def _rip_with_abcde(
|
||||||
if tracks:
|
if tracks:
|
||||||
logger.info("CDDB data found: %d tracks", len(tracks))
|
logger.info("CDDB data found: %d tracks", len(tracks))
|
||||||
|
|
||||||
# Find files (case-insensitive, recursive)
|
# Extract track files from abcde's temp dir into output_dir (flat)
|
||||||
audio_files = _get_audio_files(output_dir, audio_format)
|
audio_files = _extract_tracks(output_dir, audio_format)
|
||||||
|
|
||||||
if not audio_files:
|
if not audio_files:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue