#!/usr/bin/env python3 """ Reformat ∆•MIX mp3 filenames and metadata titles. Filename: ∆•MIX01.mp3 → ∆•MIX_001.mp3 Metadata title: ∆•MIX01 → ∆•MIX 001 Files with no number (∆•MIXΩ, ∆•MIX+, ∆•RYZ...) are left alone. Files with extra text (∆•MIX12_(PVFM_12th_Anniversary)) are handled correctly. Usage: python3 reformat_mixes.py /path/to/folder python3 reformat_mixes.py /path/to/folder --dry-run (preview only) """ import os import re import sys from pathlib import Path try: from mutagen.mp3 import MP3 from mutagen.id3 import ID3, TIT2 except ImportError: print("Missing dependency. Install with: pip install mutagen") sys.exit(1) MIX_PREFIX = r'([∆Δ]•MIX)' NUMBER_RE = re.compile(MIX_PREFIX + r'(\d+)(.*)', re.DOTALL) def transform_title(title: str) -> str: """∆•MIX01 → ∆•MIX 001 (space + zero-padded)""" m = NUMBER_RE.match(title) if not m: return title prefix, num, rest = m.group(1), m.group(2), m.group(3) return f"{prefix} {int(num):03d}{rest}" def transform_filename(name: str) -> str: """∆•MIX01_(extra).mp3 → ∆•MIX_001_(extra).mp3""" stem, ext = os.path.splitext(name) m = NUMBER_RE.match(stem) if not m: return name prefix, num, rest = m.group(1), m.group(2), m.group(3) return f"{prefix}_{int(num):03d}{rest}{ext}" def process(directory: str, dry_run: bool = False): folder = Path(directory) mp3_files = sorted(folder.glob('*.mp3')) if not mp3_files: print(f"No .mp3 files found in {folder}") return rename_queue = [] for path in mp3_files: print(f"\n{path.name}") # --- metadata --- try: audio = MP3(str(path), ID3=ID3) if audio.tags is None: audio.add_tags() old_title = str(audio.tags['TIT2']) if 'TIT2' in audio.tags else None new_title = transform_title(old_title) if old_title else None if new_title and new_title != old_title: print(f" title: {old_title!r} → {new_title!r}") if not dry_run: audio.tags['TIT2'] = TIT2(encoding=3, text=new_title) audio.save() else: print(f" title: {old_title!r} (no change)") except Exception as e: print(f" ERROR reading metadata: {e}") # --- filename --- new_name = transform_filename(path.name) if new_name != path.name: new_path = path.parent / new_name print(f" file: {path.name} → {new_name}") if not dry_run: rename_queue.append((path, new_path)) else: print(f" file: {path.name} (no change)") for old, new in rename_queue: old.rename(new) renamed = len(rename_queue) if dry_run: print(f"\n[dry run] would rename {renamed} files. Run without --dry-run to apply.") else: print(f"\nDone. {renamed} files renamed.") if __name__ == '__main__': args = sys.argv[1:] dry = '--dry-run' in args args = [a for a in args if a != '--dry-run'] directory = args[0] if args else '.' process(directory, dry_run=dry)