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