Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
@@ -266,8 +266,8 @@ def resize_image_landscape(image: Image.Image) -> Image.Image:
|
|
266 |
return image.resize((LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT), Image.LANCZOS)
|
267 |
|
268 |
def get_duration(input_image, prompt, steps, negative_prompt, duration_seconds, guidance_scale, guidance_scale_2, seed, randomize_seed):
|
269 |
-
#
|
270 |
-
return int(steps) *
|
271 |
|
272 |
@spaces.GPU(duration=get_duration)
|
273 |
def generate_video(
|
@@ -275,7 +275,7 @@ def generate_video(
|
|
275 |
prompt,
|
276 |
steps=4,
|
277 |
negative_prompt=default_negative_prompt,
|
278 |
-
duration_seconds=
|
279 |
guidance_scale=1,
|
280 |
guidance_scale_2=1,
|
281 |
seed=42,
|
@@ -286,61 +286,76 @@ def generate_video(
|
|
286 |
if input_image is None:
|
287 |
raise gr.Error("Please generate or upload an image first.")
|
288 |
|
289 |
-
# Initialize pipeline if needed
|
290 |
-
initialize_video_pipeline()
|
291 |
-
|
292 |
-
if video_pipe is None:
|
293 |
-
raise gr.Error("Video pipeline not initialized. Please check GPU availability.")
|
294 |
-
|
295 |
try:
|
296 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
num_frames = int(round(duration_seconds * FIXED_FPS))
|
298 |
-
num_frames = np.clip(num_frames,
|
299 |
-
# Round to nearest number divisible by 4
|
300 |
num_frames = ((num_frames - 1) // 4) * 4 + 1
|
301 |
|
302 |
current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
|
303 |
-
resized_image = resize_image_for_video(input_image)
|
304 |
|
305 |
-
#
|
306 |
-
|
307 |
-
gc.collect()
|
308 |
|
309 |
-
# Generate
|
310 |
with torch.inference_mode():
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
|
|
323 |
|
324 |
# Clear cache after generation
|
325 |
torch.cuda.empty_cache()
|
326 |
gc.collect()
|
327 |
|
|
|
328 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
|
329 |
video_path = tmpfile.name
|
330 |
|
331 |
export_to_video(output_frames_list, video_path, fps=FIXED_FPS)
|
332 |
|
333 |
-
return video_path, current_seed, "🎬 Video generated successfully!"
|
334 |
|
335 |
except RuntimeError as e:
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
raise gr.Error("GPU memory
|
340 |
else:
|
341 |
-
raise gr.Error(f"
|
342 |
except Exception as e:
|
343 |
-
raise gr.Error(f"
|
344 |
|
345 |
# ===========================
|
346 |
# Enhanced CSS
|
@@ -541,12 +556,12 @@ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
|
|
541 |
lines=3
|
542 |
)
|
543 |
duration_input = gr.Slider(
|
544 |
-
minimum=
|
545 |
-
maximum=
|
546 |
step=0.1,
|
547 |
-
value=
|
548 |
label="Duration (seconds)",
|
549 |
-
info=
|
550 |
)
|
551 |
|
552 |
with gr.Accordion("Advanced Settings", open=False):
|
@@ -568,10 +583,10 @@ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
|
|
568 |
)
|
569 |
steps_slider = gr.Slider(
|
570 |
minimum=1,
|
571 |
-
maximum=
|
572 |
step=1,
|
573 |
-
value=
|
574 |
-
label="Inference Steps"
|
575 |
)
|
576 |
guidance_1 = gr.Slider(
|
577 |
minimum=0.0,
|
|
|
266 |
return image.resize((LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT), Image.LANCZOS)
|
267 |
|
268 |
def get_duration(input_image, prompt, steps, negative_prompt, duration_seconds, guidance_scale, guidance_scale_2, seed, randomize_seed):
|
269 |
+
# Shorter duration for stability
|
270 |
+
return min(60, int(steps) * 10)
|
271 |
|
272 |
@spaces.GPU(duration=get_duration)
|
273 |
def generate_video(
|
|
|
275 |
prompt,
|
276 |
steps=4,
|
277 |
negative_prompt=default_negative_prompt,
|
278 |
+
duration_seconds=2.0, # Reduced default
|
279 |
guidance_scale=1,
|
280 |
guidance_scale_2=1,
|
281 |
seed=42,
|
|
|
286 |
if input_image is None:
|
287 |
raise gr.Error("Please generate or upload an image first.")
|
288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
try:
|
290 |
+
# Initialize pipeline if needed (simplified)
|
291 |
+
global video_pipe
|
292 |
+
if video_pipe is None:
|
293 |
+
print("Initializing video pipeline...")
|
294 |
+
video_pipe = WanImageToVideoPipeline.from_pretrained(
|
295 |
+
VIDEO_MODEL_ID,
|
296 |
+
torch_dtype=torch.bfloat16,
|
297 |
+
variant="fp16",
|
298 |
+
use_safetensors=True
|
299 |
+
).to('cuda')
|
300 |
+
|
301 |
+
# Load Lightning LoRA for faster generation
|
302 |
+
try:
|
303 |
+
video_pipe.load_lora_weights("Kijai/WanVideo_comfy", weight_name="Wan22-Lightning-4-cfg1_bf16_v0.9.safetensors")
|
304 |
+
video_pipe.fuse_lora(lora_scale=1.0)
|
305 |
+
except:
|
306 |
+
pass
|
307 |
+
|
308 |
+
# Clear cache before generation
|
309 |
+
torch.cuda.empty_cache()
|
310 |
+
gc.collect()
|
311 |
+
|
312 |
+
# Ensure frames are divisible by 4 and limit to reasonable range
|
313 |
num_frames = int(round(duration_seconds * FIXED_FPS))
|
314 |
+
num_frames = np.clip(num_frames, 9, 33) # Limit to 0.5-2 seconds
|
|
|
315 |
num_frames = ((num_frames - 1) // 4) * 4 + 1
|
316 |
|
317 |
current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
|
|
|
318 |
|
319 |
+
# Resize image
|
320 |
+
resized_image = resize_image_for_video(input_image)
|
|
|
321 |
|
322 |
+
# Generate with reduced settings
|
323 |
with torch.inference_mode():
|
324 |
+
with torch.autocast('cuda', dtype=torch.bfloat16):
|
325 |
+
output_frames_list = video_pipe(
|
326 |
+
image=resized_image,
|
327 |
+
prompt=prompt,
|
328 |
+
negative_prompt=negative_prompt,
|
329 |
+
height=resized_image.height,
|
330 |
+
width=resized_image.width,
|
331 |
+
num_frames=num_frames,
|
332 |
+
guidance_scale=float(guidance_scale),
|
333 |
+
guidance_scale_2=float(guidance_scale_2),
|
334 |
+
num_inference_steps=int(steps),
|
335 |
+
generator=torch.Generator(device="cuda").manual_seed(current_seed),
|
336 |
+
).frames[0]
|
337 |
|
338 |
# Clear cache after generation
|
339 |
torch.cuda.empty_cache()
|
340 |
gc.collect()
|
341 |
|
342 |
+
# Save video
|
343 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
|
344 |
video_path = tmpfile.name
|
345 |
|
346 |
export_to_video(output_frames_list, video_path, fps=FIXED_FPS)
|
347 |
|
348 |
+
return video_path, current_seed, f"🎬 Video generated successfully! ({num_frames} frames)"
|
349 |
|
350 |
except RuntimeError as e:
|
351 |
+
torch.cuda.empty_cache()
|
352 |
+
gc.collect()
|
353 |
+
if "out of memory" in str(e).lower():
|
354 |
+
raise gr.Error("GPU memory exceeded. Try reducing duration to 1-2 seconds and steps to 4.")
|
355 |
else:
|
356 |
+
raise gr.Error(f"GPU error: {str(e)[:100]}")
|
357 |
except Exception as e:
|
358 |
+
raise gr.Error(f"Error: {str(e)[:200]}")
|
359 |
|
360 |
# ===========================
|
361 |
# Enhanced CSS
|
|
|
556 |
lines=3
|
557 |
)
|
558 |
duration_input = gr.Slider(
|
559 |
+
minimum=0.5,
|
560 |
+
maximum=2.0,
|
561 |
step=0.1,
|
562 |
+
value=1.5,
|
563 |
label="Duration (seconds)",
|
564 |
+
info="Shorter videos use less memory"
|
565 |
)
|
566 |
|
567 |
with gr.Accordion("Advanced Settings", open=False):
|
|
|
583 |
)
|
584 |
steps_slider = gr.Slider(
|
585 |
minimum=1,
|
586 |
+
maximum=8,
|
587 |
step=1,
|
588 |
+
value=4,
|
589 |
+
label="Inference Steps (4 recommended)"
|
590 |
)
|
591 |
guidance_1 = gr.Slider(
|
592 |
minimum=0.0,
|