Last month a friend handed me two drives: about 2,100 tracks ripped from his own CD collection between 2003 and 2011, plus a folder of 480 cue sheets. The audio was all there, but the tags were a mess — most files had no track numbers, many had no titles, and a few hundred had the album artist in the title field. The cue files were the only intact record of how the albums were sequenced. This is a catalog rebuild, not a playlist fix, and cue sheets turned out to be enough to do most of it.
I wrote a small parser rather than trusting a library, because the cue files were inconsistent: some used FILE “track01.wav” WAVE with one file per track, others used a single FILE for the whole album with INDEX 01 marks. The latter is the case that matters, since the cue holds the only reliable timestamps. I normalized each sheet into a table of (album, disc, track_no, title, performer, start_ms).
import re, pathlib
INDEX = re.compile(r'^\s*INDEX\s+01\s+(\d+):(\d+):(\d+)\s*$')
TITLE = re.compile(r'^\s*TITLE\s+"(.*)"\s*$')
PERF = re.compile(r'^\s*PERFORMER\s+"(.*)"\s*$')
def parse_cue(path):
rows, cur = [], {}
for line in pathlib.Path(path).read_text(errors='replace').splitlines():
if m := TITLE.match(line):
if 'track_no' in cur: cur['title'] = m.group(1)
else: cur['album'] = m.group(1)
elif m := PERF.match(line):
cur.setdefault('performer', m.group(1))
elif m := INDEX.match(line):
mm, ss, ff = map(int, m.groups())
cur['start_ms'] = ((mm * 60) + ss) * 1000 + ff * 10
cur['track_no'] = len([r for r in rows if r.get('album') == cur.get('album')]) + 1
rows.append(cur); cur = {k: v for k, v in cur.items() if k in ('album', 'performer')}
return rows
Then I matched each cue album to a directory by fuzzy-comparing the album title and the file count, and wrote the tags back with mutagen. For single-file albums I used ffmpeg with -ss and -to derived from consecutive start_ms values, copying the audio stream (-c:a copy) so nothing was re-encoded.
ffmpeg -ss 00:03:41.20 -to 00:07:12.05 -i album.flac \
-c:a copy -metadata title="Track 04" -metadata track="4/11" out/04.flac
Why cue sheets and not acoustic fingerprinting: fingerprints identify a recording, but they don’t tell you the album sequence or the exact gap between tracks. The cue sheet does both, for free, and it’s already sitting next to the audio. The cost is that cues are only as good as whoever wrote them — I found six albums where the INDEX marks were off by a second or more, and I had to hand-correct those against the audible gap.
Where this breaks: cues with PREGAP or INDEX 00 entries will shift every timestamp if you ignore them, which I did at first and generated about forty tracks with clipped intros. Multi-disc sets that reuse the same FILE name across discs silently overwrite each other in my matcher; I added the parent directory to the key after losing a disc’s worth of tags. And if the cue was written against a different rip than the one you have, timestamps drift — check the total duration before trusting anything.
One thing I still can’t automate: cue sheets rarely carry genre or year, so those fields stayed empty. If you’ve rebuilt a catalog like this, how did you decide whether to trust the cue’s PERFORMER over the directory name when they disagreed?


0 条评论
还没有评论,写下第一条。