From db04590ce227a9d9b4d84667ceb72146c8e2c43c Mon Sep 17 00:00:00 2001 From: yakamo Date: Wed, 8 Apr 2026 15:47:35 +0100 Subject: [PATCH] first upload --- movie-search-app.py | 250 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 movie-search-app.py diff --git a/movie-search-app.py b/movie-search-app.py new file mode 100644 index 0000000..9d7a894 --- /dev/null +++ b/movie-search-app.py @@ -0,0 +1,250 @@ +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()