Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from bundle
Generate talking-head avatar videos from text. Pipeline: ElevenLabs V3 TTS → OmniHuman 1.5 lipsync → Kling v3 motion enhancement.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/kling_motion_enhance.py
1#!/usr/bin/env python32"""Kling v3 pro motion control — enhance OmniHuman output with better quality."""3import argparse4import json5import os6from pathlib import Path7from urllib.request import urlopen8910def load_fal_key() -> str:11for name in ("FAL_KEY", "FAL_AI_KEY", "FAL_API_KEY"):12value = os.getenv(name)13if value:14return value.strip()15raise SystemExit("Missing fal.ai key. Set FAL_KEY or FAL_AI_KEY.")161718def main() -> None:19parser = argparse.ArgumentParser(description="Kling v3 pro motion control: image + motion video → enhanced video")20parser.add_argument("--image", required=True, help="Character reference image.")21parser.add_argument("--video", required=True, help="Motion reference video (e.g. OmniHuman output).")22parser.add_argument("--face-image", help="Frontal face image for identity preservation.")23parser.add_argument("--prompt", default="", help="Guidance prompt.")24parser.add_argument("--output", required=True, help="Output mp4 path.")25parser.add_argument("--json-out", help="Optional path to save raw fal response JSON.")26parser.add_argument("--model", default="fal-ai/kling-video/v3/pro/motion-control")27args = parser.parse_args()2829os.environ["FAL_KEY"] = load_fal_key()30import fal_client3132image_path = Path(args.image).expanduser().resolve()33video_path = Path(args.video).expanduser().resolve()34output_path = Path(args.output).expanduser().resolve()35output_path.parent.mkdir(parents=True, exist_ok=True)3637print(f"Uploading image: {image_path}")38image_url = fal_client.upload_file(str(image_path))39print(f"Uploading video: {video_path}")40video_url = fal_client.upload_file(str(video_path))4142payload = {43"image_url": image_url,44"video_url": video_url,45"character_orientation": "video",46"keep_original_sound": True,47}4849if args.prompt.strip():50payload["prompt"] = args.prompt.strip()5152if args.face_image:53face_path = Path(args.face_image).expanduser().resolve()54print(f"Uploading face image: {face_path}")55face_url = fal_client.upload_file(str(face_path))56payload["elements"] = [{"frontal_image_url": face_url, "reference_image_urls": [face_url]}]5758print(f"Submitting to {args.model}...")5960def _print_logs(update):61status = getattr(update, "status", None)62if status:63print(f"STATUS: {status}")6465result = fal_client.subscribe(66args.model,67arguments=payload,68with_logs=True,69on_queue_update=_print_logs,70)7172if args.json_out:73json_out = Path(args.json_out).expanduser().resolve()74json_out.parent.mkdir(parents=True, exist_ok=True)75json_out.write_text(json.dumps(result, ensure_ascii=False, indent=2))7677video = result.get("video") if isinstance(result, dict) else None78if not isinstance(video, dict) or not video.get("url"):79raise SystemExit(f"Unexpected result: {json.dumps(result, ensure_ascii=False)[:2000]}")8081print(f"Downloading: {video['url']}")82with urlopen(video["url"], timeout=300) as resp:83output_path.write_bytes(resp.read())84print(f"Saved: {output_path}")85print(json.dumps({"ok": True, "output": str(output_path)}))868788if __name__ == "__main__":89main()90