JSenkCC's picture
Update app.py
692ccba verified
import streamlit as st
import requests
from transformers import pipeline
# Enter your TMDb API key here
TMDB_API_KEY = '2cc77b08a475b510f54a0dcca2c5c0c7'
IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w200" # TMDb base URL for poster images (width 200)
@st.cache_resource
def load_emotion_analyzer():
return pipeline("text-classification", model="j-hartmann/emotion-english-distilroberta-base")
emotion_analyzer = load_emotion_analyzer()
def analyze_emotion(description):
"""Analyze emotion of a movie's description."""
emotions = emotion_analyzer(description)
return emotions[0]['label'] # Return the top emotion
def get_movie_genres():
"""Fetches movie genres from TMDb API and returns a dictionary of genres."""
url = f'https://api.themoviedb.org/3/genre/movie/list?api_key={TMDB_API_KEY}&language=en-US'
response = requests.get(url)
if response.status_code == 200:
genres = response.json().get('genres', [])
return {genre['name']: genre['id'] for genre in genres}
else:
return None # Return None if the request failed
def get_top_movies_by_genre(genre_id):
"""Fetches the top 10 highest-rated movies for a given genre ID from TMDb, sorted by title length."""
url = f'https://api.themoviedb.org/3/discover/movie?api_key={TMDB_API_KEY}&language=en-US&sort_by=vote_average.desc&vote_count.gte=1000&with_genres={genre_id}'
response = requests.get(url)
if response.status_code == 200:
movies = response.json().get('results', [])[:10] # Get the top 10 results
# Sort movies by title length, shortest to longest
return sorted([(movie['title'], movie['vote_average'], movie['id'], movie['poster_path']) for movie in movies], key=lambda x: len(x[0]))
else:
return None
def get_similar_movies(movie_id, genre_id):
"""Fetches similar movies for a given movie ID from TMDb and filters by the selected genre."""
url = f'https://api.themoviedb.org/3/movie/{movie_id}/similar?api_key={TMDB_API_KEY}&language=en-US'
response = requests.get(url)
if response.status_code == 200:
movies = response.json().get('results', [])[:15]
# Include the movie description (overview) in the returned values
return [
(movie['title'], movie['vote_average'], movie['poster_path'], movie['id'], movie['overview'])
for movie in movies if genre_id in movie['genre_ids'] and 'overview' in movie
]
else:
return None
def get_movie_details(movie_id):
"""Fetches detailed information for a given movie ID from TMDb, including description, release date, director, and cast."""
url = f'https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US&append_to_response=credits'
response = requests.get(url)
if response.status_code == 200:
movie = response.json()
directors = [member['name'] for member in movie['credits']['crew'] if member['job'] == 'Director']
cast = [member['name'] for member in movie['credits']['cast'][:5]] # Get top 5 cast members
return {
"title": movie['title'],
"rating": movie['vote_average'],
"description": movie['overview'],
"poster_path": movie['poster_path'],
"release_date": movie['release_date'],
"director": ", ".join(directors),
"cast": ", ".join(cast)
}
else:
return None
# Initialize session state to store selected movies and recommendations
if 'selected_movies' not in st.session_state:
st.session_state['selected_movies'] = set()
if 'recommendations' not in st.session_state:
st.session_state['recommendations'] = []
if 'last_genre' not in st.session_state:
st.session_state['last_genre'] = None
# Streamlit app title
st.title("Movie Recommender")
# CSS for alignment and button styling
st.markdown("""
<style>
.movie-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.movie-caption {
min-height: 50px; /* Set a consistent height for caption sections */
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.movie-caption button {
width: 100%;
}
</style>
""", unsafe_allow_html=True)
# Fetch genres from TMDb and display in a dropdown
genres = get_movie_genres()
if genres is None:
st.error("Failed to load movie genres. Please check your API key or connection.")
else:
genre_name = st.selectbox("Select a movie genre:", [""] + list(genres.keys())) # Include an empty option
# Check if genre has changed, and reset selections if so
if genre_name != st.session_state['last_genre']:
st.session_state['selected_movies'].clear()
st.session_state['recommendations'].clear()
st.session_state['last_genre'] = genre_name
# Only fetch and display movies if a genre is selected
if genre_name:
st.write("Please select at least one movie to generate recommendations.")
genre_id = genres[genre_name] # Get the genre ID based on the selected genre name
top_movies = get_top_movies_by_genre(genre_id) # Fetch top 10 movies in this genre, sorted by title length
# Check if movies were successfully loaded
if top_movies is None:
st.error("Failed to load movies. Please check your API key or connection.")
else:
# Display the top 10 movies as clickable titles in a 5x2 grid layout with aligned rows
st.write(f"Top 10 movies in the {genre_name} genre:")
columns = st.columns(5) # Create 5 columns for each row
# First row of 5 movies
for index, (title, _, movie_id, poster_path) in enumerate(top_movies[:5]):
col = columns[index]
with col:
st.markdown("<div class='movie-container'>", unsafe_allow_html=True)
# Display image
st.image(f"{IMAGE_BASE_URL}{poster_path}", use_container_width=True)
# Display clickable title as a button to toggle selection, with consistent height
if st.button(f"{title} {'✅' if movie_id in st.session_state['selected_movies'] else ''}", key=f"title_{movie_id}"):
if movie_id in st.session_state['selected_movies']:
st.session_state['selected_movies'].remove(movie_id)
else:
st.session_state['selected_movies'].add(movie_id)
st.markdown("</div>", unsafe_allow_html=True)
# Second row of 5 movies
for index, (title, _, movie_id, poster_path) in enumerate(top_movies[5:]):
col = columns[index]
with col:
st.markdown("<div class='movie-container'>", unsafe_allow_html=True)
# Display image
st.image(f"{IMAGE_BASE_URL}{poster_path}", use_container_width=True)
# Display clickable title as a button to toggle selection, with consistent height
if st.button(f"{title} {'✅' if movie_id in st.session_state['selected_movies'] else ''}", key=f"title_2_{movie_id}"):
if movie_id in st.session_state['selected_movies']:
st.session_state['selected_movies'].remove(movie_id)
else:
st.session_state['selected_movies'].add(movie_id)
st.markdown("</div>", unsafe_allow_html=True)
# Button to generate recommendations based on selected movies
if st.button("Show Recommendations"):
if st.session_state['selected_movies']:
# Step 1: Collect emotions of user-selected movies
with st.spinner("Analyzing emotions of selected movies..."):
selected_emotions = []
for movie_id in st.session_state['selected_movies']:
movie_details = get_movie_details(movie_id)
if movie_details:
emotion = analyze_emotion(movie_details["description"])
selected_emotions.append(emotion)
# Step 2: Collect similar movies for all selected movies
all_similar_movies = []
for movie_id in st.session_state['selected_movies']:
similar_movies = get_similar_movies(movie_id, genre_id)
if similar_movies:
all_similar_movies.extend(similar_movies)
# Step 3: Filter similar movies by matching emotions and remove duplicates
recommendations = []
seen_movie_ids = set(st.session_state['selected_movies']) # Start with user-selected movies to exclude them
with st.spinner("Filtering movies by description emotion..."):
for title, rating, poster_path, movie_id, description in all_similar_movies:
if movie_id not in seen_movie_ids: # Exclude already-seen movies
movie_emotion = analyze_emotion(description)
if movie_emotion in selected_emotions:
seen_movie_ids.add(movie_id) # Mark this movie as seen to prevent duplicates
recommendations.append((title, rating, poster_path, movie_id, movie_emotion))
# Store unique recommendations in session state
st.session_state['recommendations'] = recommendations
# Step 4: Display final recommendations
if st.session_state['recommendations']:
st.write("Based on your selections, you might also enjoy these top movies:")
for title, rating, poster_path, movie_id, emotion in sorted(st.session_state['recommendations'], key=lambda x: x[1], reverse=True)[:5]:
movie_details = get_movie_details(movie_id)
if movie_details:
col1, col2 = st.columns([1, 3])
with col1:
st.image(f"{IMAGE_BASE_URL}{movie_details['poster_path']}", use_container_width=True)
with col2:
st.markdown(f"**Title:** {movie_details['title']}")
st.markdown(f"**TMDb Rating:** {movie_details['rating']}")
st.markdown(f"**Description:** {movie_details['description']}")
# Additional Information button with overlay using expander
with st.expander("Additional Information"):
st.markdown(f"**Release Date:** {movie_details['release_date']}")
st.markdown(f"**Director:** {movie_details['director']}")
st.markdown(f"**Cast:** {movie_details['cast']}")
else:
st.warning("No movies matched your selected emotions.")