9 Commits

3 changed files with 141 additions and 55 deletions

2
.gitignore vendored
View File

@@ -180,3 +180,5 @@ cython_debug/
.cursorignore
.cursorindexingignore
# ed1337x
config.ini

View File

@@ -1,14 +1,61 @@
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
CD_SIZE = 700 * 1024 * 1024 # 700 MB
GROUP_SIZE = 5
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.mkdir(parents=True, exist_ok=True)
def load_config():
config_path = Path("config.ini")
default_config_path = Path("def_config.ini")
if not config_path.exists():
if default_config_path.exists():
shutil.copy(default_config_path, config_path)
print("config.ini created from def_config.ini with default settings.")
else:
print("Error: def_config.ini not found! Please create it and rerun.")
sys.exit(1)
default_config = configparser.ConfigParser()
default_config.read(default_config_path)
user_config = configparser.ConfigParser()
user_config.read(config_path)
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[0])))
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:
@@ -16,57 +63,85 @@ def get_bpm(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
# Collect all tracks
all_tracks = []
for file in dir_a.glob("*.mp3"):
bpm = get_bpm(file)
if bpm is None:
continue
size = file.stat().st_size
bpm_range = f"{(bpm // GROUP_SIZE) * GROUP_SIZE}-to-{((bpm // GROUP_SIZE) * GROUP_SIZE) + GROUP_SIZE - 1}"
all_tracks.append({"file": file, "size": size, "bpm_range": bpm_range})
# 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) # ceiling division
print(f"Total size: {total_size / 1024**2:.2f} MB, splitting into {num_cds} CDs")
# Group tracks by BPM range
from collections import defaultdict
bpm_groups = defaultdict(list)
for track in all_tracks:
bpm_groups[track["bpm_range"]].append(track)
# Sort each bpm group by filename (or size if you want)
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
def split_evenly(lst, n):
"""Split list lst into n chunks as evenly as possible by count."""
k, m = divmod(len(lst), n)
return [lst[i*k + min(i, m):(i+1)*k + min(i+1, m)] for i in range(n)]
bpm_chunks = {}
for bpm_range, tracks in bpm_groups.items():
def main():
config = load_config()
CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024
bWriteNonPresentBPM = config.getboolean("Settings", "bWriteNonPresentBPM")
bCheckAllTracksBPM = config.getboolean("Settings", "bCheckAllTracksBPM")
run_date = datetime.now().strftime("%Y-%m-%d_%H%M%S")
source_input = input("Drag and drop your music folder here, then press Enter: ").strip().strip('"')
source_media_path = Path(source_input)
if not source_media_path.exists() or not source_media_path.is_dir():
print(f"Error: {source_media_path} is not a valid directory.")
sys.exit(1)
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)
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]"
else:
bpm_range = f"{(bpm // GROUP_SIZE) * GROUP_SIZE}-to-{((bpm // GROUP_SIZE) * GROUP_SIZE) + GROUP_SIZE - 1}"
all_tracks.append({"file": file, "size": size, "bpm_range": bpm_range})
if not all_tracks:
print("No MP3 files found.")
sys.exit(0)
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")
bpm_groups = defaultdict(list)
for track in all_tracks:
bpm_groups[track["bpm_range"]].append(track)
for bpm_range in bpm_groups:
bpm_groups[bpm_range].sort(key=lambda x: x["file"].name)
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)]
cd_contents = [[] for _ in range(num_cds)]
# Fill CDs with chunks from each BPM range
for bpm_range in 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 = dir_b / f"CD-{i:02}"
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)
size_accum = 0
for track in tracks:
@@ -74,5 +149,10 @@ for i, tracks in enumerate(cd_contents, start=1):
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!")
if __name__ == "__main__":
main()

4
src/def_config.ini Normal file
View File

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