import streamlit as st import pandas as pd import sqlite3 import json from datetime import datetime # Complete Language Map LANGUAGE_MAP = { "aa": "Afar", "ab": "Abkhazian", "ae": "Avestan", "af": "Afrikaans", "ak": "Akan", "am": "Amharic", "an": "Aragonese", "ar": "Arabic", "as": "Assamese", "av": "Avaric", "ay": "Aymara", "az": "Azerbaijani", "ba": "Bashkir", "be": "Belarusian", "bg": "Bulgarian", "bh": "Bihari", "bi": "Bislama", "bm": "Bambara", "bn": "Bengali", "bo": "Tibetan", "br": "Breton", "bs": "Bosnian", "ca": "Catalan", "ce": "Chechen", "ch": "Chamorro", "co": "Corsican", "cr": "Cree", "cs": "Czech", "cu": "Church Slavic", "cv": "Chuvash", "cy": "Welsh", "da": "Danish", "de": "German", "dv": "Divehi", "dz": "Dzongkha", "ee": "Ewe", "el": "Greek", "en": "English", "eo": "Esperanto", "es": "Spanish", "et": "Estonian", "eu": "Basque", "fa": "Persian", "ff": "Fulah", "fi": "Finnish", "fj": "Fijian", "fo": "Faroese", "fr": "French", "fy": "Western Frisian", "ga": "Irish", "gd": "Scottish Gaelic", "gl": "Galician", "gn": "Guarani", "gu": "Gujarati", "gv": "Manx", "ha": "Hausa", "he": "Hebrew", "hi": "Hindi", "ho": "Hiri Motu", "hr": "Croatian", "ht": "Haitian", "hu": "Hungarian", "hy": "Armenian", "hz": "Herero", "ia": "Interlingua", "id": "Indonesian", "ie": "Interlingue", "ig": "Igbo", "ii": "Sichuan Yi", "ik": "Inupiaq", "io": "Ido", "is": "Icelandic", "it": "Italian", "iu": "Inuktitut", "ja": "Japanese", "jv": "Javanese", "ka": "Georgian", "kg": "Kongo", "ki": "Kikuyu", "kj": "Kwanyama", "kk": "Kazakh", "kl": "Kalaallisut", "km": "Khmer", "kn": "Kannada", "ko": "Korean", "kr": "Kanuri", "ks": "Kashmiri", "ku": "Kurdish", "kv": "Komi", "kw": "Cornish", "ky": "Kirghiz", "la": "Latin", "lb": "Luxembourgish", "lg": "Ganda", "li": "Limburgish", "ln": "Lingala", "lo": "Lao", "lt": "Lithuanian", "lu": "Luba-Katanga", "lv": "Latvian", "mg": "Malagasy", "mh": "Marshallese", "mi": "Maori", "mk": "Macedonian", "ml": "Malayalam", "mn": "Mongolian", "mr": "Marathi", "ms": "Malay", "mt": "Maltese", "my": "Burmese", "na": "Nauru", "nb": "Norwegian Bokmål", "nd": "North Ndebele", "ne": "Nepali", "ng": "Ndonga", "nl": "Dutch", "nn": "Norwegian Nynorsk", "no": "Norwegian", "nr": "South Ndebele", "nv": "Navajo", "ny": "Chichewa", "oc": "Occitan", "oj": "Ojibwa", "om": "Oromo", "or": "Oriya", "os": "Ossetian", "pa": "Punjabi", "pi": "Pali", "pl": "Polish", "ps": "Pashto", "pt": "Portuguese", "qu": "Quechua", "rm": "Romansh", "rn": "Kirundi", "ro": "Romanian", "ru": "Russian", "rw": "Kinyarwanda", "sa": "Sanskrit", "sc": "Sardinian", "sd": "Sindhi", "se": "Northern Sami", "sg": "Sango", "si": "Sinhala", "sk": "Slovak", "sl": "Slovenian", "sm": "Samoan", "sn": "Shona", "so": "Somali", "sq": "Albanian", "sr": "Serbian", "ss": "Swati", "st": "Southern Sotho", "su": "Sundanese", "sv": "Swedish", "sw": "Swahili", "ta": "Tamil", "te": "Telugu", "tg": "Tajik", "th": "Thai", "ti": "Tigrinya", "tk": "Turkmen", "tl": "Tagalog", "tn": "Tswana", "to": "Tonga", "tr": "Turkish", "ts": "Tsonga", "tt": "Tatar", "tw": "Twi", "ty": "Tahitian", "ug": "Uighur", "uk": "Ukrainian", "ur": "Urdu", "uz": "Uzbek", "ve": "Venda", "vi": "Vietnamese", "vo": "Volapük", "wa": "Walloon", "wo": "Wolof", "xh": "Xhosa", "yi": "Yiddish", "yo": "Yoruba", "za": "Zhuang", "zh": "Chinese", "zu": "Zulu" } st.set_page_config(page_title="Movie Search", layout="wide", page_icon="🎥") st.title("🎥 Local Movie Database") conn = sqlite3.connect('movies.db') # Session state if 'results' not in st.session_state: st.session_state.results = None if 'loaded_count' not in st.session_state: st.session_state.loaded_count = 0 if 'total_available' not in st.session_state: st.session_state.total_available = 0 if 'current_filters' not in st.session_state: st.session_state.current_filters = None st.sidebar.header("Search & Filters") search_term = st.sidebar.text_input("Search by Title or Keyword") # FIXED Language filter languages_df = pd.read_sql(""" SELECT DISTINCT original_language FROM movies WHERE original_language IS NOT NULL AND original_language != '' """, conn) unique_lang_codes = sorted([str(lang).lower() for lang in languages_df['original_language'] if lang]) selected_lang_codes = st.sidebar.multiselect( "Language(s)", options=unique_lang_codes, format_func=lambda x: LANGUAGE_MAP.get(x, str(x).upper() if x else "Unknown") ) # Genre filter genres_df = pd.read_sql("SELECT DISTINCT genres FROM movies WHERE genres IS NOT NULL AND genres != ''", conn) genre_list = [] for g in genres_df['genres']: if isinstance(g, str): try: if g.startswith('['): parsed = json.loads(g) for item in parsed: if isinstance(item, dict) and 'name' in item: genre_list.append(item['name']) else: genre_list.extend([x.strip() for x in g.split(',') if x.strip()]) except: if g: genre_list.append(g) unique_genres = sorted(set(genre_list)) selected_genres = st.sidebar.multiselect("Genre(s)", unique_genres) min_rating = st.sidebar.slider("Minimum Rating", 0.0, 10.0, 6.5, 0.1) min_votes = st.sidebar.number_input("Minimum Votes", value=500, step=100) min_year = st.sidebar.number_input("From Year", value=1990) max_year = st.sidebar.number_input("To Year", value=datetime.now().year) # ====================== NEW SEARCH ====================== if st.sidebar.button("🔍 New Search", type="primary"): base_query = "SELECT title, release_date, vote_average, vote_count, overview, original_language, poster_path FROM movies WHERE 1=1" count_query = "SELECT COUNT(*) as total FROM movies WHERE 1=1" params = [] if search_term: base_query += " AND (title LIKE ? OR overview LIKE ?)" count_query += " AND (title LIKE ? OR overview LIKE ?)" params.extend([f"%{search_term}%", f"%{search_term}%"]) if selected_lang_codes: lang_cond = " OR ".join(["original_language = ?" for _ in selected_lang_codes]) base_query += f" AND ({lang_cond})" count_query += f" AND ({lang_cond})" params.extend(selected_lang_codes) if selected_genres: genre_cond = " OR ".join(["genres LIKE ?" for _ in selected_genres]) base_query += f" AND ({genre_cond})" count_query += f" AND ({genre_cond})" params.extend([f"%{g}%" for g in selected_genres]) base_query += " AND vote_average >= ? AND vote_count >= ?" count_query += " AND vote_average >= ? AND vote_count >= ?" params.extend([min_rating, min_votes]) base_query += " AND (release_date >= ? OR release_date IS NULL)" count_query += " AND (release_date >= ? OR release_date IS NULL)" params.append(f"{min_year}-01-01") if max_year: base_query += " AND (release_date <= ? OR release_date IS NULL)" count_query += " AND (release_date <= ? OR release_date IS NULL)" params.append(f"{max_year}-12-31") # Get total count total_df = pd.read_sql(count_query, conn, params=params) st.session_state.total_available = int(total_df.iloc[0]['total']) # Get first 100 full_query = base_query + " ORDER BY vote_average DESC, popularity DESC, vote_count DESC LIMIT 100" df = pd.read_sql(full_query, conn, params=params) st.session_state.results = df st.session_state.loaded_count = len(df) st.session_state.current_filters = { 'search_term': search_term, 'selected_lang_codes': selected_lang_codes, 'selected_genres': selected_genres, 'min_rating': min_rating, 'min_votes': min_votes, 'min_year': min_year, 'max_year': max_year } # ====================== DISPLAY RESULTS ====================== if st.session_state.results is not None: if st.session_state.results.empty: st.warning("No movies found with your current filters.") else: st.success(f"Showing {st.session_state.loaded_count} of {st.session_state.total_available} movies") cols = st.columns(4) for idx, row in st.session_state.results.iterrows(): with cols[idx % 4]: with st.container(border=True): # Poster if pd.notna(row.get('poster_path')) and row['poster_path']: poster_url = f"https://image.tmdb.org/t/p/w300{row['poster_path']}" st.image(poster_url, use_container_width=True) else: st.image("https://via.placeholder.com/300x450?text=No+Poster", use_container_width=True) # Title + Year year = row['release_date'][:4] if pd.notna(row['release_date']) and row['release_date'] else "Unknown" st.subheader(f"{row['title']} ({year})") # Language lang_code = str(row['original_language']).lower() if pd.notna(row['original_language']) else "" full_lang = LANGUAGE_MAP.get(lang_code, lang_code.upper() if lang_code else "Unknown") st.caption(f"🌐 {full_lang}") # Rating st.write(f"⭐ **{row['vote_average']:.1f}** ({row['vote_count']:,} votes)") # Overview overview = row['overview'] if pd.notna(row['overview']) and row['overview'].strip() else "No description available." st.write(overview[:380] + "..." if len(overview) > 380 else overview) # Load More Button - No limit if st.session_state.loaded_count < st.session_state.total_available: if st.button("⬇️ Load More (100 more)", type="secondary", use_container_width=True): offset = st.session_state.loaded_count base_query = "SELECT title, release_date, vote_average, vote_count, overview, original_language, poster_path FROM movies WHERE 1=1" params = [] f = st.session_state.current_filters if f['search_term']: base_query += " AND (title LIKE ? OR overview LIKE ?)" params.extend([f"%{f['search_term']}%", f"%{f['search_term']}%"]) if f['selected_lang_codes']: lang_cond = " OR ".join(["original_language = ?" for _ in f['selected_lang_codes']]) base_query += f" AND ({lang_cond})" params.extend(f['selected_lang_codes']) if f['selected_genres']: genre_cond = " OR ".join(["genres LIKE ?" for _ in f['selected_genres']]) base_query += f" AND ({genre_cond})" params.extend([f"%{g}%" for g in f['selected_genres']]) base_query += " AND vote_average >= ? AND vote_count >= ?" params.extend([f['min_rating'], f['min_votes']]) base_query += " AND (release_date >= ? OR release_date IS NULL)" params.append(f"{f['min_year']}-01-01") if f['max_year']: base_query += " AND (release_date <= ? OR release_date IS NULL)" params.append(f"{f['max_year']}-12-31") next_query = base_query + f" ORDER BY vote_average DESC, popularity DESC, vote_count DESC LIMIT 100 OFFSET {offset}" next_batch = pd.read_sql(next_query, conn, params=params) st.session_state.results = pd.concat([st.session_state.results, next_batch], ignore_index=True) st.session_state.loaded_count += len(next_batch) st.rerun() else: st.info("✅ All matching results have been loaded.") else: st.info("Adjust filters in the sidebar and click **New Search** to begin.") conn.close()