Travito213 commited on
Commit
ca73bad
·
verified ·
1 Parent(s): 2a29b27

Update to script-to-shots app with FLUX model

Browse files
Files changed (1) hide show
  1. app.py +303 -136
app.py CHANGED
@@ -1,154 +1,321 @@
1
  import gradio as gr
2
- import numpy as np
3
- import random
4
-
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
8
-
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
-
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
81
 
82
- result = gr.Image(label="Result", show_label=False)
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
98
- )
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
101
 
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
 
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
 
 
 
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
135
 
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
 
 
 
 
 
 
 
 
 
 
151
  )
 
 
 
 
 
 
 
 
 
 
 
152
 
153
  if __name__ == "__main__":
154
- demo.launch()
 
1
  import gradio as gr
2
+ import requests
3
+ import json
4
+ import re
5
+ from typing import List, Dict, Any
6
+ import os
7
+
8
+ # Hugging Face configuration
9
+ HF_TOKEN = os.getenv("HUGGING_FACE_API_TOKEN", "")
10
+ HF_API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev"
11
+
12
+ def parse_script(script_text: str) -> Dict[str, Any]:
13
+ """Parse script text and extract scenes and characters"""
14
+ lines = script_text.strip().split('\n')
15
+ scenes = []
16
+ characters = set()
17
+ current_scene = None
18
+
19
+ for line in lines:
20
+ line = line.strip()
21
+ if not line:
22
+ continue
23
+
24
+ # Scene headers (INT./EXT.)
25
+ if line.upper().startswith(('INT.', 'EXT.', 'SCENE')):
26
+ if current_scene:
27
+ scenes.append(current_scene)
28
+ current_scene = {
29
+ 'location': line,
30
+ 'dialogue': [],
31
+ 'action': []
32
+ }
33
+
34
+ # Character dialogue (ALL CAPS followed by dialogue)
35
+ elif line.isupper() and len(line.split()) <= 3 and current_scene:
36
+ characters.add(line)
37
+ current_scene['dialogue'].append({'character': line, 'lines': []})
38
+
39
+ # Dialogue lines
40
+ elif current_scene and current_scene['dialogue'] and not line.isupper():
41
+ current_scene['dialogue'][-1]['lines'].append(line)
42
+
43
+ # Action lines
44
+ elif current_scene and not line.isupper():
45
+ current_scene['action'].append(line)
46
+
47
+ if current_scene:
48
+ scenes.append(current_scene)
49
+
50
+ return {
51
+ 'scenes': scenes,
52
+ 'characters': list(characters),
53
+ 'total_scenes': len(scenes)
54
+ }
55
+
56
+ def generate_shot_list(script_data: Dict[str, Any]) -> List[Dict[str, Any]]:
57
+ """Generate shot list from parsed script"""
58
+ shots = []
59
+ shot_id = 1
60
+
61
+ for scene_idx, scene in enumerate(script_data['scenes']):
62
+ # Establishing shot
63
+ shots.append({
64
+ 'id': shot_id,
65
+ 'type': 'Establishing Shot',
66
+ 'description': f"Wide shot of {scene['location']}",
67
+ 'scene': scene_idx + 1,
68
+ 'location': scene['location']
69
+ })
70
+ shot_id += 1
71
+
72
+ # Character shots
73
+ dialogue_chars = set()
74
+ for dialogue in scene['dialogue']:
75
+ char = dialogue['character']
76
+ if char not in dialogue_chars:
77
+ shots.append({
78
+ 'id': shot_id,
79
+ 'type': 'Medium Shot',
80
+ 'description': f"Medium shot of {char}",
81
+ 'scene': scene_idx + 1,
82
+ 'character': char,
83
+ 'location': scene['location']
84
+ })
85
+ dialogue_chars.add(char)
86
+ shot_id += 1
87
+
88
+ # Action shots
89
+ for action in scene['action']:
90
+ if len(action) > 20: # Only significant action lines
91
+ shots.append({
92
+ 'id': shot_id,
93
+ 'type': 'Action Shot',
94
+ 'description': action[:100] + "..." if len(action) > 100 else action,
95
+ 'scene': scene_idx + 1,
96
+ 'location': scene['location']
97
+ })
98
+ shot_id += 1
99
+
100
+ return shots
101
+
102
+ def generate_image(prompt: str) -> str:
103
+ """Generate image using Hugging Face API"""
104
+ if not HF_TOKEN:
105
+ return "https://via.placeholder.com/512x512?text=No+API+Key"
106
+
107
+ headers = {
108
+ "Authorization": f"Bearer {HF_TOKEN}",
109
+ "Content-Type": "application/json"
110
+ }
111
+
112
+ payload = {
113
+ "inputs": f"{prompt}, cinematic, professional, high quality"
114
+ }
115
+
116
+ try:
117
+ response = requests.post(HF_API_URL, headers=headers, json=payload, timeout=30)
118
+ if response.status_code == 200:
119
+ # Save image and return path
120
+ import base64
121
+ image_data = response.content
122
+ image_b64 = base64.b64encode(image_data).decode()
123
+ return f"data:image/png;base64,{image_b64}"
124
+ else:
125
+ return f"https://via.placeholder.com/512x512?text=API+Error+{response.status_code}"
126
+ except Exception as e:
127
+ return f"https://via.placeholder.com/512x512?text=Error"
128
+
129
+ def process_script(script_text: str, generate_images: bool = True):
130
+ """Main function to process script and generate shot list"""
131
+ if not script_text.strip():
132
+ return "Please enter a script.", "", ""
133
+
134
+ # Parse script
135
+ script_data = parse_script(script_text)
136
+
137
+ # Generate shot list
138
+ shots = generate_shot_list(script_data)
139
+
140
+ # Create summary
141
+ summary = f"""
142
+ ## Script Analysis Summary
143
+ - **Total Scenes:** {script_data['total_scenes']}
144
+ - **Characters:** {', '.join(script_data['characters'])}
145
+ - **Generated Shots:** {len(shots)}
146
  """
147
+
148
+ # Create shot list display
149
+ shot_list_html = "<div style='max-height: 400px; overflow-y: auto;'>"
150
+
151
+ for shot in shots:
152
+ # Generate image if requested
153
+ image_html = ""
154
+ if generate_images:
155
+ image_url = generate_image(shot['description'])
156
+ image_html = f'<img src="{image_url}" style="width: 200px; height: 150px; object-fit: cover; border-radius: 8px;" />'
157
+
158
+ shot_list_html += f"""
159
+ <div style='border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 8px; background: #f9f9f9;'>
160
+ <div style='display: flex; gap: 15px; align-items: flex-start;'>
161
+ <div style='flex: 1;'>
162
+ <h3 style='margin: 0 0 10px 0; color: #333;'>Shot {shot['id']}: {shot['type']}</h3>
163
+ <p style='margin: 5px 0;'><strong>Scene:</strong> {shot['scene']}</p>
164
+ <p style='margin: 5px 0;'><strong>Description:</strong> {shot['description']}</p>
165
+ {f"<p style='margin: 5px 0;'><strong>Character:</strong> {shot.get('character', 'N/A')}</p>" if shot.get('character') else ""}
166
+ <p style='margin: 5px 0;'><strong>Location:</strong> {shot.get('location', 'N/A')}</p>
167
+ </div>
168
+ <div style='flex-shrink: 0;'>
169
+ {image_html}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ """
174
+
175
+ shot_list_html += "</div>"
176
+
177
+ return summary, shot_list_html, f"Generated {len(shots)} shots successfully!"
178
 
179
+ # Sample script for demo
180
+ SAMPLE_SCRIPT = """INT. COFFEE SHOP - DAY
 
 
 
 
 
 
 
 
 
 
181
 
182
+ A bustling coffee shop filled with the morning crowd. Steam rises from espresso machines.
183
 
184
+ SARAH sits at a corner table, typing furiously on her laptop. She glances at her watch nervously.
185
 
186
+ SARAH
187
+ (muttering to herself)
188
+ Come on, come on... where is he?
 
 
 
 
189
 
190
+ The door chimes as MIKE enters, scanning the room. He spots Sarah and approaches.
 
 
 
 
 
 
191
 
192
+ MIKE
193
+ Sorry I'm late! Traffic was insane.
194
 
195
+ SARAH
196
+ (relieved)
197
+ Thank god you're here. I've been going crazy waiting.
 
 
 
 
 
198
 
199
+ Mike sits down across from her.
200
+
201
+ MIKE
202
+ So, what's this big emergency about?
203
+
204
+ Sarah closes her laptop and leans in conspiratorially.
205
+
206
+ SARAH
207
+ I found something. Something that could change everything.
208
+
209
+ EXT. CITY STREET - DAY
210
 
211
+ Sarah and Mike walk quickly down a busy sidewalk, weaving through pedestrians.
212
+
213
+ MIKE
214
+ Are you sure about this?
215
+
216
+ SARAH
217
+ I've never been more sure of anything in my life.
218
+
219
+ They stop at a red light, looking around nervously."""
220
+
221
+ # Create Gradio interface
222
+ with gr.Blocks(title="Script to Shots - AI Storyboard Generator") as demo:
223
+ gr.Markdown("""
224
+ # 🎬 Script to Shots - AI Storyboard Generator
225
+
226
+ Transform your scripts into visual shot lists with AI-generated reference images!
227
+
228
+ **How it works:**
229
+ 1. Paste your script in the text area below
230
+ 2. Choose whether to generate AI images
231
+ 3. Get an automated shot list with visual references
232
+ """)
233
+
234
+ with gr.Row():
235
+ with gr.Column(scale=1):
236
+ script_input = gr.Textbox(
237
+ label="Script Text",
238
+ placeholder="Paste your script here...",
239
+ lines=15,
240
+ value=SAMPLE_SCRIPT
241
+ )
242
+
243
  with gr.Row():
244
+ generate_images_checkbox = gr.Checkbox(
245
+ label="Generate AI Images",
246
+ value=True,
247
+ info="Generate visual references (requires API key)"
 
 
248
  )
249
+ process_btn = gr.Button("Generate Shot List", variant="primary")
250
+
251
+ with gr.Column(scale=1):
252
+ summary_output = gr.Markdown(label="Analysis Summary")
253
+ status_output = gr.Textbox(label="Status", interactive=False)
254
+
255
+ with gr.Row():
256
+ shot_list_output = gr.HTML(label="Generated Shot List")
257
+
258
+ # Example scripts
259
+ gr.Markdown("### 📝 Example Scripts")
260
+ with gr.Row():
261
+ example_btn1 = gr.Button("Coffee Shop Scene")
262
+ example_btn2 = gr.Button("Action Sequence")
263
+ example_btn3 = gr.Button("Clear Script")
264
+
265
+ # Event handlers
266
+ process_btn.click(
267
+ fn=process_script,
268
+ inputs=[script_input, generate_images_checkbox],
269
+ outputs=[summary_output, shot_list_output, status_output]
270
+ )
271
+
272
+ example_btn1.click(
273
+ fn=lambda: SAMPLE_SCRIPT,
274
+ outputs=script_input
275
+ )
276
+
277
+ example_btn2.click(
278
+ fn=lambda: """EXT. ROOFTOP - NIGHT
279
 
280
+ Rain pours down on the city skyline. Lightning illuminates the darkness.
 
 
 
 
 
 
281
 
282
+ ALEX crouches behind an air conditioning unit, breathing heavily.
283
+
284
+ ALEX
285
+ (into radio)
286
+ I'm in position. Do you see them?
287
+
288
+ VOICE (V.O.)
289
+ (filtered)
290
+ Two guards on the east side. Move now!
291
+
292
+ Alex sprints across the rooftop, water splashing with each step.
293
+
294
+ Suddenly, a spotlight sweeps across the roof. Alex dives behind a chimney just in time.
295
+
296
+ GUARD
297
+ (shouting)
298
+ There! On the roof!
299
+
300
+ Gunshots ring out. Alex pulls out a grappling hook and fires it toward the next building.""",
301
+ outputs=script_input
302
+ )
303
+
304
+ example_btn3.click(
305
+ fn=lambda: "",
306
+ outputs=script_input
307
  )
308
+
309
+ gr.Markdown("""
310
+ ### 🔧 Setup Instructions
311
+
312
+ To enable AI image generation, you need a Hugging Face API token:
313
+ 1. Get a free token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
314
+ 2. Set it as an environment variable: `HUGGING_FACE_API_TOKEN`
315
+ 3. Restart the application
316
+
317
+ **Note:** Without an API token, placeholder images will be shown instead.
318
+ """)
319
 
320
  if __name__ == "__main__":
321
+ demo.launch()