diff --git a/.env.example b/.env.example index 32d827d..e32d404 100644 --- a/.env.example +++ b/.env.example @@ -1,29 +1,122 @@ -# aeon-movie-maker — environment variables. +# ──────────────────────────────────────────────────────────────────────── +# aeon-movie-maker — environment variables +# ──────────────────────────────────────────────────────────────────────── +# Copy to `.env` and fill in your values. movie_maker_fast.py reads these +# via os.environ at runtime. +# +# cp .env.example .env && $EDITOR .env && ./setup.sh +# ════════════════════════════════════════════════════════════════════════ -# === ComfyUI server === + +# ──────────────────────────────────────────────────────────────────────── +# 1. ComfyUI server endpoint (REQUIRED) +# ──────────────────────────────────────────────────────────────────────── +# HTTP endpoint of your ComfyUI server. The CLI sends LTX 2.3 workflow +# JSONs to /prompt and polls /history for results. +# +# ─── LOCAL MODE — ComfyUI on this machine: ────────────────────────────── +# COMFYUI_URL=http://127.0.0.1:8188 +# +# ─── REMOTE MODE — ComfyUI on a different machine: ────────────────────── +# Direct HTTP: +# COMFYUI_URL=http://:8188 +# # Start ComfyUI with --listen 0.0.0.0 and open port 8188 in firewall. +# +# SSH tunnel (recommended): +# # In a separate terminal: +# # ssh -L 8188:127.0.0.1:8188 user@gpu-host +# COMFYUI_URL=http://127.0.0.1:8188 +# COMFYUI_URL=http://127.0.0.1:8188 -# === ComfyUI install root (REQUIRED for screenplay mode) === -# This is where the script stages intermediate seed-image frames into -# input/_movie_fast_frames/ so the ComfyUI VAE encoder can pick them up. -# Must be the actual path of your ComfyUI install on whatever host the -# ComfyUI server is running on. -# COMFYUI_ROOT=/path/to/your/ComfyUI -# === Output directory === +# ──────────────────────────────────────────────────────────────────────── +# 2. ComfyUI install root (REQUIRED for screenplay mode + I2V) +# ──────────────────────────────────────────────────────────────────────── +# IMPORTANT: this script needs file-system access to ComfyUI's `input/` +# directory because it stages seed-image frames at: +# ${COMFYUI_ROOT}/input/_movie_fast_frames//frame_.png +# so the ComfyUI VAE encoder can read them as I2V conditioning. +# +# This means COMFYUI_ROOT must point at a path that the ComfyUI server +# can also read. Three valid configurations: +# +# ─── LOCAL MODE: ──────────────────────────────────────────────────────── +# Just point at your local ComfyUI install: +# Linux/macOS: COMFYUI_ROOT=/home/yourname/ComfyUI +# Windows: COMFYUI_ROOT=C:/Users/yourname/Documents/ComfyUI +# +# ─── REMOTE MODE Option A — Run CLI on the REMOTE machine: ────────────── +# Set COMFYUI_ROOT to the ComfyUI path on the remote box (the same +# filesystem as the ComfyUI process). Invoke the CLI via SSH: +# ssh user@remote 'cd /path/to/aeon-movie-maker && python scripts/...' +# +# ─── REMOTE MODE Option B — Run CLI locally with a SHARED filesystem: ─── +# Mount or symlink the remote ComfyUI's `input/` directory locally +# (NFS, SMB, sshfs, Tailscale Files, etc.) so the local CLI can write +# frames where the remote ComfyUI server can read them. +# COMFYUI_ROOT=/mnt/remote-comfyui # local mount point +# +# ─── DOES NOT WORK: ───────────────────────────────────────────────────── +# Local CLI + remote ComfyUI without shared filesystem (HTTP-only). +# The ComfyUI server can't read seed images from your local disk. +# For T2V (no seed image), this would work — but I2V + screenplay +# carry-forward needs disk access. Use Option A or B above for video. +# +COMFYUI_ROOT= + + +# ──────────────────────────────────────────────────────────────────────── +# 3. Output directory (OPTIONAL) +# ──────────────────────────────────────────────────────────────────────── +# Where rendered MP4s + clips_manifest.json land. Defaults to +# ${COMFYUI_ROOT}/output if unset. +# # OUTPUT_DIR=./output -# === Binary paths (rely on PATH if unset) === -# FFMPEG=/usr/bin/ffmpeg -# FFPROBE=/usr/bin/ffprobe -# === HuggingFace token (required for some gated LTX 2.3 / EROS variants) === -# Get one at: https://huggingface.co/settings/tokens -# HF_TOKEN= +# ──────────────────────────────────────────────────────────────────────── +# 4. Binary paths (OPTIONAL) +# ──────────────────────────────────────────────────────────────────────── +# FFMPEG=/usr/local/bin/ffmpeg +# FFPROBE=/usr/local/bin/ffprobe + + +# ──────────────────────────────────────────────────────────────────────── +# 5. HuggingFace token (OPTIONAL — for gated LTX 2.3 / EROS auto-download) +# ──────────────────────────────────────────────────────────────────────── +# Some Lightricks models on HuggingFace require a token even for public +# read access. Get one (free) at: +# https://huggingface.co/settings/tokens +# Choose "Read" scope. Token starts with "hf_...". +# +# Most users don't need this — install models via ComfyUI Manager UI. +# +# HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + -# === Civitai API token (required for the 4 style LoRAs sourced from Civitai) === -# Get one at: https://civitai.com/user/account → API Keys -# Required for the optional style LoRAs (CyberPunkAI, Smooth_Tribal, Claymation, -# LTX23-GalaxyAce, StudioGhibli, ghibli_style_offset, Illustration concept Variant 3A). -# Civitai download URL pattern: https://civitai.com/api/download/models/?token=$CIVITAI_TOKEN +# ──────────────────────────────────────────────────────────────────────── +# 6. Civitai API token (OPTIONAL — only for the 7 style LoRAs) +# ──────────────────────────────────────────────────────────────────────── +# Style LoRAs (cyberpunk / claymation / ghibli / galaxy / tribal / +# illustration) are hosted on Civitai, not HuggingFace, and require a +# Civitai API token to download. +# +# How to get one: +# 1. Sign in at https://civitai.com (free account) +# 2. Go to https://civitai.com/user/account +# 3. Scroll to "API Keys" → "Add API key" +# 4. Name it (e.g. "aeon-movie-maker"), copy the token +# 5. Paste below +# +# Style LoRAs are STRICTLY OPTIONAL. Plain prompts without `style: ` +# tags work without any of them. Don't bother getting a Civitai token +# unless you actually want stylized output. +# +# Download URL pattern (the 'ltx2/' subfolder is mandatory): +# curl -L --create-dirs \ +# -H "Authorization: Bearer $CIVITAI_TOKEN" \ +# -o "$COMFYUI_ROOT/models/loras/.safetensors" \ +# "https://civitai.com/api/download/models/" +# # CIVITAI_TOKEN= diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e9a059a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# Sponsor button — points to the tip jar on the AEON-7 profile README. +custom: ['https://github.com/AEON-7/AEON-7#-support-the-work'] diff --git a/AGENTS.md b/AGENTS.md index 504c405..383f167 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,39 @@ Instructions for AI agents that operate this tool. +## Step 0 — Determine execution mode + +This tool has **stricter execution requirements** than its sibling repos because LTX 2.3 I2V and screenplay carry-forward write seed-image PNGs to `${COMFYUI_ROOT}/input/_movie_fast_frames/` for the ComfyUI VAE encoder to read. **Pure-HTTP remote mode (no shared filesystem) does NOT work for I2V or screenplay** — only for `clip` subcommand with `--no-image` / T2V. + +### Local mode — CLI on the same machine as ComfyUI ✓ all subcommands work + +Symptoms: `COMFYUI_URL=http://127.0.0.1:8188` reachable + `COMFYUI_ROOT` is a real local dir with `models/`, `input/`, `output/`. Invoke directly: +```bash +python scripts/movie_maker_fast.py {clip|screenplay|stitch} ... +``` + +### Remote mode — ComfyUI on a different machine + +Pick the variant based on what subcommand you need: + +**Remote-A — Run CLI on the remote machine via SSH** (recommended for `screenplay` and `clip --seed-image`): +```bash +ssh ${SSH_USER}@ 'cd /path/to/aeon-movie-maker && python scripts/movie_maker_fast.py screenplay screenplay.json' +scp ${SSH_USER}@:/path/to/output/movie_fast//finished.mp4 . +``` +✓ I2V works. ✓ screenplay carry-forward works. Outputs stay remote until you `scp` them. + +**Remote-B — Local CLI + shared filesystem mount** (advanced): +User must have NFS / SMB / sshfs / Tailscale Files mounting the remote ComfyUI's `input/` dir locally. `COMFYUI_ROOT` points at the local mount. Don't recommend this unless the user explicitly tells you they have it set up. + +**Remote-C — Local CLI + HTTP only** (T2V `clip` only): +```bash +python scripts/movie_maker_fast.py clip --prompt "..." --output local.mp4 +# No --seed-image, no screenplay — these would fail because ComfyUI can't reach local files +``` + +**Default**: Remote-A (SSH-invoke on the remote box) for any work involving I2V or screenplay. Remote-C is only viable for one-off T2V clips with no character continuity needed. + ## When to invoke - User asks for a "film", "movie", "cinematic video", "short film", "music video with cinematic clips" diff --git a/README.md b/README.md index 9d8cf0d..0b96726 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # aeon-movie-maker + +[![☕ Tips](https://img.shields.io/badge/%E2%98%95_Tips-Support_the_work-ff5e5b?style=flat)](https://github.com/AEON-7/AEON-7#-support-the-work) > Fast cinematic video generation built around LTX 2.3 22B (distilled fp8). Three subcommands: render a single clip, render a full screenplay (sequential clips with last-frame carry-forward for character/scene continuity), or stitch dialogue + music + SFX into a finished film with sidechain ducking. ~10–15× faster than WAN-based pipelines while delivering comparable cinematic quality. Part of the **AEON Media Production** family. @@ -141,6 +143,102 @@ For non-narrative work (music videos), substitute `aeon-music-maker` for the aud `setup.sh` checks all of this and lists download commands for any missing pieces. See `references/AGENT_CINEMA_AUTOPILOT.md` for the full agent runbook. +## Configuration + +All config goes through environment variables. Copy `.env.example` to `.env` and fill in your values. + +### Where to run this CLI: local vs remote ComfyUI + +> ⚠️ **Movie Maker has a constraint the other AEON tools don't have:** the screenplay mode + I2V (image-to-video with seed images and last-frame carry-forward) writes intermediate seed-image PNGs into `${COMFYUI_ROOT}/input/_movie_fast_frames//` so the ComfyUI VAE encoder can read them. This means the CLI needs **filesystem-level write access to a path that the ComfyUI server can also read**. Pure-HTTP remote mode (without shared filesystem) does NOT work for I2V or screenplay mode — only for T2V single clips. + +#### Mode A — Local (CLI runs on the same machine as ComfyUI) ✓ supports everything + +The simplest setup. Both processes share the same filesystem. + +```bash +COMFYUI_URL=http://127.0.0.1:8188 +COMFYUI_ROOT=/path/to/local/ComfyUI +``` + +All three subcommands (`clip`, `screenplay`, `stitch`) work without restriction. + +#### Mode B — Remote (ComfyUI on a different machine) + +Pick the sub-option based on whether you need I2V / screenplay carry-forward: + +**B1 — Run the CLI ON the remote machine** (recommended for screenplay work): +```bash +# In .env on the REMOTE box: +COMFYUI_URL=http://127.0.0.1:8188 +COMFYUI_ROOT=/path/to/ComfyUI/on/remote +``` +Invoke from local terminal: +```bash +ssh ${SSH_USER}@ 'cd /path/to/aeon-movie-maker && python scripts/movie_maker_fast.py screenplay screenplay.json' +scp ${SSH_USER}@:/path/to/output/movie_fast//finished_film.mp4 . +``` +Everything stays on the remote box; you pull the final cut. ✓ I2V works. ✓ screenplay carry-forward works. + +**B2 — Run CLI locally + shared filesystem** (advanced): +Use NFS / SMB / sshfs / Tailscale Files / similar to mount the remote ComfyUI's `input/` directory as a local path. Then point `COMFYUI_ROOT` at the local mount point. The local CLI writes into the shared mount; the remote ComfyUI reads from its native path. ✓ everything works, but adds infrastructure complexity. + +**B3 — Run CLI locally + remote HTTP only** (T2V only): +```bash +COMFYUI_URL=http://:8188 +COMFYUI_ROOT=./local-staging # local dir; only used for output mp4 collection +``` +Works for `clip` subcommand with **no `--seed-image`** (T2V mode). Does NOT work for screenplay mode or any I2V. The ComfyUI server can't reach your local files for VAE encoding. + +### All environment variables + +| Variable | Required? | Default | What it is | +|---|---|---|---| +| `COMFYUI_URL` | required | `http://127.0.0.1:8188` | ComfyUI HTTP endpoint. | +| `COMFYUI_ROOT` | **required for I2V/screenplay** | (none) | Path the CLI uses to stage seed-image frames into `input/_movie_fast_frames/`. **Must be readable by the ComfyUI server** — see "local vs remote" above. | +| `OUTPUT_DIR` | optional | `${COMFYUI_ROOT}/output` | Where rendered MP4s + clips_manifest.json land | +| `FFMPEG` / `FFPROBE` | optional | PATH lookup | Override binary paths if not on PATH | +| `HF_TOKEN` | optional | (none) | HuggingFace token for gated Lightricks/LTX-Video models. Get one at https://huggingface.co/settings/tokens (Read scope). Most users install via ComfyUI Manager and never need this. | +| `CIVITAI_TOKEN` | optional | (none) | Civitai API token for the 7 style LoRAs (cyberpunk / claymation / ghibli / galaxy / tribal / illustration / ghibli_offset). **Only needed if you actually use those `style:` tags.** Get one at https://civitai.com/user/account → API Keys. | + +### How to know which model files you need + +Run `./setup.sh`. It walks the canonical model paths under `${COMFYUI_ROOT}/models/` and reports what's missing. Easiest installation paths: + +1. **ComfyUI Manager** (in-browser UI button) — most LTX 2.3 models are one-click installable +2. **`huggingface-cli download Lightricks/LTX-Video --include '*.safetensors'`** — for batch installs from the official HF repo +3. **Manual download** — visit https://huggingface.co/Lightricks/LTX-Video and grab the specific filenames `setup.sh` lists, place at the canonical paths + +For Civitai LoRAs (style tags), search Civitai by filename to find each model's page, then download via the API URL pattern shown in `setup.sh`. License terms are set per-LoRA by the original Civitai uploader. + +## Updating an existing install + +```bash +cd /path/to/aeon-movie-maker +./sync.sh +``` + +The script: +1. **Detects local uncommitted changes** and offers to stash + re-apply them +2. **Shows a diff preview** of incoming commits + files-changed list +3. **Asks for confirmation** before pulling +4. **Refreshes Python deps** + re-runs `setup.sh` model check (so any new LTX 2.3 / EROS / LoRA additions are flagged) + +### Flags + +| Flag | What it does | +|---|---| +| `./sync.sh` | Default — interactive, shows diff | +| `./sync.sh --dry-run` (or `-n`) | Show what would change without pulling | +| `./sync.sh --yes` (or `-y`) | Non-interactive | +| `./sync.sh --no-models` | Skip the model file check (faster) | +| `./sync.sh --help` | Print usage | + +### What if I customized something? + +The sync script auto-stashes any uncommitted local edits before pulling, then re-applies them. `.env`, your `output/` directory, the staging frames at `${COMFYUI_ROOT}/input/_movie_fast_frames/`, and other personal files are gitignored — they're never touched by sync. + +If you've added your own custom LoRA mappings to the `SCENE_LORAS` dict in `scripts/movie_maker_fast.py`, those local edits will be auto-stashed and re-applied. If they conflict with upstream changes (rare), sync stops with clear instructions for resolving. + ## Project structure ``` @@ -172,3 +270,38 @@ MIT. - [`aeon-music-maker`](https://github.com/AEON-7/aeon-music-maker) — music score - [`aeon-music-video`](https://github.com/AEON-7/aeon-music-video) — audio-reactive editing - [`comfyui-aeon-spark`](https://github.com/AEON-7/comfyui-aeon-spark) — base ComfyUI Docker stack + +--- + +## ☕ Support the work + +If this release has been useful, tips are deeply appreciated — they go directly toward more compute, more models, and more open releases. + + + + + + + + + + +
+ ₿ Bitcoin (BTC)
+ BTC QR
+ bc1q09xmzn00q4z3c5raene0f3pzn9d9pvawfm0py4 +
+ Ξ Ethereum (ETH)
+ ETH QR
+ 0x1512667F6D61454ad531d2E45C0a5d1fd82D0500 +
+ ◎ Solana (SOL)
+ SOL QR
+ DgQsjHdAnT5PNLQTNpJdpLS3tYGpVcsHQCkpoiAKsw8t +
+ ⓜ Monero (XMR)
+ XMR QR
+ 836XrSKw4R76vNi3QPJ5Fa9ugcyvE2cWmKSPv3AhpTNNKvqP8v5ba9JRL4Vh7UnFNjDz3E2GXZDVVenu3rkZaNdUFhjAvgd +
+ +> **Ethereum L2s (Base, Arbitrum, Optimism, Polygon, etc.) and EVM-compatible tokens** can be sent to the same Ethereum address. diff --git a/scripts/movie_maker_fast.py b/scripts/movie_maker_fast.py index 24e2fdc..84e6add 100644 --- a/scripts/movie_maker_fast.py +++ b/scripts/movie_maker_fast.py @@ -31,8 +31,8 @@ Base: models/checkpoints/ltx2/ltx-2.3-eros.safetensors Video VAE: models/vae/LTX23_video_vae_bf16.safetensors - Text enc: models/text_encoders/gemma-3-12b-abliterated-text-encoder.safetensors - Distill LoRA:models/loras/ltx2/ltx-2.3-22b-distilled-lora-384.safetensors + Text enc: models/text_encoders/comfy_gemma_3_12B_it.safetensors + Distill LoRA:models/loras/ltxv/ltx2/ltx-2.3-22b-distilled-lora-384-1.1.safetensors Union LoRA: models/loras/ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors VBVR LoRA: models/loras/ltx2/Ltx2.3-Licon-VBVR-I2V-96000-R32.safetensors """ @@ -66,7 +66,7 @@ # for nested model subdirectories (forward-slash paths fail validation with # `value_not_in_list`). All nested paths here use `\` for Windows — plain basenames # stay as-is. If porting to Linux, replace `\\` with `/`. -_SEP = "\\" +_SEP = os.sep # Two render modes — picks checkpoint + audio-pipeline strategy per call: # @@ -92,7 +92,7 @@ # CLI overrides: --vbvr-strength, --ic-lora-strength, --distill-strength. "checkpoint": "ltx-2.3-22b-distilled-fp8.safetensors", "video_vae": "LTX23_video_vae_bf16.safetensors", - "text_encoder": "gemma-3-12b-abliterated-text-encoder.safetensors", + "text_encoder": "comfy_gemma_3_12B_it.safetensors", "joint_av": False, "always_on_loras": [ ("ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors", 0.7), @@ -104,10 +104,10 @@ # Distill LoRA at 0.5 is intentional partial distillation of the EROS base. "checkpoint": f"ltx2{_SEP}ltx-2.3-eros.safetensors", "video_vae": "LTX23_video_vae_bf16.safetensors", - "text_encoder": "gemma-3-12b-abliterated-text-encoder.safetensors", + "text_encoder": "comfy_gemma_3_12B_it.safetensors", "joint_av": True, "always_on_loras": [ - (f"ltx2{_SEP}ltx-2.3-22b-distilled-lora-384.safetensors", 0.5), + (f"ltxv{_SEP}ltx2{_SEP}ltx-2.3-22b-distilled-lora-384-1.1.safetensors", 0.5), ("ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors", 0.7), (f"ltx2{_SEP}Ltx2.3-Licon-VBVR-I2V-96000-R32.safetensors", 0.7), ], @@ -120,7 +120,7 @@ # for tighter prompt adherence on unfamiliar content. "checkpoint": "ltx-2.3-22b-distilled-fp8.safetensors", "video_vae": "LTX23_video_vae_bf16.safetensors", - "text_encoder": "gemma-3-12b-abliterated-text-encoder.safetensors", + "text_encoder": "comfy_gemma_3_12B_it.safetensors", "joint_av": False, "always_on_loras": [], # pure: no physics, no reference control "default_cfg": 5.0, @@ -131,6 +131,88 @@ DEFAULT_MODE = "fast" +# Path to workflow JSONs (these contain the proven working T2V/I2V pipelines) +# The builder (build_ltx_i2v_workflow) produces broken T2V — use these JSONs instead. +WORKFLOW_JSON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "workflows", "api") +WORKFLOW_T2V_JSON = os.path.join(WORKFLOW_JSON_DIR, "ltx_t2v_pure.json") +WORKFLOW_I2V_JSON = os.path.join(WORKFLOW_JSON_DIR, "ltx_i2v_api.json") + + +def _load_t2v_workflow(prompt, negative_prompt, duration_s, fps, width, height, + steps, cfg, seed, loras, mode, filename_prefix): + """ + Load ltx_t2v_pure.json and patch it for the given parameters. + + The ltx_t2v_pure.json workflow has the PROVEN working T2V pipeline with: + - LTXVImgToVideoConditionOnly nodes (3159, 4970) that accept bypass=True + - $from_node() VAE sourcing from CheckpointLoader output [3940, 2] + - LTXVLatentUpsampler for spatial upscale 960×544 → 1920×1088 + - Distilled LoRA at node 4922 (ltxv/ltx2/ltx-2.3-22b-distilled-lora-384-1.1.safetensors @ 0.5) + + The builder's build_ltx_i2v_workflow produces BROKEN T2V (still image with zoom) + because it has no I2V conditioning infrastructure at all. + + LoRA strategy: For clean T2V, keep the workflow's existing distilled LoRA only. + Additional LoRAs from mode config can be appended, but for beach/tropical content + (where IC-LoRA/VBVR cause oversaturation), use mode="abstract" to get no extra LoRAs. + """ + import uuid, json as _json + + with open(WORKFLOW_T2V_JSON) as f: + wf = _json.load(f) + + # Set prompts + wf["2483"]["inputs"]["text"] = prompt + wf["2612"]["inputs"]["text"] = negative_prompt + + # MANDATORY: set bypass=True on BOTH I2V nodes for T2V + wf["3159"]["inputs"]["bypass"] = True + wf["4970"]["inputs"]["bypass"] = True + + # Set latent dimensions and frame count + target_frames = int(round(duration_s * fps)) + frame_count = max(9, ((target_frames - 1) // 8) * 8 + 1) + wf["3059"]["inputs"]["length"] = frame_count + wf["3059"]["inputs"]["width"] = width + wf["3059"]["inputs"]["height"] = height + + # Set fps + wf["3336"]["inputs"]["target_fps"] = fps + + # Set seed on both RandomNoise nodes + wf["4832"]["inputs"]["noise_seed"] = seed + wf["4967"]["inputs"]["noise_seed"] = seed + + # Set sigmas/steps + wf["4984"]["inputs"]["model"] = ["3940", 0] + wf["4985"]["inputs"]["model"] = ["3940", 0] + + # Set LoRA chain (from builder's MODES config) + # Clear existing LoRA nodes first + lora_keys = [k for k in wf if isinstance(wf[k], dict) and + wf[k].get("class_type") == "LoraLoaderModelOnly"] + for k in lora_keys: + del wf[k] + + # Add LoRAs from mode config + lora_last = "3940" + for i, (lora_name, strength) in enumerate(loras, start=100): + wf[str(100 + i)] = { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": [lora_last, 0], + "lora_name": lora_name, + "strength_model": float(strength), + }, + } + lora_last = str(100 + i) + + # Set output prefix + wf["4852"]["inputs"]["filename_prefix"] = filename_prefix + + return wf, seed + + # Back-compat shims — old callers used these constants directly DEFAULT_MODELS = {k: MODES[DEFAULT_MODE][k] for k in ("checkpoint", "video_vae", "text_encoder")} ALWAYS_ON_LORAS = MODES[DEFAULT_MODE]["always_on_loras"] @@ -191,7 +273,7 @@ def apply_cli_lora_overrides(args): } # 7-second max enforced; LTX 2.3 coherence degrades noticeably past ~8 s -MAX_CLIP_SECONDS = 7.0 +MAX_CLIP_SECONDS = 10.0 DEFAULT_FPS = 24 # LTX native; set 25 for film, 30 for broadcast # LTX likes width/height divisible by 32 and resolutions close to its training set DEFAULT_WIDTH = 832 @@ -1280,20 +1362,37 @@ def main(): if not args.t2v and not args.image: raise SystemExit("`clip` requires --image (or use --t2v for text-to-video mode)") - wf, seed_used = build_ltx_i2v_workflow( - image_path=args.image or "", # ignored when t2v=True - prompt=args.prompt, negative_prompt=args.negative, - duration_s=args.duration, fps=args.fps, - width=args.width, height=args.height, - steps=steps, cfg=cfg, seed=seed, - loras=loras, mode=args.mode, filename_prefix=internal_prefix, - persistence=args.persistence, sampler_name=sampler, - t2v=args.t2v, - audio_reference=args.audio_reference, - audio_guidance_scale=args.audio_guidance, - audio_start_percent=args.audio_start_pct, - audio_end_percent=args.audio_end_pct, - ) + # T2V: use the proven workflow JSON (not the builder — builder produces broken T2V) + if args.t2v: + wf, seed_used = _load_t2v_workflow( + prompt=args.prompt, + negative_prompt=args.negative, + duration_s=args.duration, + fps=args.fps, + width=args.width, + height=args.height, + steps=steps, + cfg=cfg, + seed=seed, + loras=loras, + mode=args.mode, + filename_prefix=internal_prefix, + ) + else: + wf, seed_used = build_ltx_i2v_workflow( + image_path=args.image or "", + prompt=args.prompt, negative_prompt=args.negative, + duration_s=args.duration, fps=args.fps, + width=args.width, height=args.height, + steps=steps, cfg=cfg, seed=seed, + loras=loras, mode=args.mode, filename_prefix=internal_prefix, + persistence=args.persistence, sampler_name=sampler, + t2v=False, + audio_reference=args.audio_reference, + audio_guidance_scale=args.audio_guidance, + audio_start_percent=args.audio_start_pct, + audio_end_percent=args.audio_end_pct, + ) # Reflect effective mode after auto-force (e.g. A2V forces quality) effective_mode = "quality" if (args.audio_reference and not mode_cfg["joint_av"]) else args.mode @@ -1321,7 +1420,8 @@ def main(): print(f" Prompt: {args.prompt[:80]}{'...' if len(args.prompt) > 80 else ''}") print(f" Tags: {args.tags or '(none)'}") print(f" Dims: {args.width}×{args.height} @ {args.fps} fps") - print(f" Duration: {args.duration} s → {wf['41']['inputs']['length']} frames") + latent_node = "3059" if args.t2v else "41" + print(f" Duration: {args.duration} s → {wf[latent_node]['inputs']['length']} frames") print(f" Seed: {seed_used}") print(f" Output: {out_path}") print() diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 diff --git a/sync.sh b/sync.sh index 0dc06f0..ac7c9f9 100644 --- a/sync.sh +++ b/sync.sh @@ -1,30 +1,179 @@ #!/usr/bin/env bash -# sync.sh — incremental update for aeon-movie-maker. +# sync.sh — pull latest changes, refresh deps, check models. Idempotent + safe. +# +# What it does (in order): +# 1. Detect uncommitted local changes; offer to stash them +# 2. Fetch from origin, show a diff preview of what's new +# 3. Confirm before pulling (unless --yes) +# 4. Reinstall Python deps from requirements.txt +# 5. Re-run setup.sh model check (unless --no-models) +# +# Flags: +# --dry-run, -n Show what would change, don't actually pull +# --yes, -y Non-interactive: skip confirmation prompts +# --no-models Skip the model-file delta check (faster) +# --help, -h Show this message and exit +# +# Examples: +# ./sync.sh # interactive, recommended for first-time updaters +# ./sync.sh --dry-run # see what would change without pulling +# ./sync.sh -y --no-models # fast non-interactive update of code only +# +# Safe to run from anywhere — it cd's to the script dir first. + set -euo pipefail + +# ── Args ──────────────────────────────────────────────────────────────── +DRY_RUN=0 +YES=0 +NO_MODELS=0 +for arg in "$@"; do + case "$arg" in + --dry-run|-n) DRY_RUN=1 ;; + --yes|-y) YES=1 ;; + --no-models) NO_MODELS=1 ;; + --help|-h) + sed -n '2,30p' "$0" | sed 's/^# \?//' + exit 0 ;; + *) echo "unknown flag: $arg (try --help)"; exit 2 ;; + esac +done + +# ── Make path-independent ─────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Load .env if present [[ -f .env ]] && { set -a; source .env; set +a; } -c_blu(){ printf '\033[36m%s\033[0m\n' "$*"; } +# ── Colors ────────────────────────────────────────────────────────────── +c_red(){ printf '\033[31m%s\033[0m\n' "$*"; } c_grn(){ printf '\033[32m%s\033[0m\n' "$*"; } c_yel(){ printf '\033[33m%s\033[0m\n' "$*"; } +c_blu(){ printf '\033[36m%s\033[0m\n' "$*"; } -NO_MODELS=0 -for arg in "$@"; do [[ "$arg" == "--no-models" ]] && NO_MODELS=1; done +REPO_NAME=$(basename "$SCRIPT_DIR") +c_blu "==> $REPO_NAME sync" + +# ── 1. Detect local modifications ─────────────────────────────────────── +STASH_NEEDED=0 +if ! git diff --quiet || ! git diff --cached --quiet; then + c_yel "[!] You have local uncommitted changes:" + git status --short + if [[ $YES -eq 1 ]]; then + STASH_NEEDED=1 + c_yel " --yes given: will auto-stash and re-apply after pull." + elif [[ $DRY_RUN -eq 1 ]]; then + c_yel " (dry-run: changes will not be touched)" + else + echo "" + echo "Options:" + echo " [s] Stash changes, pull, then re-apply stash (recommended)" + echo " [a] Abort sync — commit or stash manually first" + read -rp "Choose [s/a]: " choice + case "$choice" in + s|S) STASH_NEEDED=1 ;; + *) c_red "Aborted."; exit 1 ;; + esac + fi +fi + +# ── 2. Fetch + show what's new ────────────────────────────────────────── +c_blu "[1/3] Fetching latest from origin..." +git fetch origin --quiet + +# Resolve the tracked remote branch (main or master) +REMOTE_REF=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/@@' || true) +if [[ -z "$REMOTE_REF" ]]; then + if git show-ref --verify --quiet refs/remotes/origin/main; then + REMOTE_REF="origin/main" + elif git show-ref --verify --quiet refs/remotes/origin/master; then + REMOTE_REF="origin/master" + else + c_red " ✗ couldn't resolve remote default branch — is 'origin' set?" + exit 1 + fi +fi + +CURRENT=$(git rev-parse HEAD) +INCOMING=$(git rev-parse "$REMOTE_REF") + +if [[ "$CURRENT" == "$INCOMING" ]]; then + c_grn " ✓ Already up to date with $REMOTE_REF" + PULL_NEEDED=0 +else + PULL_NEEDED=1 + N_COMMITS=$(git rev-list --count "$CURRENT..$INCOMING") + N_FILES=$(git diff --name-only "$CURRENT..$INCOMING" | wc -l | tr -d ' ') + c_yel " $N_COMMITS new commit(s) on $REMOTE_REF, touching $N_FILES file(s):" + echo "" + git log --pretty=format:" %C(yellow)%h%C(reset) %s %C(dim)(%an, %ar)%C(reset)" \ + "$CURRENT..$INCOMING" | head -20 + echo "" + echo "" + c_yel " Files changed:" + git diff --name-status "$CURRENT..$INCOMING" | head -30 | sed 's/^/ /' + if [[ $N_FILES -gt 30 ]]; then + echo " … and $((N_FILES - 30)) more" + fi + echo "" + + if [[ $DRY_RUN -eq 1 ]]; then + c_yel "[DRY RUN] Would pull the changes above. Use 'git diff $CURRENT..$INCOMING -- '" + c_yel " to see specific file changes. Re-run without --dry-run to apply." + exit 0 + fi + + if [[ $YES -eq 0 ]]; then + read -rp "Pull these changes? [y/N]: " confirm + case "$confirm" in + y|Y|yes|YES) ;; + *) c_red "Aborted."; exit 1 ;; + esac + fi -c_blu "==> aeon-movie-maker sync" + if [[ $STASH_NEEDED -eq 1 ]]; then + c_blu " Stashing local changes..." + git stash push -m "sync.sh auto-stash $(date +%Y%m%d-%H%M%S)" --quiet + fi -c_blu "[1/3] git pull" -git pull --ff-only + git pull --ff-only --quiet origin "${REMOTE_REF#origin/}" + c_grn " ✓ Pulled $N_COMMITS commit(s)." -c_blu "[2/3] pip install -r requirements.txt" -python -m pip install --quiet -r requirements.txt -c_grn " ✓ deps up to date" + if [[ $STASH_NEEDED -eq 1 ]]; then + c_blu " Re-applying your stashed changes..." + if git stash pop --quiet; then + c_grn " ✓ Stash re-applied cleanly." + else + c_red " ✗ Stash pop conflicted. Resolve with 'git status', then:" + c_red " git stash drop (if you've integrated the changes)" + exit 1 + fi + fi +fi + +# ── 3. Refresh Python deps ────────────────────────────────────────────── +c_blu "[2/3] Refreshing Python dependencies..." +if [[ -f requirements.txt ]]; then + python -m pip install --quiet -r requirements.txt + c_grn " ✓ deps up to date." +else + c_yel " no requirements.txt found, skipping." +fi +# ── 4. Model delta-check ──────────────────────────────────────────────── if [[ $NO_MODELS -eq 1 ]]; then c_yel "[3/3] --no-models: skipping model check" +elif [[ -x ./setup.sh ]]; then + c_blu "[3/3] Model delta-check (re-running setup.sh model section)..." + ./setup.sh 2>&1 | tail -n 40 || true else - c_blu "[3/3] model delta-check" - ./setup.sh | tail -n 40 || true + c_yel "[3/3] no setup.sh, skipping model check" fi echo "" -c_grn "==> sync complete" +if [[ $PULL_NEEDED -eq 1 ]]; then + c_grn "==> sync complete — pulled $N_COMMITS commit(s) + refreshed deps" +else + c_grn "==> sync complete — nothing to pull, deps verified" +fi diff --git a/workflows/api/ltx_i2v_api.json b/workflows/api/ltx_i2v_api.json new file mode 100644 index 0000000..12261fc --- /dev/null +++ b/workflows/api/ltx_i2v_api.json @@ -0,0 +1,461 @@ +{ + "3059": { + "class_type": "EmptyLTXVLatentVideo", + "inputs": { + "length": 121, + "width": 960, + "height": 544, + "batch_size": 1 + } + }, + "3980": { + "class_type": "LTXVEmptyLatentAudio", + "inputs": { + "audio_vae": [ + "4010", + 0 + ], + "frames_number": 97, + "frame_rate": 25, + "batch_size": 1 + } + }, + "4528": { + "class_type": "LTXVConcatAVLatent", + "inputs": { + "video_latent": [ + "3159", + 0 + ], + "audio_latent": [ + "3980", + 0 + ] + } + }, + "3159": { + "class_type": "LTXVImgToVideoConditionOnly", + "inputs": { + "vae": [ + "3940", + 2 + ], + "image": [ + "3336", + 0 + ], + "latent": [ + "3059", + 0 + ], + "bypass": false, + "strength": 0.7 + } + }, + "1241": { + "class_type": "LTXVConditioning", + "inputs": { + "positive": [ + "2483", + 0 + ], + "negative": [ + "2612", + 0 + ], + "frame_rate": 24 + } + }, + "4976": { + "class_type": "KSamplerSelect", + "inputs": { + "sampler_name": "euler_cfg_pp" + } + }, + "4971": { + "class_type": "SamplerCustomAdvanced", + "inputs": { + "noise": [ + "4967", + 0 + ], + "guider": [ + "4964", + 0 + ], + "sampler": [ + "4976", + 0 + ], + "sigmas": [ + "4985", + 0 + ], + "latent_image": [ + "4969", + 0 + ] + } + }, + "4964": { + "class_type": "CFGGuider", + "inputs": { + "model": [ + "4922", + 0 + ], + "positive": [ + "1241", + 0 + ], + "negative": [ + "9001", + 0 + ], + "cfg": 1 + } + }, + "4980": { + "class_type": "GemmaAPITextEncode", + "inputs": { + "api_key": "", + "prompt": "", + "use_bolt": "ltx-2.3-22b-dev.safetensors" + } + }, + "4979": { + "class_type": "PrimitiveString", + "inputs": { + "value": "" + } + }, + "3336": { + "class_type": "LTXVPreprocess", + "inputs": { + "image": [ + "9990", + 0 + ], + "target_fps": 18, + "img_compression": 3 + } + }, + "2612": { + "class_type": "CLIPTextEncode", + "inputs": { + "clip": [ + "4982", + 0 + ], + "text": "Starting from the spiraling mahogany walkway. 0-6s: The bookish adventurer winds the key on her mechanical lantern; it flares to life with a warm cognac glow that pushes amber light across the polished mahogany and brass railings. She looks up the spiraling atrium. Camera is low-angle looking up the spiral, slowly craning upward to reveal the impossible vertical scale of living bookshelves stretching into warm golden haze. Three leather-bound books with paper wings flutter past in the foreground, leaving trails of warm parchment-light that fade like sparkler embers. Her expression: quiet anticipation, eyes widening slightly, pupils catching the lantern flare.\n\n6-14s: She begins walking up the spiraling walkway. The camera tracks beside her at medium distance while simultaneously craning upward, creating a spiraling camera path that mirrors the architecture. Dozens of books take flight from the shelves, their paper wings beating with overlapping action on staggered timing\u2014each book moves on a slightly different flutter rhythm, creating a living swarm rather than synchronized movement. The books leave ink-sparkle trails that swirl in the warm air. Dust motes dance in the light beams, each mote on its own lazy independent drift path. Her hair and messenger bag strap bounce with follow-through motion lagging 4 frames behind her stride. She reaches out to let a small poetry book brush her fingertips, and it leaves a smudge of warm gold ink on her skin that glows faintly.\n14-20s: She stops at a brass railing, leaning over to look down into the infinite depth below. The camera pushes in past her shoulder to reveal the vertiginous drop, then whips back up as a massive atlas-book with broad paper wings swoops down from the upper shelves. Its wing-shadow flits across the stained-glass window, momentarily darkening the ruby and amber geometric patterns cast across the floor and her face. She gasps\u2014expression shifting from awe to pure wonder\u2014and reaches out. The atlas hovers, pages riffling with secondary motion, releasing a burst of ink-sparkles that catch the lantern light and glow like warm fireflies in the cognac-toned air.\n20-25s: The camera slowly orbits 60 degrees around her as she raises the lantern toward the hovering atlas. The lantern's warm glow and the stained-glass ruby/amber patterns mix on her face, creating dynamic color bleeding that shifts across her skin as the orbit progresses. She smiles, a crooked, curious grin. The ink-sparkles spiral around the lantern in a helix formation, their light reflecting in her large expressive eyes. Camera dollies in for an extreme close-up of her eyes, which reflect the infinite spiraling bookshelves above. A single dust mote drifts through the frame in foreground, shallow depth of field keeping it creamy. Final hold: the mechanical lantern ticks softly, its glow pulsing once, and a distant rolling ladder rumbles along its brass track." + } + }, + "4832": { + "class_type": "RandomNoise", + "inputs": { + "noise_seed": 43, + "seed_gen_type": "fixed" + } + }, + "4828": { + "class_type": "CFGGuider", + "inputs": { + "model": [ + "4922", + 0 + ], + "positive": [ + "1241", + 0 + ], + "negative": [ + "9001", + 0 + ], + "cfg": 1 + } + }, + "4831": { + "class_type": "KSamplerSelect", + "inputs": { + "sampler_name": "euler_ancestral_cfg_pp" + } + }, + "4829": { + "class_type": "SamplerCustomAdvanced", + "inputs": { + "noise": [ + "4832", + 0 + ], + "guider": [ + "4828", + 0 + ], + "sampler": [ + "4831", + 0 + ], + "sigmas": [ + "4984", + 0 + ], + "latent_image": [ + "4528", + 0 + ] + } + }, + "4967": { + "class_type": "RandomNoise", + "inputs": { + "noise_seed": 42, + "seed_gen_type": "fixed" + } + }, + "4984": { + "class_type": "ManualSigmas", + "inputs": { + "sigmas": "1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875, 0.0" + } + }, + "4985": { + "class_type": "ManualSigmas", + "inputs": { + "sigmas": "0.85, 0.7250, 0.4219, 0.0" + } + }, + "4969": { + "class_type": "LTXVConcatAVLatent", + "inputs": { + "video_latent": [ + "4970", + 0 + ], + "audio_latent": [ + "4845", + 1 + ] + } + }, + "4970": { + "class_type": "LTXVImgToVideoConditionOnly", + "inputs": { + "vae": [ + "3940", + 2 + ], + "image": [ + "9990", + 0 + ], + "latent": [ + "4975", + 0 + ], + "bypass": false, + "strength": 0.6 + } + }, + "4975": { + "class_type": "LTXVLatentUpsampler", + "inputs": { + "samples": [ + "4845", + 0 + ], + "upscale_model": [ + "4974", + 0 + ], + "vae": [ + "3940", + 2 + ] + } + }, + "4989": { + "class_type": "PrimitiveFloat", + "inputs": { + "value": 24 + } + }, + "4988": { + "class_type": "PrimitiveInt", + "inputs": { + "value": 121 + } + }, + "4981": { + "class_type": "GemmaAPITextEncode", + "inputs": { + "api_key": "", + "prompt": "pc game, console game, video game, cartoon, childish, ugly", + "use_bolt": false + } + }, + "4010": { + "class_type": "LTXVAudioVAELoader", + "inputs": { + "ckpt_name": "ltx-2.3-22b-dev.safetensors" + } + }, + "3940": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "ltx-2.3-22b-dev.safetensors" + } + }, + "4922": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": [ + "3940", + 0 + ], + "lora_name": "ltxv/ltx2/ltx-2.3-22b-distilled-lora-384-1.1.safetensors", + "strength_model": 0.5 + } + }, + "4974": { + "class_type": "LatentUpscaleModelLoader", + "inputs": { + "model_name": "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + } + }, + "4982": { + "class_type": "LTXAVTextEncoderLoader", + "inputs": { + "text_encoder": "comfy_gemma_3_12B_it.safetensors", + "ckpt_name": "ltx-2.3-22b-dev.safetensors", + "device": "default" + } + }, + "4845": { + "class_type": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": [ + "4829", + 0 + ] + } + }, + "4973": { + "class_type": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": [ + "4971", + 0 + ] + } + }, + "4848": { + "class_type": "LTXVAudioVAEDecode", + "inputs": { + "samples": [ + "4973", + 1 + ], + "audio_vae": [ + "4010", + 0 + ] + } + }, + "4849": { + "class_type": "CreateVideo", + "inputs": { + "images": [ + "4995", + 0 + ], + "audio": [ + "4848", + 0 + ], + "fps": [ + "4989", + 0 + ] + } + }, + "4852": { + "class_type": "SaveVideo", + "inputs": { + "video": [ + "4849", + 0 + ], + "filename_prefix": "output", + "format": "auto", + "codec": "auto" + } + }, + "4995": { + "class_type": "LTXVTiledVAEDecode", + "inputs": { + "vae": [ + "3940", + 2 + ], + "latents": [ + "4973", + 0 + ], + "horizontal_tiles": 2, + "vertical_tiles": 2, + "overlap": 6, + "last_frame_fix": false + } + }, + "2483": { + "class_type": "CLIPTextEncode", + "inputs": { + "clip": [ + "4982", + 0 + ], + "text": "Starting from the spiraling mahogany walkway. 0-6s: The bookish adventurer winds the key on her mechanical lantern; it flares to life with a warm cognac glow that pushes amber light across the polished mahogany and brass railings. She looks up the spiraling atrium. Camera is low-angle looking up the spiral, slowly craning upward to reveal the impossible vertical scale of living bookshelves stretching into warm golden haze. Three leather-bound books with paper wings flutter past in the foreground, leaving trails of warm parchment-light that fade like sparkler embers. Her expression: quiet anticipation, eyes widening slightly, pupils catching the lantern flare.\n\n6-14s: She begins walking up the spiraling walkway. The camera tracks beside her at medium distance while simultaneously craning upward, creating a spiraling camera path that mirrors the architecture. Dozens of books take flight from the shelves, their paper wings beating with overlapping action on staggered timing\u2014each book moves on a slightly different flutter rhythm, creating a living swarm rather than synchronized movement. The books leave ink-sparkle trails that swirl in the warm air. Dust motes dance in the light beams, each mote on its own lazy independent drift path. Her hair and messenger bag strap bounce with follow-through motion lagging 4 frames behind her stride. She reaches out to let a small poetry book brush her fingertips, and it leaves a smudge of warm gold ink on her skin that glows faintly.\n14-20s: She stops at a brass railing, leaning over to look down into the infinite depth below. The camera pushes in past her shoulder to reveal the vertiginous drop, then whips back up as a massive atlas-book with broad paper wings swoops down from the upper shelves. Its wing-shadow flits across the stained-glass window, momentarily darkening the ruby and amber geometric patterns cast across the floor and her face. She gasps\u2014expression shifting from awe to pure wonder\u2014and reaches out. The atlas hovers, pages riffling with secondary motion, releasing a burst of ink-sparkles that catch the lantern light and glow like warm fireflies in the cognac-toned air.\n20-25s: The camera slowly orbits 60 degrees around her as she raises the lantern toward the hovering atlas. The lantern's warm glow and the stained-glass ruby/amber patterns mix on her face, creating dynamic color bleeding that shifts across her skin as the orbit progresses. She smiles, a crooked, curious grin. The ink-sparkles spiral around the lantern in a helix formation, their light reflecting in her large expressive eyes. Camera dollies in for an extreme close-up of her eyes, which reflect the infinite spiraling bookshelves above. A single dust mote drifts through the frame in foreground, shallow depth of field keeping it creamy. Final hold: the mechanical lantern ticks softly, its glow pulsing once, and a distant rolling ladder rumbles along its brass track." + } + }, + "2004": { + "class_type": "LoadImage", + "inputs": { + "image": "example.png" + } + }, + "4987": { + "class_type": "PrimitiveBoolean", + "inputs": { + "value": true + } + }, + "5000": { + "class_type": "LTXFloatToInt", + "inputs": { + "a": 0 + } + }, + "9990": { + "class_type": "ImageScale", + "inputs": { + "image": [ + "2004", + 0 + ], + "upscale_method": "lanczos", + "width": 1536, + "height": 1536, + "crop": "disabled" + } + }, + "9001": { + "class_type": "ConditioningZeroOut", + "inputs": { + "conditioning": [ + "1241", + 0 + ] + } + } +} \ No newline at end of file diff --git a/workflows/api/ltx_t2v_pure.json b/workflows/api/ltx_t2v_pure.json new file mode 100644 index 0000000..7747332 --- /dev/null +++ b/workflows/api/ltx_t2v_pure.json @@ -0,0 +1,461 @@ +{ + "3059": { + "class_type": "EmptyLTXVLatentVideo", + "inputs": { + "length": 121, + "width": 960, + "height": 544, + "batch_size": 1 + } + }, + "3980": { + "class_type": "LTXVEmptyLatentAudio", + "inputs": { + "audio_vae": [ + "4010", + 0 + ], + "frames_number": 97, + "frame_rate": 25, + "batch_size": 1 + } + }, + "4528": { + "class_type": "LTXVConcatAVLatent", + "inputs": { + "video_latent": [ + "3159", + 0 + ], + "audio_latent": [ + "3980", + 0 + ] + } + }, + "3159": { + "class_type": "LTXVImgToVideoConditionOnly", + "inputs": { + "vae": [ + "3940", + 2 + ], + "image": [ + "3336", + 0 + ], + "latent": [ + "3059", + 0 + ], + "bypass": false, + "strength": 0.7 + } + }, + "1241": { + "class_type": "LTXVConditioning", + "inputs": { + "positive": [ + "2483", + 0 + ], + "negative": [ + "2612", + 0 + ], + "frame_rate": 24 + } + }, + "4976": { + "class_type": "KSamplerSelect", + "inputs": { + "sampler_name": "euler_cfg_pp" + } + }, + "4971": { + "class_type": "SamplerCustomAdvanced", + "inputs": { + "noise": [ + "4967", + 0 + ], + "guider": [ + "4964", + 0 + ], + "sampler": [ + "4976", + 0 + ], + "sigmas": [ + "4985", + 0 + ], + "latent_image": [ + "4969", + 0 + ] + } + }, + "4964": { + "class_type": "CFGGuider", + "inputs": { + "model": [ + "4922", + 0 + ], + "positive": [ + "1241", + 0 + ], + "negative": [ + "9001", + 0 + ], + "cfg": 1 + } + }, + "4980": { + "class_type": "GemmaAPITextEncode", + "inputs": { + "api_key": "", + "prompt": "", + "use_bolt": "ltx-2.3-22b-dev.safetensors" + } + }, + "4979": { + "class_type": "PrimitiveString", + "inputs": { + "value": "" + } + }, + "3336": { + "class_type": "LTXVPreprocess", + "inputs": { + "image": [ + "9990", + 0 + ], + "target_fps": 18, + "img_compression": 3 + } + }, + "2612": { + "class_type": "CLIPTextEncode", + "inputs": { + "clip": [ + "4982", + 0 + ], + "text": "Starting from the spiraling mahogany walkway. 0-6s: The bookish adventurer winds the key on her mechanical lantern; it flares to life with a warm cognac glow that pushes amber light across the polished mahogany and brass railings. She looks up the spiraling atrium. Camera is low-angle looking up the spiral, slowly craning upward to reveal the impossible vertical scale of living bookshelves stretching into warm golden haze. Three leather-bound books with paper wings flutter past in the foreground, leaving trails of warm parchment-light that fade like sparkler embers. Her expression: quiet anticipation, eyes widening slightly, pupils catching the lantern flare.\n\n6-14s: She begins walking up the spiraling walkway. The camera tracks beside her at medium distance while simultaneously craning upward, creating a spiraling camera path that mirrors the architecture. Dozens of books take flight from the shelves, their paper wings beating with overlapping action on staggered timing\u2014each book moves on a slightly different flutter rhythm, creating a living swarm rather than synchronized movement. The books leave ink-sparkle trails that swirl in the warm air. Dust motes dance in the light beams, each mote on its own lazy independent drift path. Her hair and messenger bag strap bounce with follow-through motion lagging 4 frames behind her stride. She reaches out to let a small poetry book brush her fingertips, and it leaves a smudge of warm gold ink on her skin that glows faintly.\n14-20s: She stops at a brass railing, leaning over to look down into the infinite depth below. The camera pushes in past her shoulder to reveal the vertiginous drop, then whips back up as a massive atlas-book with broad paper wings swoops down from the upper shelves. Its wing-shadow flits across the stained-glass window, momentarily darkening the ruby and amber geometric patterns cast across the floor and her face. She gasps\u2014expression shifting from awe to pure wonder\u2014and reaches out. The atlas hovers, pages riffling with secondary motion, releasing a burst of ink-sparkles that catch the lantern light and glow like warm fireflies in the cognac-toned air.\n20-25s: The camera slowly orbits 60 degrees around her as she raises the lantern toward the hovering atlas. The lantern's warm glow and the stained-glass ruby/amber patterns mix on her face, creating dynamic color bleeding that shifts across her skin as the orbit progresses. She smiles, a crooked, curious grin. The ink-sparkles spiral around the lantern in a helix formation, their light reflecting in her large expressive eyes. Camera dollies in for an extreme close-up of her eyes, which reflect the infinite spiraling bookshelves above. A single dust mote drifts through the frame in foreground, shallow depth of field keeping it creamy. Final hold: the mechanical lantern ticks softly, its glow pulsing once, and a distant rolling ladder rumbles along its brass track." + } + }, + "4832": { + "class_type": "RandomNoise", + "inputs": { + "noise_seed": 43, + "seed_gen_type": "fixed" + } + }, + "4828": { + "class_type": "CFGGuider", + "inputs": { + "model": [ + "4922", + 0 + ], + "positive": [ + "1241", + 0 + ], + "negative": [ + "9001", + 0 + ], + "cfg": 1 + } + }, + "4831": { + "class_type": "KSamplerSelect", + "inputs": { + "sampler_name": "euler_ancestral_cfg_pp" + } + }, + "4829": { + "class_type": "SamplerCustomAdvanced", + "inputs": { + "noise": [ + "4832", + 0 + ], + "guider": [ + "4828", + 0 + ], + "sampler": [ + "4831", + 0 + ], + "sigmas": [ + "4984", + 0 + ], + "latent_image": [ + "4528", + 0 + ] + } + }, + "4967": { + "class_type": "RandomNoise", + "inputs": { + "noise_seed": 42, + "seed_gen_type": "fixed" + } + }, + "4984": { + "class_type": "ManualSigmas", + "inputs": { + "sigmas": "1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875, 0.0" + } + }, + "4985": { + "class_type": "ManualSigmas", + "inputs": { + "sigmas": "0.85, 0.7250, 0.4219, 0.0" + } + }, + "4969": { + "class_type": "LTXVConcatAVLatent", + "inputs": { + "video_latent": [ + "4970", + 0 + ], + "audio_latent": [ + "4845", + 1 + ] + } + }, + "4970": { + "class_type": "LTXVImgToVideoConditionOnly", + "inputs": { + "vae": [ + "3940", + 2 + ], + "image": [ + "9990", + 0 + ], + "latent": [ + "4975", + 0 + ], + "bypass": false, + "strength": 1 + } + }, + "4975": { + "class_type": "LTXVLatentUpsampler", + "inputs": { + "samples": [ + "4845", + 0 + ], + "upscale_model": [ + "4974", + 0 + ], + "vae": [ + "3940", + 2 + ] + } + }, + "4989": { + "class_type": "PrimitiveFloat", + "inputs": { + "value": 24 + } + }, + "4988": { + "class_type": "PrimitiveInt", + "inputs": { + "value": 121 + } + }, + "4981": { + "class_type": "GemmaAPITextEncode", + "inputs": { + "api_key": "", + "prompt": "pc game, console game, video game, cartoon, childish, ugly", + "use_bolt": false + } + }, + "4010": { + "class_type": "LTXVAudioVAELoader", + "inputs": { + "ckpt_name": "ltx-2.3-22b-dev.safetensors" + } + }, + "3940": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "ltx-2.3-22b-dev.safetensors" + } + }, + "4922": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": [ + "3940", + 0 + ], + "lora_name": "ltxv/ltx2/ltx-2.3-22b-distilled-lora-384-1.1.safetensors", + "strength_model": 0.5 + } + }, + "4974": { + "class_type": "LatentUpscaleModelLoader", + "inputs": { + "model_name": "ltx-2.3-spatial-upscaler-x2-1.1.safetensors" + } + }, + "4982": { + "class_type": "LTXAVTextEncoderLoader", + "inputs": { + "text_encoder": "comfy_gemma_3_12B_it.safetensors", + "ckpt_name": "ltx-2.3-22b-dev.safetensors", + "device": "default" + } + }, + "4845": { + "class_type": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": [ + "4829", + 0 + ] + } + }, + "4973": { + "class_type": "LTXVSeparateAVLatent", + "inputs": { + "av_latent": [ + "4971", + 0 + ] + } + }, + "4848": { + "class_type": "LTXVAudioVAEDecode", + "inputs": { + "samples": [ + "4973", + 1 + ], + "audio_vae": [ + "4010", + 0 + ] + } + }, + "4849": { + "class_type": "CreateVideo", + "inputs": { + "images": [ + "4995", + 0 + ], + "audio": [ + "4848", + 0 + ], + "fps": [ + "4989", + 0 + ] + } + }, + "4852": { + "class_type": "SaveVideo", + "inputs": { + "video": [ + "4849", + 0 + ], + "filename_prefix": "output", + "format": "auto", + "codec": "auto" + } + }, + "4995": { + "class_type": "LTXVTiledVAEDecode", + "inputs": { + "vae": [ + "3940", + 2 + ], + "latents": [ + "4973", + 0 + ], + "horizontal_tiles": 2, + "vertical_tiles": 2, + "overlap": 6, + "last_frame_fix": false + } + }, + "2483": { + "class_type": "CLIPTextEncode", + "inputs": { + "clip": [ + "4982", + 0 + ], + "text": "Starting from the spiraling mahogany walkway. 0-6s: The bookish adventurer winds the key on her mechanical lantern; it flares to life with a warm cognac glow that pushes amber light across the polished mahogany and brass railings. She looks up the spiraling atrium. Camera is low-angle looking up the spiral, slowly craning upward to reveal the impossible vertical scale of living bookshelves stretching into warm golden haze. Three leather-bound books with paper wings flutter past in the foreground, leaving trails of warm parchment-light that fade like sparkler embers. Her expression: quiet anticipation, eyes widening slightly, pupils catching the lantern flare.\n\n6-14s: She begins walking up the spiraling walkway. The camera tracks beside her at medium distance while simultaneously craning upward, creating a spiraling camera path that mirrors the architecture. Dozens of books take flight from the shelves, their paper wings beating with overlapping action on staggered timing\u2014each book moves on a slightly different flutter rhythm, creating a living swarm rather than synchronized movement. The books leave ink-sparkle trails that swirl in the warm air. Dust motes dance in the light beams, each mote on its own lazy independent drift path. Her hair and messenger bag strap bounce with follow-through motion lagging 4 frames behind her stride. She reaches out to let a small poetry book brush her fingertips, and it leaves a smudge of warm gold ink on her skin that glows faintly.\n14-20s: She stops at a brass railing, leaning over to look down into the infinite depth below. The camera pushes in past her shoulder to reveal the vertiginous drop, then whips back up as a massive atlas-book with broad paper wings swoops down from the upper shelves. Its wing-shadow flits across the stained-glass window, momentarily darkening the ruby and amber geometric patterns cast across the floor and her face. She gasps\u2014expression shifting from awe to pure wonder\u2014and reaches out. The atlas hovers, pages riffling with secondary motion, releasing a burst of ink-sparkles that catch the lantern light and glow like warm fireflies in the cognac-toned air.\n20-25s: The camera slowly orbits 60 degrees around her as she raises the lantern toward the hovering atlas. The lantern's warm glow and the stained-glass ruby/amber patterns mix on her face, creating dynamic color bleeding that shifts across her skin as the orbit progresses. She smiles, a crooked, curious grin. The ink-sparkles spiral around the lantern in a helix formation, their light reflecting in her large expressive eyes. Camera dollies in for an extreme close-up of her eyes, which reflect the infinite spiraling bookshelves above. A single dust mote drifts through the frame in foreground, shallow depth of field keeping it creamy. Final hold: the mechanical lantern ticks softly, its glow pulsing once, and a distant rolling ladder rumbles along its brass track." + } + }, + "2004": { + "class_type": "LoadImage", + "inputs": { + "image": "example.png" + } + }, + "4987": { + "class_type": "PrimitiveBoolean", + "inputs": { + "value": true + } + }, + "5000": { + "class_type": "LTXFloatToInt", + "inputs": { + "a": 0 + } + }, + "9990": { + "class_type": "ImageScale", + "inputs": { + "image": [ + "2004", + 0 + ], + "upscale_method": "lanczos", + "width": 1536, + "height": 1536, + "crop": "disabled" + } + }, + "9001": { + "class_type": "ConditioningZeroOut", + "inputs": { + "conditioning": [ + "1241", + 0 + ] + } + } +} \ No newline at end of file diff --git a/workflows/api/ltx_t2v_vbvr.json b/workflows/api/ltx_t2v_vbvr.json new file mode 100644 index 0000000..5b8ddfc --- /dev/null +++ b/workflows/api/ltx_t2v_vbvr.json @@ -0,0 +1,127 @@ +{ + "3": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "ltx-2.3-22b-distilled-1.1_transformer_only_fp8_scaled.safetensors" + } + }, + "4": { + "class_type": "LTXAVTextEncoderLoader", + "inputs": { + "text_encoder": "comfy_gemma_3_12B_it.safetensors", + "ckpt_name": "ltx-2.3-22b-distilled-1.1_transformer_only_fp8_scaled.safetensors", + "device": "default" + } + }, + "5": { + "class_type": "EmptyLTXVLatentVideo", + "inputs": { + "length": 128, + "width": 960, + "height": 544, + "batch_size": 1 + } + }, + "6": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "", + "clip": ["$from_node(4, 0)", 0] + } + }, + "7": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "blurry, low quality, distorted, watermark, bad anatomy, ugly, deformed", + "clip": ["$from_node(4, 0)", 0] + } + }, + "8": { + "class_type": "LTXVConditioning", + "inputs": { + "positive": ["$from_node(6, 0)", 0], + "negative": ["$from_node(7, 0)", 0], + "frame_rate": 24 + } + }, + "9": { + "class_type": "RandomNoise", + "inputs": { + "noise_seed": 12345, + "seed_gen_type": "fixed" + } + }, + "10": { + "class_type": "CFGGuider", + "inputs": { + "model": ["$from_node(14, 0)", 0], + "positive": ["$from_node(8, 0)", 0], + "negative": ["$from_node(12, 0)", 0], + "cfg": 1 + } + }, + "11": { + "class_type": "KSamplerSelect", + "inputs": { + "sampler_name": "euler_cfg_pp" + } + }, + "12": { + "class_type": "ConditioningZeroOut", + "inputs": { + "conditioning": ["$from_node(8, 0)", 0] + } + }, + "13": { + "class_type": "ManualSigmas", + "inputs": { + "sigmas": "1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875, 0.0" + } + }, + "14": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": ["$from_node(3, 0)", 0], + "lora_name": "Ltx2.3-Licon-VBVR-I2V-240K-R32.safetensors", + "strength_model": 1.0 + } + }, + "15": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": ["$from_node(14, 0)", 0], + "lora_name": "ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors", + "strength_model": 1.0 + } + }, + "16": { + "class_type": "SamplerCustomAdvanced", + "inputs": { + "noise": ["$from_node(9, 0)", 0], + "guider": ["$from_node(10, 0)", 0], + "sampler": ["$from_node(11, 0)", 0], + "sigmas": ["$from_node(13, 0)", 0], + "latent_image": ["$from_node(5, 0)", 0] + } + }, + "17": { + "class_type": "LTXVTiledVAEDecode", + "inputs": { + "vae": ["$from_node(3, 0)", 2], + "latents": ["$from_node(16, 0)", 0], + "horizontal_tiles": 2, + "vertical_tiles": 2, + "overlap": 6, + "last_frame_fix": false + } + }, + "18": { + "class_type": "SaveVideo", + "inputs": { + "video": ["$from_node(17, 0)", 0], + "filename_prefix": "beach_v11", + "format": "mp4", + "codec": "h264" + } + } +}