Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
992009a
1
Parent(s):
b407bb1
init
Browse files
app.py
CHANGED
@@ -1,390 +1,211 @@
|
|
1 |
-
# app.py
|
2 |
-
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
import os
|
6 |
import sys
|
7 |
-
import
|
8 |
-
import shutil
|
9 |
-
import time # Moved import time to the top for global access
|
10 |
import argparse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
#
|
13 |
-
|
14 |
-
CKPT_DIR = "./checkpoints/Wan2.1-T2V-1.3B"
|
15 |
-
EDIT_SCRIPT_PATH = "edit.py" # Assumes edit.py is in the same directory
|
16 |
OUTPUT_DIR = "gradio_outputs"
|
17 |
-
|
18 |
-
|
19 |
|
20 |
-
# Create output directory if it doesn't exist
|
21 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
22 |
-
os.makedirs(VIDEO_EXAMPLES_DIR, exist_ok=True)
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
)
|
28 |
-
parser.add_argument(
|
29 |
-
"--ckpt",
|
30 |
-
type=str,
|
31 |
-
default="./checkpoints/Wan2.1-T2V-1.3B",
|
32 |
-
help="The path to the checkpoint directory.")
|
33 |
|
|
|
|
|
|
|
34 |
return parser.parse_args()
|
35 |
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
38 |
if not text:
|
39 |
return "untitled"
|
40 |
-
safe_text = "".join(c if c.isalnum() or c in [
|
41 |
-
safe_text = "_".join(safe_text.split())
|
42 |
return safe_text[:max_len]
|
43 |
|
|
|
|
|
|
|
|
|
44 |
@spaces.GPU
|
45 |
-
def run_video_edit(
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
if not source_video_path:
|
48 |
raise gr.Error("Please upload a source video.")
|
49 |
if not source_prompt:
|
50 |
raise gr.Error("Please provide a source prompt.")
|
51 |
if not target_prompt:
|
52 |
-
raise gr.Error("Please provide a target prompt
|
53 |
-
|
54 |
-
|
55 |
-
raise gr.Error("Please provide source words (can be empty string for additions).")
|
56 |
if not target_words:
|
57 |
raise gr.Error("Please provide target words.")
|
58 |
|
59 |
-
progress(0, desc="Preparing for video editing
|
60 |
-
print(f"Source video received at: {source_video_path}")
|
61 |
-
print(f"Omega value: {omega_value}")
|
62 |
-
print(f"N_max value: {n_max_value}")
|
63 |
-
print(f"N_avg value: {n_avg_value}")
|
64 |
|
65 |
worse_avg_value = n_avg_value // 2
|
66 |
-
print(f"Calculated Worse_avg value: {worse_avg_value}")
|
67 |
-
|
68 |
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
69 |
src_words_fn = generate_safe_filename_part(source_words)
|
70 |
tar_words_fn = generate_safe_filename_part(target_words)
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
74 |
|
|
|
75 |
cmd = [
|
76 |
-
PYTHON_EXECUTABLE,
|
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 |
-
progress(0.
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
if process.poll() is not None:
|
111 |
-
break
|
112 |
-
progress(0.1 + i * 0.08, desc=f"Editing in progress... (simulated step {i+1}/10)")
|
113 |
-
time.sleep(1)
|
114 |
-
|
115 |
-
stdout, stderr = process.communicate()
|
116 |
-
|
117 |
-
progress(0.9, desc="Finalizing video...")
|
118 |
-
|
119 |
-
if process.returncode != 0:
|
120 |
-
print(f"Error during video editing:\nStdout:\n{stdout}\nStderr:\n{stderr}")
|
121 |
-
raise gr.Error(f"Video editing failed. Stderr: {stderr[:500]}")
|
122 |
-
|
123 |
-
print(f"Video editing successful. Output at: {output_video_path}")
|
124 |
-
if not os.path.exists(output_video_path):
|
125 |
-
print(f"Error: Output file {output_video_path} was not created.")
|
126 |
-
raise gr.Error(f"Output file not found, though script reported success. Stdout: {stdout}")
|
127 |
-
|
128 |
-
progress(1, desc="Video ready!")
|
129 |
-
return output_video_path
|
130 |
-
|
131 |
-
except FileNotFoundError:
|
132 |
-
progress(1, desc="Error")
|
133 |
-
print(f"Error: The script '{EDIT_SCRIPT_PATH}' or python executable '{PYTHON_EXECUTABLE}' was not found.")
|
134 |
-
raise gr.Error(f"Execution error: Ensure '{EDIT_SCRIPT_PATH}' and Python are correctly pathed.")
|
135 |
-
except Exception as e:
|
136 |
-
progress(1, desc="Error")
|
137 |
-
print(f"An unexpected error occurred: {e}")
|
138 |
-
raise gr.Error(f"An unexpected error: {str(e)}")
|
139 |
-
|
140 |
-
# --- Gradio UI Definition ---
|
141 |
-
|
142 |
-
# Define all examples to be loaded
|
143 |
-
examples_to_load_definitions = [
|
144 |
-
{ # Original bear_g example (corresponds to bear_g_03 in YAML)
|
145 |
-
"video_base_name": "bear_g",
|
146 |
-
"src_prompt": "A large brown bear is walking slowly across a rocky terrain in a zoo enclosure, surrounded by stone walls and scattered greenery. The camera remains fixed, capturing the bear's deliberate movements.",
|
147 |
-
"tar_prompt": "A large dinosaur is walking slowly across a rocky terrain in a zoo enclosure, surrounded by stone walls and scattered greenery. The camera remains fixed, capturing the dinosaur's deliberate movements.",
|
148 |
-
"src_words": "large brown bear",
|
149 |
-
"tar_words": "large dinosaur",
|
150 |
-
},
|
151 |
-
{ # blackswan_02
|
152 |
-
"video_base_name": "blackswan",
|
153 |
-
"src_prompt": "A black swan with a red beak swimming in a river near a wall and bushes.",
|
154 |
-
"tar_prompt": "A white duck with a red beak swimming in a river near a wall and bushes.",
|
155 |
-
"src_words": "black swan",
|
156 |
-
"tar_words": "white duck",
|
157 |
-
},
|
158 |
-
{ # jeep_01
|
159 |
-
"video_base_name": "jeep",
|
160 |
-
"src_prompt": "A silver jeep driving down a curvy road in the countryside.",
|
161 |
-
"tar_prompt": "A Porsche car driving down a curvy road in the countryside.",
|
162 |
-
"src_words": "silver jeep",
|
163 |
-
"tar_words": "Porsche car",
|
164 |
-
},
|
165 |
-
{ # woman_02 (additive edit)
|
166 |
-
"video_base_name": "woman",
|
167 |
-
"src_prompt": "A woman in a black dress is walking along a paved path in a lush green park, with trees and a wooden bench in the background. The camera remains fixed, capturing her steady movement.",
|
168 |
-
"tar_prompt": "A woman in a black dress and a red baseball cap is walking along a paved path in a lush green park, with trees and a wooden bench in the background. The camera remains fixed, capturing her steady movement.",
|
169 |
-
"src_words": "", # Empty source words for addition
|
170 |
-
"tar_words": "a red baseball cap",
|
171 |
-
}
|
172 |
-
]
|
173 |
-
|
174 |
-
examples_data = []
|
175 |
-
# Default advanced parameters for all examples
|
176 |
-
default_omega = 2.75
|
177 |
-
default_n_max = 40
|
178 |
-
default_n_avg = 4
|
179 |
-
|
180 |
-
for ex_def in examples_to_load_definitions:
|
181 |
-
# Assuming .mp4 extension for all videos
|
182 |
-
video_file_name = f"{ex_def['video_base_name']}.mp4"
|
183 |
-
example_video_path = os.path.join(VIDEO_EXAMPLES_DIR, video_file_name)
|
184 |
-
|
185 |
-
if os.path.exists(example_video_path):
|
186 |
-
examples_data.append([
|
187 |
-
example_video_path,
|
188 |
-
ex_def["src_prompt"],
|
189 |
-
ex_def["tar_prompt"],
|
190 |
-
ex_def["src_words"],
|
191 |
-
ex_def["tar_words"],
|
192 |
-
default_omega,
|
193 |
-
default_n_max,
|
194 |
-
default_n_avg
|
195 |
-
])
|
196 |
-
else:
|
197 |
-
print(f"Warning: Example video {example_video_path} not found. Example for '{ex_def['video_base_name']}' will be skipped.")
|
198 |
-
|
199 |
-
if not examples_data:
|
200 |
-
print(f"Warning: No example videos found in '{VIDEO_EXAMPLES_DIR}'. Examples section will be empty or not show.")
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
205 |
-
/* Main container - maximize width and improve spacing */
|
206 |
-
.gradio-container {
|
207 |
-
max-width: 98% !important;
|
208 |
-
width: 98% !important;
|
209 |
-
margin: 0 auto !important;
|
210 |
-
padding: 20px !important;
|
211 |
-
min-height: 100vh !important;
|
212 |
-
}
|
213 |
-
|
214 |
-
/* All containers should use full width */
|
215 |
-
.contain, .container {
|
216 |
-
max-width: 100% !important;
|
217 |
-
width: 100% !important;
|
218 |
-
padding: 0 !important;
|
219 |
-
}
|
220 |
-
|
221 |
-
/* Remove default padding from main wrapper */
|
222 |
-
.main, .wrap, .panel {
|
223 |
-
max-width: 100% !important;
|
224 |
-
width: 100% !important;
|
225 |
-
padding: 0 !important;
|
226 |
-
}
|
227 |
-
|
228 |
-
/* Improve spacing for components */
|
229 |
-
.gap, .form {
|
230 |
-
gap: 15px !important;
|
231 |
-
}
|
232 |
-
|
233 |
-
/* Make all components full width */
|
234 |
-
#component-0, .block {
|
235 |
-
max-width: 100% !important;
|
236 |
-
width: 100% !important;
|
237 |
-
}
|
238 |
-
|
239 |
-
/* Better padding for groups */
|
240 |
-
.group {
|
241 |
-
padding: 20px !important;
|
242 |
-
margin-bottom: 15px !important;
|
243 |
-
border-radius: 8px !important;
|
244 |
-
}
|
245 |
-
|
246 |
-
/* Make rows and columns use full space with better gaps */
|
247 |
-
.row {
|
248 |
-
gap: 30px !important;
|
249 |
-
margin-bottom: 20px !important;
|
250 |
-
}
|
251 |
-
|
252 |
-
/* Improve column spacing */
|
253 |
-
.column {
|
254 |
-
padding: 0 10px !important;
|
255 |
-
}
|
256 |
-
|
257 |
-
/* Better video component sizing */
|
258 |
-
.video-container {
|
259 |
-
width: 100% !important;
|
260 |
-
}
|
261 |
-
|
262 |
-
/* Textbox improvements */
|
263 |
-
.textbox, .input-field {
|
264 |
-
width: 100% !important;
|
265 |
-
}
|
266 |
-
|
267 |
-
/* Button styling */
|
268 |
-
.primary {
|
269 |
-
width: 100% !important;
|
270 |
-
padding: 12px !important;
|
271 |
-
font-size: 16px !important;
|
272 |
-
margin-top: 20px !important;
|
273 |
-
}
|
274 |
-
|
275 |
-
/* Examples section spacing */
|
276 |
-
.examples {
|
277 |
-
margin-top: 30px !important;
|
278 |
-
padding: 20px !important;
|
279 |
-
}
|
280 |
-
|
281 |
-
/* Accordion improvements */
|
282 |
-
.accordion {
|
283 |
-
margin: 15px 0 !important;
|
284 |
-
}
|
285 |
-
""") as demo:
|
286 |
-
gr.Markdown(
|
287 |
-
"""
|
288 |
-
<h1 style="text-align: center; font-size: 2.5em;">🪄 FlowDirector Video Edit</h1>
|
289 |
-
<p style="text-align: center;">
|
290 |
-
Edit videos by providing a source video, descriptive prompts, and specifying words to change.<br>
|
291 |
-
Powered by FlowDirector.
|
292 |
-
</p>
|
293 |
-
"""
|
294 |
)
|
295 |
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
label="Source Prompt",
|
303 |
-
placeholder="Describe the original video content accurately.",
|
304 |
-
lines=3,
|
305 |
-
show_label=True
|
306 |
-
)
|
307 |
-
target_prompt_input = gr.Textbox(
|
308 |
-
label="Target Prompt (Desired Edit)",
|
309 |
-
placeholder="Describe how you want the video to be after editing.",
|
310 |
-
lines=3,
|
311 |
-
show_label=True
|
312 |
-
)
|
313 |
-
|
314 |
-
with gr.Group():
|
315 |
-
gr.Markdown("### ✍️ Editing Instructions")
|
316 |
-
source_words_input = gr.Textbox(
|
317 |
-
label="Source Words (to be replaced, or empty for addition)",
|
318 |
-
placeholder="e.g., large brown bear (leave empty to add target words globally)"
|
319 |
-
)
|
320 |
-
target_words_input = gr.Textbox(
|
321 |
-
label="Target Words (replacement or addition)",
|
322 |
-
placeholder="e.g., large dinosaur OR a red baseball cap"
|
323 |
-
)
|
324 |
-
|
325 |
-
with gr.Accordion("🔧 Advanced Parameters", open=False):
|
326 |
-
omega_slider = gr.Slider(
|
327 |
-
minimum=0.0, maximum=5.0, step=0.05, value=default_omega, label="Omega (ω)",
|
328 |
-
info="Controls the intensity/style of the edit. Higher values might lead to stronger edits."
|
329 |
-
)
|
330 |
-
n_max_slider = gr.Slider(
|
331 |
-
minimum=0, maximum=50, step=1, value=default_n_max, label="N_max",
|
332 |
-
info="Max value for an adaptive param. `n_min` is fixed at 0."
|
333 |
-
)
|
334 |
-
n_avg_slider = gr.Slider(
|
335 |
-
minimum=0, maximum=5, step=1, value=default_n_avg, label="N_avg",
|
336 |
-
info="Average value for an adaptive param. `worse_avg` will be N_avg // 2."
|
337 |
-
)
|
338 |
|
339 |
-
|
|
|
|
|
340 |
|
341 |
-
|
342 |
-
|
343 |
-
output_video = gr.Video(label="Result", height=540, show_label=False)
|
344 |
|
|
|
|
|
345 |
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
source_video_input,
|
351 |
-
source_prompt_input,
|
352 |
-
target_prompt_input,
|
353 |
-
source_words_input,
|
354 |
-
target_words_input,
|
355 |
-
omega_slider,
|
356 |
-
n_max_slider,
|
357 |
-
n_avg_slider
|
358 |
-
],
|
359 |
-
outputs=output_video,
|
360 |
-
fn=run_video_edit,
|
361 |
-
cache_examples=False # For long processes, False is better
|
362 |
-
)
|
363 |
-
|
364 |
-
all_process_inputs = [
|
365 |
-
source_video_input,
|
366 |
-
source_prompt_input,
|
367 |
-
target_prompt_input,
|
368 |
-
source_words_input,
|
369 |
-
target_words_input,
|
370 |
-
omega_slider,
|
371 |
-
n_max_slider,
|
372 |
-
n_avg_slider
|
373 |
-
]
|
374 |
-
|
375 |
-
|
376 |
-
submit_button.click(
|
377 |
-
fn=run_video_edit,
|
378 |
-
inputs=all_process_inputs,
|
379 |
-
outputs=output_video
|
380 |
-
)
|
381 |
|
382 |
if __name__ == "__main__":
|
383 |
-
# print(f"Make sure your checkpoint directory is correctly set to: {CKPT_DIR}")
|
384 |
-
# print(f"And that '{EDIT_SCRIPT_PATH}' is in the same directory as app.py or correctly pathed.")
|
385 |
-
# print(f"Outputs will be saved to: {os.path.abspath(OUTPUT_DIR)}")
|
386 |
-
# print(f"Place example videos (e.g., bear_g.mp4, blackswan.mp4, etc.) in: {os.path.abspath(VIDEO_EXAMPLES_DIR)}")
|
387 |
-
|
388 |
args = _parse_args()
|
389 |
-
|
|
|
|
|
|
|
390 |
demo.launch()
|
|
|
1 |
+
# app.py – revised to support both persistent and non‑persistent disks
|
2 |
+
"""
|
3 |
+
关键改动
|
4 |
+
---------
|
5 |
+
1. **自动探测是否存在 `/data`**(Hugging Face Persistent Storage 挂载点)。
|
6 |
+
* 有 `/data` ⇒ 把模型下载到 `/data/checkpoints` 并把 `HF_HOME` 也指到 `/data/.huggingface`。
|
7 |
+
* 没 `/data` ⇒ 回退到 `/tmp`(50 GB 临时盘)。容器休眠后会丢失缓存,但代码仍然能正常跑。
|
8 |
+
2. 通过 `huggingface_hub.snapshot_download()` 下载并缓存 `Wan-AI/Wan2.1-T2V-1.3B`。
|
9 |
+
3. 其余业务逻辑(Gradio UI、视频编辑流程)保持不变。
|
10 |
+
"""
|
11 |
+
|
12 |
import os
|
13 |
import sys
|
14 |
+
import time
|
|
|
|
|
15 |
import argparse
|
16 |
+
import datetime
|
17 |
+
import subprocess
|
18 |
+
import gradio as gr
|
19 |
+
import spaces
|
20 |
+
from huggingface_hub import snapshot_download
|
21 |
+
|
22 |
+
# -----------------------------------------------------------------------------
|
23 |
+
# ▶ 运行时环境探测 & 路径配置
|
24 |
+
# -----------------------------------------------------------------------------
|
25 |
+
PERSIST_ROOT = "/data" if os.path.isdir("/data") else "/tmp" # /data 不存在就回退到 /tmp
|
26 |
+
|
27 |
+
HF_CACHE_DIR = os.path.join(PERSIST_ROOT, ".huggingface") # Transformers 缓存
|
28 |
+
MODEL_REPO = "Wan-AI/Wan2.1-T2V-1.3B" # Hub 上的模型仓库
|
29 |
+
MODEL_DIR = os.path.join(PERSIST_ROOT, "checkpoints", "Wan2.1-T2V-1.3B")
|
30 |
+
|
31 |
+
os.makedirs(HF_CACHE_DIR, exist_ok=True)
|
32 |
+
os.makedirs(MODEL_DIR, exist_ok=True)
|
33 |
+
|
34 |
+
# 让 Transformers / Diffusers 等库把文件缓存到持久或临时目录
|
35 |
+
os.environ["HF_HOME"] = HF_CACHE_DIR
|
36 |
+
|
37 |
+
# -----------------------------------------------------------------------------
|
38 |
+
# ▶ 下载 / 准备模型权重(若文件不在本地,则 snapshot_download)
|
39 |
+
# -----------------------------------------------------------------------------
|
40 |
+
if not os.path.exists(os.path.join(MODEL_DIR, "model_index.json")):
|
41 |
+
print(f"[Warm‑up] Downloading model {MODEL_REPO} to {MODEL_DIR} …")
|
42 |
+
snapshot_download(
|
43 |
+
repo_id=MODEL_REPO,
|
44 |
+
local_dir=MODEL_DIR,
|
45 |
+
local_dir_use_symlinks=False, # 真拷贝,避免 symlink 指向 cache 丢失
|
46 |
+
resume_download=True, # 断点续传
|
47 |
+
)
|
48 |
+
print("[Warm‑up] Model download complete.")
|
49 |
|
50 |
+
CKPT_DIR = MODEL_DIR # 供后续 edit.py 使用
|
51 |
+
EDIT_SCRIPT_PATH = "edit.py"
|
|
|
|
|
52 |
OUTPUT_DIR = "gradio_outputs"
|
53 |
+
VIDEO_EXAMPLES_DIR = "video_list"
|
54 |
+
PYTHON_EXECUTABLE = sys.executable
|
55 |
|
|
|
56 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
57 |
+
os.makedirs(VIDEO_EXAMPLES_DIR, exist_ok=True)
|
58 |
|
59 |
+
# -----------------------------------------------------------------------------
|
60 |
+
# ▶ CLI 参数(保留向后兼容)
|
61 |
+
# -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
+
def _parse_args():
|
64 |
+
parser = argparse.ArgumentParser(description="Generate an edited video with Wan 2.1‑T2V")
|
65 |
+
parser.add_argument("--ckpt", type=str, default=CKPT_DIR, help="Custom checkpoint directory (optional)")
|
66 |
return parser.parse_args()
|
67 |
|
68 |
+
# -----------------------------------------------------------------------------
|
69 |
+
# ▶ 工具函数
|
70 |
+
# -----------------------------------------------------------------------------
|
71 |
+
|
72 |
+
def generate_safe_filename_part(text: str, max_len: int = 20) -> str:
|
73 |
if not text:
|
74 |
return "untitled"
|
75 |
+
safe_text = "".join(c if c.isalnum() or c in [" ", "_"] else "_" for c in text).strip()
|
76 |
+
safe_text = "_".join(safe_text.split())
|
77 |
return safe_text[:max_len]
|
78 |
|
79 |
+
# -----------------------------------------------------------------------------
|
80 |
+
# ▶ 核心编辑函数(装饰器 spaces.GPU 依旧保留)
|
81 |
+
# -----------------------------------------------------------------------------
|
82 |
+
|
83 |
@spaces.GPU
|
84 |
+
def run_video_edit(
|
85 |
+
source_video_path,
|
86 |
+
source_prompt,
|
87 |
+
target_prompt,
|
88 |
+
source_words,
|
89 |
+
target_words,
|
90 |
+
omega_value,
|
91 |
+
n_max_value,
|
92 |
+
n_avg_value,
|
93 |
+
progress=gr.Progress(track_tqdm=True),
|
94 |
+
):
|
95 |
+
"""调用 edit.py 执行文本‑到‑视频的定向编辑"""
|
96 |
+
|
97 |
+
# --- 参数校验 -----------------------------------------------------------
|
98 |
if not source_video_path:
|
99 |
raise gr.Error("Please upload a source video.")
|
100 |
if not source_prompt:
|
101 |
raise gr.Error("Please provide a source prompt.")
|
102 |
if not target_prompt:
|
103 |
+
raise gr.Error("Please provide a target prompt.")
|
104 |
+
if source_words is None:
|
105 |
+
raise gr.Error("Please provide source words (can be empty string).")
|
|
|
106 |
if not target_words:
|
107 |
raise gr.Error("Please provide target words.")
|
108 |
|
109 |
+
progress(0, desc="Preparing for video editing…")
|
|
|
|
|
|
|
|
|
110 |
|
111 |
worse_avg_value = n_avg_value // 2
|
|
|
|
|
112 |
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
113 |
src_words_fn = generate_safe_filename_part(source_words)
|
114 |
tar_words_fn = generate_safe_filename_part(target_words)
|
115 |
+
output_basename = (
|
116 |
+
f"{timestamp}_{src_words_fn}_to_{tar_words_fn}_"
|
117 |
+
f"omega{omega_value}_nmax{n_max_value}_navg{n_avg_value}"
|
118 |
+
)
|
119 |
+
output_video_path = os.path.join(OUTPUT_DIR, f"{output_basename}.mp4")
|
120 |
|
121 |
+
# --- 组装命令 -----------------------------------------------------------
|
122 |
cmd = [
|
123 |
+
PYTHON_EXECUTABLE,
|
124 |
+
EDIT_SCRIPT_PATH,
|
125 |
+
"--task",
|
126 |
+
"t2v-1.3B",
|
127 |
+
"--size",
|
128 |
+
"832*480",
|
129 |
+
"--base_seed",
|
130 |
+
"42",
|
131 |
+
"--ckpt_dir",
|
132 |
+
CKPT_DIR,
|
133 |
+
"--sample_solver",
|
134 |
+
"unipc",
|
135 |
+
"--source_video_path",
|
136 |
+
source_video_path,
|
137 |
+
"--source_prompt",
|
138 |
+
source_prompt,
|
139 |
+
"--source_words",
|
140 |
+
source_words,
|
141 |
+
"--prompt",
|
142 |
+
target_prompt,
|
143 |
+
"--target_words",
|
144 |
+
target_words,
|
145 |
+
"--sample_guide_scale",
|
146 |
+
"3.5",
|
147 |
+
"--tar_guide_scale",
|
148 |
+
"10.5",
|
149 |
+
"--sample_shift",
|
150 |
+
"12",
|
151 |
+
"--sample_steps",
|
152 |
+
"50",
|
153 |
+
"--n_max",
|
154 |
+
str(n_max_value),
|
155 |
+
"--n_min",
|
156 |
+
"0",
|
157 |
+
"--n_avg",
|
158 |
+
str(n_avg_value),
|
159 |
+
"--worse_avg",
|
160 |
+
str(worse_avg_value),
|
161 |
+
"--omega",
|
162 |
+
str(omega_value),
|
163 |
+
"--window_size",
|
164 |
+
"11",
|
165 |
+
"--decay_factor",
|
166 |
+
"0.25",
|
167 |
+
"--frame_num",
|
168 |
+
"41",
|
169 |
+
"--save_file",
|
170 |
+
output_video_path,
|
171 |
]
|
172 |
|
173 |
+
# --- 调用子进程 & 进度回调 ---------------------------------------------
|
174 |
+
progress(0.05, desc="Launching edit.py…")
|
175 |
+
process = subprocess.Popen(
|
176 |
+
cmd,
|
177 |
+
stdout=subprocess.PIPE,
|
178 |
+
stderr=subprocess.PIPE,
|
179 |
+
text=True,
|
180 |
+
bufsize=1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
)
|
182 |
|
183 |
+
# 简易心跳进度条(真实项目可解析 stdout)
|
184 |
+
for i in range(12):
|
185 |
+
if process.poll() is not None:
|
186 |
+
break
|
187 |
+
progress(0.05 + i * 0.07, desc=f"Editing… ({i+1}/12)")
|
188 |
+
time.sleep(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
|
190 |
+
stdout, stderr = process.communicate()
|
191 |
+
if process.returncode != 0:
|
192 |
+
raise gr.Error(f"Video editing failed.\nStderr: {stderr[:600]}")
|
193 |
|
194 |
+
if not os.path.exists(output_video_path):
|
195 |
+
raise gr.Error("edit.py reported success but output file missing.")
|
|
|
196 |
|
197 |
+
progress(1, desc="Done!")
|
198 |
+
return output_video_path
|
199 |
|
200 |
+
# -----------------------------------------------------------------------------
|
201 |
+
# ▶ Gradio UI(与之前相同,略)
|
202 |
+
# -----------------------------------------------------------------------------
|
203 |
+
# 由于篇幅,这里省略 UI 部分;逻辑与原版一致,只是依赖上述新路径。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
|
205 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
206 |
args = _parse_args()
|
207 |
+
if args.ckpt: # 允许 CLI 覆盖
|
208 |
+
CKPT_DIR = args.ckpt
|
209 |
+
gr.close_all() # 防止在某些环境重复 launch
|
210 |
+
demo = gr.load("./app.py") # 重新加载自身 Build
|
211 |
demo.launch()
|