#!/usr/bin/env python3 """ Create a new post for National Film Registry movies in the "Found in the Darkroom" series. Usage: python scripts/new_nfr.py tt1234567 # From IMDB ID python scripts/new_nfr.py "Movie Title" # From title (searches TMDB) python scripts/new_nfr.py --list-2024 # Show 2024 NFR list python scripts/new_nfr.py --nfr-year 2024 # Set NFR induction year The script will: 1. Fetch movie data from TMDB (poster, year, director, runtime, genres) 2. Download the poster 3. Create a draft post using the darkroom archetype 4. Pre-fill metadata including NFR year """ import argparse import os import re import sys from datetime import datetime, timezone from pathlib import Path import requests # Configuration try: from config import TMDB_API_KEY except ImportError: raise SystemExit("Error: scripts/config.py not found. Copy config.example.py to config.py and add your API key.") # Paths (relative to script location) SCRIPT_DIR = Path(__file__).parent PROJECT_ROOT = SCRIPT_DIR.parent CONTENT_DIR = PROJECT_ROOT / "content" / "posts" IMAGES_DIR = PROJECT_ROOT / "static" / "images" / "posters" # 2024 National Film Registry inductees with LOC descriptions # Source: https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/ NFR_2024 = { "Annabelle Serpentine Dance": { "year": 1895, "description": 'Preserved as a foundational cinema work that "enticed and enchanted audiences" during film\'s infancy, demonstrating early technical innovations like hand-tinted color.' }, "Koko's Earth Control": { "year": 1928, "description": "Selected for representing the Fleischer Studios' competitive animation style against Disney, featuring innovative techniques like rotoscoping that advanced the medium." }, "Angels with Dirty Faces": { "year": 1938, "description": 'Recognized for depicting "Depression-era immigrant, segregated, hardscrabble neighborhoods" while navigating Production Code restrictions through redemptive storytelling.' }, "The Pride of the Yankees": { "year": 1942, "description": "Honored as one of cinema's seminal sports films, featuring authentic appearances by former Yankees teammates and Lou Gehrig's iconic farewell speech recreation." }, "Invaders from Mars": { "year": 1953, "description": 'Selected for establishing "the visual language of science fiction cinema" and influencing subsequent sci-fi works through post-war paranoia themes.' }, "The Miracle Worker": { "year": 1962, "description": 'Preserved for Arthur Penn\'s "stark black and white" presentation of Helen Keller\'s story, told with minimal sentimentality to highlight human potential.' }, "The Chelsea Girls": { "year": 1966, "description": 'Recognized as a Warhol experimental work that challenged narrative form through dual-projection and "infinite audience interpretations."' }, "Ganja and Hess": { "year": 1973, "description": 'Honored for addressing "complexities of addiction, sexuality and Black identity" through Bill Gunn\'s visionary filmmaking that remained underrecognized.' }, "The Texas Chain Saw Massacre": { "year": 1974, "description": 'Selected for establishing "tenets of the gore/slasher/splatter genre" despite initial controversy, becoming a "cultural and filmmaking touchstone."' }, "Uptown Saturday Night": { "year": 1974, "description": 'Preserved as Sidney Poitier\'s directorial effort "dispelling stereotypes" of the Blaxploitation era through an entertaining crime comedy ensemble cast.' }, "Zora Lathan Student Films": { "year": 1975, "description": "Six short films recognized for showcasing filmmaking techniques and design problem-solving approaches, documenting intimate domestic moments from early 1980s perspectives." }, "Up in Smoke": { "year": 1978, "description": 'Selected for arguably establishing the "stoner" film genre and paving "the way for subsequent memorable movie characters" through comic improvisation.' }, "Will": { "year": 1981, "description": 'Honored as "the first independent feature-length film directed by a Black woman," documenting early 1980s Harlem while addressing addiction and resilience themes.' }, "Star Trek: The Wrath of Khan": { "year": 1982, "description": 'Preserved as "often considered the best of the six original-cast Star Trek theatrical films," featuring expert direction and exploration of sacrifice.' }, "Beverly Hills Cop": { "year": 1984, "description": 'Recognized as "Eddie Murphy\'s first feature film on the registry" and establishing his "box-office superstar" status through this buddy-cop action-comedy.' }, "Dirty Dancing": { "year": 1987, "description": 'Selected for remaining "influential and imitated" despite addressing serious themes including pregnancy, abortion, and breaking class barriers through dance.' }, "Common Threads: Stories from the Quilt": { "year": 1989, "description": 'Honored as an Oscar-winning documentary serving as "a monument to the power of grief and activism" chronicling the AIDS Memorial Quilt\'s creation.' }, "Powwow Highway": { "year": 1989, "description": 'Preserved as "one of the first" films treating "Native Americans as ordinary people," departing from Hollywood stereotypes through a witty buddy road narrative.' }, "My Own Private Idaho": { "year": 1991, "description": 'Recognized for Gus Van Sant\'s "magnificently original cult classic" reimagining Shakespeare through street hustlers\' journeys with "dream-like vision and hardcore reality."' }, "American Me": { "year": 1992, "description": 'Selected for Edward James Olmos\'s directorial debut depicting "dark, brutal realities of Chicano gang life" addressing prison drug trafficking with unflinching honesty.' }, "Mi Familia": { "year": 1995, "description": 'Preserved as Gregory Nava\'s "emotional and evocative" multi-generational Mexican-American family story celebrating immigration\'s role in American vitality.' }, "Compensation": { "year": 1999, "description": 'Honored for director Zeinabu irene Davis\'s innovative accessibility approach incorporating "American Sign Language and title cards" for deaf and hearing audiences.' }, "Spy Kids": { "year": 2001, "description": 'Selected for Robert Rodriguez\'s incorporation of "Hispanic culture" through family-centered storytelling emphasizing "familial bonds and cultural heritage" authenticity.' }, "No Country for Old Men": { "year": 2007, "description": 'Preserved as a Coen Brothers modern-day Western adaptation "hailed as a classic nearly from the moment of release," winning Best Picture Oscar recognition.' }, "The Social Network": { "year": 2010, "description": 'Recognized for transforming a potentially "dry, geeky" corporate narrative into "a riveting examination" of modern business ethics and technology\'s societal impact.' }, } def slugify(title): """Convert title to URL-friendly slug.""" slug = title.lower() slug = re.sub(r"[^a-z0-9\s-]", "", slug) slug = re.sub(r"[\s_]+", "-", slug) slug = re.sub(r"-+", "-", slug) return slug.strip("-") def search_tmdb_by_title(title, year=None): """Search TMDB for a movie by title and optionally year.""" url = "https://api.themoviedb.org/3/search/movie" params = { "api_key": TMDB_API_KEY, "query": title, } if year: params["year"] = year resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() data = resp.json() if not data.get("results"): return None # Return the first result return data["results"][0] def get_tmdb_details(tmdb_id): """Fetch movie details from TMDB.""" url = f"https://api.themoviedb.org/3/movie/{tmdb_id}" params = {"api_key": TMDB_API_KEY} resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() return resp.json() def get_imdb_id_from_tmdb(tmdb_id): """Get IMDB ID from TMDB ID.""" data = get_tmdb_details(tmdb_id) return data.get("imdb_id", "") def get_tmdb_id_from_imdb(imdb_id): """Convert IMDB ID to TMDB ID.""" url = f"https://api.themoviedb.org/3/find/{imdb_id}" params = { "api_key": TMDB_API_KEY, "external_source": "imdb_id", } resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() data = resp.json() results = data.get("movie_results", []) if not results: raise ValueError(f"No TMDB match found for IMDB ID: {imdb_id}") return results[0]["id"] def download_poster(poster_path, filename): """Download poster from TMDB to static/images/posters/.""" if not poster_path: print(" No poster available") return None # Use w500 size for good quality without being huge url = f"https://image.tmdb.org/t/p/w500{poster_path}" resp = requests.get(url, timeout=10) resp.raise_for_status() IMAGES_DIR.mkdir(parents=True, exist_ok=True) filepath = IMAGES_DIR / filename filepath.write_bytes(resp.content) print(f" Poster saved: {filepath.relative_to(PROJECT_ROOT)}") return f"/images/posters/{filename}" def extract_imdb_id(input_str): """Extract IMDB ID from string (handles raw ID or URL).""" # Check if it's already just an ID if re.match(r'^tt\d+$', input_str): return input_str # Try to extract from URL match = re.search(r'(tt\d+)', input_str) if match: return match.group(1) return None def format_director(directors): """Format director(s) for YAML frontmatter.""" if not directors: return '""' if len(directors) == 1: return f'"{directors[0]}"' # Multiple directors - use YAML list format return "[" + ", ".join(f'"{d}"' for d in directors) + "]" def create_nfr_post(tmdb_data, imdb_id, nfr_year=2024): """Create a draft post for an NFR movie.""" title = tmdb_data.get("title", "Unknown") slug = slugify(title) filename = f"{slug}.md" filepath = CONTENT_DIR / filename if filepath.exists(): print(f" Post already exists: {filepath.relative_to(PROJECT_ROOT)}") overwrite = input(" Overwrite? (y/N): ").strip().lower() if overwrite != 'y': return None # Format the date for Hugo now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") # Extract metadata year = tmdb_data.get("release_date", "")[:4] if tmdb_data.get("release_date") else "" runtime = tmdb_data.get("runtime", "") overview = tmdb_data.get("overview", "") # Get directors from crew directors = [] # Note: Full crew info requires a second API call, so we'll leave it blank for now # Users can fill it in or we can enhance this later # Genres genres = [g["name"] for g in tmdb_data.get("genres", [])] genres_yaml = "[" + ", ".join(genres) + "]" if genres else "[]" # Poster poster_url = "" if tmdb_data.get("poster_path"): print(" Downloading poster...") poster_filename = f"{slug}.jpg" poster_url = download_poster(tmdb_data["poster_path"], poster_filename) # Look up LOC description if this is a 2024 NFR film loc_description = "" if nfr_year == 2024: # Try to match the title to our NFR_2024 dictionary for nfr_title, nfr_data in NFR_2024.items(): if title.lower() in nfr_title.lower() or nfr_title.lower() in title.lower(): loc_description = nfr_data["description"] print(f" Found LOC description for NFR 2024: {nfr_title}") break # Build NFR section content if loc_description: nfr_section = f"""## Why It's in the National Film Registry {loc_description} *Source: [Library of Congress National Film Registry 2024 announcement](https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/)*""" else: nfr_section = """## Why It's in the National Film Registry [Add information about why this film was selected for preservation by the Library of Congress]""" # Build the frontmatter and content content = f'''--- title: '{title}' date: {now} draft: true series: "Found in the Darkroom" summary: "" imdb: "{imdb_id}" poster: "{poster_url or ''}" year: {year} runtime: {runtime} director: "" genres: {genres_yaml} nfr_year: {nfr_year} letterboxd_url: "" tags: - national-film-registry - home-video --- {{{{< imdbposter >}}}} | Date watched | | |------------------------|-----------------------| | Format | | | Watched Multiple Times | | | Added to NFR | {nfr_year} | | Letterboxd Rating | | | Personal Notes | | {{{{< /imdbposter >}}}} {nfr_section} ## My Thoughts {overview} ''' filepath.write_text(content) print(f" Draft created: {filepath.relative_to(PROJECT_ROOT)}") print(f"\nNext steps:") print(f" 1. Fill in director and other metadata by running:") print(f" python scripts/fetch_movie_data.py") print(f" 2. Add your viewing details and thoughts") if not loc_description: print(f" 3. Research why it was added to the NFR") print(f" {'4' if not loc_description else '3'}. Add your Letterboxd URL if you've logged it there") return filepath def list_nfr_2024(): """Display the 2024 NFR inductees.""" print("\n2024 National Film Registry Inductees:\n") for i, (title, data) in enumerate(NFR_2024.items(), 1): print(f" {i:2}. {title} ({data['year']})") print() def main(): parser = argparse.ArgumentParser( description="Create NFR movie posts for 'Found in the Darkroom' series" ) parser.add_argument("input", nargs="?", help="IMDB ID (tt1234567) or movie title") parser.add_argument("--list-2024", action="store_true", help="List 2024 NFR inductees") parser.add_argument("--nfr-year", type=int, default=2024, help="NFR induction year (default: 2024)") args = parser.parse_args() if args.list_2024: list_nfr_2024() sys.exit(0) if not args.input: print("Error: Please provide an IMDB ID or movie title") print("\nUsage:") print(" python scripts/new_nfr.py tt1234567") print(" python scripts/new_nfr.py 'No Country for Old Men'") print(" python scripts/new_nfr.py --list-2024") sys.exit(1) try: # Try to extract IMDB ID imdb_id = extract_imdb_id(args.input) if imdb_id: print(f"Looking up movie by IMDB ID: {imdb_id}") tmdb_id = get_tmdb_id_from_imdb(imdb_id) tmdb_data = get_tmdb_details(tmdb_id) else: # Assume it's a title search print(f"Searching for: {args.input}") # Try to find year in NFR list year_hint = None for title, data in NFR_2024.items(): if args.input.lower() in title.lower() or title.lower() in args.input.lower(): year_hint = data["year"] print(f"Found in NFR 2024 list: {title} ({data['year']})") break search_result = search_tmdb_by_title(args.input, year_hint) if not search_result: print(f"Error: No movie found for '{args.input}'") sys.exit(1) print(f"Found: {search_result['title']} ({search_result.get('release_date', '')[:4]})") confirm = input("Is this correct? (Y/n): ").strip().lower() if confirm == 'n': print("Search cancelled") sys.exit(0) tmdb_id = search_result["id"] tmdb_data = get_tmdb_details(tmdb_id) imdb_id = tmdb_data.get("imdb_id", "") if not imdb_id: print("Warning: No IMDB ID found for this movie") print(f"\nCreating post for: {tmdb_data.get('title')}") create_nfr_post(tmdb_data, imdb_id, args.nfr_year) except Exception as e: print(f"Error: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()