File size: 9,387 Bytes
b757024
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886adf9
b757024
 
 
 
 
 
 
 
 
 
 
886adf9
 
 
 
 
 
 
 
 
 
 
 
b757024
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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!")