import gradio as gr
import logging
from services.tmdb_client import tmdb_client
from utils.config import config
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('movie_app.log'),
logging.StreamHandler()
]
)
# Create logger for this module
logger = logging.getLogger(__name__)
def extract_country_code(country_selection):
"""Extract country code from selection like 'United States (US)' -> 'US'"""
if not country_selection:
return "US" # Default fallback
# Extract code from parentheses
if "(" in country_selection and ")" in country_selection:
return country_selection.split("(")[-1].replace(")", "").strip()
# Fallback if format is unexpected
return "US"
def get_movie_recommendations(decade, country, genre, num_movies):
"""Get movie recommendations based on user preferences."""
logger.info(f"Recommendation request: decade={decade}, country={country}, genre={genre}, num_movies={num_movies}")
if not decade or not country:
logger.warning("Missing required parameters: decade or country")
return "Please select decade and country."
# Check if country requires genre selection
if country in config.TOP_COUNTRIES and not genre:
logger.warning(f"Genre required for top country {country} but not provided")
return "Please select a genre for this country."
# Convert decade string to integer (e.g., "2000s" -> 2000)
decade_year = int(decade.replace("s", ""))
# Extract country code from the selection
country_code = extract_country_code(country)
# Get recommendations (genre is optional for non-top countries)
logger.info(f"Fetching movies for {country_code}, {decade_year}, genre: {genre}")
movies = tmdb_client.get_random_movies(
decade=decade_year,
country=country_code,
genre=genre if country in config.TOP_COUNTRIES else None,
n=num_movies
)
# Update result message based on country type
if country in config.TOP_COUNTRIES:
search_criteria = f"{genre} from {country} ({decade})"
else:
search_criteria = f"from {country} ({decade})"
if not movies:
logger.warning(f"No movies found {search_criteria}")
return f"No movies found {search_criteria}."
logger.info(f"Successfully found {len(movies)} movies {search_criteria}")
# Format results with mobile-friendly movie information
output = f"🎬 **{len(movies)} movies {search_criteria}:**\n\n"
for i, movie in enumerate(movies, 1):
# Format title - show original title if different from English title
title_display = movie['title']
if movie.get('original_title') and movie['original_title'] != movie['title']:
title_display = f"{movie['title']} ({movie['original_title']})"
# Create a mobile-friendly vertical card layout
output += f"
\n"
# Movie title header
output += f"
{i}. {title_display}
\n"
# Poster image section - centered and smaller
if movie.get('poster_url'):
output += f"
\n"
output += f"

\n"
output += f"
\n"
# Basic Info Row - more compact
output += f"
\n"
output += f"
Year: {movie['year']}
\n"
if movie.get('has_omdb_data') and movie.get('runtime') != 'N/A':
output += f"
Runtime: {movie['runtime']}
\n"
if movie.get('has_omdb_data') and movie.get('rated') != 'N/A':
output += f"
Rated: {movie['rated']}
\n"
output += f"
\n"
# Director and Cast (if available) - more compact
if movie.get('has_omdb_data'):
if movie.get('director') != 'N/A':
output += f"
🎬 Director: {movie['director']}
\n"
if movie.get('actors') != 'N/A':
actors = movie['actors'][:120] + '...' if len(movie['actors']) > 120 else movie['actors']
output += f"
🎭 Cast: {actors}
\n"
# Ratings Section - 2x2 grid layout for mobile
output += f"
\n"
output += f"
📊 Ratings
\n"
output += f"
\n"
# TMDB Rating
output += f"
\n"
output += f"
TMDB
\n"
output += f"
⭐ {movie['rating']}/10
\n"
output += f"
({movie['vote_count']} votes)
\n"
output += f"
\n"
# IMDB Rating (if available)
if movie.get('has_omdb_data') and movie.get('imdb_rating') != 'N/A':
output += f"
\n"
output += f"
IMDb
\n"
output += f"
⭐ {movie['imdb_rating']}/10
\n"
if movie.get('imdb_votes') != 'N/A':
output += f"
({movie['imdb_votes']} votes)
\n"
output += f"
\n"
# Rotten Tomatoes (if available)
if movie.get('has_omdb_data') and movie.get('rotten_tomatoes') != 'N/A':
output += f"
\n"
output += f"
RT
\n"
output += f"
🍅 {movie.get('rotten_tomatoes')}
\n"
output += f"
\n"
# Metacritic (if available)
if movie.get('has_omdb_data') and movie.get('metascore') != 'N/A':
output += f"
\n"
output += f"
Meta
\n"
output += f"
📊 {movie['metascore']}/100
\n"
output += f"
\n"
output += f"
\n"
output += f"
\n"
# Plot/Overview - more compact
plot_text = movie.get('plot', movie['overview']) if movie.get('has_omdb_data') else movie['overview']
plot_display = plot_text[:250] + '...' if len(plot_text) > 250 else plot_text
output += f"
📝 Plot: {plot_display}
\n"
# Awards (if available) - more compact
if movie.get('has_omdb_data') and movie.get('awards') != 'N/A' and movie['awards'] != 'No awards.':
awards_text = movie['awards'][:150] + '...' if len(movie['awards']) > 150 else movie['awards']
output += f"
🏆 Awards: {awards_text}
\n"
# Streaming Services Section - more compact
streaming_services = movie.get('all_services', [])
if streaming_services:
output += f"
\n"
output += f"
📺Watch Now
\n"
# Free services
free_services = movie.get('free_services', [])
if free_services:
output += f"
\n"
output += f"
🆓 Free
\n"
output += f"
\n"
for service in free_services:
if service.get('logo_url'):
output += f"
\n"
output += f"

\n"
output += f"
{service['name']}\n"
output += f"
\n"
else:
output += f"
{service['name']}
\n"
output += f"
\n"
output += f"
\n"
# Subscription services
subscription_services = movie.get('subscription_services', [])
if subscription_services:
output += f"
\n"
output += f"
💳 Free with Subscription
\n"
output += f"
\n"
for service in subscription_services:
if service.get('logo_url'):
output += f"
\n"
output += f"

\n"
output += f"
{service['name']}\n"
output += f"
\n"
else:
output += f"
{service['name']}
\n"
output += f"
\n"
output += f"
\n"
# JustWatch attribution (required by TMDB terms)
output += f"
Streaming data provided by JustWatch
\n"
output += f"
\n"
# Box Office (if available) - more compact
if movie.get('has_omdb_data') and movie.get('box_office') != 'N/A':
output += f"
💰 Box Office: {movie['box_office']}
\n"
# External Links - centered and more compact
links_html = ""
if movie.get('imdb_url'):
links_html += f"
\n"
if links_html:
output += links_html
output += f"
\n\n"
return output
# Create simple Gradio interface
with gr.Blocks(
title="Movie Recommendation",
theme="gstaff/xkcd",
css="""
footer {
display: none !important;
}
.gradio-container .footer {
display: none !important;
}
.gradio-container .footer-text {
display: none !important;
}
.gradio-container .built-with {
display: none !important;
}
"""
) as app:
gr.Markdown("# 🎬 Random Movie Recommendations")
gr.Markdown("Select what you are in the mood for and get a movie recommendation!")
with gr.Row():
with gr.Column():
decade = gr.Dropdown(
choices=config.DECADES,
label="Decade",
value="2000s"
)
country = gr.Dropdown(
choices=config.COUNTRIES,
label="Country",
value="United States of America (US)",
filterable=True
)
genre = gr.Dropdown(
choices=config.GENRES,
label="Genre",
value="Action",
visible=True # Initially visible since default is US
)
num_movies = gr.Slider(
minimum=1,
maximum=10,
value=5,
step=1,
label="Number of Movies"
)
get_btn = gr.Button("Get Recommendations", variant="primary")
results = gr.Markdown(label="Recommendations")
# Function to update genre visibility based on country selection
def update_genre_visibility(selected_country):
if selected_country in config.TOP_COUNTRIES:
return gr.update(visible=True)
else:
return gr.update(visible=False, value=None)
# Update genre dropdown when country changes
country.change(
update_genre_visibility,
inputs=[country],
outputs=[genre]
)
# Connect the recommendation function
get_btn.click(
get_movie_recommendations,
inputs=[decade, country, genre, num_movies],
outputs=results
)
if __name__ == "__main__":
# Validate configuration before starting the app
try:
config.validate()
logger.info("Starting Movie Recommendation App")
app.launch()
except Exception as e:
logger.error(f"Failed to start application: {e}")
raise