$ cat node-template.py

P

Portrait Animation

// Generates a video by animating a portrait image driven by an audio clip. Supports configurable output resolution and reproducible seeds. Cloud/hybrid: VEED Fabric 1.0 (480p/720p, audio-driven duration). Local: Sonic.

Process
Video
template.py
1import os2import sys3import json4import traceback56from gais import Gais78INPUT_DIR = "/data/input"9OUTPUT_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        image = inputs.get("image", "")19        audio = inputs.get("audio", "")20        prompt = inputs.get("prompt", "") or ""21        resolution = inputs.get("resolution", "768x512")22        seed = inputs.get("seed", 72589)2324        if not image:25            raise ValueError("Input image is required")26        if not audio:27            raise ValueError("Input audio is required")2829        image_path = os.path.join(INPUT_DIR, image)30        audio_path = os.path.join(INPUT_DIR, audio)3132        if not os.path.exists(image_path):33            raise FileNotFoundError(f"Input image not found: {image_path}")34        if not os.path.exists(audio_path):35            raise FileNotFoundError(f"Input audio not found: {audio_path}")3637        os.makedirs(OUTPUT_DIR, exist_ok=True)3839        print(40            f"Requesting speech-driven video: resolution={resolution}, "41            f"seed={seed}, prompt_len={len(prompt)}",42            file=sys.stderr,43        )4445        result = Gais.video.create_from_speech(46            image=image_path,47            audio=audio_path,48            prompt=prompt,49            resolution=resolution,50            seed=seed,51        )5253        # Save result54        out_filename = "speech_video.mp4"55        out_path = os.path.join(OUTPUT_DIR, out_filename)56        with open(out_path, "wb") as f:57            f.write(result.content)5859        inference_time = result.metadata.get("inference_time_ms", "unknown")60        print(f"Video generated: time={inference_time}ms", file=sys.stderr)6162        # Probe output dimensions and emit aspect_ratio + resolution so63        # downstream nodes can chain.64        try:65            import av66            with av.open(out_path) as _c:67                _s = next(s for s in _c.streams if s.type == "video")68                _w = int(_s.codec_context.width)69                _h = int(_s.codec_context.height)70        except Exception:71            _w, _h = 0, 07273        output = {74            "video": out_filename,75            "aspect_ratio": round(_w / _h, 4) if _h else 0.0,76            "resolution": f"{_w}x{_h}",77        }78        print(json.dumps(output, indent=2))7980    except Exception as e:81        error_output = {82            "error": str(e),83            "errorType": type(e).__name__,84            "traceback": traceback.format_exc(),85        }86        print(json.dumps(error_output), file=sys.stderr)87        sys.exit(1)888990if __name__ == "__main__":91    main()

$ git log --oneline

v3.12.0
HEAD
2026-08-18
v3.9.02026-05-07
v3.8.02026-04-23
v3.4.02026-03-29
v3.3.02026-03-20