File size: 3,351 Bytes
559617d
 
 
fca9e42
559617d
60f0957
559617d
fca9e42
559617d
 
 
ac9a9f2
fca9e42
 
ac9a9f2
fca9e42
ac9a9f2
fca9e42
ac9a9f2
fca9e42
 
 
ac9a9f2
 
fca9e42
 
ac9a9f2
fca9e42
 
559617d
 
60f0957
fca9e42
 
 
 
 
 
 
 
 
 
 
 
 
 
ac9a9f2
 
559617d
ac9a9f2
 
 
 
 
 
 
 
 
 
 
559617d
 
60f0957
559617d
 
ac9a9f2
60f0957
 
559617d
60f0957
 
fca9e42
559617d
60f0957
 
559617d
60f0957
 
 
 
 
 
fca9e42
559617d
 
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
import spaces
import torch
import gc
import os
import gradio as gr
from diffusers import WanPipeline
from diffusers.utils import export_to_video
from huggingface_hub import snapshot_download
import tempfile
import time

# ============================================================
# DOWNLOAD model to DISK only at startup (near-zero RAM usage)
# This runs during container startup = NO time limit
# ============================================================
print("πŸ“₯ Pre-caching model files to disk (no RAM used)...")
start = time.time()
model_path = snapshot_download(
    "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
    token=os.environ.get("HF_TOKEN"),
    allow_patterns=["*.safetensors", "*.json", "*.txt", "*.model"],
    ignore_patterns=["*.bin", "*.onnx", "*.msgpack"],
)
gc.collect()
print(f"βœ… Files cached to disk in {time.time()-start:.0f}s")
print(f"πŸ“ Path: {model_path}")

# Model loaded lazily inside GPU function
pipe = None

@spaces.GPU(duration=240)
def generate_video(prompt, negative_prompt, num_frames, height, width, num_inference_steps, guidance_scale):
    global pipe

    if pipe is None:
        print("πŸ“¦ Loading to GPU from local disk cache...")
        load_start = time.time()
        pipe = WanPipeline.from_pretrained(
            model_path,
            torch_dtype=torch.float16,
            low_cpu_mem_usage=True,
        )
        pipe.to("cuda")
        pipe.vae.enable_tiling()
        gc.collect()
        print(f"βœ… On GPU in {time.time()-load_start:.0f}s")

    start = time.time()
    with torch.inference_mode():
        result = pipe(
            prompt=prompt,
            negative_prompt=negative_prompt,
            num_frames=int(num_frames),
            height=int(height),
            width=int(width),
            num_inference_steps=int(num_inference_steps),
            guidance_scale=float(guidance_scale),
        ).frames[0]

    print(f"βœ… Generated in {time.time()-start:.1f}s")
    output_path = tempfile.mktemp(suffix=".mp4")
    export_to_video(result, output_path, fps=16)
    gc.collect(); torch.cuda.empty_cache()
    return output_path


with gr.Blocks(title="Shotarch Video Gen", theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🎬 Shotarch Video Generator\n### Wan2.1-1.3B on ZeroGPU")
    with gr.Row():
        with gr.Column():
            prompt = gr.Textbox(label="Prompt", lines=3, placeholder="Describe your video...")
            negative = gr.Textbox(label="Negative Prompt", lines=2, value="Bright tones, overexposed, static, blurred details, worst quality, low quality, ugly, deformed, still picture")
            with gr.Row():
                width = gr.Slider(480, 1280, value=1280, step=16, label="Width")
                height = gr.Slider(320, 720, value=720, step=16, label="Height")
            with gr.Row():
                num_frames = gr.Slider(17, 81, value=81, step=4, label="Frames (81=5sec)")
                steps = gr.Slider(10, 50, value=25, step=1, label="Steps")
            guidance = gr.Slider(1.0, 15.0, value=5.0, step=0.5, label="Guidance Scale")
            btn = gr.Button("🎬 Generate Video", variant="primary")
        with gr.Column():
            output = gr.Video(label="Generated Video")
    btn.click(fn=generate_video, inputs=[prompt, negative, num_frames, height, width, steps, guidance], outputs=output)

demo.launch()