Skip to content

Instantly share code, notes, and snippets.

@pcewing
Last active December 1, 2022 21:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pcewing/2b5ec2f2a75773f889e845c6b6bd4ec9 to your computer and use it in GitHub Desktop.
Save pcewing/2b5ec2f2a75773f889e845c6b6bd4ec9 to your computer and use it in GitHub Desktop.
Rename MP3 Files
#!/usr/bin/env python3
import os
import mutagen.id3
import unicodedata
import re
import errno
from shutil import copyfile
OUTPUT_FILENAMES = set()
DUPLICATES = set()
def slugify(filename):
"""
Heavily inspired by:
https://stackoverflow.com/questions/295135/turn-a-string-into-a-valid-filename/46801075
"""
filename = str(filename)
# Convert to ASCII
filename = (
unicodedata.normalize("NFKD", filename)
.encode("ascii", "ignore")
.decode("ascii")
)
# Replace ampersands with 'and'
filename = re.sub(r"&", "and", filename)
# Remove characters that aren't alphanumerics, underscores, or hyphens
filename = re.sub(r"[^\w\s-]", "", filename)
# Convert spaces or repeated dashes to single dashes
filename = re.sub(r"[-\s]+", "_", filename)
# Also strip leading and trailing whitespace, dashes, and underscores
return filename.strip("-_")
def is_mp3(filename):
return filename.lower().endswith(".mp3")
def mkdir(path: str) -> None:
"""Make a directory recursively
Functionality should be equivalent to `mkdir -p`.
"""
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST or not os.path.isdir(path):
raise
def process_file(directory, filename):
fullpath = os.path.join(directory, filename)
if not is_mp3(filename):
print("Not an MP3, skipping: {}".format(fullpath))
return
print("Processing file: {}".format(fullpath))
metadata = mutagen.id3.Open(fullpath)
artists = metadata["TPE1"]
title = metadata["TIT2"]
new_filename = "{} {}".format(title, artists)
new_filename = slugify(new_filename)
new_filename = "{}.mp3".format(new_filename)
print("New filename: {}".format(new_filename))
new_directory = "{}.renamed".format(directory)
mkdir(new_directory)
new_fullpath = os.path.join(new_directory, new_filename)
# Detect duplicates so we can warn at the end
global OUTPUT_FILENAMES
global DUPLICATES
if new_fullpath in OUTPUT_FILENAMES:
DUPLICATES.add(new_fullpath)
else:
OUTPUT_FILENAMES.add(new_fullpath)
copyfile(fullpath, new_fullpath)
def process_files(directory):
for root, _, files in os.walk(directory):
for f in files:
process_file(root, f)
def main():
# Download files from beatport and unzip them into a directory structure such as:
# Temp/
# |_ Beatport/
# |_ Song 1.mp3
# |_ Song 2.mp3
#
# The Temp directory should not contain any other mp3 files, either
# directly or in a child directory. Run the script, which will copy the
# folder structure of Beatport into Beatport.renamed, renaming all mp3
# files based on metadata in the ID3 tags.
process_files(".")
global DUPLICATES
for dupe in DUPLICATES:
print(
'WARNING: Duplicate output filename "{}", possible loss of data. This can occur if two files have the same ID3 metadata for title and artist.'.format(
dupe
)
)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment