Spaces:
Running
on
Zero
Running
on
Zero
File size: 8,788 Bytes
70b0d1a 354787b bdc21c2 354787b 70b0d1a 354787b 042d9ad c44ce69 70b0d1a 354787b 70b0d1a 46c3ea1 70b0d1a 354787b 70b0d1a 042d9ad 0422489 acd33ac 0422489 acd33ac 0422489 70b0d1a 354787b 70b0d1a 354787b 70b0d1a acd33ac 70b0d1a 354787b 70b0d1a 354787b 70b0d1a 0422489 354787b 70b0d1a 0422489 354787b 70b0d1a 354787b 70b0d1a 0422489 70b0d1a 0422489 042d9ad 70b0d1a 354787b 70b0d1a 354787b 1eb64aa 70b0d1a 354787b 70b0d1a 354787b 70b0d1a 354787b 70b0d1a 354787b 70b0d1a 354787b 0c81186 70b0d1a 354787b 70b0d1a bdc21c2 70b0d1a 0422489 70b0d1a 0422489 70b0d1a 0422489 70b0d1a 0422489 354787b 70b0d1a 1eb64aa |
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
from __future__ import annotations
from typing import List, Tuple
import gradio as gr
from cinegen import CharacterDesigner, StoryGenerator, VideoDirector
from cinegen.models import Storyboard
try: # pragma: no cover - spaces is only available inside HF Spaces
import spaces # type: ignore
except Exception: # pragma: no cover - keep local dev working without spaces pkg
spaces = None # type: ignore
if spaces:
@spaces.GPU(duration=60) # short duration is enough
def __cinegen_gpu_warmup():
"""Dummy function — never called, only exists to satisfy HF Spaces GPU detection"""
pass
STYLE_CHOICES = [
"Cinematic Realism",
"Neo-Noir Animation",
"Analog Horror",
"Retro-Futuristic",
"Dreamlike Documentary",
]
VIDEO_MODEL_CHOICES = [
("Wan 2.2 TI2V (fal-ai)", "Wan-AI/Wan2.2-TI2V-5B"),
("LTX Video 0.9.7", "Lightricks/LTX-Video-0.9.7-distilled"),
("Hunyuan Video 1.5", "tencent/HunyuanVideo-1.5"),
("CogVideoX 5B", "THUDM/CogVideoX-5b"),
]
SCENE_COLUMNS = ["Scene", "Title", "Action", "Visuals", "Characters", "Duration (s)"]
CHARACTER_COLUMNS = ["ID", "Name", "Role", "Traits"]
def gpu_guard(duration: int = 120):
def decorator(fn):
if not spaces:
return fn
return spaces.GPU(duration=duration)(fn)
return decorator
def _character_dropdown_update(board: Storyboard | None):
if not board or not board.characters:
return gr.update(choices=[], value=None, interactive=False)
choices = [character.identifier for character in board.characters]
return gr.update(choices=choices, value=choices[0], interactive=True)
def _gallery_from_board(board: Storyboard) -> List[Tuple[str, str]]:
gallery: List[Tuple[str, str]] = []
for character in board.characters:
if not character.reference_image:
continue
caption = f"{character.name} — {character.role}"
gallery.append((character.reference_image, caption))
return gallery
def _ensure_storyboard(board: Storyboard | None) -> Storyboard:
if not board:
raise gr.Error("Create a storyboard first.")
return board
def _validate_inputs(idea: str | None, image_path: str | None):
if not idea and not image_path:
raise gr.Error("Provide either a story idea or upload a reference image.")
def handle_storyboard(
idea: str,
inspiration_image: str | None,
style: str,
scene_count: int,
google_api_key: str,
) -> Tuple[str, List[List[str]], List[List[str]], Storyboard, dict]:
_validate_inputs(idea, inspiration_image)
generator = StoryGenerator(api_key=google_api_key or None)
storyboard = generator.generate(
idea=idea,
style=style,
scene_count=scene_count,
inspiration_path=inspiration_image,
)
summary_md = f"### {storyboard.title}\n{storyboard.synopsis}"
scene_rows = storyboard.scenes_table()
character_rows = storyboard.characters_table()
dropdown_update = _character_dropdown_update(storyboard)
return (
summary_md,
[[row[col] for col in SCENE_COLUMNS] for row in scene_rows],
[[row[col] for col in CHARACTER_COLUMNS] for row in character_rows],
storyboard,
dropdown_update,
)
def handle_character_design(
storyboard: Storyboard | None,
google_api_key: str,
):
board = _ensure_storyboard(storyboard)
designer = CharacterDesigner(api_key=google_api_key or None)
_, updated_board = designer.design(board)
gallery = _gallery_from_board(updated_board)
if not gallery:
raise gr.Error("Failed to design characters.")
return gallery, updated_board
def handle_character_regen(
storyboard: Storyboard | None,
character_id: str | None,
google_api_key: str,
):
board = _ensure_storyboard(storyboard)
if not character_id:
raise gr.Error("Select a character ID to regenerate.")
designer = CharacterDesigner(api_key=google_api_key or None)
try:
_, updated_board = designer.redesign_character(board, character_id)
except ValueError as exc:
raise gr.Error(str(exc)) from exc
gallery = _gallery_from_board(updated_board)
if not gallery:
raise gr.Error("Failed to refresh character art.")
return gallery, updated_board
@gpu_guard(duration=300)
def handle_video_render(
storyboard: Storyboard | None,
hf_token: str,
model_choice: str,
):
board = _ensure_storyboard(storyboard)
prioritized_models = [model_choice] + [
model for _, model in VIDEO_MODEL_CHOICES if model != model_choice
]
director = VideoDirector(token=hf_token or None, models=prioritized_models)
final_cut, logs = director.render(board)
log_md = "\n".join(f"- {line}" for line in logs)
return final_cut, log_md
css = """
#cinegen-app {
max-width: 1080px;
margin: 0 auto;
}
"""
with gr.Blocks(fill_height=True, elem_id="cinegen-app") as demo:
gr.Markdown(
"## 🎬 CineGen AI Director\n"
"Drop an idea or inspiration image and let CineGen produce a storyboard, character boards, "
"and a compiled short film using Hugging Face video models."
)
story_state = gr.State()
with gr.Row():
idea_box = gr.Textbox(
label="Movie Idea",
placeholder="E.g. A time loop love story set in a neon bazaar.",
lines=3,
)
inspiration = gr.Image(label="Reference Image (optional)", type="filepath")
with gr.Row():
style_dropdown = gr.Dropdown(
label="Visual Style",
choices=STYLE_CHOICES,
value=STYLE_CHOICES[0],
)
scene_slider = gr.Slider(
label="Scene Count",
minimum=3,
maximum=8,
value=4,
step=1,
)
video_model_dropdown = gr.Dropdown(
label="Preferred Video Model",
choices=[choice for choice, _ in VIDEO_MODEL_CHOICES],
value=VIDEO_MODEL_CHOICES[0][0],
)
with gr.Accordion("API Keys", open=True):
gr.Markdown(
"Provide your own API credentials for live Gemini and Hugging Face calls. "
"Keys stay within your browser session and are not stored on the server."
)
google_key_input = gr.Textbox(
label="Google API Key (Gemini)",
type="password",
placeholder="Required for live Gemini calls. Leave blank to use offline stubs.",
)
hf_token_input = gr.Textbox(
label="Hugging Face Token",
type="password",
placeholder="Needed for Wan/LTX/Hunyuan video generation.",
)
storyboard_btn = gr.Button("Create Storyboard", variant="primary")
summary_md = gr.Markdown("Storyboard output will appear here.")
scenes_df = gr.Dataframe(headers=SCENE_COLUMNS, wrap=True)
characters_df = gr.Dataframe(headers=CHARACTER_COLUMNS, wrap=True)
with gr.Row():
design_btn = gr.Button("Design Characters", variant="secondary")
render_btn = gr.Button("Render Short Film", variant="primary")
with gr.Row():
character_select = gr.Dropdown(
label="Character Slot",
choices=[],
interactive=False,
info="Select an ID from the storyboard table to regenerate its portrait.",
)
regen_btn = gr.Button("Regenerate Selected Character", variant="secondary")
gallery = gr.Gallery(label="Character References", columns=4, height=320)
render_logs = gr.Markdown(label="Render Log")
final_video = gr.Video(label="CineGen Short Film", interactive=False)
storyboard_btn.click(
fn=handle_storyboard,
inputs=[idea_box, inspiration, style_dropdown, scene_slider, google_key_input],
outputs=[summary_md, scenes_df, characters_df, story_state, character_select],
)
design_btn.click(
fn=handle_character_design,
inputs=[story_state, google_key_input],
outputs=[gallery, story_state],
)
regen_btn.click(
fn=handle_character_regen,
inputs=[story_state, character_select, google_key_input],
outputs=[gallery, story_state],
)
def _model_value(label: str) -> str:
lookup = dict(VIDEO_MODEL_CHOICES)
return lookup.get(label, VIDEO_MODEL_CHOICES[0][1])
def render_wrapper(board, token, label):
return handle_video_render(board, token, _model_value(label))
render_btn.click(
fn=render_wrapper,
inputs=[story_state, hf_token_input, video_model_dropdown],
outputs=[final_video, render_logs],
queue=True,
concurrency_limit=1,
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(), css=css)
|