Files
hermes-skills/ltx-video-pipeline/references/modify-and-re-render-recipe.md
T

5.8 KiB
Raw Blame History

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):

    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:

    # 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:

    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:

    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):

    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):

    find ~/comfy-ui/output -name "*.mp4" -newer /tmp/api_2clip_C1_v2.json -type f
    
  7. Verify frame counts:

    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 [email protected]:<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:
    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:
    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.