Roshanurs's picture
Update app.py
886adf9 verified
import streamlit as st
import pandas as pd
import os
# ==========================================
# 1. PAGE CONFIGURATION & STATE
# ==========================================
st.set_page_config(page_title="Horror Reference Library", layout="wide")
st.title("πŸ“½οΈ Horror Reference Library")
st.markdown("### Search 11,500+ Cinematic AI-Tagged Comic Panels")
# This is the "Memory" for the Load More button
if 'display_limit' not in st.session_state:
st.session_state.display_limit = 100
# This resets the count back to 100 anytime you change a filter
def reset_limit():
st.session_state.display_limit = 100
# ==========================================
# 2. DATA BUCKETING & CLEANING
# ==========================================
def categorize_camera(text):
text = str(text).lower()
if 'dutch' in text: return 'Dutch Angle'
elif 'extreme close' in text or 'ecu' in text: return 'Extreme Close Up'
elif 'close' in text or 'cu' in text: return 'Close Up'
elif 'wide' in text or 'long' in text or 'establishing' in text: return 'Wide Shot'
elif 'mid' in text or 'medium' in text: return 'Mid Shot'
elif 'low angle' in text or 'looking up' in text: return 'Low Angle'
elif 'high angle' in text or 'looking down' in text: return 'High Angle'
elif 'pov' in text or 'point of view' in text: return 'Point of View'
else: return 'Other / Mixed'
def categorize_mood(text):
text = str(text).lower()
if 'tense' in text or 'suspense' in text or 'anxiety' in text: return 'Tense & Suspenseful'
elif 'action' in text or 'chaos' in text or 'dynamic' in text: return 'Action & Chaos'
elif 'creepy' in text or 'eerie' in text or 'ominous' in text: return 'Creepy & Eerie'
elif 'gore' in text or 'violent' in text or 'blood' in text: return 'Gore & Violence'
elif 'sad' in text or 'melancholy' in text or 'somber' in text: return 'Somber & Melancholic'
else: return 'Neutral / Standard'
def categorize_lighting(text):
text = str(text).lower()
if 'silhouette' in text: return 'Silhouetted'
elif 'high contrast' in text or 'chiaroscuro' in text: return 'High Contrast'
elif 'low key' in text or 'shadow' in text or 'dark' in text: return 'Low Key (Shadowy)'
elif 'harsh' in text or 'bright' in text: return 'Harsh & Bright'
elif 'flat' in text or 'even' in text: return 'Flat Lighting'
else: return 'Standard Lighting'
def categorize_location(row):
text = str(row.get('location_setup', '')).lower() + " " + str(row.get('description', '')).lower()
if any(w in text for w in ['indoor', 'interior', 'room', 'house', 'building', 'office', 'corridor', 'hallway', 'wall', 'window', 'door', 'basement', 'stairs']): return 'Indoor'
if any(w in text for w in ['outdoor', 'exterior', 'street', 'sky', 'forest', 'mountain', 'landscape', 'city', 'outside', 'woods', 'road', 'night', 'moon', 'ocean']): return 'Outdoor'
return 'Unspecified / Mixed'
def categorize_subject(row):
text = str(row.get('staging', '')).lower() + " " + str(row.get('description', '')).lower()
if any(w in text for w in ['group', 'crowd', 'three', 'four', 'multiple', 'several', 'guests', 'army', 'mob', 'people']): return 'Group (3+ People)'
if any(w in text for w in ['two', 'couple', 'duo', 'both', 'pair']): return 'Two Characters'
if any(w in text for w in ['man', 'woman', 'boy', 'girl', 'figure', 'character', 'person', 'creature', 'monster']): return 'Single Subject'
return 'Object / Environment'
def categorize_action(row):
text = str(row.get('staging', '')).lower() + " " + str(row.get('description', '')).lower()
if any(w in text for w in ['action', 'fight', 'strike', 'combat', 'running', 'chasing', 'attack', 'lunging', 'falling', 'fleeing', 'struggle', 'violence', 'grab']): return 'Action Sequence'
if any(w in text for w in ['dialogue', 'talking', 'discussing', 'speaking', 'speech', 'conversation', 'yelling', 'screaming', 'whispering', 'saying']): return 'Dialogue / Conversation'
if any(w in text for w in ['reacts', 'reaction', 'looking', 'staring', 'observing', 'gazing', 'watching', 'shock', 'listening']): return 'Reaction / Observation'
return 'Static / Establishing'
@st.cache_data
def load_data():
df = pd.read_csv("horror_shot_database.csv")
df['broad_camera'] = df['camera_angle'].apply(categorize_camera)
df['broad_mood'] = df['mood'].apply(categorize_mood)
df['broad_lighting'] = (df['mood'].fillna('') + " " + df['description'].fillna('')).apply(categorize_lighting)
df['location_type'] = df.apply(categorize_location, axis=1)
df['subject_type'] = df.apply(categorize_subject, axis=1)
df['action_type'] = df.apply(categorize_action, axis=1)
return df
try:
df = load_data()
except Exception as e:
st.error(f"Error loading database: {e}")
st.stop()
# ==========================================
# 3. SHOTDECK-STYLE SEARCH & FILTERS
# ==========================================
st.sidebar.header("πŸ” Search Library")
# Notice the on_change=reset_limit on all inputs now!
search_query = st.sidebar.text_input("Keyword Search", placeholder="e.g., monster, shadow, weapon...", on_change=reset_limit)
st.sidebar.write("---")
st.sidebar.header("πŸ“‚ Filter Categories")
with st.sidebar.expander("🌍 Location & Subjects", expanded=True):
all_locations = ["Any"] + sorted(df['location_type'].unique().tolist())
selected_location = st.selectbox("Setting", all_locations, on_change=reset_limit)
all_subjects = ["Any"] + sorted(df['subject_type'].unique().tolist())
selected_subject = st.selectbox("Characters in Frame", all_subjects, on_change=reset_limit)
with st.sidebar.expander("🎬 Action & Scene Type", expanded=True):
all_actions = ["Any"] + sorted(df['action_type'].unique().tolist())
selected_action = st.selectbox("Scene Action", all_actions, on_change=reset_limit)
with st.sidebar.expander("πŸŽ₯ Camera & Framing"):
all_angles = ["Any"] + sorted(df['broad_camera'].unique().tolist())
selected_angle = st.selectbox("Shot Type", all_angles, on_change=reset_limit)
with st.sidebar.expander("🎭 Atmosphere"):
all_lighting = ["Any"] + sorted(df['broad_lighting'].unique().tolist())
selected_lighting = st.selectbox("Lighting Style", all_lighting, on_change=reset_limit)
all_moods = ["Any"] + sorted(df['broad_mood'].unique().tolist())
selected_mood = st.selectbox("Mood", all_moods, on_change=reset_limit)
# ==========================================
# 4. FILTERING LOGIC
# ==========================================
results = df.copy()
if search_query:
results = results[results['description'].str.contains(search_query, case=False, na=False)]
if selected_location != "Any":
results = results[results['location_type'] == selected_location]
if selected_subject != "Any":
results = results[results['subject_type'] == selected_subject]
if selected_action != "Any":
results = results[results['action_type'] == selected_action]
if selected_angle != "Any":
results = results[results['broad_camera'] == selected_angle]
if selected_lighting != "Any":
results = results[results['broad_lighting'] == selected_lighting]
if selected_mood != "Any":
results = results[results['broad_mood'] == selected_mood]
base_url = "https://huggingface.co/datasets/Roshanurs/Horror-Reference-Data/resolve/main/Panels_Out"
valid_images = []
for idx, row in results.iterrows():
img_url = f"{base_url}/{row['filename']}"
valid_images.append({
"url": img_url,
"filename": row['filename'],
"desc": row['description']
})
st.markdown(f"**Found {len(valid_images)} matching shots**")
st.write("---")
# ==========================================
# 5. THE HTML GALLERY (Ultimate Anti-Flicker)
# ==========================================
if len(valid_images) > 0:
current_limit = st.session_state.display_limit
display_list = valid_images[:current_limit]
for i in range(0, len(display_list), 4):
cols = st.columns(4)
for j in range(4):
if i + j < len(display_list):
img_data = display_list[i + j]
with cols[j]:
# We bypass st.image and use pure HTML with loading="lazy"
html_card = f"""
<div style="margin-bottom: 15px;">
<img src='{img_data["url"]}' style='width: 100%; border-radius: 6px; display: block;' loading='lazy'>
<a href='{img_data["url"]}' target='_blank'>
<button style='width:100%; padding:8px; margin-top: 8px; border-radius:4px; border:1px solid #444; background:#222; color:white; cursor:pointer;'>
View Full Size
</button>
</a>
</div>
"""
st.markdown(html_card, unsafe_allow_html=True)
# The Load More Button
if len(valid_images) > current_limit:
st.write("---")
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
if st.button("⬇️ Load 100 More Images", use_container_width=True):
st.session_state.display_limit += 100
st.rerun()
else:
st.warning("No shots found matching those exact parameters. Try widening your search!")