#!/usr/bin/env python3 import gradio as gr import asyncio from director_bake_off import run_bake_off import traceback # --- Start of Updated CSS --- # Professional, theme-adaptive CSS for Gradio on Hugging Face Spaces SWEDISH_CSS = """ /* Professional & Theme-Adaptive Design */ :root { /* Define a consistent font stack and custom accent colors */ --font-family-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; --accent-blue: #4A90A4; --accent-blue-soft: #E8F4F8; --accent-gold: #D4AF37; --accent-green: #7FB069; --warm-beige: #E8DCC6; --charcoal: #2C2C2C; /* Light Mode Custom Colors */ --card-bg-color: var(--accent-blue-soft); --card-border-color: var(--accent-blue); --explanation-bg: var(--background-fill-secondary); --details-bg-color: #f9fafb; /* A slightly off-white for light mode */ } /* Define Dark Mode specific overrides */ html[data-theme="dark"] { --card-bg-color: #1e293b; /* A deep, cool blue for dark mode cards */ --card-border-color: var(--accent-blue); --explanation-bg: #283647; /* Darker blue for explanation box */ --details-bg-color: #111827; /* Very dark gray for details dropdown */ } /* Global styling */ .gradio-container { font-family: var(--font-family-sans) !important; background-color: var(--body-background-fill) !important; } /* Header styling */ .main-header { text-align: center; padding: 2rem 1rem; border-bottom: 1px solid var(--border-color-primary); margin-bottom: 2rem; } .main-title { font-size: 2.5rem; font-weight: 600; color: var(--text-color-primary); margin-bottom: 0.5rem; letter-spacing: -0.02em; } .main-subtitle { font-size: 1.1rem; color: var(--accent-blue); font-weight: 400; } /* Input/Results container shared style */ .input-section, .results-container { background: var(--background-fill-primary); border-radius: 12px; padding: 2rem; border: 1px solid var(--border-color-primary); margin-bottom: 2rem; } /* Button styling */ .submit-btn { background: var(--accent-blue) !important; border: none !important; border-radius: 8px !important; padding: 12px 32px !important; font-weight: 600 !important; font-size: 1rem !important; color: white !important; transition: background 0.2s ease, transform 0.2s ease !important; } .submit-btn:hover { background: #5BA0B5 !important; /* Slightly lighter blue on hover */ transform: translateY(-2px) !important; } /* Director card styling */ .director-card { background: var(--card-bg-color); border-radius: 10px; padding: 1.5rem; margin-bottom: 1.5rem; border-left: 4px solid var(--card-border-color); transition: box-shadow 0.3s ease; } .director-card:hover { box-shadow: 0 8px 25px rgba(0,0,0,0.1); } .rank-badge { display: inline-block; color: white; padding: 4px 12px; border-radius: 20px; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.5rem; } /* Ranking badge colors */ .rank-1 { background: var(--accent-gold); } .rank-2 { background: #A0A0A0; } .rank-3 { background: #CD7F32; } .director-name { font-size: 1.3rem; font-weight: 600; color: var(--text-color-primary); margin-bottom: 1rem; } .prompt-text { font-size: 1rem; line-height: 1.6; color: var(--text-color-secondary); background: var(--background-fill-secondary); padding: 1rem; border-radius: 8px; border: 1px solid var(--border-color-primary); margin-bottom: 1rem; } /* Custom card for the additional director */ .additional-director { background: var(--warm-beige); border-radius: 10px; padding: 1.5rem; margin-bottom: 2rem; border-left: 4px solid var(--accent-green); } .additional-director .section-title, .additional-director div { color: var(--charcoal) !important; /* Force dark text on light background */ } /* Explanation section */ .explanation-section { background: var(--explanation-bg); border-radius: 10px; padding: 1.5rem; margin-top: 2rem; border-left: 4px solid var(--accent-blue); } .explanation-section .section-title, .explanation-section div { color: var(--text-color-primary); } .section-title { font-size: 1.2rem; font-weight: 600; color: var(--text-color-primary); margin-bottom: 1rem; } /* Details dropdown styling for director cards */ details > summary { cursor: pointer; font-weight: 500; color: var(--accent-blue); } details > div { margin-top: 1rem; padding: 1rem; background: var(--details-bg-color); border-radius: 6px; border: 1px solid var(--border-color-primary); } """ # --- End of Updated CSS --- def format_results_html(result): """Format the results into beautiful HTML with Swedish design.""" if not result: return "
No results to display.
" try: # Header with additional director suggestion html = f"""
🎬 AI Suggested Director
{result.additional_director} - A perfect match for your vision!
""" # Director ideas with rankings html += '
🏆 Director Interpretations (Ranked)
' # Sort director ideas by ranking ranked_ideas = [] for i, idea in enumerate(result.director_ideas): rank = result.director_ranks.director_rankings[i] ranked_ideas.append((rank, idea.director_cut)) ranked_ideas.sort(key=lambda x: x[0]) # Sort by rank (1 is best) for rank, director_cut in ranked_ideas: rank_class = f"rank-{min(rank, 3)}" # Use rank-1, rank-2, rank-3 classes html += f"""
#{rank}
{director_cut.director}
{director_cut.assemble_prompt()}
View Detailed Breakdown
Subject: {director_cut.subject_description}
Action: {director_cut.action_description}
Setting: {director_cut.setting_description}
Style: {director_cut.cinematic_style}
Shot & Framing: {director_cut.shot_and_framing}
Camera Movement: {director_cut.camera_movement}
Lighting & Color: {director_cut.lighting_and_color}
""" # Explanation section with HTML formatting from DSPy html += f"""
🤔 Judge's Reasoning
{result.director_ranks.explanation}
""" return html except Exception as e: return f"""
Error formatting results: {str(e)}
""" def run_director_bakeoff(video_idea, directors): """Run the director bake-off and return formatted results.""" if not video_idea or not video_idea.strip(): return "
Please enter a video idea to get started!
" try: # Initial loading message with expectations yield """
🎬 Director Bake-Off in Progress...
Please be patient, this may take some minutes...
We're consulting with legendary directors to bring your vision to life!
This process typically takes 30-60 seconds.
What's happening:
• Finding the perfect additional director for your concept
• Generating unique interpretations from each director
• Ranking all concepts to find the best match
""" # Run the bake-off result = run_bake_off(video_idea, directors) # Format and return results formatted_html = format_results_html(result) yield formatted_html except Exception as e: error_html = f"""
Something went wrong!
{str(e)}
View technical details
{traceback.format_exc()}
                    
""" yield error_html # Create the Gradio interface def create_interface(): with gr.Blocks(css=SWEDISH_CSS, title="Director Bake-Off Studio") as interface: # Header gr.HTML("""

Director Bake-Off Studio

Let legendary directors compete to bring your vision to life

""") # How it Works section - moved to top gr.HTML("""

How it works:

  1. Enter your video idea in the text area below
  2. List your favorite directors (or use our defaults)
  3. Our AI will suggest an additional director perfect for your vision
  4. Watch as each director creates their unique interpretation
  5. See them ranked by how well they match your concept!
""") # Results section - will only show content when there are actual results results_html = gr.HTML( value="", elem_classes=["results-display"], visible=False ) with gr.Row(): with gr.Column(scale=1): # Input section video_idea = gr.Textbox( label="🎥 Your Video Idea", placeholder="Describe your video concept... (e.g., 'A futuristic cityscape with flying cars and neon lights')", lines=4, elem_classes=["input-field"] ) directors = gr.Textbox( label="🎬 Directors (comma-separated)", placeholder="e.g., Quentin Tarantino, Alfred Hitchcock", value="Quentin Tarantino, Alfred Hitchcock", lines=2, elem_classes=["input-field"] ) submit_btn = gr.Button( "🚀 Start the Bake-Off!", elem_classes=["submit-btn"], variant="primary" ) # Set up the interaction - removed show_progress=True to eliminate progress bar def handle_submit(video_idea, directors): # Make results visible and update content for result in run_director_bakeoff(video_idea, directors): yield gr.update(value=result, visible=True) submit_btn.click( fn=handle_submit, inputs=[video_idea, directors], outputs=[results_html] ) # Footer gr.HTML("""

Powered by DSPy and the creative genius of legendary directors 🎬

""") return interface if __name__ == "__main__": # Create and launch the interface interface = create_interface() interface.launch( server_name="0.0.0.0", server_port=7860, share=False, show_error=True, debug=True )