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"{movie[\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"{service[\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"{service[\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" links_html += f"🎬 View on IMDb\n" 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