Add --in-place mode to apply: rename and tag without moving files
When no output_dir is given (or --in-place is set), files are renamed and tagged directly in the source directory instead of being moved into a separate Jellyfin library hierarchy. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
67b8653b3c
commit
070a0573ae
2 changed files with 37 additions and 12 deletions
|
|
@ -133,19 +133,36 @@ def apply(
|
|||
..., help="Verzeichnis mit gerippten Audiodateien"
|
||||
),
|
||||
album_json: Path = typer.Argument(..., help="Album-JSON aus 'scan'"),
|
||||
output_dir: Path = typer.Argument(..., help="Jellyfin-Musikverzeichnis"),
|
||||
output_dir: Path | None = typer.Argument(
|
||||
None, help="Jellyfin-Musikverzeichnis (weggelassen = --in-place)"
|
||||
),
|
||||
front: Path | None = typer.Option(None, "--front", help="Front-Cover-Bild"),
|
||||
back: Path | None = typer.Option(
|
||||
None, "--back", help="Rückseiten-Cover-Bild"
|
||||
),
|
||||
in_place: bool = typer.Option(
|
||||
False, "--in-place", help="Dateien im Quellverzeichnis umbenennen/taggen"
|
||||
),
|
||||
dry_run: bool = typer.Option(
|
||||
False, "--dry-run", help="Nur anzeigen, nichts ändern"
|
||||
),
|
||||
) -> None:
|
||||
"""Album-JSON + Audiodateien → Jellyfin-Struktur aufbauen."""
|
||||
"""Album-JSON + Audiodateien → Jellyfin-Struktur aufbauen.
|
||||
|
||||
Ohne output_dir (oder mit --in-place): Dateien werden im Quellverzeichnis
|
||||
umbenannt und getaggt, nicht in eine separate Bibliothek verschoben.
|
||||
"""
|
||||
raw = json.loads(album_json.read_text(encoding="utf-8"))
|
||||
album = Album.model_validate(raw)
|
||||
|
||||
# --in-place wenn kein output_dir angegeben
|
||||
if output_dir is None:
|
||||
in_place = True
|
||||
|
||||
if not in_place and output_dir is None:
|
||||
typer.echo("Fehler: output_dir oder --in-place angeben.", err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Prüfe Track-Anzahl pro Disc
|
||||
checks = check_disc_counts(album, input_dir)
|
||||
problems = [c for c in checks if not c.ok]
|
||||
|
|
@ -176,10 +193,11 @@ def apply(
|
|||
typer.echo(f"\nBitte {album_json} korrigieren und erneut aufrufen.", err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
mapping = build_mapping(album, input_dir, output_dir)
|
||||
mapping = build_mapping(album, input_dir, output_dir, in_place=in_place)
|
||||
typer.echo(f"Mapping: {len(mapping)} Dateien")
|
||||
display_root = input_dir if in_place else output_dir
|
||||
for src, dst in mapping.items():
|
||||
typer.echo(f" {src.name} → {dst.relative_to(output_dir)}")
|
||||
typer.echo(f" {src.name} → {dst.relative_to(display_root)}")
|
||||
|
||||
if dry_run:
|
||||
typer.echo("[DRY-RUN] Keine Änderungen vorgenommen.")
|
||||
|
|
@ -187,11 +205,11 @@ def apply(
|
|||
|
||||
apply_mapping(mapping)
|
||||
|
||||
first_target = next(iter(mapping.values()))
|
||||
if len(album.discs) > 1:
|
||||
album_dir = first_target.parent.parent
|
||||
if in_place:
|
||||
album_dir = input_dir
|
||||
else:
|
||||
album_dir = first_target.parent
|
||||
first_target = next(iter(mapping.values()))
|
||||
album_dir = first_target.parent.parent if len(album.discs) > 1 else first_target.parent
|
||||
|
||||
typer.echo("Setze Audio-Tags...")
|
||||
tag_album(album, album_dir)
|
||||
|
|
|
|||
|
|
@ -86,20 +86,27 @@ def check_disc_counts(album: Album, input_dir: Path) -> list[DiscCheck]:
|
|||
def build_mapping(
|
||||
album: Album,
|
||||
input_dir: Path,
|
||||
output_root: Path,
|
||||
output_root: Path | None = None,
|
||||
in_place: bool = False,
|
||||
) -> dict[Path, Path]:
|
||||
"""Berechnet das Quell→Ziel-Mapping für alle Audiodateien.
|
||||
|
||||
Args:
|
||||
album: Validiertes Album-Modell
|
||||
input_dir: Verzeichnis mit den gerippten Dateien
|
||||
output_root: Jellyfin-Musikverzeichnis
|
||||
output_root: Jellyfin-Musikverzeichnis (wird bei in_place ignoriert)
|
||||
in_place: Wenn True, Dateien nur innerhalb von input_dir umbenennen
|
||||
|
||||
Returns:
|
||||
Dict von Quellpfad → Zielpfad
|
||||
"""
|
||||
artist_dir = _sanitize_filename(album.artist)
|
||||
album_dir = output_root / artist_dir / _sanitize_filename(album.folder_name)
|
||||
if in_place:
|
||||
album_dir = input_dir
|
||||
else:
|
||||
assert output_root is not None, "output_root erforderlich wenn nicht in_place"
|
||||
artist_dir = _sanitize_filename(album.artist)
|
||||
album_dir = output_root / artist_dir / _sanitize_filename(album.folder_name)
|
||||
|
||||
mapping: dict[Path, Path] = {}
|
||||
multi_disc = len(album.discs) > 1
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue