Shalmoni commited on
Commit
0c80388
·
verified ·
1 Parent(s): 7219c51

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +232 -0
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import uuid
4
+ from datetime import datetime
5
+ import numpy as np
6
+ from PIL import Image, ImageDraw, ImageFont
7
+ import gradio as gr
8
+ from moviepy.editor import (
9
+ ImageClip,
10
+ ColorClip,
11
+ TextClip,
12
+ CompositeVideoClip,
13
+ concatenate_videoclips,
14
+ VideoFileClip,
15
+ )
16
+
17
+ # ---------- Simple "video generator" stub ----------
18
+ # Replace `generate_video_from_prompt` with your real model call.
19
+ # It creates a short MP4 with a first frame (optionally from a previous video),
20
+ # then overlays prompt text for a few seconds.
21
+
22
+ OUT_DIR = "outputs"
23
+ USED_DIR = os.path.join(OUT_DIR, "used")
24
+ TMP_DIR = "tmp"
25
+ os.makedirs(OUT_DIR, exist_ok=True)
26
+ os.makedirs(USED_DIR, exist_ok=True)
27
+ os.makedirs(TMP_DIR, exist_ok=True)
28
+
29
+ FPS = 24
30
+ W, H = 768, 432 # 16:9 HD-ish to keep files light
31
+ DURATION = 3.0 # seconds per generated clip for demo
32
+
33
+ def _solid_bg(color=(18, 18, 18)):
34
+ return ColorClip(size=(W, H), color=color, duration=DURATION)
35
+
36
+ def _text_overlay(txt: str):
37
+ # Use TextClip if ImageMagick is available; otherwise fallback to PIL.
38
+ try:
39
+ return TextClip(
40
+ txt,
41
+ fontsize=48,
42
+ color="white",
43
+ font="Arial-Bold",
44
+ size=(W - 80, None),
45
+ method="caption",
46
+ ).set_position(("center", "center")).set_duration(DURATION)
47
+ except Exception:
48
+ # PIL fallback
49
+ img = Image.new("RGBA", (W, H), (0, 0, 0, 0))
50
+ draw = ImageDraw.Draw(img)
51
+ # Try to load a font; fallback to default if not available on HF
52
+ try:
53
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", 48)
54
+ except Exception:
55
+ font = ImageFont.load_default()
56
+ # simple multiline center
57
+ lines = []
58
+ words = txt.split()
59
+ line = ""
60
+ for w in words:
61
+ test = (line + " " + w).strip()
62
+ if draw.textlength(test, font=font) > (W - 80):
63
+ lines.append(line)
64
+ line = w
65
+ else:
66
+ line = test
67
+ lines.append(line)
68
+
69
+ total_h = sum(font.getbbox(l)[3] for l in lines) + (len(lines)-1)*8
70
+ y = (H - total_h)//2
71
+ for l in lines:
72
+ w_px = draw.textlength(l, font=font)
73
+ x = (W - w_px)//2
74
+ draw.text((x, y), l, fill=(255,255,255,255), font=font)
75
+ y += font.getbbox(l)[3] + 8
76
+ pil_path = os.path.join(TMP_DIR, f"txt_{uuid.uuid4().hex}.png")
77
+ img.save(pil_path)
78
+ return ImageClip(pil_path, duration=DURATION).set_position(("center","center"))
79
+
80
+ def extract_last_frame_as_image(video_path: str) -> str:
81
+ """Save last frame of video to an image file and return its path."""
82
+ with VideoFileClip(video_path) as v:
83
+ frame = v.get_frame(v.duration - 1.0 / max(1, v.fps))
84
+ img = Image.fromarray(frame)
85
+ frame_path = os.path.join(TMP_DIR, f"seed_{uuid.uuid4().hex}.png")
86
+ img.save(frame_path)
87
+ return frame_path
88
+
89
+ def generate_video_from_prompt(prompt: str, seed_frame_path: str | None) -> str:
90
+ """
91
+ Make a short demo MP4 using:
92
+ - If seed_frame_path: start 0.5s with that still frame
93
+ - Then a solid background + prompt text
94
+ """
95
+ # Clips to concatenate
96
+ clips = []
97
+
98
+ if seed_frame_path and os.path.exists(seed_frame_path):
99
+ seed = ImageClip(seed_frame_path, duration=0.5).set_fps(FPS)
100
+ clips.append(seed)
101
+
102
+ bg = _solid_bg().set_fps(FPS)
103
+ txt = _text_overlay(prompt)
104
+ comp = CompositeVideoClip([bg, txt]).set_duration(DURATION).set_fps(FPS)
105
+ clips.append(comp)
106
+
107
+ final = concatenate_videoclips(clips, method="compose")
108
+ out_name = f"gen_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.mp4"
109
+ out_path = os.path.join(OUT_DIR, out_name)
110
+ final.write_videofile(out_path, fps=FPS, codec="libx264", audio=False, verbose=False, logger=None)
111
+ final.close()
112
+ return out_path
113
+
114
+ def concat_used_videos(video_paths: list[str]) -> str:
115
+ clips = [VideoFileClip(p) for p in video_paths]
116
+ final = concatenate_videoclips(clips, method="compose")
117
+ out_path = os.path.join(OUT_DIR, f"continuous_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.mp4")
118
+ final.write_videofile(out_path, fps=FPS, codec="libx264", audio=False, verbose=False, logger=None)
119
+ for c in clips:
120
+ c.close()
121
+ return out_path
122
+
123
+ def zip_used_videos(video_paths: list[str]) -> str:
124
+ # Copy into a temp folder to zip cleanly
125
+ stamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
126
+ pack_dir = os.path.join(TMP_DIR, f"used_{stamp}_{uuid.uuid4().hex[:6]}")
127
+ os.makedirs(pack_dir, exist_ok=True)
128
+ for p in video_paths:
129
+ shutil.copy(p, pack_dir)
130
+ zip_base = os.path.join(OUT_DIR, f"used_{stamp}")
131
+ shutil.make_archive(zip_base, "zip", pack_dir)
132
+ shutil.rmtree(pack_dir, ignore_errors=True)
133
+ return f"{zip_base}.zip"
134
+
135
+ # ---------- Gradio App ----------
136
+ with gr.Blocks(css=".grow {flex: 1}") as demo:
137
+ gr.Markdown("# Continuous Video Prompt → Use → Chain → Download")
138
+
139
+ # Session state
140
+ state_used_paths = gr.State([]) # list[str]
141
+ state_seed_frame = gr.State(None) # str | None
142
+ state_current_path = gr.State(None) # str | None
143
+
144
+ with gr.Row():
145
+ prompt = gr.Textbox(
146
+ label="Prompt",
147
+ placeholder="Describe your next shot…",
148
+ lines=2,
149
+ autofocus=True,
150
+ )
151
+ with gr.Row(equal_height=True):
152
+ video_out = gr.Video(label="Video Output", interactive=False).style(height=360)
153
+ with gr.Row():
154
+ btn_generate = gr.Button("Generate", variant="primary")
155
+ btn_use = gr.Button("Use (chain this)", variant="secondary")
156
+ btn_download = gr.Button("Download (A+B+C & ZIP)", variant="secondary")
157
+ btn_reset = gr.Button("Reset Session", variant="stop")
158
+
159
+ files_out = gr.Files(label="Downloads (concatenated MP4 + ZIP of used clips)", height=100)
160
+
161
+ # ---- Handlers ----
162
+ def do_generate(prompt_text, seed_frame_path):
163
+ if not prompt_text or not prompt_text.strip():
164
+ return None, None
165
+ out_path = generate_video_from_prompt(prompt_text.strip(), seed_frame_path)
166
+ return out_path, out_path # gr.Video path AND state_current_path
167
+
168
+ btn_generate.click(
169
+ do_generate,
170
+ inputs=[prompt, state_seed_frame],
171
+ outputs=[video_out, state_current_path],
172
+ )
173
+
174
+ def do_use(current_path, used_paths):
175
+ """
176
+ Save current_path to used list, extract its last frame as the next seed.
177
+ """
178
+ if not current_path or not os.path.exists(current_path):
179
+ # no-op if nothing to use
180
+ return used_paths, gr.update(interactive=True), None
181
+
182
+ # Append to used list
183
+ new_used = list(used_paths)
184
+ if current_path not in new_used:
185
+ new_used.append(current_path)
186
+
187
+ # Extract last frame for next generation seed
188
+ next_seed = extract_last_frame_as_image(current_path)
189
+ return new_used, gr.update(interactive=True), next_seed
190
+
191
+ btn_use.click(
192
+ do_use,
193
+ inputs=[state_current_path, state_used_paths],
194
+ outputs=[state_used_paths, prompt, state_seed_frame],
195
+ )
196
+
197
+ def do_download(used_paths):
198
+ """
199
+ Build concatenated video (A+B+C) and a ZIP of used clips.
200
+ Returns list of two files for the Files component.
201
+ """
202
+ if not used_paths:
203
+ return []
204
+ concat_path = concat_used_videos(used_paths)
205
+ zip_path = zip_used_videos(used_paths)
206
+ return [concat_path, zip_path]
207
+
208
+ btn_download.click(
209
+ do_download,
210
+ inputs=[state_used_paths],
211
+ outputs=[files_out],
212
+ )
213
+
214
+ def do_reset():
215
+ # Clear session state and temp
216
+ try:
217
+ for f in os.listdir(TMP_DIR):
218
+ fp = os.path.join(TMP_DIR, f)
219
+ if os.path.isfile(fp):
220
+ os.remove(fp)
221
+ except Exception:
222
+ pass
223
+ return None, [], None, None, gr.update(value=None), gr.update(value=[])
224
+
225
+ btn_reset.click(
226
+ do_reset,
227
+ inputs=None,
228
+ outputs=[state_seed_frame, state_used_paths, state_current_path, prompt, video_out, files_out],
229
+ )
230
+
231
+ if __name__ == "__main__":
232
+ demo.launch()