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
|
||||
|
||||
|
||||
def _get_audio_files(output_dir: Path, audio_format: AudioFormat) -> list[Path]:
|
||||
"""Find all audio files in directory recursively (case-insensitive).
|
||||
def _extract_tracks(output_dir: Path, audio_format: AudioFormat) -> list[Path]:
|
||||
"""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:
|
||||
output_dir: Target directory
|
||||
output_dir: Directory to search and target for flat layout
|
||||
audio_format: Audio format
|
||||
|
||||
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(".")
|
||||
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 = []
|
||||
# rglob: search recursively so abcde subdirs are also covered
|
||||
for file in output_dir.rglob("*"):
|
||||
moved = []
|
||||
for file in sorted(output_dir.rglob("*")):
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
return moved
|
||||
|
||||
|
||||
def _rename_files(
|
||||
|
|
@ -154,26 +140,26 @@ def _rename_files(
|
|||
tracks: list[TrackInfo],
|
||||
audio_format: AudioFormat,
|
||||
) -> 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:
|
||||
output_dir: Directory with files
|
||||
tracks: Track information
|
||||
tracks: Track information from CDDB
|
||||
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, ...
|
||||
abcde_pattern = re.compile(r"^(\d+)\.")
|
||||
audio_files = sorted(output_dir.glob(f"track*.{ext}"))
|
||||
|
||||
for track in tracks:
|
||||
# Find matching file
|
||||
for file in audio_files:
|
||||
match = abcde_pattern.match(file.name)
|
||||
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}"
|
||||
artist_clean = _sanitize_name(track.artist)
|
||||
title_clean = _sanitize_name(track.title)
|
||||
|
|
@ -181,14 +167,22 @@ def _rename_files(
|
|||
f"{track_num_padded}_-_{title_clean}_-_"
|
||||
f"{artist_clean}{audio_format.extension}"
|
||||
)
|
||||
|
||||
new_path = output_dir / new_name
|
||||
|
||||
if file != new_path:
|
||||
logger.info("Renaming: %s -> %s", file.name, new_name)
|
||||
file.rename(new_path)
|
||||
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(
|
||||
device: str,
|
||||
|
|
@ -215,12 +209,8 @@ def _rip_with_abcde(
|
|||
"""
|
||||
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:
|
||||
# -c config: use our config (OUTPUTDIR, OUTPUTFORMAT)
|
||||
# -a actions: cddb+read+encode+tag+move, or read+encode+move
|
||||
# -a actions: cddb+read+encode+tag (no 'move' — we extract files ourselves)
|
||||
# -p: pad track numbers with zeros
|
||||
# -o format: output format
|
||||
# -d device: CD drive
|
||||
|
|
@ -228,7 +218,6 @@ def _rip_with_abcde(
|
|||
# -N: non-interactive (no prompts, auto-select first CDDB match)
|
||||
cmd = [
|
||||
"abcde",
|
||||
"-c", str(config_path),
|
||||
"-p",
|
||||
"-o", audio_format.get_abcde_format(),
|
||||
"-d", device,
|
||||
|
|
@ -236,11 +225,10 @@ def _rip_with_abcde(
|
|||
"-N",
|
||||
]
|
||||
|
||||
# Actions — move is required so files land in OUTPUTDIR
|
||||
if use_cddb:
|
||||
cmd.extend(["-a", "cddb,read,encode,tag,move"])
|
||||
cmd.extend(["-a", "cddb,read,encode,tag"])
|
||||
else:
|
||||
cmd.extend(["-a", "read,encode,move"])
|
||||
cmd.extend(["-a", "read,encode"])
|
||||
|
||||
# Parallel encodes
|
||||
if parallel_jobs > 1:
|
||||
|
|
@ -290,8 +278,8 @@ def _rip_with_abcde(
|
|||
if tracks:
|
||||
logger.info("CDDB data found: %d tracks", len(tracks))
|
||||
|
||||
# Find files (case-insensitive, recursive)
|
||||
audio_files = _get_audio_files(output_dir, audio_format)
|
||||
# Extract track files from abcde's temp dir into output_dir (flat)
|
||||
audio_files = _extract_tracks(output_dir, audio_format)
|
||||
|
||||
if not audio_files:
|
||||
raise RuntimeError(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue