BPM functionality

This commit is contained in:
Roncero Blanco, Edgar
2025-05-26 17:20:55 +02:00
parent 63953f45e8
commit 76d25ebe5e
2 changed files with 45 additions and 23 deletions

View File

@@ -1,11 +1,12 @@
import os
import shutil
from pathlib import Path
from mutagen.id3 import ID3
from mutagen.id3 import ID3, TBPM, ID3NoHeaderError
from datetime import datetime
from collections import defaultdict
import configparser
import sys
import librosa
GROUP_SIZE = 5
@@ -13,7 +14,6 @@ def load_config():
config_path = Path("config.ini")
default_config_path = Path("def_config.ini")
# If config.ini missing, copy def_config.ini
if not config_path.exists():
if default_config_path.exists():
shutil.copy(default_config_path, config_path)
@@ -22,29 +22,49 @@ def load_config():
print("Error: def_config.ini not found! Please create it and rerun.")
sys.exit(1)
# Read default config
default_config = configparser.ConfigParser()
default_config.read(default_config_path)
# Read user config (overrides default)
user_config = configparser.ConfigParser()
user_config.read(config_path)
# Merge configs: start with defaults, update with user settings
merged_config = configparser.ConfigParser()
merged_config.read_dict(default_config)
merged_config.read_dict(user_config)
return merged_config
def analyze_bpm_librosa(file_path):
try:
y, sr = librosa.load(file_path, mono=True)
tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
return int(round(float(tempo)))
except Exception as e:
print(f"Error analyzing BPM for {file_path.name}: {e}")
return None
def write_bpm_tag(file_path, bpm):
try:
try:
tags = ID3(file_path)
except ID3NoHeaderError:
tags = ID3()
tags.delall("TBPM")
tags.add(TBPM(encoding=3, text=str(bpm)))
tags.save(file_path)
print(f"Written BPM={bpm} to {file_path.name}")
except Exception as e:
print(f"Failed to write BPM tag for {file_path.name}: {e}")
def get_bpm(file_path):
try:
tags = ID3(file_path)
bpm_tag = tags.get("TBPM")
if bpm_tag:
return int(float(bpm_tag.text[0]))
except Exception as e:
print(f"Skipping {file_path.name} (no BPM): {e}")
except Exception:
pass
return None
def split_evenly(lst, n):
@@ -54,13 +74,12 @@ def split_evenly(lst, n):
def main():
config = load_config()
# Use SplitFolderMB from config, convert MB to bytes
CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024
bWriteNonPresentBPM = config.getboolean("Settings", "bWriteNonPresentBPM")
bCheckAllTracksBPM = config.getboolean("Settings", "bCheckAllTracksBPM")
# Store the current date for folder naming
run_date = datetime.now().strftime("%Y-%m-%d_%H%M%S")
# Ask for source path, strip quotes if pasted
source_input = input("Drag and drop your music folder here, then press Enter: ").strip().strip('"')
source_media_path = Path(source_input)
@@ -68,16 +87,26 @@ def main():
print(f"Error: {source_media_path} is not a valid directory.")
sys.exit(1)
# Build destination path
parent = source_media_path.parent
folder_name = source_media_path.name
dest_media_path = parent / f"[CDs-{run_date}]{folder_name}"
dest_media_path.mkdir(parents=True, exist_ok=True)
# Collect all tracks recursively, group unknown BPM separately
all_tracks = []
for file in source_media_path.rglob("*.mp3"):
bpm = get_bpm(file)
if bpm is None and bWriteNonPresentBPM:
bpm = analyze_bpm_librosa(file)
if bpm is not None:
write_bpm_tag(file, bpm)
elif bpm is not None and bCheckAllTracksBPM:
new_bpm = analyze_bpm_librosa(file)
if new_bpm is not None and new_bpm != bpm:
bpm = new_bpm
write_bpm_tag(file, bpm)
size = file.stat().st_size
if bpm is None:
bpm_range = "[Unknown BPM]"
@@ -89,35 +118,28 @@ def main():
print("No MP3 files found.")
sys.exit(0)
# Calculate total size and number of CDs needed
total_size = sum(t["size"] for t in all_tracks)
num_cds = max(1, (total_size + CD_SIZE - 1) // CD_SIZE)
print(f"Total size: {total_size / 1024**2:.2f} MB, splitting into {num_cds} CDs")
# Group tracks by BPM range
bpm_groups = defaultdict(list)
for track in all_tracks:
bpm_groups[track["bpm_range"]].append(track)
# Sort each bpm group
for bpm_range in bpm_groups:
bpm_groups[bpm_range].sort(key=lambda x: x["file"].name)
# Split each BPM group evenly into num_cds parts
bpm_chunks = {}
for bpm_range, tracks in bpm_groups.items():
bpm_chunks[bpm_range] = split_evenly(tracks, num_cds)
# Prepare CDs
cd_contents = [[] for _ in range(num_cds)]
# Fill CDs with balanced BPM chunks
for bpm_range in bpm_chunks:
for i, chunk in enumerate(bpm_chunks[bpm_range]):
cd_contents[i].extend(chunk)
# Write CDs with BPM subfolders
for i, tracks in enumerate(cd_contents, start=1):
cd_folder = dest_media_path / f"CD-{i:02}"
cd_folder.mkdir(parents=True, exist_ok=True)
@@ -127,9 +149,9 @@ def main():
bpm_subfolder.mkdir(parents=True, exist_ok=True)
shutil.copy(track["file"], bpm_subfolder / track["file"].name)
size_accum += track["size"]
print(f"[WRITE] CD-{i:02}: {len(tracks)} tracks, approx {size_accum/1024**2:.2f} MB")
print(f"[WRITE] CD-{i:02}: {len(tracks)} tracks, approx {size_accum / 1024**2:.2f} MB")
print("\n Done!")
print("\n Done!")
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
[Settings]
bCheckNonPresentBPM = False
bReCheckPresentBPM = False
bWriteNonPresentBPM = False
bCheckAllTracksBPM = False
SplitFolderMB = 695