# I built an AI-powered movie curator with Python and Streamlit — here's what I learned

> Source: <https://dev.to/claudiomjedi1979/i-built-an-ai-powered-movie-curator-with-python-and-streamlit-heres-what-i-learned-48j1>
> Published: 2026-06-26 15:07:24+00:00

I've been a movie person my whole life.

Growing up in the 80s and 90s, I'd go to the video store without knowing what I wanted and walk out with five tapes. Browsing shelves, reading back covers, picking something just because the title was weird. It was slow and it worked.

Today you open Netflix with 10,000 options and spend 40 minutes picking nothing.

The problem isn't lack of content. It's lack of curation. So I built something about it.

**What I built**

CineAntologia AI — a public catalog of movies and TV shows from the 80s to today, with an AI chat that works like a film curator instead of a search engine.

You describe what you want to feel while watching:

"Something like Stranger Things, that 80s atmosphere and suspense"

"Heavy crime drama like The Wire"

"Philosophical sci-fi in the style of Blade Runner"

And it gives you suggestions with actual cultural context — not just a list.

Live demo: ([https://cineantologia-ai.streamlit.app](https://cineantologia-ai.streamlit.app))

**The stack**

Nothing exotic:

Python 3.11

Streamlit — for the UI and deploy

TMDb API — free, great data, covers posters, genres, ratings, where to watch

OpenAI GPT-4o-mini or Groq for the AI chat

Streamlit Community Cloud — free deploy for public repos

No database in v1. No auth. No over-engineering.

**Three decisions worth talking about**

Ship on day one

I pushed the repo public and deployed the moment it minimally worked. No months of polishing in private.

When it's a drawer project you optimize for the code. When it's public you optimize for the person using it. That shift in perspective is worth the discomfort of shipping something imperfect.

Users bring their own API key

The AI Chat uses GPT-4o-mini or Groq. Instead of covering API costs for everyone — unpredictable at scale — users paste their own key in the sidebar. It lives only in their browser session, never hits my server.

Three wins: zero cost to scale, real privacy, and the user actually learns how an AI API works.

For anyone without a key, I point them to Groq — free, no credit card, fast as hell:

Dumb pages, smart services

app/

├── pages/ # only renders, no logic

├── services/ # all data and AI logic lives here

└── utils/ # config, CSS, shared sidebar

Each page is as dumb as possible — it just calls a service and renders the result. All TMDb calls, normalization, and AI prompting stay in services/. Made the whole thing much easier to iterate on.

**What was actually hard**

The system prompt. Getting the AI to respond like a real curator took more iteration than I expected. The fix that worked was enforcing a strict output format:

```
SYSTEM_PROMPT = """You are CineAntologia AI, a film curator specialized 
in movies and TV shows from every decade.

Always end your response with suggestions in this exact format:
Title (Year) — reason in one line

Minimum 4, maximum 6 suggestions."""
```

That fixed format made a huge difference in consistency.

Normalizing TMDb data. Movies have title and release_date. Shows have name and first_air_date. I wrote a _normalize() function that flattens everything into the same schema before any page touches the data:

``` php
def _normalize(items: list) -> list[dict]:
    out = []
    for item in items:
        mt = item.get("media_type", "movie")
        if mt == "person":
            continue
        title = item.get("title") or item.get("name") or "—"
        date = item.get("release_date") or item.get("first_air_date") or ""
        year = int(date[:4]) if date and len(date) >= 4 else None
        out.append({
            "id": item.get("id"),
            "title": title,
            "year": year,
            "type": "Movie" if mt == "movie" else "Series",
            "genres": [...],
            "synopsis": item.get("overview", ""),
            "poster": poster_url(item.get("poster_path")),
            "rating": round(item.get("vote_average", 0), 1),
        })
    return out
```

CSS in Streamlit. Streamlit has strong opinions about styling. Custom dark theme with Bebas Neue + Inter required injecting CSS via st.markdown() with unsafe_allow_html=True. Not pretty, but it works.

**What's next**

Anime and K-drama — huge audience, badly served by recommendation tools

Oscar Hall of Fame since 1929 — every category, with poster and where to watch today

Semantic search — embeddings to search by vibe, not just title

Specialized agents — horror agent, sci-fi agent, drama agent

**Try it**

🎬 ([https://cineantologia-ai.streamlit.app](https://cineantologia-ai.streamlit.app))

For the AI Chat, grab a free Groq key at [https://console.groq.com](https://console.groq.com) — no credit card, takes 2 minutes.

Feedback, suggestions, and PRs are very welcome. Still early, lots of room to grow.

Built with Python, Streamlit, TMDb API, and coffee.
