kritnatee's picture
download
raw
5.79 kB
"""
run-ltx-cloud.py — Run LTX-2.3 video inference on HF Jobs cloud GPU
Usage:
hf jobs run --flavor t4-medium --image python:3.12 \
"python run-ltx-cloud.py --scenes scenes.json --output final.mp4"
"""
import json, os, sys, subprocess, time, shutil, glob, argparse, gc
parser = argparse.ArgumentParser()
parser.add_argument("--scenes", default="scenes.json")
parser.add_argument("--output", default="final.mp4")
parser.add_argument("--bucket", default="hf://buckets/kritnatee/Creator360.Studio-storage")
parser.add_argument("--steps", type=int, default=20)
parser.add_argument("--frames", type=int, default=49)
args = parser.parse_args()
# Step 1: Install system + Python deps
print("[1/5] Installing deps...")
# Check if ffmpeg is available, install if not
if subprocess.run(["which", "ffmpeg"], capture_output=True).returncode != 0:
print(" Installing ffmpeg...")
try:
subprocess.run(["apt-get", "update", "-qq"], check=True, capture_output=True)
subprocess.run(["apt-get", "install", "-y", "-qq", "ffmpeg"], check=True, capture_output=True)
except:
print(" WARN: could not install ffmpeg; video assembly may fail")
subprocess.run(["pip", "install", "-q",
"diffusers", "transformers", "accelerate",
"safetensors", "imageio[ffmpeg]", "psutil", "huggingface_hub[hf_xet]"], check=True)
# Step 2: Download assets
print("[2/5] Downloading from bucket...")
os.makedirs("images", exist_ok=True)
subprocess.run(["hf", "sync", f"{args.bucket}/images", "images"], check=True)
if not os.path.exists(args.scenes):
subprocess.run(["hf", "sync", f"{args.bucket}/scripts/{args.scenes}", "."], capture_output=True)
image_files = sorted(sum([glob.glob(f"images/**/*{e}", recursive=True)
for e in ("png", "jpg", "jpeg", "webp")], []))
# Step 3: Load plan
print("[3/5] Loading scene plan...")
if os.path.exists(args.scenes):
with open(args.scenes) as f:
scenes = json.load(f)["scenes"]
else:
scenes = [{"scene": i+1, "start_image": img, "transition_in": "fade",
"transition_out": "fade" if i == len(image_files)-1 else "crossfade",
"duration": 3.0, "mood": "calm"}
for i, img in enumerate(image_files)]
print(f" {len(scenes)} scenes")
# Step 4: Run LTX-2.3 for each scene
print("[4/5] LTX-2.3 inference...")
gpu_info = os.popen("nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null").read().strip()
print(f" GPU: {gpu_info}")
from diffusers import LTXPipeline
import torch
from PIL import Image
import numpy as np
import imageio
os.makedirs("clips", exist_ok=True)
# Load model with appropriate memory settings
print(" Loading LTX-Video (22B)...")
import psutil
ram_gb = psutil.virtual_memory().total / (1024**3)
vram_gb = 0
try:
vram_gb = torch.cuda.get_device_properties(0).total_memory / (1024**3)
except:
pass
print(f" RAM: {ram_gb:.0f} GB | VRAM: {vram_gb:.0f} GB")
# Try loading; fallback to 8-bit if RAM is tight
load_kwargs = {"torch_dtype": torch.bfloat16}
if ram_gb < 35:
print(" Low RAM: enabling 8-bit + CPU offload")
load_kwargs["torch_dtype"] = torch.float16
pipe = LTXPipeline.from_pretrained("Lightricks/LTX-Video", **load_kwargs)
pipe.enable_model_cpu_offload()
pipe.vae.enable_slicing()
for i, scene in enumerate(scenes):
img_ref = scene.get("start_image", "")
img_path = None
# Find image
if img_ref and os.path.exists(img_ref):
img_path = img_ref
elif img_ref:
matches = [f for f in image_files if img_ref in f]
if matches:
img_path = matches[0]
if not img_path and i < len(image_files):
img_path = image_files[i]
if not img_path:
print(f" Scene {scene['scene']}: SKIP")
continue
print(f" Scene {scene['scene']}: {os.path.basename(img_path)} ", end="")
sys.stdout.flush()
t0 = time.time()
try:
input_img = Image.open(img_path).convert("RGB")
prompt = scene.get("narrative", f"Cinematic {scene.get('mood','calm')} scene")
frames = pipe(
image=input_img,
prompt=prompt,
negative_prompt="blurry, low quality",
num_frames=args.frames,
width=704, height=480,
num_inference_steps=args.steps,
generator=torch.Generator(device="cpu").manual_seed(42 + i),
).frames[0]
clip_path = f"clips/scene_{i:03d}.mp4"
imageio.mimwrite(clip_path, frames, fps=24, codec="libx264", quality=8)
print(f" OK ({time.time()-t0:.0f}s)")
except Exception as e:
print(f" FAIL: {e}")
# Fallback: static image
try:
img_np = np.array(Image.open(img_path).resize((704, 480)))
fallback = np.tile(img_np[None], (args.frames, 1, 1, 1))
imageio.mimwrite(f"clips/scene_{i:03d}.mp4", fallback, fps=24, codec="libx264")
print(" Used static fallback")
except:
pass
torch.cuda.empty_cache()
gc.collect()
# Step 5: Assemble & upload
print("[5/5] Assembling video + upload...")
clips = sorted(glob.glob("clips/scene_*.mp4"))
if not clips:
print("ERROR: no clips"); sys.exit(1)
concat = "\n".join(f"file '{os.path.abspath(c)}'" for c in clips)
with open("_concat.txt", "w") as f:
f.write(concat)
subprocess.run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", "_concat.txt",
"-c:v", "libx264", "-preset", "slow", "-crf", "18",
"-pix_fmt", "yuv420p", "-movflags", "+faststart",
args.output], check=True)
subprocess.run(["hf", "sync", os.path.abspath(args.output),
f"{args.bucket}/video/{args.output}"], check=True)
size_mb = os.path.getsize(args.output) / 1024 / 1024
print(f"\nDONE: {args.output} ({size_mb:.1f} MB)")
print(f"Bucket: {args.bucket}/video/{args.output}")

Xet Storage Details

Size:
5.79 kB
·
Xet hash:
7ca8698f0348c231dd82e19d1f247a370baf62cfd7d8593b20015343ce9be1dd

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.