File size: 8,521 Bytes
4d15178
 
 
 
 
 
 
 
 
 
 
 
 
7078f15
4d15178
82ce6d4
7078f15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82ce6d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d15178
 
 
7078f15
82ce6d4
7078f15
 
 
 
82ce6d4
 
 
 
 
4d15178
 
 
 
 
 
 
 
 
5cf0296
4d15178
5cf0296
4d15178
82ce6d4
5cf0296
4d15178
5cf0296
4d15178
82ce6d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7078f15
 
5cf0296
82ce6d4
 
7078f15
82ce6d4
 
7078f15
82ce6d4
4d15178
 
7078f15
4d15178
5cf0296
 
 
 
 
82ce6d4
 
 
 
 
 
4d15178
7078f15
 
 
4d15178
7078f15
4d15178
c7be050
4d15178
7078f15
 
 
 
 
 
 
 
 
 
 
4d15178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
import pandas as pd
import os

# ==========================================
# 1. PAGE CONFIGURATION
# ==========================================
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")

# ==========================================
# 2. DATA BUCKETING & CLEANING
# ==========================================
# Camera, Mood, and Lighting Buckets
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'

# NEW: Location, Subjects, and Action Buckets
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")
    
    # Apply standard categories
    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)
    
    # Apply new Storyboard categories
    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")

search_query = st.sidebar.text_input("Keyword Search", placeholder="e.g., monster, shadow, weapon...")
st.sidebar.write("---")

st.sidebar.header("πŸ“‚ Filter Categories")

# Expander 1: Location & Subjects
with st.sidebar.expander("🌍 Location & Subjects", expanded=True):
    all_locations = ["Any"] + sorted(df['location_type'].unique().tolist())
    selected_location = st.selectbox("Setting", all_locations)
    
    all_subjects = ["Any"] + sorted(df['subject_type'].unique().tolist())
    selected_subject = st.selectbox("Characters in Frame", all_subjects)

# Expander 2: Scene & Action
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)

# Expander 3: Camera
with st.sidebar.expander("πŸŽ₯ Camera & Framing"):
    all_angles = ["Any"] + sorted(df['broad_camera'].unique().tolist())
    selected_angle = st.selectbox("Shot Type", all_angles)

# Expander 4: Atmosphere
with st.sidebar.expander("🎭 Atmosphere"):
    all_lighting = ["Any"] + sorted(df['broad_lighting'].unique().tolist())
    selected_lighting = st.selectbox("Lighting Style", all_lighting)
    
    all_moods = ["Any"] + sorted(df['broad_mood'].unique().tolist())
    selected_mood = st.selectbox("Mood", all_moods)

# ==========================================
# 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 MASONRY GALLERY 
# ==========================================
if len(valid_images) > 0:
    display_limit = 100
    display_list = valid_images[:display_limit]
    
    cols = st.columns(4)
    
    for index, img_data in enumerate(display_list):
        with cols[index % 4]:
            st.image(img_data["url"], use_container_width=True)
            
            st.markdown(
                f'<a href="{img_data["url"]}" target="_blank">'
                f'<button style="width:100%; padding:8px; border-radius:4px; border:1px solid #444; background:#222; color:white; cursor:pointer;">'
                f'View Full Size</button></a>', 
                unsafe_allow_html=True
            )
            st.write("") 
            
    if len(valid_images) > display_limit:
        st.info(f"Showing first {display_limit} results. Refine your filters to see more specific shots.")
else:
    st.warning("No shots found matching those exact parameters. Try widening your search!")