system-manager-wakasu
  1#!/usr/bin/env -S uv run --quiet --script
  2# /// script
  3# dependencies = [
  4#   "requests",
  5# ]
  6# ///
  7"""
  8Rename movies in Radarr with interactive confirmation.
  9
 10This script:
 111. Fetches all movies from Radarr API
 122. Checks which movies have files that need renaming
 133. Previews the rename changes for each movie
 144. Asks for confirmation before applying renames
 15
 16Usage:
 17    ./radarr-rename-movies.py <radarr_url> <api_key>
 18
 19Example:
 20    ./radarr-rename-movies.py http://localhost:7878 your-api-key
 21"""
 22
 23from typing import Any, Dict, List
 24
 25from arrlib import (
 26    ArrClient,
 27    create_arr_parser,
 28    get_confirmation_decision,
 29    print_final_summary,
 30    print_item_list,
 31    print_section_header,
 32)
 33
 34
 35def get_rename_preview(
 36    client: ArrClient, movie_id: int
 37) -> List[Dict[str, Any]]:
 38    """Get preview of files that will be renamed for a movie."""
 39    return client.get("/api/v3/rename", params={"movieId": movie_id})
 40
 41
 42def execute_rename(client: ArrClient, movie_id: int) -> Dict[str, Any]:
 43    """Execute rename operation for a movie."""
 44    payload = {"name": "RenameMovie", "movieIds": [movie_id]}
 45    return client.post("/api/v3/command", payload)
 46
 47
 48def main():
 49    parser = create_arr_parser(
 50        "Radarr", "Rename Radarr movies with confirmation", 7878
 51    )
 52    args = parser.parse_args()
 53
 54    # Create client
 55    client = ArrClient(args.radarr_url, args.api_key)
 56
 57    print(f"Fetching movies from {client.base_url}...")
 58    all_movies = client.get("/api/v3/movie")
 59    print(f"Found {len(all_movies)} movies\n")
 60
 61    movies_with_renames = []
 62    movies_without_renames = []
 63
 64    # Check each movie for rename candidates
 65    print("Checking which movies need renaming...")
 66    for movie in all_movies:
 67        movie_id = movie.get("id")
 68        movie_title = movie.get("title", "Unknown")
 69        year = movie.get("year", "")
 70        display_title = (
 71            f"{movie_title} ({year})" if year else movie_title
 72        )
 73
 74        rename_preview = get_rename_preview(client, movie_id)
 75
 76        if rename_preview:
 77            movies_with_renames.append(
 78                (movie_id, display_title, rename_preview)
 79            )
 80        else:
 81            movies_without_renames.append(display_title)
 82
 83    # Print summary
 84    print_section_header("SUMMARY")
 85    print_item_list(movies_without_renames, "✓ No renames needed")
 86
 87    if movies_with_renames:
 88        count = len(movies_with_renames)
 89        print(f"\n→ Movies with renames needed: {count}")
 90
 91    if not movies_with_renames:
 92        print("\nNo movies need renaming!")
 93        return
 94
 95    # Process each movie that needs renaming
 96    print_section_header("RENAME PREVIEW")
 97
 98    renamed_count = 0
 99    skipped_count = 0
100
101    for movie_id, movie_title, rename_preview in movies_with_renames:
102        print(f"\n{'=' * 80}")
103        print(f"Movie: {movie_title}")
104        print(f"Files to rename: {len(rename_preview)}")
105        print("=" * 80)
106
107        # Show preview of renames
108        for i, item in enumerate(rename_preview):
109            existing_path = item.get("existingPath", "Unknown")
110            new_path = item.get("newPath", "Unknown")
111            print(f"\n  File {i + 1}:")
112            print(f"    FROM: {existing_path}")
113            print(f"    TO:   {new_path}")
114
115        # Ask for confirmation
116        file_word = "file" if len(rename_preview) == 1 else "files"
117        prompt = (
118            f"\nRename {len(rename_preview)} {file_word} "
119            f"for '{movie_title}'?"
120        )
121        should_rename = get_confirmation_decision(args, prompt)
122
123        if should_rename:
124            print("Executing rename...")
125            result = execute_rename(client, movie_id)
126            if result:
127                print("✓ Rename command queued successfully")
128                renamed_count += 1
129            else:
130                print("✗ Failed to queue rename command")
131                skipped_count += 1
132        else:
133            if not args.dry_run:
134                print("Skipped")
135            skipped_count += 1
136
137    # Final summary
138    if args.dry_run:
139        print_section_header("FINAL SUMMARY")
140        print(
141            f"\n[DRY RUN] Found {len(movies_with_renames)} movies "
142            "that need renaming"
143        )
144        print("No changes were made. Remove --dry-run to apply renames.")
145    else:
146        print_final_summary(
147            len(movies_with_renames),
148            renamed_count,
149            skipped_count,
150            "Renamed",
151        )
152
153
154if __name__ == "__main__":
155    main()