Group MP3 files by non-overlapping 5-BPM bins using ID3v2 tags and copy to structured folders
This commit is contained in:
45
src/app.py
45
src/app.py
@@ -1,53 +1,38 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from collections import defaultdict
|
|
||||||
from mutagen.id3 import ID3, TBPM
|
from mutagen.id3 import ID3, TBPM
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Config
|
# Define source (dir A) and destination (dir B) directories
|
||||||
dir_a = Path(r"C:\Users\Edgar\Desktop\Peak Time BOF (ed1337x)\Already where in MP3")
|
dir_a = Path(r"C:\Users\Edgar\Desktop\Peak Time BOF (ed1337x)\Already where in MP3")
|
||||||
dir_b = Path(r"C:\Users\Edgar\Desktop\Peak Time BOF (ed1337x)\CD")
|
dir_b = Path(r"C:\Users\Edgar\Desktop\Peak Time BOF (ed1337x)\CD")
|
||||||
bin_size = 5 # BPM range size
|
|
||||||
|
|
||||||
# Ensure target directory exists
|
# Make sure destination base exists
|
||||||
dir_b.mkdir(parents=True, exist_ok=True)
|
dir_b.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Function to get BPM from file
|
|
||||||
def get_bpm(file_path):
|
def get_bpm(file_path):
|
||||||
try:
|
try:
|
||||||
tags = ID3(file_path)
|
tags = ID3(file_path)
|
||||||
bpm_tag = tags.get('TBPM')
|
bpm_tag = tags.get('TBPM')
|
||||||
if bpm_tag:
|
if bpm_tag:
|
||||||
bpm_str = str(bpm_tag.text[0])
|
bpm_str = str(bpm_tag.text[0])
|
||||||
return int(float(bpm_str))
|
return int(float(bpm_str)) # Some BPMs are floats or strings
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Could not read BPM from {file_path}: {e}")
|
print(f"[!] Error reading BPM from {file_path.name}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Collect BPMs
|
|
||||||
bpm_to_files = defaultdict(list)
|
|
||||||
|
|
||||||
for file in dir_a.glob("*.mp3"):
|
for file in dir_a.glob("*.mp3"):
|
||||||
bpm = get_bpm(file)
|
bpm = get_bpm(file)
|
||||||
if bpm:
|
if bpm is not None:
|
||||||
bpm_to_files[bpm].append(file)
|
# Calculate non-overlapping 5-BPM bin
|
||||||
|
lower_bound = (bpm // 5) * 5
|
||||||
|
upper_bound = lower_bound + 4 # Inclusive range: e.g., 130–134
|
||||||
|
folder_name = f"{lower_bound}-to-{upper_bound}"
|
||||||
|
target_folder = dir_b / folder_name
|
||||||
|
target_folder.mkdir(exist_ok=True)
|
||||||
|
target_file = target_folder / file.name
|
||||||
|
shutil.copy(file, target_file)
|
||||||
|
print(f"[+] Copied {file.name} to {folder_name}")
|
||||||
else:
|
else:
|
||||||
print(f"Skipping {file.name} (no BPM)")
|
print(f"[-] No BPM found for {file.name}, skipping.")
|
||||||
|
|
||||||
# Group into dynamic 5-BPM bins
|
|
||||||
bins = defaultdict(list)
|
|
||||||
|
|
||||||
for bpm, files in bpm_to_files.items():
|
|
||||||
bin_start = (bpm // bin_size) * bin_size
|
|
||||||
bin_end = bin_start + bin_size
|
|
||||||
bin_label = f"{bin_start}-to-{bin_end}"
|
|
||||||
bins[bin_label].extend(files)
|
|
||||||
|
|
||||||
# Copy files to corresponding bins
|
|
||||||
for bin_label, files in bins.items():
|
|
||||||
target_folder = dir_b / bin_label
|
|
||||||
target_folder.mkdir(exist_ok=True)
|
|
||||||
for file in files:
|
|
||||||
shutil.copy(file, target_folder / file.name)
|
|
||||||
print(f"Copied {file.name} to {bin_label}")
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user