import os import shutil from pathlib import Path from mutagen.id3 import ID3, TBPM, ID3NoHeaderError from datetime import datetime import configparser import sys import librosa import warnings GROUP_SIZE = 5 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.") input("Press Enter to exit...") 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: with warnings.catch_warnings(): warnings.simplefilter("ignore") 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: pass return None def main(): config = load_config() SAFETY_MARGIN = 1024 * 1024 * 2 # 2 MB buffer CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024 - SAFETY_MARGIN 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.") input("Press Enter to exit...") sys.exit(1) 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 bpm_range = f"{(bpm // GROUP_SIZE) * GROUP_SIZE}-to-{((bpm // GROUP_SIZE) * GROUP_SIZE) + GROUP_SIZE - 1}" if bpm else "[Unknown BPM]" all_tracks.append({"file": file, "size": size, "bpm_range": bpm_range}) if not all_tracks: print("No MP3 files found.") input("Press Enter to exit...") sys.exit(0) 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.sort(key=lambda x: -x["size"]) # Sort by size descending for better packing cd_contents = [] current_cd = [] current_size = 0 for track in all_tracks: if current_size + track["size"] > CD_SIZE: cd_contents.append(current_cd) current_cd = [] current_size = 0 current_cd.append(track) current_size += track["size"] if current_cd: cd_contents.append(current_cd) print(f"Total size: {sum(t['size'] for t in all_tracks) / 1024**2:.2f} MB, splitting into {len(cd_contents)} CDs") 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: bpm_subfolder = cd_folder / track["bpm_range"] 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("\n✓ Done!") input("Press Enter to exit...") if __name__ == "__main__": main()