Skip to content

Instantly share code, notes, and snippets.

@lambdan
Last active August 16, 2025 08:05
Show Gist options
  • Save lambdan/04e267266908bdc932912d69b9aac429 to your computer and use it in GitHub Desktop.
Save lambdan/04e267266908bdc932912d69b9aac429 to your computer and use it in GitHub Desktop.
Spotify CSV (Exportify) to m3u by matching ISRC
import os, json, csv, sys, subprocess
from tqdm import tqdm
# Export from Spotify using exportify.app, then run this on it when you have the songs!
metaFile = "meta.json"
audioExts = [".mp3", ".flac", ".ogg", ".opus", ".wav", ".m4a", ".aac"]
musicDir = "/Volumes/Media/Music/Organized/" # CHANGE THIS!
playlistBasepath = "../" # CHANGE THIS relative to musicDir from the playlists perspective
if len(sys.argv) < 2:
print("Usage: python csv-to-m3u.py <csv_file>")
print("Example: python csv-to-m3u.py my_playlist.csv")
sys.exit(1)
csvFile = sys.argv[1]
if not os.path.exists(csvFile):
print(f"CSV file {csvFile} does not exist.")
sys.exit(1)
def getMeta(filepath):
try:
fullpath = os.path.abspath(filepath)
if not os.path.exists(fullpath):
print(f"File {fullpath} does not exist.")
sys.exit(1)
cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", fullpath]
called = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
jsonData = json.loads(called.stdout)
isrc = jsonData.get("format", {}).get("tags", {}).get("ISRC", "")
return {
"ext": os.path.splitext(filepath)[1].lower().strip().removeprefix("."),
"isrc": isrc.strip() if isrc else None,
"ffprobe": jsonData
}
except Exception as e:
print(f"Error extracting ISRC from {filepath}: {e}")
return None
def save(data, filename):
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
print("Saved")
seen = set()
metaDict = {}
if os.path.isfile(metaFile):
with open(metaFile, 'r', encoding='utf-8') as f:
metaDict = json.load(f)
seen = set(metaDict.keys())
def buildDict():
for root, dirs, files in tqdm(os.walk(musicDir)):
needToSave = False
for file in tqdm(files):
if any(file.lower().endswith(ext) for ext in audioExts):
filepath = os.path.join(root, file)
if filepath in seen:
continue
metaDict[filepath] = getMeta(filepath)
seen.add(filepath)
needToSave = True
if needToSave:
save(metaDict, metaFile)
# go through folder and build ISRC dict
buildDict()
# check for missing files
needToSave = False
for filepath in tqdm(list(metaDict.keys())):
if not os.path.exists(filepath):
print(f"File {filepath} does not exist, removing from metaDict")
del metaDict[filepath]
seen.remove(filepath)
needToSave = True
if needToSave:
save(metaDict, metaFile)
final = {}
missing = []
with open(csvFile, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # type: ignore
data = [row for row in reader]
print(len(data), "rows found in CSV")
for d in data:
#for k,v in d.items():
#print(f"{k}={v}")
title = d.get("Track Name", "")
artist = d.get("Artist Name(s)", "")
album = d.get("Album Name", "")
album_artist = d.get("Album Artist Name(s)", "")
isrc = d.get("ISRC", "")
key = isrc if isrc else f"{artist} - {title} - {album}"
found = False
for filepath in metaDict:
fp_isrc = metaDict[filepath].get("isrc", "")
# TODO Match by title/artist if ISRC is not available
if fp_isrc and isrc in fp_isrc:
found = True
if key in final and final[key]["ext"] == "flac":
continue # prefer FLAC if available
final[key] = {
"filepath": filepath,
"title": title,
"artist": artist,
"album": album,
"album_artist": album_artist,
"isrc": fp_isrc,
"ext": metaDict[filepath].get("ext", "")
}
if not found:
print("Missing", isrc, title, artist, album, album_artist)
missing.append({
"title": title,
"artist": artist,
"album": album,
"album_artist": album_artist,
"isrc": isrc
})
print("Got ", len(final), "tracks with ISRCs")
out = ""
for key, track in final.items():
basepath = track["filepath"].replace(musicDir, "../")
out += f"#EXTINF:-1,{track['artist']} - {track['title']}\n"
out += f"{basepath}\n"
for m in missing:
out += f"# Missing: {m['artist']} - {m['title']}\n"
#print(out)
outputFile = os.path.splitext(csvFile)[0] + ".m3u"
with open(outputFile, 'w', encoding='utf-8') as f:
f.write(out)
print(f"Output written to {outputFile}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment