first upload
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user