119 lines
5.8 KiB
Markdown
119 lines
5.8 KiB
Markdown
# 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`.
|