""" SIXR Prompt‑to‑Prototype Studio – Gradio starter app ==================================================== A lightweight, one‑file scaffold you can push to a **Hugging Face Gradio Space**. It collects a creative prompt (plus optional reference image & visual style), then returns placeholder outputs for: • Loglines (3 variants) • Mood board (1 placeholder image + 9 themed text cells) • Shot list (6 items) • Animatic description (≈4 s) • Pitch summary • JSON download bundle Swap the stub generators with your real AI calls (OpenAI, Replicate, etc.) when you're ready. Works locally via `python app.py` or `gradio app.py`. """ from __future__ import annotations import datetime as _dt import json import uuid from pathlib import Path import os import gradio as gr from PIL import Image, ImageDraw, ImageFont # ----------------------------------------------------------------------------- # Config # ----------------------------------------------------------------------------- STYLES = [ "Default", "Cyberpunk", "Film Noir", "Watercolor", "Neo‑Deco", ] PLACEHOLDER_COLOR = (24, 24, 32) # dark bluish‑gray bg TEXT_COLOR = (220, 220, 230) TMP_DIR = Path("/tmp") # It's good practice to load secrets from environment variables. # On Hugging Face Spaces, you can set these in the repository settings. ACCESS_TOKEN = os.environ.get("HF_TOKEN") # Or whatever you named your secret # ----------------------------------------------------------------------------- # Helper functions # ----------------------------------------------------------------------------- def _make_placeholder_image(text: str = "Moodboard", size: tuple[int, int] = (512, 512)) -> Image.Image: # noqa: E501 """Generate a simple placeholder PIL image with centered text.""" img = Image.new("RGB", size, color=PLACEHOLDER_COLOR) draw = ImageDraw.Draw(img) try: font = ImageFont.truetype("arial.ttf", 32) except Exception: # font might be missing in container – fall back font = ImageFont.load_default() w, h = draw.textsize(text, font=font) draw.text(((size[0] - w) / 2, (size[1] - h) / 2), text, fill=TEXT_COLOR, font=font) return img def _timestamp() -> str: """Return an ISO‑8601 UTC timestamp (second precision).""" return _dt.datetime.utcnow().isoformat(timespec="seconds") + "Z" def read_readme(): with open("README.md", "r") as f: return f.read() # ----------------------------------------------------------------------------- # Core generator (stub – replace with your own AI calls) # ----------------------------------------------------------------------------- def generate_prototype(prompt: str, reference_image: Image.Image | None, style: str): """Return all UI outputs plus a path to the JSON bundle file.""" prompt = (prompt or "").strip() if not prompt: raise gr.Error("Please enter a creative prompt first! ☝️") # --- Loglines ------------------------------------------------------------ loglines = [ f"{prompt.capitalize()} – through the lens of {style.lower()} (take {i})." for i in range(1, 4) ] # --- Moodboard ----------------------------------------------------------- moodboard_img = _make_placeholder_image(f"{style} moodboard") moodboard_words = [f"{style} motif #{i}" for i in range(1, 10)] # --- Shot list ----------------------------------------------------------- framings = [ "Close‑up", "Medium shot", "Wide shot", "Low‑angle", "Over‑the‑shoulder", "Drone reveal", ] movements = [ "push‑in", "pull‑out", "pan‑left", "tilt‑up", "handheld", "steadycam glide", ] shot_list = [f"{framings[i]} with a {movements[i]}" for i in range(len(framings))] # --- Animatic ------------------------------------------------------------ animatic = ( "4‑second sequence: stars dissolve into silhouette; camera drifts forward, " "title flares in neon before cutting to black." ) # --- Pitch summary ------------------------------------------------------- pitch = ( f"An immersive concept blending {style.lower()} aesthetics with audience agency " f"around the theme: ‘{prompt}'." ) # --- Bundle everything into a JSON dict --------------------------------- bundle = { "prompt": prompt, "style": style, "generated_at": _timestamp(), "loglines": loglines, "moodboard": { "words": moodboard_words, "image_placeholder": "(placeholder – no image bytes)", }, "shot_list": shot_list, "animatic": animatic, "pitch_summary": pitch, } json_path = TMP_DIR / f"prototype_{uuid.uuid4().hex}.json" json_path.write_text(json.dumps(bundle, indent=2), encoding="utf-8") # Return tuple aligned with UI outputs order return ( loglines, moodboard_img, "\n".join(moodboard_words), "\n".join(shot_list), animatic, pitch, str(json_path), ) # ----------------------------------------------------------------------------- # Gradio interface definition # ----------------------------------------------------------------------------- def build_ui() -> gr.Blocks: with gr.Blocks(title="SIXR Prompt‑to‑Prototype Studio") as demo: gr.Markdown(read_readme()) if ACCESS_TOKEN: gr.Markdown("Your access token is configured!") else: gr.Markdown("Access token not found. Set it in your Space secrets.") # --------------------------- Inputs ---------------------------------- with gr.Row(): prompt_in = gr.Textbox( label="Creative prompt", placeholder="Describe your immersive idea…", lines=3, ) image_in = gr.Image( label="Reference image (optional)", type="pil", image_mode="RGB", ) style_in = gr.Dropdown(STYLES, value=STYLES[0], label="Visual style") generate_btn = gr.Button("Generate prototype", variant="primary") # --------------------------- Outputs --------------------------------- with gr.Tabs(): with gr.Tab("Loglines"): loglines_out = gr.JSON(label="Three logline variants") with gr.Tab("Moodboard"): mood_img_out = gr.Image(label="Moodboard image") mood_txt_out = gr.Textbox(label="Moodboard keywords", lines=10) with gr.Tab("Shot list"): shots_out = gr.Textbox(label="Shot list", lines=8) with gr.Tab("Animatic"): anim_out = gr.Textbox(label="Animatic description") with gr.Tab("Pitch summary"): pitch_out = gr.Textbox(label="Pitch summary") json_file_out = gr.File(label="Download JSON bundle", interactive=False) # Callback wiring generate_btn.click( fn=generate_prototype, inputs=[prompt_in, image_in, style_in], outputs=[ loglines_out, mood_img_out, mood_txt_out, shots_out, anim_out, pitch_out, json_file_out, ], ) return demo # ----------------------------------------------------------------------------- # Main entrypoint # ----------------------------------------------------------------------------- if __name__ == "__main__": ui = build_ui() # In Spaces, host/port are auto‑set; defaults work fine. ui.launch()