Erstelle organize_mp3s.py
This commit is contained in:
183
organize_mp3s.py
Normal file
183
organize_mp3s.py
Normal 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()
|
||||
Reference in New Issue
Block a user