JSenkCC commited on
Commit
cc2570f
·
verified ·
1 Parent(s): d662c53

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +228 -0
app.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ from transformers import pipeline
4
+
5
+ # Enter your TMDb API key here
6
+ TMDB_API_KEY = '2cc77b08a475b510f54a0dcca2c5c0c7'
7
+ IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w200" # TMDb base URL for poster images (width 200)
8
+
9
+ @st.cache_resource
10
+ def load_emotion_analyzer():
11
+ return pipeline("text-classification", model="j-hartmann/emotion-english-distilroberta-base")
12
+
13
+ emotion_analyzer = load_emotion_analyzer()
14
+
15
+ def analyze_emotion(description):
16
+ """Analyze emotion of a movie's description."""
17
+ emotions = emotion_analyzer(description)
18
+ return emotions[0]['label'] # Return the top emotion
19
+
20
+ def get_movie_genres():
21
+ """Fetches movie genres from TMDb API and returns a dictionary of genres."""
22
+ url = f'https://api.themoviedb.org/3/genre/movie/list?api_key={TMDB_API_KEY}&language=en-US'
23
+ response = requests.get(url)
24
+ if response.status_code == 200:
25
+ genres = response.json().get('genres', [])
26
+ return {genre['name']: genre['id'] for genre in genres}
27
+ else:
28
+ return None # Return None if the request failed
29
+
30
+ def get_top_movies_by_genre(genre_id):
31
+ """Fetches the top 10 highest-rated movies for a given genre ID from TMDb, sorted by title length."""
32
+ 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}'
33
+ response = requests.get(url)
34
+ if response.status_code == 200:
35
+ movies = response.json().get('results', [])[:10] # Get the top 10 results
36
+ # Sort movies by title length, shortest to longest
37
+ return sorted([(movie['title'], movie['vote_average'], movie['id'], movie['poster_path']) for movie in movies], key=lambda x: len(x[0]))
38
+ else:
39
+ return None
40
+
41
+ def get_similar_movies(movie_id, genre_id):
42
+ """Fetches similar movies for a given movie ID from TMDb and filters by the selected genre."""
43
+ url = f'https://api.themoviedb.org/3/movie/{movie_id}/similar?api_key={TMDB_API_KEY}&language=en-US'
44
+ response = requests.get(url)
45
+ if response.status_code == 200:
46
+ movies = response.json().get('results', [])[:15]
47
+ # Include the movie description (overview) in the returned values
48
+ return [
49
+ (movie['title'], movie['vote_average'], movie['poster_path'], movie['id'], movie['overview'])
50
+ for movie in movies if genre_id in movie['genre_ids'] and 'overview' in movie
51
+ ]
52
+ else:
53
+ return None
54
+
55
+ def get_movie_details(movie_id):
56
+ """Fetches detailed information for a given movie ID from TMDb, including description, release date, director, and cast."""
57
+ url = f'https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US&append_to_response=credits'
58
+ response = requests.get(url)
59
+ if response.status_code == 200:
60
+ movie = response.json()
61
+ directors = [member['name'] for member in movie['credits']['crew'] if member['job'] == 'Director']
62
+ cast = [member['name'] for member in movie['credits']['cast'][:5]] # Get top 5 cast members
63
+ return {
64
+ "title": movie['title'],
65
+ "rating": movie['vote_average'],
66
+ "description": movie['overview'],
67
+ "poster_path": movie['poster_path'],
68
+ "release_date": movie['release_date'],
69
+ "director": ", ".join(directors),
70
+ "cast": ", ".join(cast)
71
+ }
72
+ else:
73
+ return None
74
+
75
+ # Initialize session state to store selected movies and recommendations
76
+ if 'selected_movies' not in st.session_state:
77
+ st.session_state['selected_movies'] = set()
78
+ if 'recommendations' not in st.session_state:
79
+ st.session_state['recommendations'] = []
80
+ if 'last_genre' not in st.session_state:
81
+ st.session_state['last_genre'] = None
82
+
83
+ # Streamlit app title
84
+ st.title("Movie Recommender")
85
+
86
+ # CSS for alignment and button styling
87
+ st.markdown("""
88
+ <style>
89
+ .movie-container {
90
+ display: flex;
91
+ flex-direction: column;
92
+ align-items: center;
93
+ justify-content: flex-start;
94
+ }
95
+ .movie-caption {
96
+ min-height: 50px; /* Set a consistent height for caption sections */
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ text-align: center;
101
+ }
102
+ .movie-caption button {
103
+ width: 100%;
104
+ }
105
+ </style>
106
+ """, unsafe_allow_html=True)
107
+
108
+ # Fetch genres from TMDb and display in a dropdown
109
+ genres = get_movie_genres()
110
+ if genres is None:
111
+ st.error("Failed to load movie genres. Please check your API key or connection.")
112
+ else:
113
+ genre_name = st.selectbox("Select a movie genre:", [""] + list(genres.keys())) # Include an empty option
114
+
115
+ # Check if genre has changed, and reset selections if so
116
+ if genre_name != st.session_state['last_genre']:
117
+ st.session_state['selected_movies'].clear()
118
+ st.session_state['recommendations'].clear()
119
+ st.session_state['last_genre'] = genre_name
120
+
121
+ # Only fetch and display movies if a genre is selected
122
+ if genre_name:
123
+ genre_id = genres[genre_name] # Get the genre ID based on the selected genre name
124
+ top_movies = get_top_movies_by_genre(genre_id) # Fetch top 10 movies in this genre, sorted by title length
125
+
126
+ # Check if movies were successfully loaded
127
+ if top_movies is None:
128
+ st.error("Failed to load movies. Please check your API key or connection.")
129
+ else:
130
+ # Display the top 10 movies as clickable titles in a 5x2 grid layout with aligned rows
131
+ st.write(f"Top 10 movies in the {genre_name} genre:")
132
+ columns = st.columns(5) # Create 5 columns for each row
133
+
134
+ # First row of 5 movies
135
+ for index, (title, _, movie_id, poster_path) in enumerate(top_movies[:5]):
136
+ col = columns[index]
137
+ with col:
138
+ st.markdown("<div class='movie-container'>", unsafe_allow_html=True)
139
+
140
+ # Display image
141
+ st.image(f"{IMAGE_BASE_URL}{poster_path}", use_container_width=True)
142
+
143
+ # Display clickable title as a button to toggle selection, with consistent height
144
+ if st.button(f"{title} {'✅' if movie_id in st.session_state['selected_movies'] else ''}", key=f"title_{movie_id}"):
145
+ if movie_id in st.session_state['selected_movies']:
146
+ st.session_state['selected_movies'].remove(movie_id)
147
+ else:
148
+ st.session_state['selected_movies'].add(movie_id)
149
+
150
+ st.markdown("</div>", unsafe_allow_html=True)
151
+
152
+ # Second row of 5 movies
153
+ for index, (title, _, movie_id, poster_path) in enumerate(top_movies[5:]):
154
+ col = columns[index]
155
+ with col:
156
+ st.markdown("<div class='movie-container'>", unsafe_allow_html=True)
157
+
158
+ # Display image
159
+ st.image(f"{IMAGE_BASE_URL}{poster_path}", use_container_width=True)
160
+
161
+ # Display clickable title as a button to toggle selection, with consistent height
162
+ if st.button(f"{title} {'✅' if movie_id in st.session_state['selected_movies'] else ''}", key=f"title_2_{movie_id}"):
163
+ if movie_id in st.session_state['selected_movies']:
164
+ st.session_state['selected_movies'].remove(movie_id)
165
+ else:
166
+ st.session_state['selected_movies'].add(movie_id)
167
+
168
+ st.markdown("</div>", unsafe_allow_html=True)
169
+
170
+ # Button to generate recommendations based on selected movies
171
+ if st.button("Show Recommendations"):
172
+ if st.session_state['selected_movies']:
173
+ # Step 1: Collect emotions of user-selected movies
174
+ st.write("Analyzing emotions of selected movies...")
175
+ selected_emotions = []
176
+ for movie_id in st.session_state['selected_movies']:
177
+ movie_details = get_movie_details(movie_id)
178
+ if movie_details:
179
+ emotion = analyze_emotion(movie_details["description"])
180
+ selected_emotions.append(emotion)
181
+ st.write(f"**{movie_details['title']}** - Emotion: {emotion}")
182
+
183
+ # Step 2: Collect similar movies for all selected movies
184
+ all_similar_movies = []
185
+ for movie_id in st.session_state['selected_movies']:
186
+ similar_movies = get_similar_movies(movie_id, genre_id)
187
+ if similar_movies:
188
+ all_similar_movies.extend(similar_movies)
189
+
190
+ # Step 3: Filter similar movies by matching emotions and remove duplicates
191
+ recommendations = []
192
+ seen_movie_ids = set(st.session_state['selected_movies']) # Start with user-selected movies to exclude them
193
+
194
+ with st.spinner("Filtering movies by matching emotions..."):
195
+ for title, rating, poster_path, movie_id, description in all_similar_movies:
196
+ if movie_id not in seen_movie_ids: # Exclude already-seen movies
197
+ movie_emotion = analyze_emotion(description)
198
+ if movie_emotion in selected_emotions:
199
+ seen_movie_ids.add(movie_id) # Mark this movie as seen to prevent duplicates
200
+ recommendations.append((title, rating, poster_path, movie_id, movie_emotion))
201
+
202
+ # Store unique recommendations in session state
203
+ st.session_state['recommendations'] = recommendations
204
+
205
+ # Step 4: Display final recommendations
206
+ if st.session_state['recommendations']:
207
+ st.write("Based on your selections, you might also enjoy these top 5 movies:")
208
+ for title, rating, poster_path, movie_id, emotion in sorted(st.session_state['recommendations'], key=lambda x: x[1], reverse=True)[:5]:
209
+ movie_details = get_movie_details(movie_id)
210
+ if movie_details:
211
+ col1, col2 = st.columns([1, 3])
212
+ with col1:
213
+ st.image(f"{IMAGE_BASE_URL}{movie_details['poster_path']}", use_container_width=True)
214
+ with col2:
215
+ st.markdown(f"**Title:** {movie_details['title']}")
216
+ st.markdown(f"**TMDb Rating:** {movie_details['rating']}")
217
+ st.markdown(f"**Emotion:** {emotion}")
218
+ st.markdown(f"**Description:** {movie_details['description']}")
219
+
220
+ # Additional Information button with overlay using expander
221
+ with st.expander("Additional Information"):
222
+ st.markdown(f"**Release Date:** {movie_details['release_date']}")
223
+ st.markdown(f"**Director:** {movie_details['director']}")
224
+ st.markdown(f"**Cast:** {movie_details['cast']}")
225
+ else:
226
+ st.warning("No movies matched your selected emotions.")
227
+ else:
228
+ st.warning("Please select at least one movie to generate recommendations.")