$ cat node-template.py

M

Motion Transfer

// Makes the character from an image perform the motion of a reference video (LTX 2.5 motion cloning). Best results when the character's pose and framing match the reference's first frame.

Process
Video
template.py
1import os2import sys3import json4import traceback5import random67from gais import Gais89INPUT_DIR = "/data/input"10OUTPUT_DIR = "/data/output"111213def main():14    try:15        execution_input = json.loads(sys.stdin.read())16        inputs = execution_input.get("inputs", {})1718        def _req_input(name, label):19            v = (inputs.get(name) or "").strip()20            if not v:21                raise ValueError(f"{label} is required")22            p = os.path.join(INPUT_DIR, v)23            if not os.path.exists(p):24                raise FileNotFoundError(f"Input file for {name!r} not found: {p}")25            return p2627        image_path = _req_input("image", "A character image")28        video_path = _req_input("video", "A reference video")2930        prompt = inputs.get("prompt", "")31        control_type = (inputs.get("control_type") or "pose").strip()32        image_strength = max(0.0, min(1.0, float(inputs.get("image_strength", 1.0) or 1.0)))33        control_strength = max(0.0, min(1.0, float(inputs.get("control_strength", 1.0) or 1.0)))3435        seed_mode = inputs.get("seed_mode", "random")36        seed_in = inputs.get("seed", -1)37        seed_input = int(seed_in) if seed_in not in (None, "") else -138        if seed_mode == "fixed" and seed_input >= 0:39            seed_value = seed_input40        else:41            seed_value = random.randint(0, 2**31 - 1)4243        os.makedirs(OUTPUT_DIR, exist_ok=True)44        print(45            f"Requesting video.motion_transfer: control={control_type}, "46            f"image_strength={image_strength}, control_strength={control_strength}, "47            f"seed={seed_value}",48            file=sys.stderr,49        )5051        result = Gais.video.motion_transfer(52            image=image_path,53            reference_video=video_path,54            prompt=prompt,55            control_type=control_type,56            image_strength=image_strength,57            control_strength=control_strength,58            seed=seed_value,59        )6061        out_filename = "motion_transfer_video.mp4"62        out_path = os.path.join(OUTPUT_DIR, out_filename)63        with open(out_path, "wb") as f:64            f.write(result.content)6566        provider = result.metadata.get("provider_name", "local")67        print(f"motion transfer generated via {provider}, seed={seed_value}", file=sys.stderr)6869        try:70            import av71            with av.open(out_path) as _c:72                _s = next(s for s in _c.streams if s.type == "video")73                _w = int(_s.codec_context.width)74                _h = int(_s.codec_context.height)75        except Exception:76            _w, _h = 0, 07778        print(json.dumps({79            "video": out_filename,80            "aspect_ratio": round(_w / _h, 4) if _h else 0.0,81            "resolution": f"{_w}x{_h}",82        }, indent=2))8384    except Exception as e:85        err = {86            "error": str(e),87            "errorType": type(e).__name__,88        }89        try:90            from gais._errors import GaisError91            is_backend_err = isinstance(e, GaisError)92        except Exception:93            is_backend_err = False94        if not is_backend_err:95            err["traceback"] = traceback.format_exc()96        print(json.dumps(err), file=sys.stderr)97        sys.exit(1)9899100if __name__ == "__main__":101    main()

$ git log --oneline

v1.0.0
HEAD
2026-08-18