Fix: flatten comfyui and ltx-video-pipeline to Gitea convention (flat skill dirs). comfyui v5.1.0, ltx-video-pipeline v2.0.0.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# Modify-and-Re-Render Recipe
|
||||
|
||||
Proven pattern for changing settings on existing workflow JSONs and re-rendering without rebuilding from scratch. Used 2026-07-22 for the sampler+duration 2-clip test.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Changing sampler, steps, scheduler, duration, or other render settings
|
||||
- Testing a new LoRA or model chain variant on existing scenes
|
||||
- Re-running a failed batch with corrected settings
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Copy existing workflow JSONs** to `_v2` variants (preserve originals):
|
||||
```bash
|
||||
cp /tmp/api_2clip_C1.json /tmp/api_2clip_C1_v2.json
|
||||
cp /tmp/api_2clip_C2.json /tmp/api_2clip_C2_v2.json
|
||||
```
|
||||
|
||||
2. **Modify in-place with Python via scp** (SSH heredoc quoting is fragile — scp the script, then run it):
|
||||
|
||||
Write the modification script locally, scp it to .202, then execute:
|
||||
```bash
|
||||
# Write script locally
|
||||
cat > /tmp/modify_v3.py << 'PYEOF'
|
||||
import json
|
||||
for fname in ["/tmp/api_boss_C1_v3.json", "/tmp/api_boss_C2_v3.json"]:
|
||||
with open(fname) as f:
|
||||
d = json.load(f)
|
||||
p = d["prompt"]
|
||||
|
||||
# Sampler change (node 20 is KSamplerSelect, not KSampler)
|
||||
p["20"]["inputs"]["sampler_name"] = "euler"
|
||||
|
||||
# Duration change (node-level)
|
||||
p["131"]["inputs"]["duration_frames"] = 121
|
||||
p["131"]["inputs"]["end_frame"] = 121
|
||||
p["131"]["inputs"]["duration_seconds"] = 5.04
|
||||
|
||||
# Duration change (timeline_data JSON — CRITICAL, don't skip)
|
||||
td = json.loads(p["131"]["inputs"]["timeline_data"])
|
||||
td["normalDurationFrames"] = 121
|
||||
for seg in td["segments"]:
|
||||
seg["length"] = 121
|
||||
seg["end"] = 5.04
|
||||
p["131"]["inputs"]["timeline_data"] = json.dumps(td)
|
||||
|
||||
with open(fname, "w") as f:
|
||||
json.dump(d, f, indent=2)
|
||||
print(f"Updated {fname}: sampler={p['20']['inputs']['sampler_name']}")
|
||||
PYEOF
|
||||
|
||||
# scp to .202 and run
|
||||
sshpass -p 'passw0rd' scp /tmp/modify_v3.py [email protected]:/tmp/
|
||||
sshpass -p 'passw0rd' ssh [email protected] python3 /tmp/modify_v3.py
|
||||
```
|
||||
|
||||
**Why scp, not heredoc:** SSH heredoc quoting strips quotes from Python string-keyed dicts, causing `KeyError` on nodes like `"20"`. The scp approach preserves the script exactly as written. Confirmed 2026-07-22: heredoc failed twice (KeyError on node 20); scp worked first try.
|
||||
|
||||
3. **Verify all changes** before submitting:
|
||||
```python
|
||||
for fname in files:
|
||||
d = json.load(open(fname))
|
||||
p = d["prompt"]
|
||||
assert p["20"]["inputs"]["sampler_name"] == "euler_ancestral_cfg_pp"
|
||||
assert p["131"]["inputs"]["duration_frames"] == 121
|
||||
td = json.loads(p["131"]["inputs"]["timeline_data"])
|
||||
assert td["segments"][0]["length"] == 121
|
||||
```
|
||||
|
||||
4. **Submit to queue:**
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8188/prompt -H "Content-Type: application/json" -d @/tmp/api_2clip_C1_v2.json
|
||||
curl -s -X POST http://localhost:8188/prompt -H "Content-Type: application/json" -d @/tmp/api_2clip_C2_v2.json
|
||||
```
|
||||
|
||||
5. **Wait for queue drain** (blocking poll from Hermes host):
|
||||
```bash
|
||||
sshpass -p 'passw0rd' ssh [email protected] "while true; do q=\$(curl -s http://localhost:8188/queue | python3 -c \"import json,sys; d=json.load(sys.stdin); print(len(d['queue_running'])+len(d['queue_pending']))\"); if [ \"\$q\" -eq 0 ]; then echo 'QUEUE_EMPTY'; break; fi; echo \"queue: \$q\"; sleep 30; done"
|
||||
```
|
||||
|
||||
6. **Identify new outputs** (search both output directories):
|
||||
```bash
|
||||
find ~/comfy-ui/output -name "*.mp4" -newer /tmp/api_2clip_C1_v2.json -type f
|
||||
```
|
||||
|
||||
7. **Verify frame counts:**
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries stream=nb_frames,codec_name,width,height,duration -of csv=p=0 <file>
|
||||
```
|
||||
|
||||
8. **Concat, scp, upload to TrueNAS** (standard pipeline steps 7-8).
|
||||
|
||||
## Key Pitfalls
|
||||
|
||||
- **sshpass is NOT on .202 — pull files from Hermes host instead of pushing from .202.** When transferring output files from .202 to the Hermes host, `sshpass` is not installed on .202. Use `sshpass -p 'passw0rd' scp n8n@10.0.0.202:<remote_path> <local_path>` from the Hermes host (pull), not `ssh ... 'sshpass scp ...'` from .202 (push). Confirmed 2026-07-22: push attempt failed with `sshpass: command not found`.
|
||||
- **CRITICAL: Verify you have the RIGHT template before modifying.** `/tmp/` accumulates multiple generations with confusingly similar names: `api_2clip_C1.json` (old living room), `api_cyberpunk_C1.json` (cyberpunk), `api_2clip_01.json` (earlier variant). ALWAYS inspect `imageFile` and `prompt` before modifying:
|
||||
```bash
|
||||
python3 -c "import json; d=json.load(open('/tmp/api_XXX.json')); td=json.loads(d['prompt']['131']['inputs']['timeline_data']); print(td['segments'][0]['imageFile'], td['segments'][0]['prompt'][:80])"
|
||||
```
|
||||
Confirmed 2026-07-22: pointed delegation at wrong template, produced living room output instead of cyberpunk.
|
||||
- **Must update BOTH node-level AND timeline_data JSON fields.** The LTXDirector reads `segment["length"]` from timeline_data for actual frame count. Node-level `duration_frames` alone won't change output duration.
|
||||
- **121 frames = 8×15+1** satisfies the 8n+1 requirement for LTX.
|
||||
- **5.04 seconds** is the correct `duration_seconds` for 121 frames at 24fps (121/24 ≈ 5.0417).
|
||||
- **Verify with ffprobe, not by file size.** File size varies with content complexity; frame count is the ground truth.
|
||||
- **Use ComfyUI history API to identify outputs reliably.** After queue drains, query each prompt_id:
|
||||
```bash
|
||||
curl -s http://localhost:8188/history/<prompt_id> | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
entry=d.get('<prompt_id>',{})
|
||||
outputs=entry.get('outputs',{})
|
||||
node37=outputs.get('37',{})
|
||||
images=node37.get('images',[])
|
||||
for img in images:
|
||||
print(img.get('filename','?'))
|
||||
"
|
||||
```
|
||||
This works regardless of which output directory the workflow uses. Fallback: `find ~/comfy-ui/output -name "*.mp4" -newer <reference_file> -type f`.
|
||||
Reference in New Issue
Block a user