Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import json | |
| import re | |
| from typing import List, Dict, Any | |
| import os | |
| # Hugging Face configuration | |
| HF_TOKEN = os.getenv("HUGGING_FACE_API_TOKEN", "") | |
| HF_API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev" | |
| def parse_script(script_text: str) -> Dict[str, Any]: | |
| """Parse script text and extract scenes and characters""" | |
| lines = script_text.strip().split('\n') | |
| scenes = [] | |
| characters = set() | |
| current_scene = None | |
| for line in lines: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| # Scene headers (INT./EXT.) | |
| if line.upper().startswith(('INT.', 'EXT.', 'SCENE')): | |
| if current_scene: | |
| scenes.append(current_scene) | |
| current_scene = { | |
| 'location': line, | |
| 'dialogue': [], | |
| 'action': [] | |
| } | |
| # Character dialogue (ALL CAPS followed by dialogue) | |
| elif line.isupper() and len(line.split()) <= 3 and current_scene: | |
| characters.add(line) | |
| current_scene['dialogue'].append({'character': line, 'lines': []}) | |
| # Dialogue lines | |
| elif current_scene and current_scene['dialogue'] and not line.isupper(): | |
| current_scene['dialogue'][-1]['lines'].append(line) | |
| # Action lines | |
| elif current_scene and not line.isupper(): | |
| current_scene['action'].append(line) | |
| if current_scene: | |
| scenes.append(current_scene) | |
| return { | |
| 'scenes': scenes, | |
| 'characters': list(characters), | |
| 'total_scenes': len(scenes) | |
| } | |
| def generate_shot_list(script_data: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """Generate shot list from parsed script""" | |
| shots = [] | |
| shot_id = 1 | |
| for scene_idx, scene in enumerate(script_data['scenes']): | |
| # Establishing shot | |
| shots.append({ | |
| 'id': shot_id, | |
| 'type': 'Establishing Shot', | |
| 'description': f"Wide shot of {scene['location']}", | |
| 'scene': scene_idx + 1, | |
| 'location': scene['location'] | |
| }) | |
| shot_id += 1 | |
| # Character shots | |
| dialogue_chars = set() | |
| for dialogue in scene['dialogue']: | |
| char = dialogue['character'] | |
| if char not in dialogue_chars: | |
| shots.append({ | |
| 'id': shot_id, | |
| 'type': 'Medium Shot', | |
| 'description': f"Medium shot of {char}", | |
| 'scene': scene_idx + 1, | |
| 'character': char, | |
| 'location': scene['location'] | |
| }) | |
| dialogue_chars.add(char) | |
| shot_id += 1 | |
| # Action shots | |
| for action in scene['action']: | |
| if len(action) > 20: # Only significant action lines | |
| shots.append({ | |
| 'id': shot_id, | |
| 'type': 'Action Shot', | |
| 'description': action[:100] + "..." if len(action) > 100 else action, | |
| 'scene': scene_idx + 1, | |
| 'location': scene['location'] | |
| }) | |
| shot_id += 1 | |
| return shots | |
| def generate_image(prompt: str) -> str: | |
| """Generate image using Hugging Face API""" | |
| if not HF_TOKEN: | |
| return "https://via.placeholder.com/512x512?text=No+API+Key" | |
| headers = { | |
| "Authorization": f"Bearer {HF_TOKEN}", | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "inputs": f"{prompt}, cinematic, professional, high quality" | |
| } | |
| try: | |
| response = requests.post(HF_API_URL, headers=headers, json=payload, timeout=30) | |
| if response.status_code == 200: | |
| # Save image and return path | |
| import base64 | |
| image_data = response.content | |
| image_b64 = base64.b64encode(image_data).decode() | |
| return f"data:image/png;base64,{image_b64}" | |
| else: | |
| return f"https://via.placeholder.com/512x512?text=API+Error+{response.status_code}" | |
| except Exception as e: | |
| return f"https://via.placeholder.com/512x512?text=Error" | |
| def process_script(script_text: str, generate_images: bool = True): | |
| """Main function to process script and generate shot list""" | |
| if not script_text.strip(): | |
| return "Please enter a script.", "", "" | |
| # Parse script | |
| script_data = parse_script(script_text) | |
| # Generate shot list | |
| shots = generate_shot_list(script_data) | |
| # Create summary | |
| summary = f""" | |
| ## Script Analysis Summary | |
| - **Total Scenes:** {script_data['total_scenes']} | |
| - **Characters:** {', '.join(script_data['characters'])} | |
| - **Generated Shots:** {len(shots)} | |
| """ | |
| # Create shot list display | |
| shot_list_html = "<div style='max-height: 400px; overflow-y: auto;'>" | |
| for shot in shots: | |
| # Generate image if requested | |
| image_html = "" | |
| if generate_images: | |
| image_url = generate_image(shot['description']) | |
| image_html = f'<img src="{image_url}" style="width: 200px; height: 150px; object-fit: cover; border-radius: 8px;" />' | |
| shot_list_html += f""" | |
| <div style='border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 8px; background: #f9f9f9;'> | |
| <div style='display: flex; gap: 15px; align-items: flex-start;'> | |
| <div style='flex: 1;'> | |
| <h3 style='margin: 0 0 10px 0; color: #333;'>Shot {shot['id']}: {shot['type']}</h3> | |
| <p style='margin: 5px 0;'><strong>Scene:</strong> {shot['scene']}</p> | |
| <p style='margin: 5px 0;'><strong>Description:</strong> {shot['description']}</p> | |
| {f"<p style='margin: 5px 0;'><strong>Character:</strong> {shot.get('character', 'N/A')}</p>" if shot.get('character') else ""} | |
| <p style='margin: 5px 0;'><strong>Location:</strong> {shot.get('location', 'N/A')}</p> | |
| </div> | |
| <div style='flex-shrink: 0;'> | |
| {image_html} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| shot_list_html += "</div>" | |
| return summary, shot_list_html, f"Generated {len(shots)} shots successfully!" | |
| # Sample script for demo | |
| SAMPLE_SCRIPT = """INT. COFFEE SHOP - DAY | |
| A bustling coffee shop filled with the morning crowd. Steam rises from espresso machines. | |
| SARAH sits at a corner table, typing furiously on her laptop. She glances at her watch nervously. | |
| SARAH | |
| (muttering to herself) | |
| Come on, come on... where is he? | |
| The door chimes as MIKE enters, scanning the room. He spots Sarah and approaches. | |
| MIKE | |
| Sorry I'm late! Traffic was insane. | |
| SARAH | |
| (relieved) | |
| Thank god you're here. I've been going crazy waiting. | |
| Mike sits down across from her. | |
| MIKE | |
| So, what's this big emergency about? | |
| Sarah closes her laptop and leans in conspiratorially. | |
| SARAH | |
| I found something. Something that could change everything. | |
| EXT. CITY STREET - DAY | |
| Sarah and Mike walk quickly down a busy sidewalk, weaving through pedestrians. | |
| MIKE | |
| Are you sure about this? | |
| SARAH | |
| I've never been more sure of anything in my life. | |
| They stop at a red light, looking around nervously.""" | |
| # Create Gradio interface | |
| with gr.Blocks(title="Script to Shots - AI Storyboard Generator") as demo: | |
| gr.Markdown(""" | |
| # 🎬 Script to Shots - AI Storyboard Generator | |
| Transform your scripts into visual shot lists with AI-generated reference images! | |
| **How it works:** | |
| 1. Paste your script in the text area below | |
| 2. Choose whether to generate AI images | |
| 3. Get an automated shot list with visual references | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| script_input = gr.Textbox( | |
| label="Script Text", | |
| placeholder="Paste your script here...", | |
| lines=15, | |
| value=SAMPLE_SCRIPT | |
| ) | |
| with gr.Row(): | |
| generate_images_checkbox = gr.Checkbox( | |
| label="Generate AI Images", | |
| value=True, | |
| info="Generate visual references (requires API key)" | |
| ) | |
| process_btn = gr.Button("Generate Shot List", variant="primary") | |
| with gr.Column(scale=1): | |
| summary_output = gr.Markdown(label="Analysis Summary") | |
| status_output = gr.Textbox(label="Status", interactive=False) | |
| with gr.Row(): | |
| shot_list_output = gr.HTML(label="Generated Shot List") | |
| # Example scripts | |
| gr.Markdown("### 📝 Example Scripts") | |
| with gr.Row(): | |
| example_btn1 = gr.Button("Coffee Shop Scene") | |
| example_btn2 = gr.Button("Action Sequence") | |
| example_btn3 = gr.Button("Clear Script") | |
| # Event handlers | |
| process_btn.click( | |
| fn=process_script, | |
| inputs=[script_input, generate_images_checkbox], | |
| outputs=[summary_output, shot_list_output, status_output] | |
| ) | |
| example_btn1.click( | |
| fn=lambda: SAMPLE_SCRIPT, | |
| outputs=script_input | |
| ) | |
| example_btn2.click( | |
| fn=lambda: """EXT. ROOFTOP - NIGHT | |
| Rain pours down on the city skyline. Lightning illuminates the darkness. | |
| ALEX crouches behind an air conditioning unit, breathing heavily. | |
| ALEX | |
| (into radio) | |
| I'm in position. Do you see them? | |
| VOICE (V.O.) | |
| (filtered) | |
| Two guards on the east side. Move now! | |
| Alex sprints across the rooftop, water splashing with each step. | |
| Suddenly, a spotlight sweeps across the roof. Alex dives behind a chimney just in time. | |
| GUARD | |
| (shouting) | |
| There! On the roof! | |
| Gunshots ring out. Alex pulls out a grappling hook and fires it toward the next building.""", | |
| outputs=script_input | |
| ) | |
| example_btn3.click( | |
| fn=lambda: "", | |
| outputs=script_input | |
| ) | |
| gr.Markdown(""" | |
| ### 🔧 Setup Instructions | |
| To enable AI image generation, you need a Hugging Face API token: | |
| 1. Get a free token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) | |
| 2. Set it as an environment variable: `HUGGING_FACE_API_TOKEN` | |
| 3. Restart the application | |
| **Note:** Without an API token, placeholder images will be shown instead. | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() |