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/omnihuman_lipsync.py
1#!/usr/bin/env python32"""OmniHuman 1.5 — generate lipsync video from image + audio via fal.ai."""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="OmniHuman 1.5 lipsync: image + audio → video")20parser.add_argument("--image", required=True, help="Character image path.")21parser.add_argument("--audio", required=True, help="Audio file path (mp3/wav).")22parser.add_argument("--output", required=True, help="Output mp4 path.")23parser.add_argument("--json-out", help="Optional path to save raw fal response JSON.")24args = parser.parse_args()2526os.environ["FAL_KEY"] = load_fal_key()27import fal_client2829image_path = Path(args.image).expanduser().resolve()30audio_path = Path(args.audio).expanduser().resolve()31output_path = Path(args.output).expanduser().resolve()32output_path.parent.mkdir(parents=True, exist_ok=True)3334print(f"Uploading image: {image_path}")35image_url = fal_client.upload_file(str(image_path))36print(f"Uploading audio: {audio_path}")37audio_url = fal_client.upload_file(str(audio_path))3839model = "fal-ai/bytedance/omnihuman/v1.5"40print(f"Submitting to {model}...")4142def _print_logs(update):43status = getattr(update, "status", None)44if status:45print(f"STATUS: {status}")4647result = fal_client.subscribe(48model,49arguments={"image_url": image_url, "audio_url": audio_url},50with_logs=True,51on_queue_update=_print_logs,52)5354if args.json_out:55json_out = Path(args.json_out).expanduser().resolve()56json_out.parent.mkdir(parents=True, exist_ok=True)57json_out.write_text(json.dumps(result, ensure_ascii=False, indent=2))5859video = result.get("video") if isinstance(result, dict) else None60if not isinstance(video, dict) or not video.get("url"):61raise SystemExit(f"Unexpected result: {json.dumps(result, ensure_ascii=False)[:2000]}")6263print(f"Downloading: {video['url']}")64with urlopen(video["url"], timeout=300) as resp:65output_path.write_bytes(resp.read())66print(f"Saved: {output_path}")67print(json.dumps({"ok": True, "output": str(output_path), "duration": result.get("duration")}))686970if __name__ == "__main__":71main()72