4 Commits

Author SHA1 Message Date
Roncero Blanco, Edgar
f7ee691b4d Better split algorithm 2025-05-26 20:17:05 +02:00
Roncero Blanco, Edgar
a701e3f97a POSIX compatible 2025-05-26 19:02:37 +02:00
Roncero Blanco, Edgar
189645254b Cleaner code, it works pretty fine 2025-05-26 18:26:30 +02:00
Roncero Blanco, Edgar
a46d810d0a Fix: Only create destination folder if MP3 files exist in source directory 2025-05-26 17:31:58 +02:00
8 changed files with 79 additions and 59 deletions

1
.gitignore vendored
View File

@@ -182,3 +182,4 @@ cython_debug/
# ed1337x # ed1337x
config.ini config.ini
Run.ps1

View File

@@ -3,10 +3,10 @@ import shutil
from pathlib import Path from pathlib import Path
from mutagen.id3 import ID3, TBPM, ID3NoHeaderError from mutagen.id3 import ID3, TBPM, ID3NoHeaderError
from datetime import datetime from datetime import datetime
from collections import defaultdict
import configparser import configparser
import sys import sys
import librosa import librosa
import warnings
GROUP_SIZE = 5 GROUP_SIZE = 5
@@ -20,6 +20,7 @@ def load_config():
print("config.ini created from def_config.ini with default settings.") print("config.ini created from def_config.ini with default settings.")
else: else:
print("Error: def_config.ini not found! Please create it and rerun.") print("Error: def_config.ini not found! Please create it and rerun.")
input("Press Enter to exit...")
sys.exit(1) sys.exit(1)
default_config = configparser.ConfigParser() default_config = configparser.ConfigParser()
@@ -36,9 +37,11 @@ def load_config():
def analyze_bpm_librosa(file_path): def analyze_bpm_librosa(file_path):
try: try:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
y, sr = librosa.load(file_path, mono=True) y, sr = librosa.load(file_path, mono=True)
tempo, _ = librosa.beat.beat_track(y=y, sr=sr) tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
return int(round(float(tempo[0]))) return int(round(float(tempo)))
except Exception as e: except Exception as e:
print(f"Error analyzing BPM for {file_path.name}: {e}") print(f"Error analyzing BPM for {file_path.name}: {e}")
return None return None
@@ -49,7 +52,6 @@ def write_bpm_tag(file_path, bpm):
tags = ID3(file_path) tags = ID3(file_path)
except ID3NoHeaderError: except ID3NoHeaderError:
tags = ID3() tags = ID3()
tags.delall("TBPM") tags.delall("TBPM")
tags.add(TBPM(encoding=3, text=str(bpm))) tags.add(TBPM(encoding=3, text=str(bpm)))
tags.save(file_path) tags.save(file_path)
@@ -67,14 +69,11 @@ def get_bpm(file_path):
pass pass
return None return None
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)]
def main(): def main():
config = load_config() config = load_config()
CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024 SAFETY_MARGIN = 1024 * 1024 * 2 # 2 MB buffer
CD_SIZE = config.getint("Settings", "SplitFolderMB") * 1024 * 1024 - SAFETY_MARGIN
bWriteNonPresentBPM = config.getboolean("Settings", "bWriteNonPresentBPM") bWriteNonPresentBPM = config.getboolean("Settings", "bWriteNonPresentBPM")
bCheckAllTracksBPM = config.getboolean("Settings", "bCheckAllTracksBPM") bCheckAllTracksBPM = config.getboolean("Settings", "bCheckAllTracksBPM")
@@ -85,13 +84,9 @@ def main():
if not source_media_path.exists() or not source_media_path.is_dir(): if not source_media_path.exists() or not source_media_path.is_dir():
print(f"Error: {source_media_path} is not a valid directory.") print(f"Error: {source_media_path} is not a valid directory.")
input("Press Enter to exit...")
sys.exit(1) 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 = [] all_tracks = []
for file in source_media_path.rglob("*.mp3"): for file in source_media_path.rglob("*.mp3"):
bpm = get_bpm(file) bpm = get_bpm(file)
@@ -108,37 +103,37 @@ def main():
write_bpm_tag(file, bpm) write_bpm_tag(file, bpm)
size = file.stat().st_size size = file.stat().st_size
if bpm is None: bpm_range = f"{(bpm // GROUP_SIZE) * GROUP_SIZE}-to-{((bpm // GROUP_SIZE) * GROUP_SIZE) + GROUP_SIZE - 1}" if bpm else "[Unknown BPM]"
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}) all_tracks.append({"file": file, "size": size, "bpm_range": bpm_range})
if not all_tracks: if not all_tracks:
print("No MP3 files found.") print("No MP3 files found.")
input("Press Enter to exit...")
sys.exit(0) sys.exit(0)
total_size = sum(t["size"] for t in all_tracks) parent = source_media_path.parent
num_cds = max(1, (total_size + CD_SIZE - 1) // CD_SIZE) 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)
print(f"Total size: {total_size / 1024**2:.2f} MB, splitting into {num_cds} CDs") all_tracks.sort(key=lambda x: -x["size"]) # Sort by size descending for better packing
cd_contents = []
current_cd = []
current_size = 0
bpm_groups = defaultdict(list)
for track in all_tracks: for track in all_tracks:
bpm_groups[track["bpm_range"]].append(track) 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"]
for bpm_range in bpm_groups: if current_cd:
bpm_groups[bpm_range].sort(key=lambda x: x["file"].name) cd_contents.append(current_cd)
bpm_chunks = {} print(f"Total size: {sum(t['size'] for t in all_tracks) / 1024**2:.2f} MB, splitting into {len(cd_contents)} CDs")
for bpm_range, tracks in bpm_groups.items():
bpm_chunks[bpm_range] = split_evenly(tracks, num_cds)
cd_contents = [[] for _ in range(num_cds)]
for bpm_range in bpm_chunks:
for i, chunk in enumerate(bpm_chunks[bpm_range]):
cd_contents[i].extend(chunk)
for i, tracks in enumerate(cd_contents, start=1): for i, tracks in enumerate(cd_contents, start=1):
cd_folder = dest_media_path / f"CD-{i:02}" cd_folder = dest_media_path / f"CD-{i:02}"
@@ -152,6 +147,7 @@ def main():
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!")
input("Press Enter to exit...")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

25
Run.cmd Normal file
View File

@@ -0,0 +1,25 @@
@echo off
setlocal
REM Define paths
set VENV_DIR=venv
set APP_DIR=Program
set PYTHON=%VENV_DIR%\Scripts\python.exe
REM Check for venv
if not exist %VENV_DIR% (
echo Creating virtual environment...
python -m venv %VENV_DIR%
echo Installing dependencies...
%PYTHON% -m pip install --upgrade pip
%PYTHON% -m pip install -r requirements.txt
) else (
echo Virtual environment found, assuming dependencies are satisfied.
)
REM Run the app
cd /d %~dp0%APP_DIR%
..\%VENV_DIR%\Scripts\python.exe app.py
endlocal

23
Run.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -e # exit on error
VENV_DIR=venv
APP_DIR=Program
PYTHON=$VENV_DIR/bin/python
# Check if virtual environment exists
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Installing dependencies..."
"$PYTHON" -m pip install --upgrade pip
"$PYTHON" -m pip install -r requirements.txt
else
echo "Virtual environment found, assuming dependencies are satisfied."
fi
# Run the app
cd "$APP_DIR"
../"$PYTHON" app.py

BIN
requirements.txt Normal file

Binary file not shown.

View File

@@ -1,13 +0,0 @@
@echo off
REM Create Python virtual environment named "venv"
python -m venv venv
REM Activate the virtual environment
call venv\Scripts\activate.bat
REM Upgrade pip
python -m pip install --upgrade pip
REM Install dependencies
pip install mutagen

View File

@@ -1,12 +0,0 @@
# Create Python virtual environment named "venv"
python -m venv venv
# Activate the virtual environment
& .\venv\Scripts\Activate.ps1
# Upgrade pip
python -m pip install --upgrade pip
# Install dependencies
pip install mutagen