Skip to content

Instantly share code, notes, and snippets.

@paigeadelethompson
Created March 8, 2026 22:08
Show Gist options
  • Select an option

  • Save paigeadelethompson/4554cc80179306ca5aff3982f089355f to your computer and use it in GitHub Desktop.

Select an option

Save paigeadelethompson/4554cc80179306ca5aff3982f089355f to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import os
import sys
import warnings
import numpy as np
import librosa
from mutagen.easyid3 import EasyID3
# Suppress librosa UserWarnings and FutureWarnings
warnings.filterwarnings("ignore", category=UserWarning)
warnings.filterwarnings("ignore", category=FutureWarning)
def detect_bpm(file_path):
"""Detect BPM of an audio file using librosa."""
try:
y, sr = librosa.load(file_path, sr=None)
except Exception as e:
print(f"[ERROR] Failed to load '{file_path}': {e}")
return None
if y is None or y.size == 0:
print(f"[SKIP] '{file_path}' contains no audio data.")
return None
try:
tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
# Handle cases where tempo is returned as an array
if isinstance(tempo, np.ndarray):
tempo = float(tempo[0]) if tempo.size > 0 else None
if tempo is None or tempo <= 0:
print(f"[SKIP] Could not detect BPM for '{file_path}'")
return None
bpm = int(np.round(tempo))
return bpm
except Exception as e:
print(f"[ERROR] BPM detection failed for '{file_path}': {e}")
return None
def write_bpm_to_id3(file_path, bpm):
"""Write BPM to the ID3 tags of an MP3 file."""
try:
audio = EasyID3(file_path)
except Exception:
# Create new ID3 tags if none exist
from mutagen.id3 import ID3
audio = ID3()
audio.save(file_path)
audio = EasyID3(file_path)
try:
audio['bpm'] = str(bpm)
audio.save()
print(f"[OK] BPM {bpm} written to '{file_path}'")
except Exception as e:
print(f"[ERROR] Failed to write BPM to '{file_path}': {e}")
def process_file(file_path):
"""Process a single MP3 file."""
if not file_path.lower().endswith('.mp3'):
print(f"[SKIP] Not an MP3 file: '{file_path}'")
return
bpm = detect_bpm(file_path)
if bpm is not None:
write_bpm_to_id3(file_path, bpm)
def process_folder(folder_path):
"""Recursively process all MP3 files in a folder."""
for root, _, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
process_file(file_path)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python bpm_tag.py <file_or_folder>")
sys.exit(1)
target = sys.argv[1]
if os.path.isfile(target):
process_file(target)
elif os.path.isdir(target):
process_folder(target)
else:
print(f"[ERROR] '{target}' is not a valid file or folder")
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment