faceless / app.py
Isaacdnigerian's picture
Update app.py
1ad7be5 verified
Raw History Blame Contribute Delete
2.93 kB
"""
HF ZeroGPU Space: LTX-Video text-to-video — a free-GPU video backend for the
faceless-reels pipeline (docs §9 hybrid).
Exposes a Gradio API function `generate` that returns the MP4 as base64 (so the
pipeline can decode it in src/lib/video.ts without downloading a hosted file).
The @spaces.GPU decorator gives the function on-demand ZeroGPU access.
Deploy: create a Gradio Space, set Hardware = ZeroGPU, add app.py + requirements.txt.
See hf-space/README.md.
"""
import base64
import os
import tempfile
import gradio as gr
import spaces
import torch
from diffusers import LTXPipeline
from diffusers.utils import export_to_video
MODEL_ID = os.environ.get("MODEL_ID", "Lightricks/LTX-Video")
# Loaded on CPU at startup (ZeroGPU only exposes the GPU inside @spaces.GPU).
# The weights download once when the Space boots.
pipe = LTXPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16)
# The LTX VAE decode in bf16 can emit NaN -> uniform black/gray/white frames.
# Upcast the VAE to float32 to keep the decode numerically stable (the known fix
# for LTX black-screen output). Transformer + text encoder stay bf16.
pipe.vae.to(torch.float32)
def _round32(x) -> int:
return max(32, (int(x) // 32) * 32)
def _num_frames(duration_sec: float, fps: int) -> int:
n = int(round(duration_sec * fps))
return max(9, ((n - 1) // 8) * 8 + 1) # LTX requires num_frames % 8 == 1
@spaces.GPU(duration=120)
def generate(prompt: str, width: float = 704, height: float = 1280, duration: float = 5) -> str:
pipe.to("cuda")
pipe.vae.to(torch.float32) # re-assert float32 VAE on-device (avoids black frames)
fps = 24
result = pipe(
prompt=prompt,
negative_prompt="worst quality, inconsistent motion, blurry, jittery, distorted",
width=_round32(width),
height=_round32(height),
num_frames=_num_frames(float(duration), fps),
num_inference_steps=40,
guidance_scale=3.0,
# Timestep-aware VAE decode (LTX 0.9.1+): the documented values that
# avoid black/NaN frames.
decode_timestep=0.03,
decode_noise_scale=0.025,
)
frames = result.frames[0]
path = tempfile.mktemp(suffix=".mp4")
export_to_video(frames, path, fps=fps)
with open(path, "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
os.remove(path)
return b64
with gr.Blocks() as demo:
gr.Markdown("# Faceless LTX-Video\nReturns the generated clip as base64.")
prompt = gr.Textbox(label="prompt")
width = gr.Number(value=704, label="width")
height = gr.Number(value=1280, label="height")
duration = gr.Number(value=5, label="duration (s)")
out = gr.Textbox(label="video_base64")
btn = gr.Button("Generate")
# api_name="generate" fixes the API route the pipeline calls.
btn.click(generate, [prompt, width, height, duration], out, api_name="generate")
demo.queue().launch()