diff --git a/.gitignore b/.gitignore index c192906..64349e7 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,5 @@ cython_debug/ .cursorignore .cursorindexingignore +# ed1337x +config.ini diff --git a/src/app.py b/src/app.py index 0df3962..9f8c560 100644 --- a/src/app.py +++ b/src/app.py @@ -4,26 +4,38 @@ from pathlib import Path from mutagen.id3 import ID3 from datetime import datetime from collections import defaultdict +import configparser +import sys -CD_SIZE = 695 * 1024 * 1024 # 695 MB GROUP_SIZE = 5 -# Store the current date -run_date = datetime.now().strftime("%Y-%m-%d_%H%M%S") +def load_config(): + config_path = Path("config.ini") + default_config_path = Path("def_config.ini") -# 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) + # 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) + 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) -if not source_media_path.exists() or not source_media_path.is_dir(): - print(f"Error: {source_media_path} is not a valid directory.") - exit(1) + # Read default config + default_config = configparser.ConfigParser() + default_config.read(default_config_path) -# 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) + # 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 get_bpm(file_path): try: @@ -35,64 +47,90 @@ def get_bpm(file_path): print(f"Skipping {file_path.name} (no BPM): {e}") return None -# Collect all tracks recursively, grouping unknown BPM separately -all_tracks = [] -for file in source_media_path.rglob("*.mp3"): # <-- recursive glob - bpm = get_bpm(file) - 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.") - 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 def split_evenly(lst, n): 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(): - bpm_chunks[bpm_range] = split_evenly(tracks, num_cds) +def main(): + config = load_config() -# Prepare CDs -cd_contents = [[] for _ in range(num_cds)] + # Use SplitFolderMB from config, convert MB to bytes + CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024 -# 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) + # Store the current date for folder naming + run_date = datetime.now().strftime("%Y-%m-%d_%H%M%S") -# 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) - 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") + # 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) -print("\n✅ Done!") + 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) + + # 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) + 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) + + # 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) + 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!") + +if __name__ == "__main__": + main() diff --git a/src/def_config.ini b/src/def_config.ini new file mode 100644 index 0000000..9e41dfc --- /dev/null +++ b/src/def_config.ini @@ -0,0 +1,4 @@ +[Settings] +bCheckNonPresentBPM = False +bReCheckPresentBPM = False +SplitFolderMB = 695