#!/usr/bin/env bash
set -euo pipefail

BASE=/home/wildlama/comfy/ComfyUI
MODELS="$BASE/models"
SRC_IMG=/home/wildlama/comfy/zimage-outputs/zimage_latina_00001_.png
INPUT_IMG="$BASE/input/ltx23_latina.png"
WF_UI=/home/wildlama/comfy/ComfyUI/.venv/lib/python3.11/site-packages/comfyui_workflow_templates_media_video/templates/video_ltx2_3_i2v.json
WF_API=/home/wildlama/comfy/workflows/ltx23_latina_i2v_api.json
OUT=/home/wildlama/comfy/ltx23-outputs
CLEAN_PATH=/home/wildlama/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
mkdir -p "$MODELS/checkpoints" "$MODELS/text_encoders" "$MODELS/loras" "$MODELS/latent_upscale_models" "$BASE/input" "$OUT" /home/wildlama/comfy/workflows

log(){ printf '[ltx23-robot] %s\n' "$*"; }

download(){
  local url="$1" out="$2" min_bytes="$3"
  if [ -s "$out" ]; then
    local size; size=$(stat -c%s "$out")
    if [ "$size" -ge "$min_bytes" ]; then log "complete: $(basename "$out") ($size bytes)"; return; fi
    log "resume: $(basename "$out") ($size / $min_bytes bytes)"
  else
    log "download: $(basename "$out")"
  fi
  env -u VIRTUAL_ENV PATH="$CLEAN_PATH" curl -L --fail --retry 50 --retry-delay 10 -C - -o "$out" "$url"
  local size; size=$(stat -c%s "$out")
  if [ "$size" -lt "$min_bytes" ]; then log "ERROR too small: $out ($size < $min_bytes)" >&2; exit 1; fi
}

# LTX 2.3 official local model set from the template note.
download 'https://huggingface.co/Lightricks/LTX-2.3-fp8/resolve/main/ltx-2.3-22b-dev-fp8.safetensors' "$MODELS/checkpoints/ltx-2.3-22b-dev-fp8.safetensors" 29100000000
download 'https://huggingface.co/Comfy-Org/ltx-2/resolve/main/split_files/text_encoders/gemma_3_12B_it_fp4_mixed.safetensors' "$MODELS/text_encoders/gemma_3_12B_it_fp4_mixed.safetensors" 9400000000
download 'https://huggingface.co/Comfy-Org/ltx-2.3/resolve/main/split_files/loras/ltx_2.3_22b_distilled_1.1_lora_dynamic_fro09_avg_rank_111_bf16.safetensors' "$MODELS/loras/ltx_2.3_22b_distilled_1.1_lora_dynamic_fro09_avg_rank_111_bf16.safetensors" 2700000000
download 'https://huggingface.co/Comfy-Org/ltx-2/resolve/main/split_files/loras/gemma-3-12b-it-abliterated_lora_rank64_bf16.safetensors' "$MODELS/loras/gemma-3-12b-it-abliterated_lora_rank64_bf16.safetensors" 620000000
download 'https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-spatial-upscaler-x2-1.1.safetensors' "$MODELS/latent_upscale_models/ltx-2.3-spatial-upscaler-x2-1.1.safetensors" 990000000

cp "$SRC_IMG" "$INPUT_IMG"

log "launch ComfyUI"
env -u VIRTUAL_ENV PATH="$CLEAN_PATH" comfy --workspace "$BASE" launch --background -- --listen 0.0.0.0 --port 8188 || true
for i in $(seq 1 180); do
  if curl -fsS http://127.0.0.1:8188/system_stats >/dev/null 2>&1; then break; fi
  sleep 2
  if [ "$i" = 180 ]; then log "ERROR ComfyUI not ready" >&2; exit 1; fi
done
log "ComfyUI ready"

log "convert LTX 2.3 workflow"
env -u VIRTUAL_ENV PATH="$CLEAN_PATH" comfy --workspace "$BASE" run --workflow "$WF_UI" --host 127.0.0.1 --port 8188 --print-prompt > /tmp/ltx23_prompt_raw.json
python3 - <<'PY'
import json, pathlib, random
raw = pathlib.Path('/tmp/ltx23_prompt_raw.json').read_text()
start = raw.find('{')
if start < 0: raise SystemExit('No JSON from conversion')
prompt = json.loads(raw[start:])
for node in prompt.values():
    if not isinstance(node, dict): continue
    ct=node.get('class_type'); inp=node.get('inputs',{})
    if ct == 'LoadImage':
        inp['image']='ltx23_latina.png'
        if 'upload' in inp: inp['upload']='image'
    elif ct in ('CheckpointLoaderSimple','LTXVAudioVAELoader'):
        # LTXVAudioVAELoader uses this as model name too.
        for k in list(inp.keys()):
            if 'ckpt' in k or 'model' in k:
                inp[k]='ltx-2.3-22b-dev-fp8.safetensors'
    elif ct == 'LTXAVTextEncoderLoader':
        inp['clip_name']='gemma_3_12B_it_fp4_mixed.safetensors'
        inp['model_name']='ltx-2.3-22b-dev-fp8.safetensors'
    elif ct in ('LoraLoader','LoraLoaderModelOnly'):
        # Keep official loras; names should already match. Lower strengths slightly for a cleaner portrait.
        if 'strength_model' in inp and 'gemma' not in str(inp.get('lora_name','')).lower(): inp['strength_model']=0.45
    elif ct == 'LatentUpscaleModelLoader':
        inp['model_name']='ltx-2.3-spatial-upscaler-x2-1.1.safetensors'
    elif ct == 'LoadImage':
        inp['image']='ltx23_latina.png'
    elif ct == 'CLIPTextEncode':
        old=str(inp.get('text','')).lower()
        if 'ugly' in old or 'cartoon' in old or 'pc game' in old:
            inp['text']='ugly, distorted face, deformed, low quality, jitter, motion smear, bad anatomy, cartoon, video game, oversharpened'
        else:
            inp['text']=('A realistic cinematic video portrait of the same beautiful adult Latina woman in a red dress. '
                         'She gently turns her head toward camera, blinks naturally, subtle smile, hair moves softly in a light breeze, '
                         'warm golden-hour light on her face, elegant confident pose, shallow depth of field, smooth slow camera push-in, no dialogue.')
    elif ct == 'PrimitiveStringMultiline':
        inp['value']=('A realistic cinematic video portrait of the same beautiful adult Latina woman in a red dress. '
                      'She gently turns her head toward camera, blinks naturally, subtle smile, hair moves softly in a light breeze, '
                      'warm golden-hour light on her face, elegant confident pose, shallow depth of field, smooth slow camera push-in, no dialogue.')
    elif ct in ('RandomNoise',):
        inp['noise_seed']=random.randint(1, 2**48-1)
        if 'control_after_generate' in inp: inp['control_after_generate']='randomize'
    elif ct == 'EmptyLTXVLatentVideo':
        inp['width']=768; inp['height']=512; inp['length']=97; inp['batch_size']=1
    elif ct == 'ResizeImageMaskNode':
        # Avoid local 1080p OOM; keep the LTX 2.3 pipeline but use 1280x720 final upscale.
        inp['resize_type.width']=1280; inp['resize_type.height']=720; inp['resize_type.crop']='center'
    elif ct == 'PrimitiveInt':
        # If used for final dims/frame settings, keep local-friendly values.
        if inp.get('value') == 1920: inp['value']=1280
        elif inp.get('value') == 1088: inp['value']=720
    elif ct == 'SaveVideo':
        inp['filename_prefix']='ltx23_latina'
pathlib.Path('/home/wildlama/comfy/workflows/ltx23_latina_i2v_api.json').write_text(json.dumps(prompt, indent=2))
print('wrote api workflow with', len(prompt), 'nodes')
PY

log "run LTX 2.3 workflow"
RESULT=$(python3 /home/wildlama/.hermes/skills/creative/comfyui/scripts/run_workflow.py --workflow "$WF_API" --output-dir "$OUT" --timeout 2400)
printf '%s\n' "$RESULT" > "$OUT/result.json"
MEDIA=$(python3 - <<'PY'
import json
r=json.load(open('/home/wildlama/comfy/ltx23-outputs/result.json'))
outs=r.get('outputs') or []
for o in outs:
    if o.get('type') == 'video' or str(o.get('file','')).lower().endswith(('.mp4','.webm','.gif')):
        print(o['file']); break
else:
    print(outs[0]['file'] if outs else '')
PY
)
if [ -z "$MEDIA" ] || [ ! -s "$MEDIA" ]; then log "ERROR no output media" >&2; cat "$OUT/result.json" >&2; exit 1; fi
FINAL="$MEDIA"
if command -v ffmpeg >/dev/null 2>&1 && [[ "$MEDIA" == *.mp4 ]]; then
  FINAL="$OUT/ltx23_latina_telegram.mp4"
  ffmpeg -y -i "$MEDIA" -c:v libx264 -profile:v main -preset medium -crf 15 -pix_fmt yuv420p -an "$FINAL" >/dev/null 2>&1 || FINAL="$MEDIA"
fi
log "DONE video: $FINAL"
printf '\nLTX 2.3 terminé ✅\nMEDIA:%s\n' "$FINAL"
