$ cat node-template.py

T

Text to Video

// Generates a video from a text prompt using the LTX 2.3 22B diffusion transformer. Text-to-video (t2v) only — no input image required.

Process
Video
template.py
1import os2import sys3import json4import traceback5import random67from gais import Gais89OUTPUT_DIR = "/data/output"101112def main():13    try:14        input_json = sys.stdin.read()15        execution_input = json.loads(input_json)16        inputs = execution_input.get("inputs", {})1718        prompt = inputs.get("prompt", "")19        negative_prompt = inputs.get("negative_prompt", "")20        aspect_ratio = float(inputs.get("aspect_ratio", 1.7778) or 1.7778)21        megapixel = float(inputs.get("megapixel", 1.0) or 1.0)22        resolution = (inputs.get("resolution") or "").strip()23        num_frames = int(inputs.get("num_frames", 125) or 125)24        # fps unset → let the active video-creation backend pick its default25        # (Wan 2.2 = 16, LTX 2.3 = 24). Only pass when the user explicitly set it.26        fps_in = inputs.get("fps")27        fps = int(fps_in) if fps_in not in (None, "") else None2829        seed_mode = inputs.get("seed_mode", "random")30        seed_in = inputs.get("seed", -1)31        seed_input = int(seed_in) if seed_in not in (None, "") else -132        if seed_mode == "fixed" and seed_input >= 0:33            seed_value = seed_input34        else:35            seed_value = random.randint(0, 2**31 - 1)3637        if not prompt:38            raise ValueError("Prompt is required")3940        # Width/height: explicit `resolution` override wins; otherwise41        # derive from aspect_ratio + megapixel, rounded to a multiple of 842        # (most video codecs / vendor backends prefer 8-divisible dims).43        if resolution:44            try:45                w_str, h_str = resolution.lower().split("x")46                width = int(w_str)47                height = int(h_str)48            except Exception as e:49                raise ValueError(50                    f"Invalid resolution {resolution!r}; expected 'WxH'"51                ) from e52        else:53            import math54            target_pixels = max(0.25, min(8.0, megapixel)) * 1_000_000.055            h_raw = math.sqrt(target_pixels / max(0.1, aspect_ratio))56            height = max(8, int(round(h_raw / 8) * 8))57            width = max(8, int(round((height * aspect_ratio) / 8) * 8))58            print(59                f"[derived] aspect_ratio={aspect_ratio} megapixel={megapixel} "60                f"-> {width}x{height} (backend may snap further)",61                file=sys.stderr,62            )6364        if fps is not None and fps <= 0:65            raise ValueError(f"fps must be > 0, got {fps}")66        # When the user provided fps, derive duration_s locally so the service67        # gets a precise duration. When fps is unset, leave duration_s out too68        # — the backend will compute it from num_frames at its own default fps.69        duration_s = (num_frames / fps) if fps else None7071        os.makedirs(OUTPUT_DIR, exist_ok=True)7273        fps_label = f"{fps}" if fps else "backend-default"74        dur_label = f"{duration_s:.2f}s" if duration_s else "backend-derived"75        print(76            f"Requesting t2v via gais-video-creation slot: "77            f"{width}x{height} @ {fps_label}fps, num_frames={num_frames} "78            f"({dur_label}), seed={seed_value}",79            file=sys.stderr,80        )8182        # Profile-routed: backend (Wan22 / LTX / Helios / Luma / future) is83        # picked by HARDWARE_PROFILE via PROFILE_OVERRIDES in _registry.py.84        result = Gais.video.create_t2v(85            prompt=prompt,86            negative_prompt=negative_prompt,87            width=width,88            height=height,89            duration_s=duration_s,90            fps=fps,91            seed=seed_value,92            num_frames=num_frames,93        )9495        out_filename = "generated_video.mp4"96        out_path = os.path.join(OUTPUT_DIR, out_filename)97        with open(out_path, "wb") as f:98            f.write(result.content)99100        inference_time = result.metadata.get("inference_time_ms", "unknown")101        provider = result.metadata.get("provider_name", "local")102        print(103            f"video generated via {provider}: time={inference_time}ms, seed={seed_value}",104            file=sys.stderr,105        )106107        # Probe output dimensions and emit aspect_ratio + resolution so108        # downstream nodes can chain.109        try:110            import av111            with av.open(out_path) as _c:112                _s = next(s for s in _c.streams if s.type == "video")113                _w = int(_s.codec_context.width)114                _h = int(_s.codec_context.height)115        except Exception:116            _w, _h = 0, 0117118        print(json.dumps({119            "video": out_filename,120            "aspect_ratio": round(_w / _h, 4) if _h else 0.0,121            "resolution": f"{_w}x{_h}",122        }, indent=2))123124    except Exception as e:125        err = {126            "error": str(e),127            "errorType": type(e).__name__,128            "traceback": traceback.format_exc(),129        }130        print(json.dumps(err), file=sys.stderr)131        sys.exit(1)132133134if __name__ == "__main__":135    main()

$ git log --oneline

v1.4.2
HEAD
2026-08-18
v1.4.12026-05-07
v1.0.02026-04-23