Rebuilding a 2,100-track catalog from cue files alone

这篇内容暂时只有 English 版本,以下是原文。

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
赞同度 100% · 0 条评论 · 3 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

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