1
0

Erstelle organize_mp3s.py

This commit is contained in:
Akamaru
2025-10-25 20:09:00 +02:00
parent d39b0b59b7
commit 82a419d86a

183
organize_mp3s.py Normal file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "click",
# ]
# ///
import re
import os
import shutil
from pathlib import Path
from collections import defaultdict
import click
def extract_game_title(filename: str) -> str | None:
"""
Extract the game title from a filename like:
'Splatoon 2 - 001 - Opening - Wet Floor.mp3'
Returns: 'Splatoon 2'
Pattern: Everything before ' - [digits only] - ' is the title
"""
# Match pattern: ' - [digits] - '
match = re.match(r'^(.+?)\s+-\s+\d+\s+-\s+.+\.mp3$', filename)
if match:
return match.group(1).strip()
return None
def sanitize_folder_name(name: str) -> str:
"""
Sanitize a folder name by removing problematic characters.
- Removes trailing dots (e.g., 'Donkey Kong Jr.' -> 'Donkey Kong Jr')
- Removes trailing spaces
"""
return name.rstrip('. ')
def scan_and_group_mp3s(directory: Path) -> dict[str, list[str]]:
"""
Scan directory for MP3 files and group them by extracted game title.
Returns: dict mapping game title -> list of filenames
"""
groups = defaultdict(list)
for file in directory.glob('*.mp3'):
filename = file.name
title = extract_game_title(filename)
if title:
groups[title].append(filename)
else:
# If we can't extract a title, create a special group
groups['[Unknown Format]'].append(filename)
return dict(groups)
def display_group_info(title: str, files: list[str]) -> None:
"""Display information about a group of files."""
click.echo(f"\n{'='*70}")
click.secho(f"Vorgeschlagener Ordner: {title}", fg='cyan', bold=True)
click.secho(f"Anzahl Dateien: {len(files)}", fg='yellow')
click.echo(f"{'='*70}")
# Show up to 3 example files
examples = files[:3]
click.echo("\nBeispiel-Dateien:")
for i, filename in enumerate(examples, 1):
click.echo(f" {i}. {filename}")
if len(files) > 3:
click.secho(f" ... und {len(files) - 3} weitere", fg='bright_black')
def move_files_to_folder(files: list[str], folder_name: str, source_dir: Path) -> None:
"""Move files to the specified folder."""
# Sanitize folder name (remove trailing dots, etc.)
folder_name = sanitize_folder_name(folder_name)
target_dir = source_dir / folder_name
# Create folder if it doesn't exist
target_dir.mkdir(exist_ok=True)
moved_count = 0
error_count = 0
for filename in files:
source_file = source_dir / filename
target_file = target_dir / filename
try:
if target_file.exists():
click.secho(f" ⚠ Übersprungen (existiert bereits): {filename}", fg='yellow')
else:
shutil.move(str(source_file), str(target_file))
moved_count += 1
except Exception as e:
click.secho(f" ✗ Fehler beim Verschieben von {filename}: {e}", fg='red')
error_count += 1
if moved_count > 0:
click.secho(f"{moved_count} Datei(en) verschoben nach '{folder_name}'", fg='green')
if error_count > 0:
click.secho(f"{error_count} Fehler beim Verschieben", fg='red')
@click.command()
@click.option('--auto', is_flag=True, help='Automatisch alle Dateien verschieben ohne Bestätigung')
def main(auto: bool):
"""
Organisiert MP3-Dateien in Ordner basierend auf dem Spieltitel.
Scannt das aktuelle Verzeichnis nach MP3-Dateien, extrahiert den Spieltitel
aus dem Dateinamen und verschiebt die Dateien in entsprechende Ordner.
"""
current_dir = Path.cwd()
click.secho("\n🎵 MP3-Datei-Organizer für Nintendo Music\n", fg='green', bold=True)
click.echo(f"Verzeichnis: {current_dir}\n")
# Scan and group files
click.echo("Scanne MP3-Dateien...")
groups = scan_and_group_mp3s(current_dir)
if not groups:
click.secho("Keine MP3-Dateien gefunden!", fg='red')
return
# Sort groups by title for consistent ordering
sorted_groups = sorted(groups.items(), key=lambda x: x[0])
click.secho(f"\n{len(sorted_groups)} Gruppen gefunden", fg='green')
click.secho(f"{sum(len(files) for files in groups.values())} Dateien insgesamt", fg='green')
# Process each group
processed_count = 0
skipped_count = 0
for title, files in sorted_groups:
display_group_info(title, files)
if auto:
# In auto mode, just move everything
move_files_to_folder(files, title, current_dir)
processed_count += 1
else:
# Interactive mode
click.echo()
choice = click.prompt(
"Aktion? [Y]a / [E]dit / [S]kip",
type=str,
default='Y',
show_default=True
).strip().upper()
if choice == 'Y' or choice == 'YA' or choice == 'J' or choice == 'JA':
move_files_to_folder(files, title, current_dir)
processed_count += 1
elif choice == 'E' or choice == 'EDIT':
new_name = click.prompt("Neuer Ordnername", type=str, default=title)
if new_name.strip():
move_files_to_folder(files, new_name.strip(), current_dir)
processed_count += 1
else:
click.secho(" ⊘ Übersprungen (leerer Name)", fg='yellow')
skipped_count += 1
else:
click.secho(" ⊘ Übersprungen", fg='yellow')
skipped_count += 1
# Summary
click.echo(f"\n{'='*70}")
click.secho("Zusammenfassung:", fg='green', bold=True)
click.echo(f" Verarbeitet: {processed_count}")
click.echo(f" Übersprungen: {skipped_count}")
click.secho("\n✓ Fertig!", fg='green', bold=True)
if __name__ == '__main__':
main()