Rebuilding a 3,800-track playlist after the streaming export lost its order

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

A friend handed me a 3,800-track playlist export from a streaming service. CSV, one row per track: title, artist, album, duration, and a “position” column. The position column was the problem. It ran 1 to 3,800, but roughly 400 rows had duplicate positions and 90 had gaps, because the export merged two saved versions of the same playlist and never renumbered. On disk the tracks were fine; the order was fiction. Every attempt to import it back produced a different sequence each time.

I did not trust the position column. What I trusted was the duration. I dumped the durations in export order and looked for the places where the running sum jumped or repeated.

import csv

rows = list(csv.DictReader(open('playlist.csv')))
pos = [int(r['position']) for r in rows]
dur = [int(r['duration_ms']) for r in rows]

seen = {}
for i, p in enumerate(pos):
    seen.setdefault(p, []).append(i)

dupes = {p: idx for p, idx in seen.items() if len(idx) > 1}
print(len(dupes), 'duplicated positions')

That gave me the collision points. For each duplicate position, I compared the two (or three) candidates by duration against the neighbors I already trusted: if position 1,204 had three claimants, the one whose duration kept the running total smooth against 1,203 and 1,205 was the original. The rest were inserts from the second saved version. I kept them but pushed them after the anchor track, not before it, because in my friend’s listening habit a re-added song tends to land at the end of a block, not the start.

def resolve(cands, prev_ms, next_ms):
    # pick the candidate whose duration fits the gap best
    return min(cands, key=lambda c: abs((prev_ms + c['duration_ms']) - next_ms))

The mechanism is boring: a playlist export is a lossy projection of internal state. Position is a display artifact and gets rewritten; duration is derived from the audio file and is stable across exports. So I treated duration as the primary key and position as a hint. The cost is that any track whose duration was re-measured between exports (remasters, re-encodes) will mislead the resolver, and two tracks of identical length in the same block are genuinely ambiguous. I hit both: three duplicate-duration pairs, and I fixed those by hand against the original service UI.

Where this breaks: if the export lacks millisecond precision (some only give mm:ss), the gap-matching tolerance has to grow and you start accepting wrong candidates. If the playlist contains gapless album rips where the export rounded each track independently, the running sum drifts by a second per ten tracks and the resolver silently picks the wrong duplicate. And if two saved versions were merged at a boundary rather than interleaved, there is no local signal at all; you need the full list from the service, which usually means scraping the web player and hoping the DOM order is real.

What I still do not know: do you trust the service’s own order when you export, or do you re-derive it from durations like I did? I have a feeling people who make long mixes care about this more than people who curate short playlists, and I would like to know which camp is right.

0
赞同度 100% · 0 条评论 · 4 阅读

0 条评论

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

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