#!/usr/bin/env python3 """ Trigger Radarr searches for a priority list of films, spaced out so you don't hammer your indexers. Searches go out in batches of 10 with a 90 second gap. The built-in list is my own Criterion wishlist; pass a text file (one title per line, matching Radarr's title exactly) to use your own. Usage: export RADARR_URL="http://radarr.local:7878" export RADARR_API_KEY="your-radarr-api-key" python search_radarr.py [priority_films.txt] Requires only the Python standard library. """ import json, os, sys, time, urllib.request def env(name): value = os.environ.get(name, "") if not value: print(f"ERROR: {name} not set.", file=sys.stderr) sys.exit(1) return value BASE = env("RADARR_URL").rstrip("/") API_KEY = env("RADARR_API_KEY") BATCH_SIZE = 10 BATCH_GAP_SECONDS = 90 PRIORITY_FILMS = [ "Paris, Texas", "Yi Yi", "High and Low", "Come and See", "Harakiri", "In the Mood for Love", "A Brighter Summer Day", "The Celebration", "Cure", "Stalker", "The Worst Person in the World", "Memories of Murder", "La Notte", "The Last Picture Show", "The Spirit of the Beehive", "Anatomy of a Fall", "Chungking Express", "Bicycle Thieves", "Brief Encounter", "The Umbrellas of Cherbourg", "Secrets & Lies", "The 400 Blows", "Three Colours: Blue", "The Double Life of Véronique", "Beau Travail", "8½", "Tokyo Story", "Nights of Cabiria", "La Dolce Vita", "Y Tu Mamá También", "Rosemary's Baby", "Perfect Days", "Woman in the Dunes", "The Piano Teacher", "The Thin Red Line", "A Woman Under the Influence", "Ali: Fear Eats the Soul", "The Vanishing", "The Passion of Joan of Arc", "The Red Shoes", "Do the Right Thing", "Happy Together", "Naked", "Breaking the Waves", "La Cérémonie", "After Life", "The Great Beauty", "Still Walking", "Midnight Cowboy", "Badlands", "Lost Highway", "The Lovers on the Bridge", "The Cranes Are Flying", "Autumn Sonata", "The Virgin Spring", "The Face of Another", "Mishima: A Life in Four Chapters", "All That Jazz", "The Fire Within", "Taste of Cherry", "Wings of Desire", "The Human Condition I: No Greater Love", "The Human Condition II: Road to Eternity", "The Human Condition III: A Soldier's Prayer", "Fanny and Alexander", "The Ascent", "Andrei Rublev", "Solaris", "Mirror", "The Exterminating Angel", "La Jetée", "Persona", "Winter Light", "Cries and Whispers", "The Seventh Seal", "Le Trou", "Army of Shadows", "Le Cercle Rouge", "Rififi", "Elevator to the Gallows", "Blow Out", "Thief", "Sorcerer", "The Friends of Eddie Coyle", "The Long Good Friday", "Sweet Smell of Success", "Ace in the Hole", "In a Lonely Place", "The Big Heat", "Anatomy of a Murder", "Seconds", "Peeping Tom", "The Innocents", "Don't Look Now", "Picnic at Hanging Rock", "Blue Velvet", "Eraserhead", "Videodrome", "Dead Ringers", "Funny Games", "The Seventh Continent", "Caché", "The White Ribbon", "L'Avventura", "L'Eclisse", "Red Desert", "Vivre Sa Vie", "Contempt", "Pierrot le Fou", "Breathless", "Jules and Jim", "Day for Night", "Cléo from 5 to 7", "Le Bonheur", "Vagabond", "Céline and Julie Go Boating", "The Mother and the Whore", "My Night at Maud's", "A Summer's Tale", "The Young Girls of Rochefort", "The Leopard", "Rome, Open City", "Umberto D.", "The Rules of the Game", "Grand Illusion", "Ugetsu", "Sansho the Bailiff", "Red Beard", "The Hidden Fortress", "Ivan's Childhood", "The Color of Pomegranates", "The Cremator", "Daisies", "Marketa Lazarová", "Werckmeister Harmonies", "Close-Up", "Where Is the Friend's House?", "Certified Copy", "The Wind Will Carry Us", "Pather Panchali", "Aparajito", "Apur Sansar", "Farewell My Concubine", "Paris Is Burning", "Hoop Dreams", "Shoah", "Night and Fog", "Sans Soleil", "Jeanne Dielman, 23 quai du Commerce, 1080 Bruxelles", "PlayTime", ] if len(sys.argv) > 1: with open(sys.argv[1]) as f: PRIORITY_FILMS = [line.strip() for line in f if line.strip()] # Get all movies from Radarr print("Fetching Radarr library...", file=sys.stderr) req = urllib.request.Request( f"{BASE}/api/v3/movie", headers={"X-Api-Key": API_KEY, "User-Agent": "Mozilla/5.0 (criterion-crossref/1.0)"}, ) with urllib.request.urlopen(req, timeout=30) as r: all_movies = json.loads(r.read()) # Build lookup by title title_to_id = {} for m in all_movies: title_to_id[m["title"]] = m["id"] # Match priority films to Radarr IDs matched = [] not_found = [] for title in PRIORITY_FILMS: if title in title_to_id: matched.append((title, title_to_id[title])) else: not_found.append(title) if not_found: print(f"\nCouldn't find {len(not_found)} films in Radarr:", file=sys.stderr) for t in not_found: print(f" {t}", file=sys.stderr) print(f"\nTriggering search for {len(matched)} films, " f"~{BATCH_SIZE} per batch with {BATCH_GAP_SECONDS}s gaps...", file=sys.stderr) for i in range(0, len(matched), BATCH_SIZE): batch = matched[i:i + BATCH_SIZE] movie_ids = [mid for _, mid in batch] payload = json.dumps({ "name": "MoviesSearch", "movieIds": movie_ids, }).encode("utf-8") req = urllib.request.Request( f"{BASE}/api/v3/command", data=payload, headers={ "X-Api-Key": API_KEY, "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (criterion-crossref/1.0)", }, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as r: r.read() names = ", ".join(t for t, _ in batch) print(f" Batch {i // BATCH_SIZE + 1}: {names}", file=sys.stderr) except Exception as e: print(f" Error on batch {i // BATCH_SIZE + 1}: {e}", file=sys.stderr) if i + BATCH_SIZE < len(matched): time.sleep(BATCH_GAP_SECONDS) print(f"\nDone! Triggered searches for {len(matched)} films.", file=sys.stderr)