VirtualOasis commited on
Commit
056b10f
·
verified ·
1 Parent(s): 314f511

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -33
app.py CHANGED
@@ -1,50 +1,191 @@
1
- import importlib.util
 
2
  import os
3
- import sys
4
- from pathlib import Path
5
 
6
  import gradio as gr
7
 
8
- WAN_APP_PATH = Path(__file__).parent / "Wan-2.2-5B" / "app.py"
 
9
 
 
 
 
 
 
 
 
10
 
11
- def _resolve_server_host() -> str:
12
- """Mirror official Spaces template behavior for host selection."""
13
- return os.getenv("GRADIO_SERVER_HOST") or os.getenv("SERVER_HOST") or "0.0.0.0"
 
 
 
14
 
 
 
15
 
16
- def _resolve_server_port() -> int:
17
- """Use Spaces-provided port variables or default to 7860."""
18
- return int(os.getenv("GRADIO_SERVER_PORT") or os.getenv("SERVER_PORT") or "7860")
19
 
 
 
 
 
20
 
21
- def _load_wan_demo():
22
- """Load the Wan Blocks demo from Wan-2.2-5B/app.py via importlib."""
23
- if not WAN_APP_PATH.exists():
24
- raise FileNotFoundError(f"Wan app not found at {WAN_APP_PATH}")
25
 
26
- spec = importlib.util.spec_from_file_location("wan_space_app", WAN_APP_PATH)
27
- module = importlib.util.module_from_spec(spec)
28
- sys.modules[spec.name] = module
29
- assert spec.loader is not None
30
- spec.loader.exec_module(module)
31
 
32
- if not hasattr(module, "demo"):
33
- raise AttributeError("Wan app module does not expose a `demo` Blocks object.")
34
- return module.demo
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- # Hugging Face looks for a top-level `demo` Blocks object.
38
- demo = _load_wan_demo()
39
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- if __name__ == "__main__":
42
- demo.launch(
43
- server_name=_resolve_server_host(),
44
- server_port=_resolve_server_port(),
45
- theme=gr.themes.Soft(),
46
- css=".gradio-container {max-width: 1200px; margin: auto;}",
47
- footer_links=["gradio", "settings"],
48
- allowed_paths=[str(Path.cwd())],
49
- ssr_mode=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
  import os
4
+ from typing import List, Tuple
 
5
 
6
  import gradio as gr
7
 
8
+ from cinegen import CharacterDesigner, StoryGenerator, VideoDirector
9
+ from cinegen.models import Storyboard
10
 
11
+ STYLE_CHOICES = [
12
+ "Cinematic Realism",
13
+ "Neo-Noir Animation",
14
+ "Analog Horror",
15
+ "Retro-Futuristic",
16
+ "Dreamlike Documentary",
17
+ ]
18
 
19
+ VIDEO_MODEL_CHOICES = [
20
+ ("Wan 2.1 (fal-ai)", "Wan-AI/Wan2.1-T2V-14B"),
21
+ ("LTX Video 0.9.7", "Lightricks/LTX-Video-0.9.7-distilled"),
22
+ ("Hunyuan Video 1.5", "tencent/HunyuanVideo-1.5"),
23
+ ("CogVideoX 5B", "THUDM/CogVideoX-5b"),
24
+ ]
25
 
26
+ SCENE_COLUMNS = ["Scene", "Title", "Action", "Visuals", "Characters", "Duration (s)"]
27
+ CHARACTER_COLUMNS = ["ID", "Name", "Role", "Traits"]
28
 
 
 
 
29
 
30
+ def _ensure_storyboard(board: Storyboard | None) -> Storyboard:
31
+ if not board:
32
+ raise gr.Error("Create a storyboard first.")
33
+ return board
34
 
 
 
 
 
35
 
36
+ def _validate_inputs(idea: str | None, image_path: str | None):
37
+ if not idea and not image_path:
38
+ raise gr.Error("Provide either a story idea or upload a reference image.")
 
 
39
 
 
 
 
40
 
41
+ def handle_storyboard(
42
+ idea: str,
43
+ inspiration_image: str | None,
44
+ style: str,
45
+ scene_count: int,
46
+ google_api_key: str,
47
+ ) -> Tuple[str, List[List[str]], List[List[str]], Storyboard]:
48
+ _validate_inputs(idea, inspiration_image)
49
+ generator = StoryGenerator(api_key=google_api_key or None)
50
+ storyboard = generator.generate(
51
+ idea=idea,
52
+ style=style,
53
+ scene_count=scene_count,
54
+ inspiration_path=inspiration_image,
55
+ )
56
+ summary_md = f"### {storyboard.title}\n{storyboard.synopsis}"
57
+ scene_rows = storyboard.scenes_table()
58
+ character_rows = storyboard.characters_table()
59
+ return (
60
+ summary_md,
61
+ [[row[col] for col in SCENE_COLUMNS] for row in scene_rows],
62
+ [[row[col] for col in CHARACTER_COLUMNS] for row in character_rows],
63
+ storyboard,
64
+ )
65
 
 
 
66
 
67
+ def handle_character_design(
68
+ storyboard: Storyboard | None,
69
+ google_api_key: str,
70
+ ):
71
+ board = _ensure_storyboard(storyboard)
72
+ designer = CharacterDesigner(api_key=google_api_key or None)
73
+ gallery, updated_board = designer.design(board)
74
+ if not gallery:
75
+ raise gr.Error("Failed to design characters.")
76
+ return gallery, updated_board
77
 
78
+
79
+ def handle_video_render(
80
+ storyboard: Storyboard | None,
81
+ hf_token: str,
82
+ model_choice: str,
83
+ ):
84
+ board = _ensure_storyboard(storyboard)
85
+ prioritized_models = [model_choice] + [
86
+ model for _, model in VIDEO_MODEL_CHOICES if model != model_choice
87
+ ]
88
+ director = VideoDirector(token=hf_token or None, models=prioritized_models)
89
+ final_cut, logs = director.render(board)
90
+ log_md = "\n".join(f"- {line}" for line in logs)
91
+ return final_cut, log_md
92
+
93
+
94
+ css = """
95
+ #cinegen-app {
96
+ max-width: 1080px;
97
+ margin: 0 auto;
98
+ }
99
+ """
100
+
101
+
102
+ with gr.Blocks(css=css, fill_height=True, theme=gr.themes.Soft(), elem_id="cinegen-app") as demo:
103
+ gr.Markdown(
104
+ "## 🎬 CineGen AI Director\n"
105
+ "Drop an idea or inspiration image and let CineGen produce a storyboard, character boards, "
106
+ "and a compiled short film using Hugging Face video models."
107
+ )
108
+
109
+ story_state = gr.State()
110
+
111
+ with gr.Row():
112
+ idea_box = gr.Textbox(
113
+ label="Movie Idea",
114
+ placeholder="E.g. A time loop love story set in a neon bazaar.",
115
+ lines=3,
116
+ )
117
+ inspiration = gr.Image(label="Reference Image (optional)", type="filepath")
118
+
119
+ with gr.Row():
120
+ style_dropdown = gr.Dropdown(
121
+ label="Visual Style",
122
+ choices=STYLE_CHOICES,
123
+ value=STYLE_CHOICES[0],
124
+ )
125
+ scene_slider = gr.Slider(
126
+ label="Scene Count",
127
+ minimum=3,
128
+ maximum=8,
129
+ value=4,
130
+ step=1,
131
+ )
132
+ video_model_dropdown = gr.Dropdown(
133
+ label="Preferred Video Model",
134
+ choices=[choice for choice, _ in VIDEO_MODEL_CHOICES],
135
+ value=VIDEO_MODEL_CHOICES[0][0],
136
+ )
137
+
138
+ with gr.Accordion("API Keys", open=False):
139
+ google_key_input = gr.Textbox(
140
+ label="Google API Key (Gemini)",
141
+ type="password",
142
+ placeholder="Required for live Gemini calls. Leave blank to use offline stubs.",
143
+ value=os.environ.get("GOOGLE_API_KEY", ""),
144
+ )
145
+ hf_token_input = gr.Textbox(
146
+ label="Hugging Face Token",
147
+ type="password",
148
+ placeholder="Needed for Wan/LTX/Hunyuan video generation.",
149
+ value=os.environ.get("HF_TOKEN", ""),
150
+ )
151
+
152
+ storyboard_btn = gr.Button("Create Storyboard", variant="primary")
153
+ summary_md = gr.Markdown("Storyboard output will appear here.")
154
+ scenes_df = gr.Dataframe(headers=SCENE_COLUMNS, wrap=True)
155
+ characters_df = gr.Dataframe(headers=CHARACTER_COLUMNS, wrap=True)
156
+
157
+ storyboard_btn.click(
158
+ fn=handle_storyboard,
159
+ inputs=[idea_box, inspiration, style_dropdown, scene_slider, google_key_input],
160
+ outputs=[summary_md, scenes_df, characters_df, story_state],
161
+ )
162
+
163
+ with gr.Row():
164
+ design_btn = gr.Button("Design Characters", variant="secondary")
165
+ render_btn = gr.Button("Render Short Film", variant="primary")
166
+
167
+ gallery = gr.Gallery(label="Character References", columns=4, height=320)
168
+ render_logs = gr.Markdown(label="Render Log")
169
+ final_video = gr.Video(label="CineGen Short Film", interactive=False)
170
+
171
+ design_btn.click(
172
+ fn=handle_character_design,
173
+ inputs=[story_state, google_key_input],
174
+ outputs=[gallery, story_state],
175
  )
176
+
177
+ def _model_value(label: str) -> str:
178
+ lookup = dict(VIDEO_MODEL_CHOICES)
179
+ return lookup.get(label, VIDEO_MODEL_CHOICES[0][1])
180
+
181
+ def render_wrapper(board, token, label):
182
+ return handle_video_render(board, token, _model_value(label))
183
+
184
+ render_btn.click(
185
+ fn=render_wrapper,
186
+ inputs=[story_state, hf_token_input, video_model_dropdown],
187
+ outputs=[final_video, render_logs],
188
+ )
189
+
190
+ if __name__ == "__main__":
191
+ demo.launch()