CineMood started as a machine-learning-based mood-based movie recommendation system that analyzed user input and recommended trending movies from The Movie Database (TMDB). However, it had limitations in dynamically matching moods with real-time trending movies.
With the rise of AI agents, we upgraded CineMood to CineMood2 in this post, making it an LLM-powered system that leverages GPT-4o-mini to enhance movie recommendations.
While ChatGPT is a powerful language model, it lacks access to real-time trending movies.
However, GPT-4o-mini can still be used as a tool to analyze moods and rank movies once we fetch the latest trending data from TMDB.
It is my personal choice. Any model (e.g., Mistral-7B, Llama2, Claude, etc.) can be used for this project.
["calm", "peaceful", "serene"]
It also provides a match explanation for each movie.
π¬ Movie: "Forrest Gump"
π
Release Date: 1994-07-06
β
Match Reason: This movie embodies warmth, nostalgia, and optimism, matching the user's mood perfectly.
app.py
)π‘ Goal: Provide a simple user interface where users describe how they feel.
β How it Works:
π Technical Details:
llm.py
.Create app.py
:
import streamlit as st
from llm import detect_mood, get_movies_by_mood
from tmdb_api import fetch_movies
st.set_page_config(
page_title="π¬ Mood-Based Trending Movie Recommendation", layout="centered"
)
st.title("π¬ CineMood2: Get your Mood-based Trending Movies!β‘")
user_mood = st.text_area("π¬ How do you feel right now?", st.session_state.get("user_mood", ""), height=100)
if st.button("Find Movies"):
if user_mood.strip():
with st.spinner("π Analyzing your mood..."):
mood_words = detect_mood(user_mood)
st.success(f"π€ AI Detected Moods: {', '.join(mood_words).title()}")
with st.spinner("π₯ Fetching movies and ranking matches..."):
movies = fetch_movies(60)
recommended_movies = get_movies_by_mood(mood_words, movies)
if recommended_movies:
for movie in recommended_movies:
st.subheader(movie["title"])
st.write(f"π
Release Date: {movie['release_date']}")
st.write(f"π Match Reason: {movie['match_reason']}")
if movie["poster"]:
st.image(movie["poster"], width=200)
st.write(f"π Overview: {movie['overview']}")
st.markdown("---")
else:
st.warning("β οΈ No suitable movie recommendations found.")
else:
st.warning("β οΈ Please enter how you feel to get movie recommendations.")
# Footer Section
st.markdown("**Made by [Thanh Tung Vu](https://thanhtungvudata.github.io/)**")
llm.py
)π‘ Goal: Extract key mood words from user input.
β How it Works:
π Technical Details:
Create llm.py
:
import json
import openai
from config import OPENAI_API_KEY
# Initialize OpenAI API client
client = openai.OpenAI(api_key=OPENAI_API_KEY)
def detect_mood(user_input):
"""
Uses GPT-4o-mini to detect mood from user input.
Returns exactly 3 descriptive mood words.
"""
prompt = f"""
Analyze the following user input and determine the three best words to describe the mood.
User input: "{user_input}"
Respond with exactly 3 words, separated by commas.
Example: happy, joyful, excited.
"""
try:
response = client.chat.completions.create(
model="gpt-4o-mini", messages=[{"role": "system", "content": prompt}]
)
mood_words = response.choices[0].message.content.strip().lower().split(", ")
return mood_words if len(mood_words) == 3 else ["neutral", "neutral", "neutral"]
except Exception as e:
print(f"β οΈ Error with OpenAI API in detect_mood: {e}")
return ["neutral", "neutral", "neutral"]
def get_movies_by_mood(mood_words, movies):
"""
Uses GPT-4o-mini to rank movies based on how well their overview matches the detected mood words.
Returns the top 3 movies with match explanations, sorted by release date (latest first).
"""
if not movies:
print("β οΈ No movies available to match moods.")
return []
movie_descriptions = "\n".join(
[f"{i+1}. {m['title']}: {m['overview']}" for i, m in enumerate(movies)]
)
prompt = f"""
You must output only valid JSON and nothing else.
The JSON should be an array of exactly 3 objects.
Each object must have two keys: "index" (an integer) and "match_reason" (a non-empty string).
The user is in a mood described by these words: {", ".join(mood_words)}.
Below are movie descriptions:
{movie_descriptions}
Select the top 3 movies that best match this mood and provide a brief explanation (1-2 sentences) for each.
Respond strictly in JSON format:
[
index,
index,
index
]
"""
try:
response = client.chat.completions.create(
model="gpt-4o-mini", messages=[{"role": "system", "content": prompt}]
)
json_response = response.choices[0].message.content.strip()
ranked_movies = json.loads(json_response)
matched_movies = []
default_explanation = "This movie appears to be a good match based on its emotional tone and themes."
for entry in ranked_movies:
index = entry.get("index", 0) - 1
explanation = entry.get("match_reason", "").strip() or default_explanation
if 0 <= index < len(movies):
matched_movie = movies[index]
matched_movie["match_reason"] = explanation
matched_movies.append(matched_movie)
# Ensure exactly 3 movies are returned by filling with fallbacks if necessary
while len(matched_movies) < 3 and len(movies) >= 3:
fallback = movies[len(matched_movies)]
fallback["match_reason"] = default_explanation
matched_movies.append(fallback)
# Sort the matched movies by release date (latest first)
matched_movies = sorted(
matched_movies, key=lambda x: x["release_date"], reverse=True
)
return matched_movies[:3]
except Exception as e:
print(f"β οΈ Error ranking movies: {e}")
fallback_movies = []
default_explanation = "This movie appears to be a good match based on its emotional tone and themes."
for m in movies[:3]:
m["match_reason"] = default_explanation
fallback_movies.append(m)
fallback_movies = sorted(
fallback_movies, key=lambda x: x["release_date"], reverse=True
)
return fallback_movies
tmdb_api.py
)π‘ Goal: Get a list of currently trending movies from The Movie Database (TMDB) API.
β How it Works:
π Technical Details:
Create tmdb_api.py
:
import datetime
import requests
from config import TMDB_API_KEY
def get_first_day_of_week():
"""Returns the first day (Monday) of the current week."""
today = datetime.date.today()
return today - datetime.timedelta(days=today.weekday())
def fetch_movies(max_movies=100):
"""
Fetch up to `max_movies` trending movies, ensuring only movies with release dates before the first day
of the current week are considered, and that they have non-empty overviews.
Returns the movies sorted by release date (latest first).
"""
movies = []
pages_to_fetch = (max_movies // 20) + 1
first_day_of_week = get_first_day_of_week()
for page in range(1, pages_to_fetch + 1):
url = f"https://api.themoviedb.org/3/trending/movie/week?api_key={TMDB_API_KEY}&language=en-US&page={page}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
for movie in data.get("results", []):
release_date = movie.get("release_date", "9999-12-31")
overview = movie.get("overview", "").strip()
try:
release_date_obj = datetime.datetime.strptime(
release_date, "%Y-%m-%d"
).date()
except ValueError:
continue
if overview and release_date_obj < first_day_of_week:
movies.append(
{
"title": movie["title"],
"overview": overview,
"poster": (
f"https://image.tmdb.org/t/p/w500{movie['poster_path']}"
if movie.get("poster_path")
else None
),
"release_date": release_date,
}
)
if len(movies) >= max_movies:
break
except requests.exceptions.RequestException as e:
print(f"β οΈ Error fetching movies: {e}")
break
return sorted(movies, key=lambda x: x["release_date"], reverse=True)[:max_movies]
π‘ Goal: Select the top 3 movies that best match the userβs mood.
β How it Works:
π Technical Details:
π‘ Goal: Ensure consistent deployment and easy sharing across environments.
β How it Works:
π Technical Details:
config.py
:import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Get API keys
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
TMDB_API_KEY = os.getenv("TMDB_API_KEY")
Dockerfile
:FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install --upgrade pip && pip install -r requirements.txt
EXPOSE 8501
CMD ["streamlit", "run", "/app/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
docker-compose.yml
:version: '3.8'
services:
mood-movie-app:
build: .
container_name: mood-movie-app
ports:
- "8501:8501"
restart: always
docker-compose up --build
docker-compose down
, then docker-compose up --build
The final web app delivers mood-based movie recommendations in just a second, with fresh content every week.
You can try it here:π CineMood Live App on Hugging Face Spaces
CineMood2 provides an AI-powered, mood-based movie recommendation system that seamlessly integrates GPT-4o-mini, TMDB trending movies, and Docker deployment.
π‘ Key Advantages:
π Next Steps:
The code of this project is available here.
For further inquiries or collaboration, please contact me at my email.