Updated the metadata fetching script and wrote the first National Film Registry entry
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: '{{ replace .Name "-" " " | title }}'
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
series: "Found in the Darkroom"
|
||||
summary: "TODO: Add a summary for the homepage"
|
||||
# Fill in IMDB ID, then run: python scripts/fetch_movie_data.py
|
||||
imdb: ""
|
||||
# Auto-filled by fetch_movie_data.py:
|
||||
poster: ""
|
||||
year:
|
||||
runtime:
|
||||
director: ""
|
||||
genres: []
|
||||
# National Film Registry info
|
||||
nfr_year: 2024
|
||||
letterboxd_url: ""
|
||||
tags:
|
||||
- national-film-registry
|
||||
- home-video
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
| Date watched | |
|
||||
|------------------------|-----------------------|
|
||||
| Format | |
|
||||
| Watched Multiple Times | |
|
||||
| Added to NFR | {{ .Params.nfr_year }} |
|
||||
| Letterboxd Rating | |
|
||||
| Personal Notes | |
|
||||
|
||||
{{< /imdbposter >}}
|
||||
|
||||
## Why It's in the National Film Registry
|
||||
|
||||
[Add information about why this film was selected for preservation]
|
||||
|
||||
## My Thoughts
|
||||
|
||||
This is where our review goes and we talk about the film, its historical significance, and how it holds up today.
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
---
|
||||
title: 'Avatar Fire and Ash'
|
||||
date: 2025-12-23T03:44:12Z
|
||||
title: Avatar Fire and Ash
|
||||
date: 2025-12-23 03:44:12+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "James Cameron and the boys enjoy a hot winter evening."
|
||||
imdb: "tt1757678"
|
||||
series: Frank's Couch
|
||||
summary: James Cameron and the boys enjoy a hot winter evening.
|
||||
imdb: tt1757678
|
||||
tags:
|
||||
- ghost theater
|
||||
- anticipated
|
||||
- ghost theater
|
||||
- anticipated
|
||||
poster: /images/posters/avatar-fire-and-ash.jpg
|
||||
runtime: 198
|
||||
year: 2025
|
||||
director: James Cameron
|
||||
genres:
|
||||
- Science Fiction
|
||||
- Adventure
|
||||
- Fantasy
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
---
|
||||
title: 'Cloud Atlas Pancake'
|
||||
date: 2024-11-20T14:49:23Z
|
||||
title: Cloud Atlas Pancake
|
||||
date: 2024-11-20 14:49:23+00:00
|
||||
draft: true
|
||||
series: "Frank's Couch"
|
||||
summary: "First post about a Master Pancake movie. Cloud Atlas was confusing but it was fun!"
|
||||
imdb: "tt1371111"
|
||||
series: Frank's Couch
|
||||
summary: First post about a Master Pancake movie. Cloud Atlas was confusing but it
|
||||
was fun!
|
||||
imdb: tt1371111
|
||||
tags:
|
||||
- CYOP
|
||||
- mueller
|
||||
- no-pizza
|
||||
- CYOP
|
||||
- mueller
|
||||
- no-pizza
|
||||
poster: /images/posters/cloud-atlas-pancake.jpg
|
||||
runtime: 172
|
||||
year: 2012
|
||||
director:
|
||||
- Lilly Wachowski
|
||||
- Lana Wachowski
|
||||
- Tom Tykwer
|
||||
genres:
|
||||
- Drama
|
||||
- Science Fiction
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
---
|
||||
title: 'Joker Folie a Deux'
|
||||
date: 2024-10-07T00:03:15Z
|
||||
title: Joker Folie a Deux
|
||||
date: 2024-10-07 00:03:15+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "The boys get to B serious about Joker 2"
|
||||
imdb: "tt11315808"
|
||||
series: Frank's Couch
|
||||
summary: The boys get to B serious about Joker 2
|
||||
imdb: tt11315808
|
||||
tags:
|
||||
- gucci
|
||||
- anticipated
|
||||
- no pizza
|
||||
- gucci
|
||||
- anticipated
|
||||
- no pizza
|
||||
poster: /images/posters/joker-folie-a-deux.jpg
|
||||
runtime: 138
|
||||
year: 2024
|
||||
director: Todd Phillips
|
||||
genres:
|
||||
- Drama
|
||||
- Crime
|
||||
- Thriller
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
---
|
||||
title: 'Megalopolis'
|
||||
date: 2024-10-02T00:07:14Z
|
||||
title: Megalopolis
|
||||
date: 2024-10-02 00:07:14+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "The boys catch Megalopolis at Gucci on a Saturday Afternoon."
|
||||
imdb: "tt10128846"
|
||||
series: Frank's Couch
|
||||
summary: The boys catch Megalopolis at Gucci on a Saturday Afternoon.
|
||||
imdb: tt10128846
|
||||
tags:
|
||||
- gucci
|
||||
- anticipated
|
||||
- had pizza
|
||||
poster: /images/posters/megalopolis.jpg
|
||||
runtime: 138
|
||||
year: 2024
|
||||
director: Francis Ford Coppola
|
||||
genres:
|
||||
- Science Fiction
|
||||
- Drama
|
||||
- Fantasy
|
||||
---
|
||||
|
||||
{{< imdbposter >}}
|
||||
|
||||
| Date watched | September 28 |
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
---
|
||||
title: 'Terrifier 3'
|
||||
date: 2024-10-15T00:28:11Z
|
||||
title: Terrifier 3
|
||||
date: 2024-10-15 00:28:11+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "The Boys get terrified, late at night."
|
||||
imdb: "tt27911000"
|
||||
|
||||
series: Frank's Couch
|
||||
summary: The Boys get terrified, late at night.
|
||||
imdb: tt27911000
|
||||
tags:
|
||||
- ghost theater
|
||||
- anticipated
|
||||
- no-pizza
|
||||
- ghost theater
|
||||
- anticipated
|
||||
- no-pizza
|
||||
poster: /images/posters/terrifier-3.jpg
|
||||
runtime: 125
|
||||
year: 2024
|
||||
director: Damien Leone
|
||||
genres:
|
||||
- Horror
|
||||
- Thriller
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
---
|
||||
title: 'The Housemaid'
|
||||
date: 2026-01-01T05:54:14Z
|
||||
title: The Housemaid
|
||||
date: 2026-01-01 05:54:14+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "Marcus goes it alone on New Year's Eve to learn about manipulation and the cost of privilege."
|
||||
imdb: "tt27543632"
|
||||
poster: "/images/posters/the-housemaid.jpg"
|
||||
series: Frank's Couch
|
||||
summary: Marcus goes it alone on New Year's Eve to learn about manipulation and the
|
||||
cost of privilege.
|
||||
imdb: tt27543632
|
||||
poster: /images/posters/the-housemaid.jpg
|
||||
tags:
|
||||
- marcel
|
||||
- no-expectations
|
||||
# Mastodon comments: After posting about this on Mastodon, add the post ID below.
|
||||
# Get the ID from the end of the toot URL, e.g. https://tilde.zone/@mnw/123456789
|
||||
# mastodon_id: ""
|
||||
# To block a reply from showing, add its full URL to this list:
|
||||
# mastodon_blocked:
|
||||
# - "https://tilde.zone/@someone/123456789"
|
||||
- marcel
|
||||
- no-expectations
|
||||
runtime: 131
|
||||
year: 2025
|
||||
director: Paul Feig
|
||||
genres:
|
||||
- Mystery
|
||||
- Thriller
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
---
|
||||
title: 'The Secret Agent'
|
||||
date: 2025-12-26T00:37:12Z
|
||||
title: The Secret Agent
|
||||
date: 2025-12-26 00:37:12+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "Its Christmas lets go watch a movie or two. Secret Agent is a Brazilian 70s Spy Thriller"
|
||||
imdb: "tt27847051"
|
||||
poster: "/images/posters/the-secret-agent.jpg"
|
||||
series: Frank's Couch
|
||||
summary: Its Christmas lets go watch a movie or two. Secret Agent is a Brazilian 70s
|
||||
Spy Thriller
|
||||
imdb: tt27847051
|
||||
poster: /images/posters/the-secret-agent.jpg
|
||||
tags:
|
||||
- no-expectations
|
||||
- alamo-drafthouse
|
||||
# Mastodon comments: After posting about this on Mastodon, add the post ID below.
|
||||
# Get the ID from the end of the toot URL, e.g. https://tilde.zone/@mnw/123456789
|
||||
# mastodon_id: ""
|
||||
# To block a reply from showing, add its full URL to this list:
|
||||
# mastodon_blocked:
|
||||
# - "https://tilde.zone/@someone/123456789"
|
||||
- no-expectations
|
||||
- alamo-drafthouse
|
||||
runtime: 161
|
||||
year: 2025
|
||||
director: Kleber Mendonça Filho
|
||||
genres:
|
||||
- Crime
|
||||
- Drama
|
||||
- Thriller
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Uptown Saturday Night
|
||||
date: 2026-01-02 04:00:57+00:00
|
||||
draft: true
|
||||
series: Found in the Darkroom
|
||||
summary: 'We embark on our journey to watch movies of the National Film Archive starting with a fun to watch comedy from 1974.'
|
||||
imdb: tt0072351
|
||||
poster: /images/posters/uptown-saturday-night.jpg
|
||||
year: 1974
|
||||
runtime: 104
|
||||
director: Sidney Poitier
|
||||
genres:
|
||||
- Comedy
|
||||
- Crime
|
||||
- Action
|
||||
nfr_year: 2024
|
||||
letterboxd_url: 'https://letterboxd.com/marcuseid/film/uptown-saturday-night/'
|
||||
tags:
|
||||
- national-film-registry
|
||||
- home-video
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
| Date watched | |
|
||||
|------------------------|-----------------------|
|
||||
| Format | 4k TV w/Panasonic BRPlayer |
|
||||
| Watched Multiple Times | Second Watch |
|
||||
| Added to NFR | 2024 |
|
||||
| Letterboxd Rating | **** (4.0) |
|
||||
| Personal Notes | Fun and full of laughs |
|
||||
|
||||
{{< /imdbposter >}}
|
||||
|
||||
## Why It's in the National Film Registry
|
||||
|
||||
Preserved as Sidney Poitier's directorial effort "dispelling stereotypes" of the Blaxploitation era through an entertaining crime comedy ensemble cast.
|
||||
|
||||
*Source: [Library of Congress National Film Registry 2024 announcement](https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/)*
|
||||
|
||||
## My Thoughts
|
||||
|
||||
My friend Mehone recommended Uptown Saturday Night to me a year or two ago. I watched it back then and enjoyed it, but I never logged it or wrote about it; it was just a fun weekend movie at the time. However, now that I’m working my way through the National Film Registry (starting with the 2024 additions), I decided to revisit it, and I’m finding a lot to like.
|
||||
|
||||
**The Setup**
|
||||
|
||||
The plot follows Steve Jackson (Sidney Poitier), a blue-collar worker who has two weeks of vacation. It looks like a foundry of some kind but I believe they call it a factory in the movie for some reason it's something that is sticking in my mind. Since his wife is still working, he plans to just relax around the house. However, his best friend, Wardell Franklin (a young _beareded_ Bill Cosby), thinks Steve needs to unwind a little more aggressively.
|
||||
|
||||
Wardell suggests going to Madame Zenobia’s, a high-class, after-hours club. Everyone knows about it, but not everyone can get in. Wardell manages to forge a letter of introduction using his wife's employer's stationery, claiming they are important players in the diamond business. Surprisingly, this gets them in the door.
|
||||
The Incident
|
||||
|
||||
The club is a showcase of the hottest looks of the era—big hats, wonderful hairstyles, and wild outfits. Steve and Wardell eventually find their way to a room behind the red door where gambling is taking place. It’s a high-stakes environment; the bouncer warns them that loitering isn’t allowed and the buy-in is high. Steve lends Wardell some cash, they start betting, and suddenly, they are on a hot streak.
|
||||
|
||||
Unfortunately, the celebration is cut short when a crew busts in to rob the place. In a strange twist, or maybe it was just a thing they did at the time, the robbers force everyone to strip down to their underwear. They say in case one of you has a heater which I think means a concealed weapon. It also helps to prevent anyone from chasing them immediately. (One woman claims she isn't wearing underwear, but they make her strip anyway—though the movie keeps it PG and doesn't show anything explicit).
|
||||
|
||||
The robbery sets up the central conflict: Steve had a lottery ticket in his wallet containing his lucky numbers. The next day, he sees those numbers hit the jackpot in the newspaper, and realizes the ticket is lost! The ticket is worth $50,000 (which is over $320,000 adjusted for inflation). It’s life changing amount money that would allow his family to move out of the inner city. The rest of the movie is a hilarious race to find the wallet without letting anyone know what is in it.
|
||||
|
||||
**A Star-Studded Cast**
|
||||
|
||||
The film features a massive roster of wonderful actors, and everyone gets a moment to shine.
|
||||
|
||||
Harry Belafonte plays Geechee Dan, a menacing gangster who looks like he has bad allergies or is just really sick.
|
||||
|
||||
Flip Wilson plays the Reverend who preaches "No joy juice at the picknic!"
|
||||
|
||||
Richard Pryor has a cameo as Sharp Eye Washington, a con man, who posed as a private eye briefly.
|
||||
|
||||
Calvin Lockhart plays Silky Slim a rival gangster to Geechee Dan.
|
||||
|
||||
Rosalind Cash plays Steve’s wife, Sarah Jackson. She has some of the best lines!
|
||||
|
||||
Paula Kelly and Lee Chamberlin (Madame Zenobia) also turn in great performances.
|
||||
|
||||
One of the funniest performances comes from Roscoe Lee Browne, who plays Congressman Lincoln. His character satirizes politicians embracing the Black Power movement for votes. When constituents arrive, he hurriedly flips a picture of Richard Nixon around to reveal a portrait of Malcolm X and changes from a suit into a dashiki to play the part. It was a hilarious reminder of how post-Watergate movies depicted politicians as chameleons willing to say anything for a vote.
|
||||
The Elephant in the Room
|
||||
|
||||
It is impossible to discuss this film without acknowledging that Bill Cosby has since been convicted of heinous sex crimes. However, looking strictly at the film as a 1970s comedy, the character he plays does not have any romantic entanglements or questionable "adult" situations. If you can, as they say, separate the art from the artist's off-screen behavior, his dynamic with Poitier is genuinely funny.
|
||||
|
||||
**The 70s Aesthetic**
|
||||
|
||||
There are some very specific "70s things" in this movie that I really enjoyed. For one, the church picnic scene is massive. The church actually owns a Greyhound-style bus to transport the congregation to a fairground for food and games—it really captures the community vibe of the era.
|
||||
|
||||
The fashion is also incredible. As a child of the 80s and 90s, I used to think bellbottoms and wide lapels looked ridiculous. Watching it now, I totally get it. The lines, the colors, and the energy that the right pair of shoes or jacket conveyed allowed people to really stand out. Plus, the slang is a blast to listen to—you don't hear people getting called "turkeys" enough anymore.
|
||||
Preservation and Quality
|
||||
|
||||
To wrap things up, I was struck by how well-preserved this film is. Because it is on film it was also possible to be scanned in at high resolution and detail. The audio is just as crisp as when it was recorded.
|
||||
|
||||
It makes me wonder about our current digital era. We record video just to compress it immediately. Many movies were left behind on VHS, then DVD, then Blu-ray. In 30 years, when we are watching on "2060's Hottest new Retina 20K" displays, our current digital footage might look blown out and pixelated. But Uptown Saturday Night? It will still look sharp.
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
title: 'Urchin'
|
||||
date: 2025-12-25T15:53:35Z
|
||||
title: Urchin
|
||||
date: 2025-12-25 15:53:35+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "I watched movie that felt very real and led me to refelct on mylife. Felt almost like I could have escaped into the tv screen."
|
||||
imdb: "tt35715953"
|
||||
poster: "/images/posters/urchin.jpg"
|
||||
series: Frank's Couch
|
||||
summary: I watched movie that felt very real and led me to refelct on mylife. Felt
|
||||
almost like I could have escaped into the tv screen.
|
||||
imdb: tt35715953
|
||||
poster: /images/posters/urchin.jpg
|
||||
tags:
|
||||
- homevideo
|
||||
- no-expectations
|
||||
# Mastodon comments: After posting about this on Mastodon, add the post ID below.
|
||||
# Get the ID from the end of the toot URL, e.g. https://tilde.zone/@mnw/123456789
|
||||
# mastodon_id: ""
|
||||
# To block a reply from showing, add its full URL to this list:
|
||||
# mastodon_blocked:
|
||||
# - "https://tilde.zone/@someone/123456789"
|
||||
- homevideo
|
||||
- no-expectations
|
||||
runtime: 100
|
||||
year: 2025
|
||||
director: Harris Dickinson
|
||||
genres:
|
||||
- Drama
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
---
|
||||
title: 'Will and Harper'
|
||||
date: 2024-10-05T16:26:32Z
|
||||
title: Will and Harper
|
||||
date: 2024-10-05 16:26:32+00:00
|
||||
draft: false
|
||||
series: "Frank's Couch"
|
||||
summary: "I watched Will and Harper at Home on a Sunday afternoon."
|
||||
imdb: "tt30321133"
|
||||
series: Frank's Couch
|
||||
summary: I watched Will and Harper at Home on a Sunday afternoon.
|
||||
imdb: tt30321133
|
||||
tags:
|
||||
- no-expectations
|
||||
- no pizza
|
||||
- no-expectations
|
||||
- no pizza
|
||||
poster: /images/posters/will-and-harper.jpg
|
||||
runtime: 114
|
||||
year: 2024
|
||||
director: Josh Greenbaum
|
||||
genres:
|
||||
- Documentary
|
||||
- Comedy
|
||||
---
|
||||
{{< imdbposter >}}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{{ define "main" -}}
|
||||
<h1 class="title">{{ .Title }}</h1>
|
||||
|
||||
{{ if eq .Title "Found in the Darkroom" }}
|
||||
<div style="margin-bottom: 2em; line-height: 1.6;">
|
||||
<p><strong>Found in the Darkroom</strong> is a journey through the National Film Registry, where America's cinematic heritage is preserved one frame at a time.</p>
|
||||
|
||||
<p>The <a href="https://www.loc.gov/programs/national-film-preservation-board/film-registry/" target="_blank">National Film Registry</a> is maintained by the Library of Congress, which each year selects 25 films that are "culturally, historically, or aesthetically significant" for preservation. These aren't just the "best" films—they're the ones that matter, that capture moments in time, that changed how we see the world or simply how we see movies.</p>
|
||||
|
||||
<p>The "darkroom" is where film is developed, where latent images become visible, where memories are fixed and preserved. It's a fitting metaphor for revisiting these films: bringing them out of storage, letting them develop again in our minds, seeing what remains when we hold them up to the light today.</p>
|
||||
|
||||
<p>From the earliest experiments in motion pictures to modern classics, these are the films worth preserving, worth remembering, worth watching again.</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<ul class="entries">
|
||||
{{ range .Pages.GroupByDate "2006" }}
|
||||
<h3 style="text-align: center;">{{ .Key }}</h3>
|
||||
{{ range .Pages }}
|
||||
<li>
|
||||
<span class="title">
|
||||
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
|
||||
{{ if .Params.year }}
|
||||
<span style="color: #888;">({{ .Params.year }})</span>
|
||||
{{ end }}
|
||||
{{ if .Params.nfr_year }}
|
||||
<span style="color: #888; font-size: 0.9em;"> • NFR {{ .Params.nfr_year }}</span>
|
||||
{{ end }}
|
||||
</span>
|
||||
<span class="published">
|
||||
<time class="pull-right post-list">{{ .Date | time.Format ":date_long" }}</time>
|
||||
</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
@@ -1 +1,2 @@
|
||||
requests
|
||||
pyyaml
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
# National Film Registry Automation Guide
|
||||
|
||||
This guide explains how to automatically pull and setup data for National Film Registry movies from any year.
|
||||
|
||||
## Overview
|
||||
|
||||
The NFR automation system consists of:
|
||||
|
||||
1. **`setup_nfr.py`** - Script to fetch LOC announcements and extract film data
|
||||
2. **`new_nfr.py`** - Script to create blog posts for NFR movies
|
||||
3. **ollama** - Local AI to help extract structured data from web pages
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Setup data for a specific year
|
||||
python3 scripts/setup_nfr.py 2023
|
||||
|
||||
# With a known URL
|
||||
python3 scripts/setup_nfr.py 2015 --url "https://newsroom.loc.gov/news/..."
|
||||
|
||||
# Without ollama (basic extraction)
|
||||
python3 scripts/setup_nfr.py 2022 --no-ollama
|
||||
```
|
||||
|
||||
### With Ollama (Recommended)
|
||||
|
||||
Ollama provides much better extraction of film descriptions from the LOC announcements.
|
||||
|
||||
```bash
|
||||
# Default (uses ollama at 192.168.0.109:11434)
|
||||
python3 scripts/setup_nfr.py 2023
|
||||
|
||||
# Custom ollama host
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-host http://localhost:11434
|
||||
|
||||
# Custom model
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-model llama3.2:latest
|
||||
```
|
||||
|
||||
## Setting Up Ollama
|
||||
|
||||
### What is Ollama?
|
||||
|
||||
Ollama is a tool for running large language models locally. We use it to:
|
||||
- Parse HTML content from LOC announcements
|
||||
- Extract film titles, years, and descriptions
|
||||
- Structure the data into Python dictionaries
|
||||
|
||||
### Installing Ollama
|
||||
|
||||
Your server at `192.168.0.109` should already have ollama running. To verify:
|
||||
|
||||
```bash
|
||||
curl http://192.168.0.109:11434/api/tags
|
||||
```
|
||||
|
||||
If you need to install it locally:
|
||||
|
||||
```bash
|
||||
# macOS / Linux
|
||||
curl https://ollama.ai/install.sh | sh
|
||||
|
||||
# Start the server
|
||||
ollama serve
|
||||
|
||||
# Pull a model
|
||||
ollama pull llama3.2
|
||||
```
|
||||
|
||||
### Ollama Configuration
|
||||
|
||||
The script uses these environment variables:
|
||||
|
||||
```bash
|
||||
# Set custom ollama host
|
||||
export OLLAMA_HOST=http://192.168.0.109:11434
|
||||
|
||||
# Set custom model (default: llama3.2)
|
||||
export OLLAMA_MODEL=llama3.2
|
||||
|
||||
# Then run the script
|
||||
python3 scripts/setup_nfr.py 2023
|
||||
```
|
||||
|
||||
### Testing Ollama Connection
|
||||
|
||||
Test if ollama is accessible:
|
||||
|
||||
```bash
|
||||
# Test API endpoint
|
||||
curl http://192.168.0.109:11434/api/tags
|
||||
|
||||
# Test generation
|
||||
curl http://192.168.0.109:11434/api/generate -d '{
|
||||
"model": "llama3.2",
|
||||
"prompt": "Say hello",
|
||||
"stream": false
|
||||
}'
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Step 1: Find the LOC Announcement
|
||||
|
||||
The script needs the URL of the Library of Congress announcement for your year. For example:
|
||||
|
||||
- **2024**: https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/s/55d5285d-916f-4105-b7d4-7fc3ba8664e3
|
||||
- **2023**: Search at https://newsroom.loc.gov/
|
||||
- **Older**: Check https://blogs.loc.gov/now-see-hear/
|
||||
|
||||
You can provide the URL with `--url` or the script will prompt you.
|
||||
|
||||
### Step 2: Fetch the Content
|
||||
|
||||
The script downloads the HTML content from the announcement page.
|
||||
|
||||
### Step 3: Extract Film Data
|
||||
|
||||
**With ollama (recommended):**
|
||||
- Sends the HTML to ollama
|
||||
- Asks it to extract all 25 films with titles, years, and descriptions
|
||||
- Returns structured JSON data
|
||||
|
||||
**Without ollama (fallback):**
|
||||
- Uses regex patterns to find film titles and years
|
||||
- May miss descriptions or get incomplete data
|
||||
- Requires manual review and editing
|
||||
|
||||
### Step 4: Generate Python Dictionary
|
||||
|
||||
Creates a Python file like:
|
||||
|
||||
```python
|
||||
# 2023 National Film Registry inductees with LOC descriptions
|
||||
# Source: https://newsroom.loc.gov/news/...
|
||||
NFR_2023 = {
|
||||
"Film Title": {
|
||||
"year": 1999,
|
||||
"description": "Selected for its groundbreaking..."
|
||||
},
|
||||
# ... more films
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Integration
|
||||
|
||||
The generated file is saved to `scripts/nfr_data/nfr_YEAR.py`. You can then:
|
||||
|
||||
1. Review and edit the file
|
||||
2. Copy the dictionary into `scripts/new_nfr.py`
|
||||
3. Update the script to handle the new year
|
||||
|
||||
## Complete Example
|
||||
|
||||
Let's set up 2023 NFR data:
|
||||
|
||||
```bash
|
||||
# 1. Run the setup script
|
||||
python3 scripts/setup_nfr.py 2023
|
||||
|
||||
# The script will prompt:
|
||||
# > Please find the LOC announcement URL for 2023.
|
||||
# > Enter the URL: https://newsroom.loc.gov/news/...
|
||||
|
||||
# 2. Script fetches and extracts (using ollama)
|
||||
# ✓ Extracted 25 films
|
||||
# Preview:
|
||||
# 1. Terminator 2 (1991)
|
||||
# Recognized for groundbreaking visual effects...
|
||||
# ... and 24 more
|
||||
|
||||
# 3. Confirm and save
|
||||
# Save this data? (Y/n): y
|
||||
# ✓ Saved to scripts/nfr_data/nfr_2023.py
|
||||
|
||||
# 4. Review the generated file
|
||||
cat scripts/nfr_data/nfr_2023.py
|
||||
|
||||
# 5. Copy the dictionary into new_nfr.py
|
||||
# (You can do this manually or we can create a script to merge)
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
scripts/
|
||||
├── setup_nfr.py # Main automation script
|
||||
├── new_nfr.py # Create blog posts
|
||||
├── nfr_data/ # Generated NFR data files
|
||||
│ ├── nfr_2023.py
|
||||
│ ├── nfr_2024.py
|
||||
│ └── ...
|
||||
└── NFR_AUTOMATION.md # This file
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ollama Connection Errors
|
||||
|
||||
```bash
|
||||
# Check if ollama is running
|
||||
curl http://192.168.0.109:11434/api/tags
|
||||
|
||||
# Check network connectivity
|
||||
ping 192.168.0.109
|
||||
|
||||
# Try with localhost if running locally
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-host http://localhost:11434
|
||||
```
|
||||
|
||||
### Extraction Problems
|
||||
|
||||
If extraction fails:
|
||||
|
||||
```bash
|
||||
# Try without ollama first (gets basic structure)
|
||||
python3 scripts/setup_nfr.py 2023 --no-ollama
|
||||
|
||||
# Then manually edit the descriptions in nfr_data/nfr_2023.py
|
||||
```
|
||||
|
||||
### Model Not Found
|
||||
|
||||
```bash
|
||||
# On the ollama server, pull the model
|
||||
ssh user@192.168.0.109
|
||||
ollama pull llama3.2
|
||||
|
||||
# Or use a different model you have
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-model mistral
|
||||
```
|
||||
|
||||
## Finding LOC Announcements
|
||||
|
||||
### Recent Years (2010-present)
|
||||
|
||||
Check the newsroom:
|
||||
```
|
||||
https://newsroom.loc.gov/
|
||||
```
|
||||
|
||||
Search for "national film registry" + year
|
||||
|
||||
### Older Years
|
||||
|
||||
Check the blog:
|
||||
```
|
||||
https://blogs.loc.gov/now-see-hear/
|
||||
```
|
||||
|
||||
Or the registry page:
|
||||
```
|
||||
https://www.loc.gov/programs/national-film-preservation-board/film-registry/
|
||||
```
|
||||
|
||||
### Complete Registry List
|
||||
|
||||
For a complete list by year:
|
||||
```
|
||||
https://www.loc.gov/programs/national-film-preservation-board/film-registry/complete-national-film-registry-listing/
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Output Location
|
||||
|
||||
```bash
|
||||
python3 scripts/setup_nfr.py 2023 \
|
||||
--output /tmp/nfr_2023.py
|
||||
```
|
||||
|
||||
### Batch Processing Multiple Years
|
||||
|
||||
```bash
|
||||
# Create a simple loop
|
||||
for year in 2020 2021 2022 2023; do
|
||||
python3 scripts/setup_nfr.py $year
|
||||
done
|
||||
```
|
||||
|
||||
### Using Different AI Models
|
||||
|
||||
```bash
|
||||
# Llama 3.2 (default, good balance)
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-model llama3.2
|
||||
|
||||
# Mistral (faster, less accurate)
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-model mistral
|
||||
|
||||
# Larger models for better extraction
|
||||
python3 scripts/setup_nfr.py 2023 --ollama-model llama3.2:70b
|
||||
```
|
||||
|
||||
## Integration with new_nfr.py
|
||||
|
||||
After generating NFR data, integrate it into `new_nfr.py`:
|
||||
|
||||
### Option 1: Manual Copy
|
||||
|
||||
1. Open `scripts/nfr_data/nfr_2023.py`
|
||||
2. Copy the `NFR_2023` dictionary
|
||||
3. Add it to `scripts/new_nfr.py` after `NFR_2024`
|
||||
4. Update the `create_nfr_post` function to check `NFR_2023` too
|
||||
|
||||
### Option 2: Import (Future Enhancement)
|
||||
|
||||
```python
|
||||
# In new_nfr.py
|
||||
from nfr_data.nfr_2023 import NFR_2023
|
||||
from nfr_data.nfr_2024 import NFR_2024
|
||||
|
||||
NFR_DATA = {
|
||||
2023: NFR_2023,
|
||||
2024: NFR_2024,
|
||||
}
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Always review the output** - AI extraction is good but not perfect
|
||||
2. **Keep source URLs** - Add them to the generated dictionaries
|
||||
3. **Check film counts** - Should be 25 films per year
|
||||
4. **Verify years** - Make sure film years are in reasonable ranges
|
||||
5. **Edit descriptions** - Feel free to trim or rephrase for your blog
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Generate data for years you want to cover
|
||||
2. Review and edit the descriptions
|
||||
3. Integrate into `new_nfr.py`
|
||||
4. Start creating blog posts with `python3 scripts/new_nfr.py "Film Title"`
|
||||
|
||||
## Questions?
|
||||
|
||||
- Check if ollama is running: `curl http://192.168.0.109:11434/api/tags`
|
||||
- Test the script with 2024 (known working): `python3 scripts/setup_nfr.py 2024`
|
||||
- Use `--no-ollama` to see basic extraction
|
||||
- Look at generated files in `scripts/nfr_data/`
|
||||
@@ -0,0 +1,271 @@
|
||||
# Blog Scripts
|
||||
|
||||
Automation scripts for The Double Lunch Dispatch blog.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
# Create virtual environment (if not already done)
|
||||
python3 -m venv .venv
|
||||
|
||||
# Activate it
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Required: TMDB API Key
|
||||
|
||||
All movie scripts require a TMDB API key.
|
||||
|
||||
```bash
|
||||
# Copy the example config
|
||||
cp scripts/config.example.py scripts/config.py
|
||||
|
||||
# Edit and add your TMDB API key
|
||||
# Get one at: https://www.themoviedb.org/settings/api
|
||||
```
|
||||
|
||||
## Scripts Overview
|
||||
|
||||
### National Film Registry (NFR) Series
|
||||
|
||||
**"Found in the Darkroom"** - A series covering the National Film Registry
|
||||
|
||||
#### Setup NFR Data (New!)
|
||||
|
||||
Automatically fetch and setup data for any NFR year:
|
||||
|
||||
```bash
|
||||
# Setup data for a specific year
|
||||
python3 scripts/setup_nfr.py 2023
|
||||
|
||||
# With a known URL
|
||||
python3 scripts/setup_nfr.py 2015 --url "https://newsroom.loc.gov/news/..."
|
||||
|
||||
# See full documentation
|
||||
cat scripts/NFR_AUTOMATION.md
|
||||
```
|
||||
|
||||
#### Create NFR Movie Post
|
||||
|
||||
Create a blog post for an NFR movie:
|
||||
|
||||
```bash
|
||||
# List 2024 NFR films
|
||||
python3 scripts/new_nfr.py --list-2024
|
||||
|
||||
# Create post by title
|
||||
python3 scripts/new_nfr.py "No Country for Old Men"
|
||||
|
||||
# Create post by IMDB ID
|
||||
python3 scripts/new_nfr.py tt0477348
|
||||
|
||||
# Specify NFR year
|
||||
python3 scripts/new_nfr.py "Terminator 2" --nfr-year 2023
|
||||
```
|
||||
|
||||
### Regular Movie Posts
|
||||
|
||||
**"Frank's Couch"** - Owen's dad from TV Glow who watches TV
|
||||
|
||||
#### Create Movie Post
|
||||
|
||||
Create a new movie post from IMDB ID:
|
||||
|
||||
```bash
|
||||
# From IMDB ID
|
||||
python3 scripts/new_movie.py tt1234567
|
||||
|
||||
# From IMDB URL
|
||||
python3 scripts/new_movie.py https://www.imdb.com/title/tt1234567/
|
||||
```
|
||||
|
||||
#### Import from Letterboxd
|
||||
|
||||
Import movies from your Letterboxd diary:
|
||||
|
||||
```bash
|
||||
# Interactive mode - pick from recent
|
||||
python3 scripts/import_letterboxd.py
|
||||
|
||||
# Import most recent entry
|
||||
python3 scripts/import_letterboxd.py --latest
|
||||
|
||||
# Just list recent entries
|
||||
python3 scripts/import_letterboxd.py --list
|
||||
|
||||
# Skip to theater/home questions
|
||||
python3 scripts/import_letterboxd.py --theater
|
||||
python3 scripts/import_letterboxd.py --home
|
||||
```
|
||||
|
||||
#### Update Movie Metadata
|
||||
|
||||
Fetch and update movie metadata (poster, director, runtime, etc.):
|
||||
|
||||
```bash
|
||||
# Update all posts with IMDB IDs
|
||||
python3 scripts/fetch_movie_data.py
|
||||
|
||||
# Dry run (preview changes)
|
||||
python3 scripts/fetch_movie_data.py --dry-run
|
||||
|
||||
# Force re-fetch even if data exists
|
||||
python3 scripts/fetch_movie_data.py --force
|
||||
```
|
||||
|
||||
### Beer Posts
|
||||
|
||||
#### Add Beer Call Entry
|
||||
|
||||
Add entries to the beer call yearly log:
|
||||
|
||||
```bash
|
||||
# Interactive mode
|
||||
python3 scripts/new_beercall.py
|
||||
|
||||
# Specific date
|
||||
python3 scripts/new_beercall.py --date 2024-12-19
|
||||
|
||||
# List recent Untappd checkins
|
||||
python3 scripts/new_beercall.py --list
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Ollama (for NFR automation)
|
||||
|
||||
```bash
|
||||
# Ollama server (default: http://192.168.0.109:11434)
|
||||
export OLLAMA_HOST=http://localhost:11434
|
||||
|
||||
# Model to use (default: llama3.2)
|
||||
export OLLAMA_MODEL=llama3.2
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Creating an NFR Movie Post
|
||||
|
||||
```bash
|
||||
# 1. Create the post
|
||||
python3 scripts/new_nfr.py "Beverly Hills Cop"
|
||||
|
||||
# 2. Update metadata (director, runtime, etc.)
|
||||
python3 scripts/fetch_movie_data.py
|
||||
|
||||
# 3. Edit the post
|
||||
# - Add viewing details (format, rating)
|
||||
# - Write your thoughts
|
||||
# - Add Letterboxd URL
|
||||
|
||||
# 4. Build and preview
|
||||
hugo server -D
|
||||
|
||||
# 5. Publish (remove draft: true)
|
||||
```
|
||||
|
||||
### Importing Theater Movie from Letterboxd
|
||||
|
||||
```bash
|
||||
# 1. Import from Letterboxd
|
||||
python3 scripts/import_letterboxd.py --theater
|
||||
|
||||
# 2. Script will:
|
||||
# - Fetch recent Letterboxd entries
|
||||
# - Let you pick one
|
||||
# - Ask for theater details (venue, time, crew, etc.)
|
||||
# - Download poster from TMDB
|
||||
# - Create draft post
|
||||
|
||||
# 3. Edit and publish
|
||||
```
|
||||
|
||||
### Setting Up a New NFR Year
|
||||
|
||||
```bash
|
||||
# 1. Find the LOC announcement URL for the year
|
||||
# Example: https://newsroom.loc.gov/news/...
|
||||
|
||||
# 2. Run setup script (with ollama for best results)
|
||||
python3 scripts/setup_nfr.py 2023 --url "https://newsroom.loc.gov/..."
|
||||
|
||||
# 3. Review generated file
|
||||
cat scripts/nfr_data/nfr_2023.py
|
||||
|
||||
# 4. Integrate into new_nfr.py
|
||||
# (Copy the dictionary into the main script)
|
||||
|
||||
# 5. Start creating posts!
|
||||
python3 scripts/new_nfr.py "Terminator 2" --nfr-year 2023
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Module not found" errors
|
||||
|
||||
```bash
|
||||
# Make sure venv is activated
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install/reinstall dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### "TMDB API key" errors
|
||||
|
||||
```bash
|
||||
# Check config exists
|
||||
ls scripts/config.py
|
||||
|
||||
# If not, copy example and edit
|
||||
cp scripts/config.example.py scripts/config.py
|
||||
# Then add your API key
|
||||
```
|
||||
|
||||
### Ollama connection errors
|
||||
|
||||
```bash
|
||||
# Test ollama server
|
||||
curl http://192.168.0.109:11434/api/tags
|
||||
|
||||
# Use --no-ollama flag to skip
|
||||
python3 scripts/setup_nfr.py 2023 --no-ollama
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **NFR Automation**: `scripts/NFR_AUTOMATION.md` - Detailed guide for NFR automation with ollama
|
||||
- **Config Example**: `scripts/config.example.py` - Template for API keys
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
scripts/
|
||||
├── README.md # This file
|
||||
├── NFR_AUTOMATION.md # NFR automation guide
|
||||
├── config.example.py # Config template
|
||||
├── config.py # Your config (gitignored)
|
||||
├── nfr_data/ # Generated NFR data
|
||||
│ ├── nfr_2023.py
|
||||
│ └── nfr_2024.py
|
||||
├── venues.json # Beer venue database
|
||||
│
|
||||
├── setup_nfr.py # Setup NFR year data
|
||||
├── new_nfr.py # Create NFR movie post
|
||||
├── new_movie.py # Create movie post
|
||||
├── import_letterboxd.py # Import from Letterboxd
|
||||
├── fetch_movie_data.py # Update movie metadata
|
||||
├── new_beercall.py # Add beer call entry
|
||||
└── new_techpost.py # Create tech post
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check the specific script's `--help`: `python3 scripts/new_nfr.py --help`
|
||||
- Read `NFR_AUTOMATION.md` for NFR details
|
||||
- Check error messages - they usually point to the issue
|
||||
@@ -0,0 +1,438 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,2 @@
|
||||
# This directory holds generated NFR data files
|
||||
# Files are created by scripts/setup_nfr.py
|
||||
@@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Setup NFR (National Film Registry) data for a specific year.
|
||||
|
||||
This script fetches the Library of Congress announcement for a given year,
|
||||
extracts film titles and descriptions, and generates a Python dictionary
|
||||
that can be added to new_nfr.py.
|
||||
|
||||
Usage:
|
||||
python3 scripts/setup_nfr.py 2024
|
||||
python3 scripts/setup_nfr.py 2015 --output scripts/nfr_data/nfr_2015.py
|
||||
python3 scripts/setup_nfr.py 2023 --no-ollama # Don't use ollama for extraction
|
||||
|
||||
Requirements:
|
||||
- requests library
|
||||
- access to ollama server (optional, for better extraction)
|
||||
|
||||
The script will:
|
||||
1. Search for the LOC announcement URL for the given year
|
||||
2. Fetch the announcement page
|
||||
3. Use ollama (if available) or basic parsing to extract film data
|
||||
4. Generate a Python dictionary with film titles, years, and descriptions
|
||||
5. Save to a file or print to stdout
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
PROJECT_ROOT = SCRIPT_DIR.parent
|
||||
NFR_DATA_DIR = SCRIPT_DIR / "nfr_data"
|
||||
|
||||
# Ollama configuration
|
||||
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://192.168.0.109:11434")
|
||||
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2") # or whatever model you have
|
||||
|
||||
|
||||
def search_for_nfr_announcement(year):
|
||||
"""
|
||||
Search for the LOC NFR announcement URL for a given year.
|
||||
|
||||
Returns dict with:
|
||||
- newsroom_url: Main press release
|
||||
- blog_url: Blog announcement (if found)
|
||||
"""
|
||||
print(f"Searching for {year} NFR announcement...")
|
||||
|
||||
# Try common URL patterns for LOC announcements
|
||||
urls_to_try = []
|
||||
|
||||
# Newsroom pattern (most reliable for recent years)
|
||||
# Example: https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/
|
||||
# The URL doesn't always have the year in it, so we'll search
|
||||
|
||||
# Try searching via web
|
||||
search_queries = [
|
||||
f"site:newsroom.loc.gov national film registry {year}",
|
||||
f"site:blogs.loc.gov national film registry {year}",
|
||||
f'"national film registry" {year} site:loc.gov'
|
||||
]
|
||||
|
||||
results = {
|
||||
"newsroom_url": None,
|
||||
"blog_url": None,
|
||||
"webcast_url": None,
|
||||
}
|
||||
|
||||
# For now, return known patterns - user can manually find URL
|
||||
# We'll enhance this with actual search later
|
||||
print(f"\nPlease find the LOC announcement URL for {year}.")
|
||||
print(f"\nCommon places to look:")
|
||||
print(f" - https://newsroom.loc.gov/")
|
||||
print(f" - https://blogs.loc.gov/now-see-hear/")
|
||||
print(f" - https://www.loc.gov/programs/national-film-preservation-board/film-registry/")
|
||||
|
||||
# For 2024, we know the URL
|
||||
if year == 2024:
|
||||
results["newsroom_url"] = "https://newsroom.loc.gov/news/25-films-named-to-national-film-registry-for-preservation/s/55d5285d-916f-4105-b7d4-7fc3ba8664e3"
|
||||
results["blog_url"] = "https://blogs.loc.gov/now-see-hear/2024/12/announcing-the-2024-national-film-registry/"
|
||||
return results
|
||||
|
||||
# Prompt user for URL
|
||||
url = input(f"\nEnter the LOC announcement URL for {year} (or press Enter to skip): ").strip()
|
||||
if url:
|
||||
results["newsroom_url"] = url
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def fetch_url_content(url):
|
||||
"""Fetch content from a URL."""
|
||||
print(f"Fetching {url}...")
|
||||
resp = requests.get(url, timeout=30)
|
||||
resp.raise_for_status()
|
||||
return resp.text
|
||||
|
||||
|
||||
def call_ollama(prompt, model=OLLAMA_MODEL, system_prompt=None):
|
||||
"""
|
||||
Call ollama API to process text.
|
||||
|
||||
Args:
|
||||
prompt: The user prompt
|
||||
model: Model name (default from OLLAMA_MODEL env var)
|
||||
system_prompt: Optional system prompt
|
||||
|
||||
Returns:
|
||||
The model's response text
|
||||
"""
|
||||
url = f"{OLLAMA_HOST}/api/generate"
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
}
|
||||
|
||||
if system_prompt:
|
||||
payload["system"] = system_prompt
|
||||
|
||||
print(f"Calling ollama at {OLLAMA_HOST} with model {model}...")
|
||||
try:
|
||||
resp = requests.post(url, json=payload, timeout=300) # 5 min timeout
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data.get("response", "")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error calling ollama: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def extract_films_with_ollama(html_content, year):
|
||||
"""
|
||||
Use ollama to extract film data from HTML content.
|
||||
|
||||
Returns a list of dicts with: title, year, description
|
||||
"""
|
||||
system_prompt = """You are a helpful assistant that extracts structured data from web pages.
|
||||
Your task is to extract information about films from National Film Registry announcements.
|
||||
Output ONLY valid JSON, nothing else. No markdown formatting, no code blocks, just raw JSON."""
|
||||
|
||||
user_prompt = f"""From the following HTML content, extract ALL films that were added to the National Film Registry in {year}.
|
||||
|
||||
For each film, extract:
|
||||
1. The exact title
|
||||
2. The release year of the film
|
||||
3. The description/reason why it was selected for preservation
|
||||
|
||||
Format your response as a JSON array of objects with this structure:
|
||||
[
|
||||
{{
|
||||
"title": "Film Title",
|
||||
"year": 1999,
|
||||
"description": "The reason it was selected..."
|
||||
}}
|
||||
]
|
||||
|
||||
IMPORTANT:
|
||||
- Extract ALL {year} films, typically 25 films
|
||||
- Keep descriptions concise but complete
|
||||
- Use the exact text from the announcement
|
||||
- Output ONLY the JSON array, no other text
|
||||
- Do not include markdown code blocks
|
||||
|
||||
HTML Content:
|
||||
{html_content[:50000]}
|
||||
""" # Limit to first 50k chars to avoid token limits
|
||||
|
||||
response = call_ollama(user_prompt, system_prompt=system_prompt)
|
||||
|
||||
if not response:
|
||||
return None
|
||||
|
||||
# Try to parse JSON from response
|
||||
try:
|
||||
# Sometimes models wrap in code blocks, try to extract
|
||||
json_match = re.search(r'(\[.*\])', response, re.DOTALL)
|
||||
if json_match:
|
||||
response = json_match.group(1)
|
||||
|
||||
films = json.loads(response)
|
||||
return films
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Failed to parse JSON from ollama response: {e}")
|
||||
print(f"Response was: {response[:500]}...")
|
||||
return None
|
||||
|
||||
|
||||
def extract_films_basic(html_content, year):
|
||||
"""
|
||||
Basic extraction without ollama - looks for common patterns.
|
||||
This is a fallback method and may not work for all years.
|
||||
"""
|
||||
print("Using basic extraction (without ollama)...")
|
||||
print("Note: This may not capture all details. Consider using --ollama for better results.")
|
||||
|
||||
films = []
|
||||
|
||||
# Look for numbered lists or bold film titles
|
||||
# This is a simple heuristic and may need adjustment
|
||||
|
||||
# Pattern: Look for year in parentheses near potential titles
|
||||
# Example: "Film Title (1999)"
|
||||
pattern = r'([A-Z][^(]{3,50})\s*\((\d{4})\)'
|
||||
matches = re.findall(pattern, html_content)
|
||||
|
||||
seen_titles = set()
|
||||
for title, film_year in matches:
|
||||
title = title.strip()
|
||||
# Filter out obviously wrong matches
|
||||
if title and len(title) > 3 and title not in seen_titles:
|
||||
# Try to get a reasonable year range
|
||||
try:
|
||||
y = int(film_year)
|
||||
if 1890 <= y <= year: # Reasonable film year range
|
||||
films.append({
|
||||
"title": title,
|
||||
"year": y,
|
||||
"description": "[Description not extracted - please add manually]"
|
||||
})
|
||||
seen_titles.add(title)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return films if films else None
|
||||
|
||||
|
||||
def generate_python_dict(films, year):
|
||||
"""
|
||||
Generate Python code for the NFR dictionary.
|
||||
|
||||
Args:
|
||||
films: List of film dicts
|
||||
year: NFR induction year
|
||||
|
||||
Returns:
|
||||
String containing Python code
|
||||
"""
|
||||
output = f'''# {year} National Film Registry inductees with LOC descriptions
|
||||
# Source: [Add URL here]
|
||||
NFR_{year} = {{'''
|
||||
|
||||
for film in films:
|
||||
title = film["title"].replace("'", "\\'")
|
||||
desc = film["description"].replace("'", "\\'").replace("\n", " ")
|
||||
|
||||
output += f'''
|
||||
"{title}": {{
|
||||
"year": {film["year"]},
|
||||
"description": '{desc}'
|
||||
}},'''
|
||||
|
||||
output += "\n}\n"
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def save_nfr_data(films, year, output_path=None):
|
||||
"""
|
||||
Save NFR data to a file.
|
||||
|
||||
Args:
|
||||
films: List of film dicts
|
||||
year: NFR induction year
|
||||
output_path: Optional path to save to (default: nfr_data/nfr_YEAR.py)
|
||||
"""
|
||||
if output_path is None:
|
||||
NFR_DATA_DIR.mkdir(exist_ok=True)
|
||||
output_path = NFR_DATA_DIR / f"nfr_{year}.py"
|
||||
else:
|
||||
output_path = Path(output_path)
|
||||
|
||||
code = generate_python_dict(films, year)
|
||||
|
||||
output_path.write_text(code)
|
||||
print(f"\n✓ Saved to {output_path}")
|
||||
print(f"\nTo use this data:")
|
||||
print(f" 1. Review and edit {output_path} if needed")
|
||||
print(f" 2. Copy the NFR_{year} dictionary into scripts/new_nfr.py")
|
||||
print(f" 3. Update the script to handle multiple years")
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Setup NFR data for a specific year"
|
||||
)
|
||||
parser.add_argument("year", type=int, help="NFR induction year (e.g., 2024)")
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
help="Direct URL to LOC announcement (skip search)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
help="Output file path (default: scripts/nfr_data/nfr_YEAR.py)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-ollama",
|
||||
action="store_true",
|
||||
help="Don't use ollama for extraction (use basic parsing)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ollama-host",
|
||||
default=OLLAMA_HOST,
|
||||
help=f"Ollama server URL (default: {OLLAMA_HOST})"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ollama-model",
|
||||
default=OLLAMA_MODEL,
|
||||
help=f"Ollama model to use (default: {OLLAMA_MODEL})"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Update ollama config from args
|
||||
global OLLAMA_HOST, OLLAMA_MODEL
|
||||
OLLAMA_HOST = args.ollama_host
|
||||
OLLAMA_MODEL = args.ollama_model
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Setting up NFR data for {args.year}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
# Get announcement URL
|
||||
if args.url:
|
||||
urls = {"newsroom_url": args.url}
|
||||
else:
|
||||
urls = search_for_nfr_announcement(args.year)
|
||||
|
||||
if not urls.get("newsroom_url"):
|
||||
print("\nError: No announcement URL found.")
|
||||
print("Please provide a URL with --url")
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch content
|
||||
try:
|
||||
html_content = fetch_url_content(urls["newsroom_url"])
|
||||
except Exception as e:
|
||||
print(f"Error fetching URL: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Extract films
|
||||
films = None
|
||||
|
||||
if not args.no_ollama:
|
||||
try:
|
||||
films = extract_films_with_ollama(html_content, args.year)
|
||||
except Exception as e:
|
||||
print(f"Error using ollama: {e}")
|
||||
print("Falling back to basic extraction...")
|
||||
|
||||
if not films:
|
||||
films = extract_films_basic(html_content, args.year)
|
||||
|
||||
if not films:
|
||||
print("\nError: Could not extract films from announcement.")
|
||||
print("Try:")
|
||||
print(" 1. Using --ollama if you skipped it")
|
||||
print(" 2. Manually creating the dictionary")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n✓ Extracted {len(films)} films")
|
||||
|
||||
# Show preview
|
||||
print("\nPreview of extracted films:")
|
||||
for i, film in enumerate(films[:5], 1):
|
||||
print(f" {i}. {film['title']} ({film['year']})")
|
||||
if len(film['description']) > 100:
|
||||
print(f" {film['description'][:100]}...")
|
||||
else:
|
||||
print(f" {film['description']}")
|
||||
|
||||
if len(films) > 5:
|
||||
print(f" ... and {len(films) - 5} more")
|
||||
|
||||
# Confirm
|
||||
confirm = input("\nSave this data? (Y/n): ").strip().lower()
|
||||
if confirm == 'n':
|
||||
print("Cancelled")
|
||||
sys.exit(0)
|
||||
|
||||
# Save
|
||||
output_path = save_nfr_data(films, args.year, args.output)
|
||||
|
||||
print("\n✓ Done!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 47 KiB |