"""
FikoYT MVD - Muhteşem Video Üretim Merkezi
Sulphur t2v distilled tabanlı, T2V + I2V + native audio + LoRA stack + Türkçe gruplar.
"""
import json, copy, pathlib

SRC = pathlib.Path(r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\user\default\workflows\Sulphur t2v distilled.json")
DST = pathlib.Path(r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\user\default\workflows\FikoYT_MVD_Muhtesem_Video_Merkezi.json")

w = json.loads(SRC.read_text(encoding="utf-8"))

# Create placeholder image so LoadImage always has a valid file
ph = pathlib.Path(r"D:\Comfy\App\ComfyUI_windows_portable\ComfyUI\input\fikoyt_mvd_placeholder.png")
if not ph.exists():
    # 1280x720 dark gray PNG
    import struct, zlib
    W, H = 1280, 720
    raw = bytearray()
    for _ in range(H):
        raw.append(0)  # filter byte
        for _ in range(W):
            raw.extend([16, 18, 24])  # dark gray pixel RGB
    def chunk(t, d):
        return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
    png  = b"\x89PNG\r\n\x1a\n"
    png += chunk(b"IHDR", struct.pack(">IIBBBBB", W, H, 8, 2, 0, 0, 0))
    png += chunk(b"IDAT", zlib.compress(bytes(raw), 9))
    png += chunk(b"IEND", b"")
    ph.write_bytes(png)

# ============================================================================
# Helpers
# ============================================================================
def get_node(nid):
    for n in w["nodes"]:
        if n["id"] == nid:
            return n
    raise KeyError(f"node {nid} not found")

def new_link_id():
    w["last_link_id"] = w.get("last_link_id", 0) + 1
    return w["last_link_id"]

def new_node_id():
    w["last_node_id"] = w.get("last_node_id", 0) + 1
    return w["last_node_id"]

def add_link(from_node, from_slot, to_node, to_slot, ltype):
    """from_node: node id, from_slot: output index; to_node: node id, to_slot: input index"""
    lid = new_link_id()
    w["links"].append([lid, from_node, from_slot, to_node, to_slot, ltype])
    # update output's links
    nf = get_node(from_node)
    out = nf["outputs"][from_slot]
    if out.get("links") is None:
        out["links"] = []
    out["links"].append(lid)
    # update input's link
    nt = get_node(to_node)
    nt["inputs"][to_slot]["link"] = lid
    return lid

# ============================================================================
# 1. CLEAN DEFAULT PROMPT
# ============================================================================
# --- Detach old PrimitiveStringMultiline (#29) override from CLIPTextEncode (#30) ---
# Source workflow has #30.inputs.text wired via link 139 from #29 — it would override our widget value.
n30 = get_node(30)
for inp in n30.get("inputs", []):
    if inp.get("widget", {}).get("name") == "text":
        inp["link"] = None
# Remove link 139 from links list and from #29 outputs
w["links"] = [l for l in w["links"] if l[0] != 139]
try:
    n29 = get_node(29)
    for out in n29.get("outputs", []):
        out["links"] = [lid for lid in (out.get("links") or []) if lid != 139]
    n29["mode"] = 4  # mute the obsolete primitive
    n29["title"] = "(kullanılmıyor) Eski String"
except KeyError:
    pass

clean_prompt = (
    "A cinematic 4K product reveal commercial. Camera dolly-in toward a sleek matte-black object "
    "rotating on a marble pedestal, dramatic key light from upper-left, soft rim light, volumetric haze. "
    "Slow motion at 60% speed, shallow depth of field, color graded with teal-and-orange palette. "
    "Background: out-of-focus city night skyline with bokeh lights. "
    "Atmospheric soundtrack: a deep cinematic bass drone with subtle ticking percussion, building tension, "
    "ending with a soft impact and a whoosh. No dialogue, no watermarks, no text overlay."
)
get_node(30)["widgets_values"] = [clean_prompt]
# negative prompt
get_node(41)["widgets_values"] = [
    "low quality, blurry, jpeg artifacts, distorted, deformed, watermark, text overlay, logo, "
    "subtitles, ugly, cartoon, low resolution, oversaturated, washed out, flicker, jittery camera"
]

# ============================================================================
# 2. SENSIBLE DEFAULTS
# ============================================================================
# Width=1280, Height=720 (16:9 HD)
get_node(40)["widgets_values"] = [1280, "fixed"]; get_node(40)["title"] = "Genişlik (Width)"
get_node(25)["widgets_values"] = [720,  "fixed"]; get_node(25)["title"] = "Yükseklik (Height)"
# Length = 121 frames = ~5sn @ 24fps
get_node(27)["widgets_values"] = [121, "fixed"]; get_node(27)["title"] = "Kare Sayısı (Length)  ·  8n+1 olmalı (97/121/161/201/241)"
# Frame rate 24
get_node(26)["title"] = "FPS (24 öner)"
# Boolean switch title (T2V default)
get_node(28)["widgets_values"] = [True]
get_node(28)["title"] = "Mod: T2V (true) · I2V (false)"
# Save prefix
get_node(45)["widgets_values"] = ["video/FikoYT_MVD/reklam_klip", "auto", "auto"]
get_node(45)["title"] = "Video Kaydet (output/video/FikoYT_MVD/...)"
# Seed randomize already on (#2). Set the second noise (#1) to randomize too.
get_node(1)["widgets_values"] = [42, "randomize"]
get_node(2)["widgets_values"] = [0, "randomize"]
# CFG: 1.0 fits distilled; LTX 2.3 conditioning is via the LTXVConditioning node
# Keep CFG=1 (lcm sampler)

# ============================================================================
# 3. LoRA STACK - clean defaults
# ============================================================================
# #11 = first LoraLoaderModelOnly (currently muted) - turn into distilled LoRA stage1
n11 = get_node(11)
n11["widgets_values"] = ["ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors", 0.7]
n11["mode"] = 0  # enable
n11["title"] = "LoRA 1 · Distilled (stage1)"
# Connect model from #44 CheckpointLoaderSimple to #11 (model input)
# The chain we want: #44 -> #11 -> #46 -> #42 -> (#42 is CFGGuider for stage1)
# Current: #44.MODEL.link=138 goes to ... let's find
# Quick check: #46 LoraLoaderModelOnly has input.link=92, output goes to #42 (CFG stage1)
# #60 LoraLoaderModelOnly has input.link=110 output=90 (goes elsewhere - stage2 model path)
# Let's keep wiring and just set widgets. Models flow:
#   #44 -> #46 (lora 0 stage1) -> #42 CFG stage1
#   #44 -> #60 (lora 0 stage2) -> #8 CFG stage2
# 11, 49, 59 are extra LoRA slots in original — keep muted by default for 11, but they're in chain?
# Actually 49 and 59 are also in chain... let me leave as-is and just rename titles + adjust widgets

# Find all LoraLoaderModelOnly nodes and clean them up
lora_slots = []
for n in w["nodes"]:
    if n.get("type") == "LoraLoaderModelOnly":
        lora_slots.append(n)

# Set known defaults for known LoRAs (existing chain - widget values only, don't touch wiring)
# n#46 stage1 — distilled (main, always on)
# n#49 stage1 — extra (muted)
# n#60 stage2 — distilled (main)
# n#59 stage2 — extra (muted)
# n#11  stage1 — extra Camera Track (muted)
lora_defaults = {
    46: ("ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors", 0.7, 0, "LoRA Stage1 · Distilled (zorunlu)"),
    49: ("LTX2.3_AudioReactiveV1.safetensors",                                  0.6, 4, "LoRA Stage1 · Audio Reactive (ops, mute=4)"),
    11: ("LTX2.3-22B_IC-LoRA-Cameraman_v1_10500.safetensors",                   0.5, 4, "LoRA Stage1 · Cameraman (ops, mute=4)"),
    60: ("ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors", 0.7, 0, "LoRA Stage2 · Distilled (zorunlu)"),
    59: ("ltx-2.3-22b-ic-lora-motion-track-control-ref0.5.safetensors",         0.5, 4, "LoRA Stage2 · Motion Track (ops, mute=4)"),
}
for nid, (lora_name, strength, mode, title) in lora_defaults.items():
    try:
        n = get_node(nid)
    except KeyError:
        continue
    n["widgets_values"] = [lora_name, strength]
    n["mode"] = mode
    n["title"] = title

# ============================================================================
# 4. REWIRE LoRA chain to skip optional muted LoRAs
# ============================================================================
# Source had: #67 → #59 (muted) → #46 → #42 CFGGuider Stage1
#             #67 → #60 → #49 (muted) → #8 CFGGuider Stage2
# Rewire to:  #67 → #46 → #42 (skip #59)
#             #67 → #60 → #8  (skip #49)
def remove_link(lid):
    w["links"] = [l for l in w["links"] if l[0] != lid]
    for n in w["nodes"]:
        for inp in (n.get("inputs") or []):
            if inp.get("link") == lid:
                inp["link"] = None
        for out in (n.get("outputs") or []):
            out["links"] = [x for x in (out.get("links") or []) if x != lid]

# Drop link 92 (#59 → #46), 74 (#49 → #8), 110 (#67 → #60), 111 (#67 → #59), 138 (#44 → #67)
remove_link(92)
remove_link(74)
remove_link(110)
remove_link(111)
remove_link(138)
# Mute SageAttention (#67) — sageattention module not installed
get_node(67)["mode"] = 4
get_node(67)["title"] = "(devre dışı · sageattention yok) Sage Attention"
# Wire directly: #44 MODEL → #46 (Stage1 LoRA) → #42 CFGGuider Stage1
#                #44 MODEL → #60 (Stage2 LoRA) → #8  CFGGuider Stage2
add_link(44, 0, 46, 0, "MODEL")
add_link(44, 0, 60, 0, "MODEL")
add_link(60, 0, 8,  0, "MODEL")

# ============================================================================
# 5. ADD LOADIMAGE FOR I2V + CONNECT (always-on chain, bypass widget controls)
# ============================================================================
# Create LoadImage node
loadimg_id = new_node_id()
loadimg = {
    "id": loadimg_id,
    "type": "LoadImage",
    "pos": [2300.0, 5550.0],
    "size": [420, 380],
    "flags": {},
    "order": 20,
    "mode": 0,
    "inputs": [],
    "outputs": [
        {"name": "IMAGE", "type": "IMAGE", "links": []},
        {"name": "MASK",  "type": "MASK",  "links": []},
    ],
    "title": "Referans Görsel (I2V için yükle)",
    "properties": {
        "cnr_id": "comfy-core",
        "ver": "0.3.56",
        "Node name for S&R": "LoadImage",
        "ue_properties": {"widget_ue_connectable": {}, "version": "7.4.1", "input_ue_unconnectable": {}},
    },
    "widgets_values": ["fikoyt_mvd_placeholder.png", "image"],
}
w["nodes"].append(loadimg)

# Skip ResizeImageMaskNode (#16) — overcomplicated; pipe LoadImage → #12 → #15 directly.
# #12 currently has input link=15 from #16.output. Remove that and add LoadImage → #12 link.
n16 = get_node(16)
# Remove any output link from #16
for out in n16.get("outputs", []):
    for lid in (out.get("links") or [])[:]:
        remove_link(lid)
n16["mode"] = 4
n16["title"] = "(kullanılmıyor) Resize Mask"

# Connect LoadImage → #12 ResizeImagesByLongerEdge.input[0]
add_link(loadimg_id, 0, 12, 0, "IMAGE")

# All I2V preprocess + img2vid nodes mode=0 (active). bypass widget on LTXVImgToVideoInplace
# is wired from #28 (T2V/I2V boolean) — when true → bypass image, when false → use image.
n12 = get_node(12); n12["mode"] = 0
n15 = get_node(15); n15["mode"] = 0
n22 = get_node(22); n22["mode"] = 0
n14 = get_node(14); n14["mode"] = 0

# LTXVPreprocess output → both LTXVImgToVideoInplace.image inputs (#22, #14)
add_link(15, 0, 22, 1, "IMAGE")
add_link(15, 0, 14, 1, "IMAGE")

# Fix upscaler model name (only 1.1 installed)
get_node(39)["widgets_values"] = ["ltx-2.3-spatial-upscaler-x2-1.1.safetensors"]

# ============================================================================
# 5. UPDATE GROUPS - TURKISH
# ============================================================================
turkish_groups = [
    {"id": 1, "title": "1) MODEL & TEXT ENCODER (LTX 2.3)",     "bounding": [2766.595361217282, 4189.689419347806, 460, 830],  "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 2, "title": "3) STAGE 1 — Düşük Çözünürlük Sample",    "bounding": [4286.941201286908, 4205.199257758019, 1040, 830], "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 3, "title": "2) PROMPT (positive + negative)",          "bounding": [3313.6410056096342, 4195.819317922992, 950, 830], "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 4, "title": "5) STAGE 2 — Yüksek Çözünürlük Refine",   "bounding": [5846.595361217282, 4189.689419347806, 990, 830],  "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 5, "title": "4) LATENT UPSAMPLER (x2)",                 "bounding": [5335.340133728035, 4189.689419347806, 510, 830], "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 6, "title": "0) VİDEO AYARLARI (W/H/L/FPS/Mode)",       "bounding": [2286.595361217282, 4189.689419347806, 450, 1280], "color": "#b58b00", "font_size": 24, "flags": {}},
    {"id": 7, "title": "I2V · Görsel Önİşleme (muted, aç: I2V için)", "bounding": [2766.595361217282, 5049.689419347806, 830, 420], "color": "#a1309b", "font_size": 24, "flags": {}},
    {"id": 8, "title": "EMPTY LATENT (Video + Audio · LTX native)", "bounding": [3976.282640433426, 5084.7618986551115, 1340, 420], "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 9, "title": "Sayı Dönüşüm (W/2, H/2)",                  "bounding": [3626.595361217282, 5049.689419347806, 280, 420],  "color": "#3f789e", "font_size": 24, "flags": {}},
    {"id": 10, "title": "I2V · Referans Görsel",                   "bounding": [2280.0, 5520.0, 460, 430],                          "color": "#a1309b", "font_size": 24, "flags": {}},
    {"id": 11, "title": "6) ÇIKIŞ — Decode + Audio + Save",        "bounding": [6870.0, 4140.0, 1310, 1610],                        "color": "#26a269", "font_size": 24, "flags": {}},
]
w["groups"] = turkish_groups

# ============================================================================
# 6. ADD MARKDOWN NOTES - TURKISH USAGE GUIDE
# ============================================================================
markdown_nodes = []

def make_note(nid, pos, size, text, title=None, color="#2d3436", bg="#192a3d"):
    return {
        "id": nid,
        "type": "MarkdownNote",
        "pos": list(pos),
        "size": list(size),
        "flags": {},
        "order": 0,
        "mode": 0,
        "inputs": [],
        "outputs": [],
        "title": title or "Not",
        "properties": {
            "ue_properties": {"widget_ue_connectable": {}, "version": "7.4.1", "input_ue_unconnectable": {}}
        },
        "widgets_values": [text],
        "color": color,
        "bgcolor": bg,
    }

# Master usage note at top
nid = new_node_id()
master_text = (
    "# 🎬 FikoYT MVD · Muhteşem Video Üretim Merkezi\n\n"
    "**LTX-2.3-22B Distilled** tabanlı, **native ses + video** üretim merkezi. "
    "Tek workflow ile **reklam**, **müzik videosu**, **klip** ve **I2V karakter** sahneleri.\n\n"
    "## ⚡ Mod Seçimi\n"
    "1. **T2V (Text-to-Video) — varsayılan**: Boolean switch (sağ alt) = `true`. "
    "I2V grupları (mor) muted (mode 4). Sadece prompt yaz → render.\n"
    "2. **I2V (Image-to-Video)**: Boolean switch = `false` + **mor I2V gruplarındaki "
    "tüm node'ları unmute et** (Right-click → Bypass kapat). LoadImage'a referans görseli ekle.\n\n"
    "## 🎵 Ses Üretimi\n"
    "Prompt'unun **son cümlesinde sesi tarif et** (örn. *\"Atmospheric soundtrack: deep bass, ticking percussion, ending with impact and whoosh.\"*). "
    "LTX-2.3 native olarak audio+video latentini birlikte üretir. **Müzik videosu** için prompt'a `\"electronic dance music, 128 BPM, four-on-the-floor kick, synth lead, vocal chops\"` gibi müzikal tanımlar ekle.\n\n"
    "## 📐 Sahne Boyutu\n"
    "- **Length** (kare sayısı) → `(L-1) % 8 == 0` olmalı. Geçerli: 97, 105, 113, **121** (5sn), 161 (6.67sn), 201 (8.33sn), 241 (10sn).\n"
    "- **Width × Height** → 32'ye bölünebilir olsun. Önerilen: 1280×720 (16:9), 1024×1024 (1:1), 720×1280 (9:16 dikey).\n\n"
    "## 🎛️ LoRA Slotları\n"
    "- **Distilled LoRA** (Stage1 + Stage2): zorunlu, varsayılan **AÇIK** (1.1 versiyon, strength 0.7).\n"
    "- **Audio Reactive / Cameraman / Motion Track**: opsiyonel, varsayılan **MUTED** (mode 4). Sahne tipine göre unmute et.\n\n"
    "## 🚀 Pipeline\n"
    "`Empty Latent (V+A) → Stage1 LowRes Sample → LatentUpsampler x2 → Stage2 Refine → "
    "SeparateAV → VideoVAEDecode + AudioVAEDecode → CreateVideo + SaveVideo`"
)
markdown_nodes.append(make_note(nid, (2280, 3920), (1600, 250), master_text,
                                title="📖 KULLANIM KILAVUZU"))

nid = new_node_id()
prompt_tip = (
    "## ✍️ Prompt Yapısı\n\n"
    "LTX-2.3 **uzun ve detaylı** prompt sever. Yapı:\n\n"
    "1. **Kamera & Lens** — *\"Low-angle dolly-in, 35mm lens, shallow DOF\"*\n"
    "2. **Karakter / Ürün** — *\"A tall man in tailored navy suit, walking confidently\"*\n"
    "3. **Aksiyon** — *\"He turns his head toward camera, slight smirk\"*\n"
    "4. **Mekân & Işık** — *\"Rain-soaked Tokyo alley at night, neon signs reflecting on wet asphalt\"*\n"
    "5. **Stil & Renk** — *\"Cinematic teal-and-orange grade, film grain, anamorphic flares\"*\n"
    "6. **🎵 Ses** — *\"Soundtrack: synth bass, kick on 1 and 3, ambient rain ambience\"*\n\n"
    "**Müzik Videosu için:** Türkçe/İngilizce şarkı sözlerini prompt'a koyma — LTX söz üretmez. "
    "Bunun yerine **müziği prompt ile tarif et** (genre, tempo, enstrüman) ve **görsel hikayeyi** yaz."
)
markdown_nodes.append(make_note(nid, (3313, 3920), (950, 250), prompt_tip,
                                title="✍️ PROMPT REHBERİ", color="#2d3436", bg="#1d3a2b"))

nid = new_node_id()
i2v_tip = (
    "## 🖼️ I2V Modu\n\n"
    "Referans bir görsel (karakter, ürün, sahne) → hareketli video.\n\n"
    "**Aktivasyon adımları**:\n"
    "1. `Mod: T2V/I2V` Boolean → **`false`**\n"
    "2. Bu grubu unmute (LoadImage)\n"
    "3. Mor `I2V · Görsel Önİşleme` grubu unmute (4 node: ResizeImage, ResizeImagesByLongerEdge, LTXVPreprocess + 2x LTXVImgToVideoInplace)\n"
    "4. Prompt'ta görseldeki hareket ve sesi anlat\n\n"
    "**Strength**: Stage1 i2v `0.7` (yaratıcı), Stage2 `1.0` (kompozisyon kilit)."
)
markdown_nodes.append(make_note(nid, (2280, 5960), (460, 320), i2v_tip,
                                title="🖼️ I2V REHBERİ", color="#2d3436", bg="#3a1b3a"))

nid = new_node_id()
sigma_tip = (
    "## ⚙️ Sigma & Sampler İpuçları\n\n"
    "- **Stage1 sigmas** `0.85, 0.7250, 0.4219, 0.0` (4 step, hızlı keşif)\n"
    "- **Stage2 sigmas** `0.8, 0.55, 0.25, 0.0` (4 step, refine)\n"
    "- **Stage1 sampler** `lcm` — distilled LoRA ile en hızlı\n"
    "- **Stage2 sampler** `euler_ancestral_cfg_pp` — detayı keskinleştirir\n\n"
    "**Daha yüksek kalite** için Stage2 sigma 6 adıma çıkar:\n"
    "`0.8, 0.7, 0.6, 0.5, 0.3, 0.0`\n\n"
    "**Daha hızlı** için Stage1 3 adım:\n"
    "`0.85, 0.55, 0.0`"
)
markdown_nodes.append(make_note(nid, (5846, 5040), (990, 320), sigma_tip,
                                title="⚙️ SIGMA İPUÇLARI", color="#2d3436", bg="#3a2b1b"))

w["nodes"].extend(markdown_nodes)

# ============================================================================
# 7. TITLES (Turkish) for known nodes
# ============================================================================
titles = {
    44: "Checkpoint · LTX-2.3-22B-dev FP8",
    4:  "LTX Audio VAE Loader",
    5:  "LTX Text Encoder · Gemma 3 12B + LTX",
    39: "Latent Upsampler Model (x2 spatial)",
    63: "VAE Loader · TAE LTX2.3 (preview)",
    30: "✍️ Positive Prompt (sahne + 🎵ses)",
    41: "✍️ Negative Prompt",
    31: "LTX Conditioning · FPS=24",
    21: "Empty Latent Video (W×H×L)",
    33: "Empty Latent Audio (L kare · 25fps)",
    32: "Concat AV (V+A latent birleştir, Stage1)",
    3:  "Concat AV (V+A latent, Stage2)",
    35: "Separate AV (Stage1)",
    37: "Separate AV (Stage2 · final)",
    23: "Audio VAE Decode → AUDIO",
    68: "Video VAE Decode → IMAGE",
    38: "Create Video (Image+Audio+FPS)",
    45: "💾 Save Video (output/FikoYT_MVD/)",
    13: "LTX Latent Upsampler (x2 spatial)",
    47: "LTX Scheduler (refine sigmas auto)",
    7:  "Manual Sigmas Stage1 (LowRes)",
    58: "Manual Sigmas Stage1 alt",
    65: "Manual Sigmas Stage2 (Refine)",
    17: "Sampler Stage1 (euler_ancestral_cfg_pp)",
    6:  "Sampler Stage2 (lcm)",
    1:  "Noise Stage2",
    2:  "Noise Stage1",
    8:  "CFG Guider Stage2 (CFG=1, distilled)",
    42: "CFG Guider Stage1 (CFG=1, distilled)",
    9:  "Sampler Custom Adv (Stage1)",
    36: "Sampler Custom Adv (Stage2)",
    10: "LTX Crop Guides (Stage1)",
    22: "I2V Stage1 · ImgToVideoInplace (str 0.7)",
    14: "I2V Stage2 · ImgToVideoInplace (str 1.0)",
    12: "I2V · Resize by Longer Edge (1536)",
    15: "I2V · LTXV Preprocess (q=18)",
    16: "I2V · Resize Image+Mask",
    28: "Mod: T2V (true) · I2V (false)",
    18: "W/2 (math expr)",
    20: "H/2 (math expr)",
    24: "FPS → INT (math expr)",
    19: "Reroute · VAE",
    64: "Stage1 Sampling Preview Override",
    67: "Sage Attention Patch",
    29: "(eski) String multiline — kullanılmıyor",
    43: "(opsiyonel) VAE Decode Tiled",
}
for nid, t in titles.items():
    try:
        get_node(nid)["title"] = t
    except KeyError:
        pass

# ============================================================================
# 9. SANİYE → KARE SAYISI (8n+1)
# ============================================================================
# Add: PrimitiveFloat "Saniye" (default 5.0), PrimitiveInt FPS already #26.
# Math expr: length = floor((sec*fps - 1) / 8) * 8 + 1
# Output replaces wiring of #27 (Length) to #21 EmptyLTXVLatentVideo and #33 LTXVEmptyLatentAudio.

sec_id = new_node_id()
sec_node = {
    "id": sec_id, "type": "PrimitiveFloat",
    "pos": [2336, 4750], "size": [360, 82],
    "flags": {}, "order": 0, "mode": 0,
    "inputs": [],
    "outputs": [{"name":"FLOAT","type":"FLOAT","links":[]}],
    "title": "⏱ Saniye (örn. 5.0 = ~5sn)",
    "properties": {"cnr_id":"comfy-core","ver":"0.16.3","Node name for S&R":"PrimitiveFloat"},
    "widgets_values": [5.0, "fixed"],
}
w["nodes"].append(sec_node)

# Math: length = floor((a*b - 1)/8)*8 + 1   (a=sec, b=fps)
len_math_id = new_node_id()
len_math = {
    "id": len_math_id, "type": "ComfyMathExpression",
    "pos": [2336, 4850], "size": [360, 100],
    "flags": {}, "order": 1, "mode": 0,
    "inputs": [
        {"label":"a","name":"values.a","type":"FLOAT,INT","link":None},
        {"label":"b","name":"values.b","type":"FLOAT,INT","shape":7,"link":None},
    ],
    "outputs": [
        {"name":"FLOAT","type":"FLOAT","links":None},
        {"name":"INT","type":"INT","links":[]},
    ],
    "title": "Length = floor((sec*fps-1)/8)*8+1",
    "properties": {"cnr_id":"comfy-core","ver":"0.16.3","Node name for S&R":"ComfyMathExpression"},
    "widgets_values": ["floor((a*b-1)/8)*8+1"],
}
w["nodes"].append(len_math)

# Wire: sec → math.a ; fps (#26) → math.b
add_link(sec_id, 0, len_math_id, 0, "FLOAT,INT")
add_link(26, 0, len_math_id, 1, "FLOAT,INT")

# Disconnect old #27 from EmptyLTXVLatentVideo (#21) length input and LTXVEmptyLatentAudio (#33) frames_number input
# Then re-wire from len_math (INT output, slot 1)
def disconnect_input(node_id, input_name):
    n = get_node(node_id)
    for inp in n.get("inputs", []):
        if inp.get("name") == input_name and inp.get("link"):
            remove_link(inp["link"])
            inp["link"] = None

disconnect_input(21, "length")
disconnect_input(33, "frames_number")
add_link(len_math_id, 1, 21, 2, "INT")  # EmptyLTXVLatentVideo inputs[2] = length
add_link(len_math_id, 1, 33, 1, "INT")  # LTXVEmptyLatentAudio inputs[1] = frames_number

# Mute old #27 Length primitive
get_node(27)["mode"] = 4
get_node(27)["title"] = "(kullanılmıyor) Eski Length"

# ============================================================================
# 10. KARAKTER (tek I2V referansı, full W×H) + ÜRÜN (vision için ayrı)
# ============================================================================
# Karakter = LTX I2V başlangıç frame'i (LoadAndResizeImage → LTXVPreprocess)
# Ürün = sadece vision-AI tarif için (LoadAndResizeImage → OllamaAPI llava image1)
# Sonuç: karakter görseli sahnenin temeli, ürün prompt'ta canlı tarif edilerek
#        karakterin eline / önüne yerleştirilir. TEK koherent sahne, yan yana iki video DEĞİL.

# Remove old LoadImage #74 from chain
n74 = get_node(loadimg_id)
for out in n74.get("outputs", []):
    for lid in (out.get("links") or [])[:]:
        remove_link(lid)
n74["mode"] = 4
n74["title"] = "(kullanılmıyor) Eski LoadImage"

# Karakter loader — ham yükle (resize=False), sonrasında ImageScale crop=center ile orantılı
kar_id = new_node_id()
karakter = {
    "id": kar_id, "type": "LoadAndResizeImage",
    "pos": [2280, 5450], "size": [420, 360],
    "flags": {}, "order": 3, "mode": 0,
    "inputs": [],  # no wired inputs — ham yükle, ImageScale resize edecek
    "outputs": [
        {"name":"image","type":"IMAGE","links":[]},
        {"name":"mask","type":"MASK","links":None},
        {"name":"width","type":"INT","links":None},
        {"name":"height","type":"INT","links":None},
        {"name":"image_path","type":"STRING","links":None},
    ],
    "title": "👤 KARAKTER Görsel (ham yükle · ImageScale orantılı kırpacak)",
    "properties": {"cnr_id":"comfyui-kjnodes","Node name for S&R":"LoadAndResizeImage"},
    # resize=False → görseli hiç değiştirme, sadece dosyayı yükle
    "widgets_values": ["fikoyt_mvd_placeholder.png", False, 512, 512, 1, False, 16, "alpha", ""],
}
w["nodes"].append(karakter)

# ImageScale (standart core node) — orantıyı koruyarak target W×H'a CENTER-CROP yapar
# Görsel hangi çözünürlükte/orantıda olursa olsun → çıkış tam W×H, eğilip bükülmez
scale_id = new_node_id()
scale_node = {
    "id": scale_id, "type": "ImageScale",
    "pos": [2720, 5450], "size": [320, 130],
    "flags": {}, "order": 18, "mode": 0,
    "inputs": [
        {"name":"image","type":"IMAGE","link":None},
        {"name":"width","type":"INT","widget":{"name":"width"},"link":None},
        {"name":"height","type":"INT","widget":{"name":"height"},"link":None},
    ],
    "outputs": [{"name":"IMAGE","type":"IMAGE","links":[]}],
    "title": "📐 ImageScale (orantılı + center-crop · target W×H)",
    "properties": {"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"ImageScale"},
    "widgets_values": ["lanczos", 1280, 720, "center"],
}
w["nodes"].append(scale_node)
add_link(kar_id, 0, scale_id, 0, "IMAGE")
add_link(40, 0, scale_id, 1, "INT")  # W → scale.width
add_link(25, 0, scale_id, 2, "INT")  # H → scale.height

# Ürün loader — keep_proportion=True ile orantılı yükle (llava bozulmuş ürün görmesin)
urun_id = new_node_id()
urun = {
    "id": urun_id, "type": "LoadAndResizeImage",
    "pos": [3070, 5450], "size": [420, 360],
    "flags": {}, "order": 4, "mode": 0,
    "inputs": [],
    "outputs": [
        {"name":"image","type":"IMAGE","links":[]},
        {"name":"mask","type":"MASK","links":None},
        {"name":"width","type":"INT","links":None},
        {"name":"height","type":"INT","links":None},
        {"name":"image_path","type":"STRING","links":None},
    ],
    "title": "📦 ÜRÜN Görsel (orantılı · llava vision için)",
    "properties": {"cnr_id":"comfyui-kjnodes","Node name for S&R":"LoadAndResizeImage"},
    # resize=True, target 768x768, keep_proportion=True → uzun kenar 768, kısa kenar orantılı
    "widgets_values": ["fikoyt_mvd_placeholder.png", True, 768, 768, 1, True, 16, "alpha", ""],
}
w["nodes"].append(urun)

# Disconnect old #15 LTXVPreprocess.input and wire SCALE → #15
disconnect_input(15, "image")
add_link(scale_id, 0, 15, 0, "IMAGE")

# Old #12 mute (no longer needed)
n12 = get_node(12)
for out in n12.get("outputs", []):
    for lid in (out.get("links") or [])[:]:
        remove_link(lid)
n12["mode"] = 4
n12["title"] = "(kullanılmıyor) Resize by Longer Edge"

# concat_id and halfW_id kept as variable names but unused now — set to dummies for later references
concat_id = None
halfW_id = None

# ============================================================================
# 11. OLLAMA: Türkçe Anlatım → İngilizce LTX prompt
# ============================================================================
# Add: PrimitiveStringMultiline (Türkçe), OllamaAPI (mute by default), wire output to #30 CLIPTextEncode.text
tr_id = new_node_id()
tr_node = {
    "id": tr_id, "type": "PrimitiveStringMultiline",
    "pos": [3970, 4280], "size": [560, 280],
    "flags": {}, "order": 5, "mode": 0,
    "inputs": [],
    "outputs": [{"name":"STRING","type":"STRING","links":[]}],
    "title": "🇹🇷 TÜRKÇE ANLATIM (Ollama aktifse buraya yaz)",
    "properties": {"cnr_id":"comfy-core","ver":"0.16.3","Node name for S&R":"PrimitiveStringMultiline"},
    "widgets_values": [
        "Sahne: Gece kafeterya. Karakter elinde ürünümüzü tutuyor, kameraya gülümsüyor.\n"
        "Kamera: Yavaşça yaklaşan dolly-in çekim, sığ alan derinliği, sıcak ışık.\n"
        "Müzik: Modern lo-fi caz, hafif piyano, çıtırtılı vinil sesi.\n"
        "Stil: Sinematik teal-orange renk gradesi, hafif film grain."
    ],
    "color": "#322", "bgcolor": "#533",
}
w["nodes"].append(tr_node)

# 2 ADIMLI OLLAMA: Llava (vision · ürünü gör · İngilizce tarif) → StringConcatenate → Gemma (text · Türkçe sahneyi + ürün tarifini İngilizce LTX prompt'a çevir)
# Bu yöntem garantili İngilizce çıktı verir (llava Türkçe yetisi zayıf, gemma4 mükemmel Türkçe).

# 1) LLAVA: ürün görselini görüp İngilizce 2-3 cümle tarif yazar
ol_id = new_node_id()
llava = {
    "id": ol_id, "type": "OllamaAPI",
    "pos": [4570, 4280], "size": [560, 460],
    "flags": {}, "order": 33, "mode": 4,
    "inputs": [
        {"name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":None},
        {"name":"image1","type":"IMAGE","shape":7,"link":None},
    ],
    "outputs": [{"name":"text","type":"STRING","links":[]}],
    "title": "🤖 1/2 LLAVA · ÜrünGörsel → İngilizce ürün tarifi (UNMUTE!)",
    "properties": {"cnr_id":"comfyui-ollamagemini","Node name for S&R":"OllamaAPI"},
    "widgets_values": [
        "Describe this product image in 2-3 short ENGLISH sentences. Include: product type, color, material, "
        "distinctive features (logo, label, shape, texture). OUTPUT ENGLISH ONLY. No Turkish. No markdown. "
        "Just the description, nothing else.",
        "image",
        "llava:13b",
        0,
        False,
        "Custom",
        "ENGLISH ONLY. Output: 2-3 sentence product description. No Turkish. No explanations.",
        "raw_text",
    ],
    "color": "#222", "bgcolor": "#334",
}
w["nodes"].append(llava)
add_link(urun_id, 0, ol_id, 1, "IMAGE")

# 2) StringConcatenate: Türkçe sahne + İngilizce ürün tarifi birleştir
concat_str_id = new_node_id()
concat_str = {
    "id": concat_str_id, "type": "StringConcatenate",
    "pos": [4570, 4790], "size": [560, 180],
    "flags": {}, "order": 34, "mode": 4,
    "inputs": [
        {"name":"string_a","type":"STRING","widget":{"name":"string_a"},"link":None},
        {"name":"string_b","type":"STRING","widget":{"name":"string_b"},"link":None},
    ],
    "outputs": [{"name":"STRING","type":"STRING","links":[]}],
    "title": "🔗 Concat · Türkçe sahne + İngilizce ürün tarifi",
    "properties": {"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"StringConcatenate"},
    "widgets_values": ["", "", "\n\n[PRODUCT TO INTEGRATE (English)]:\n"],
    "color": "#222", "bgcolor": "#334",
}
w["nodes"].append(concat_str)
add_link(tr_id, 0, concat_str_id, 0, "STRING")    # Türkçe → string_a
add_link(ol_id, 0, concat_str_id, 1, "STRING")    # Llava ürün tarifi → string_b

# 3) GEMMA: birleşik metni İngilizce LTX prompt'a çevir
gemma_id = new_node_id()
gemma = {
    "id": gemma_id, "type": "OllamaAPI",
    "pos": [5170, 4280], "size": [560, 540],
    "flags": {}, "order": 35, "mode": 4,
    "inputs": [
        {"name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":None},
    ],
    "outputs": [{"name":"text","type":"STRING","links":[]}],
    "title": "🤖 2/2 GEMMA4 · Türkçe+ÜrünTarifi → İngilizce LTX prompt (UNMUTE!)",
    "properties": {"cnr_id":"comfyui-ollamagemini","Node name for S&R":"OllamaAPI"},
    "widgets_values": [
        "",
        "text",
        "gemma4:12b",
        0,
        False,
        "Custom",
        ("You are a world-class LTX-2.3 video prompt engineer. "
         "INPUT contains: (1) a TURKISH scene description, (2) an ENGLISH product description after the marker [PRODUCT TO INTEGRATE (English)]. "
         "TASK: Translate the Turkish scene to English AND naturally integrate the product into it — the character "
         "from the I2V reference frame holds, presents, or interacts with this exact product (mention its visible "
         "features). Write ONE long, vivid, cinematic English LTX-2.3 prompt. "
         "STRUCTURE: CAMERA & LENS · CHARACTER ACTION with the product · LOCATION & LIGHTING · STYLE & COLOR GRADE · "
         "LAST SENTENCE: SOUNDTRACK (genre, BPM, instruments, mood). "
         "RULES: 80-150 words. OUTPUT ENGLISH ONLY — never any Turkish word. NEVER write 'two videos' or "
         "'side by side' — character and product are in the SAME single coherent shot. "
         "NO markdown, NO explanations, NO 'Here is the prompt:'. ONLY the prompt text. "
         "End with: 'No watermarks, no text overlay.'"),
        "raw_text",
    ],
    "color": "#222", "bgcolor": "#334",
}
w["nodes"].append(gemma)
add_link(concat_str_id, 0, gemma_id, 0, "STRING")

# Wire FINAL Gemma output → #30 CLIPTextEncode.text
# When Gemma is muted, #30 falls back to its own widget value (manual English prompt).
n30 = get_node(30)
already_has_text_input = any(
    (inp.get("widget") or {}).get("name") == "text" and inp.get("name") == "text"
    for inp in n30.get("inputs", [])
)
if not already_has_text_input:
    n30["inputs"].append({"name":"text","type":"STRING","widget":{"name":"text"},"link":None})
text_input_idx = None
for i, inp in enumerate(n30["inputs"]):
    if inp.get("name") == "text" and (inp.get("widget") or {}).get("name") == "text":
        text_input_idx = i; break
add_link(gemma_id, 0, 30, text_input_idx, "STRING")

# ============================================================================
# 12. HAZIR PROMPT PRESETLERİ (kopyala-yapıştır, MarkdownNote)
# ============================================================================
presets = [
    ("🎬 PRESET 1 · Ürün Reklamı (parfüm/saat/teknik)",
     "A premium cinematic product commercial. The hero product sits on a polished black marble surface, "
     "rotating slowly under a single hard rim-light from the upper-left, soft volumetric haze in background. "
     "Camera performs a smooth 180-degree orbit, anamorphic 35mm lens, shallow depth of field, "
     "subtle reflections on the product surface. Background: out-of-focus dark gradient with sparkling bokeh particles. "
     "Color grade: deep teal shadows, warm gold highlights, ultra-clean blacks. "
     "Soundtrack: deep cinematic sub-bass drone, sparse ticking percussion, building tension, "
     "ends with a soft impact and a metallic whoosh. No dialogue, no watermarks, no text overlay."),
    ("🎤 PRESET 2 · Müzik Klibi (modern pop/RnB)",
     "A stylish music video shot. The artist stands center-frame in a rain-soaked neon-lit alley, "
     "wearing a black leather jacket, hair backlit by pink and cyan neon signs. Camera slowly pulls back "
     "while gimbal-stabilized, slight Dutch tilt. Wet asphalt reflects neon light, "
     "occasional steam rises from a manhole behind. Anamorphic flares streak across the frame. "
     "Color grade: cyan-and-magenta, deep crushed blacks, soft film grain. "
     "Soundtrack: modern pop/RnB, 95 BPM, deep 808 sub-bass on 1 and 3, snappy hi-hat triplets, "
     "warm electric piano chords, faint vocal chops. No watermarks, no text overlay."),
    ("👤 PRESET 3 · Karakter Tanıtım (sinematik intro)",
     "A cinematic character intro shot. The character walks slowly toward the camera in slow motion (60%), "
     "low-angle 24mm wide lens. Wind catches their coat, dust particles float in golden hour backlight. "
     "Background: vast desert horizon with mountains, dramatic clouds, warm sunset glow. "
     "Camera slowly tracks backward, keeping the character centered. Color grade: warm orange highlights, "
     "deep teal shadows, painterly contrast. Soundtrack: epic orchestral build, slow taiko drums on the down-beat, "
     "ethnic strings rising in the second half, ending on a powerful brass swell. No watermarks, no text overlay."),
    ("📦 PRESET 4 · Karakter + Ürün Tanıtım (e-ticaret)",
     "A friendly lifestyle commercial. The character on the left holds up the product on the right "
     "toward camera with a warm genuine smile. Soft morning kitchen light from a left window, "
     "out-of-focus plants in background. Camera is locked on a slow gentle push-in, 50mm lens, shallow depth of field. "
     "The character turns the product to reveal its label, then looks back at camera. "
     "Color grade: clean natural daylight, slightly lifted shadows, warm skin tones. "
     "Soundtrack: bright acoustic guitar fingerpicking, soft ukulele, light shaker, "
     "feel-good mood at 100 BPM. No watermarks, no text overlay."),
    ("⚡ PRESET 5 · Dinamik Spor/Enerji",
     "A high-energy action commercial. The athlete sprints across a wet rooftop at golden hour, "
     "camera tracks alongside with a Steadicam, wide-angle 28mm. Water splashes up from each step, "
     "sun flares catch the lens. Cuts of slow-motion close-ups: hand gripping, foot striking, sweat flying. "
     "Color grade: high contrast, punchy oranges and teals, slightly desaturated greens. "
     "Soundtrack: aggressive hybrid trailer cue, four-on-the-floor kick at 140 BPM, "
     "driving distorted bass synth, rising braam, ends with massive impact and reverse cymbal. "
     "No watermarks, no text overlay."),
    ("🌿 PRESET 6 · Doğal / Organik (kozmetik/gıda)",
     "A serene organic product commercial. The product rests on a moss-covered wooden surface, "
     "morning mist drifting in the foreground, fresh ingredients (leaves, water droplets, berries) "
     "arranged around it. Camera does a slow macro dolly-in, 100mm lens, extreme shallow depth of field. "
     "Soft diffused window light from the side. Color grade: cool greens, soft cream highlights, "
     "creamy bokeh. Soundtrack: gentle nylon-string acoustic guitar, distant birdsong, "
     "soft ambient piano, slow pad swells. Calm and refreshing mood at 70 BPM. "
     "No watermarks, no text overlay."),
    ("🏛 PRESET 7 · Türk Tarihî / Epik",
     "A cinematic Turkish historical shot. A Turkic warrior on horseback gallops across a vast steppe at sunrise, "
     "low-angle wide shot, dust kicks up behind. The warrior wears a fur-lined kaftan, holds a bozkurt sancak banner. "
     "Camera tracks parallel with the rider, 35mm lens. Background: misty mountains, golden sun breaking through clouds. "
     "Color grade: warm amber sunlight, deep blue shadows, painterly grade reminiscent of orientalist paintings. "
     "Soundtrack: epic mehter-style military percussion, kös drum on the down-beat, davul cracks, "
     "zurna melody soaring, building to a victorious brass climax. No watermarks, no text overlay."),
    ("💃 PRESET 8 · Dans / Performans Klibi",
     "A vibrant dance music video. A dancer performs hip-hop choreography in an empty industrial warehouse, "
     "shafts of sunlight cutting through dust through high windows. Camera circles around the dancer "
     "with smooth gimbal motion, occasional dramatic low-angle pushes. Hard top-light with deep shadows. "
     "The dancer wears streetwear, movement is sharp and rhythmic. Color grade: high-contrast monochrome with "
     "selective warm accent on skin tones. Soundtrack: modern hip-hop instrumental at 90 BPM, hard 808 kick, "
     "crisp trap hi-hats, deep sub-bass, sparse melodic vocal chop. No watermarks, no text overlay."),
]
preset_x = 5200
preset_y = 4280
for i, (title, text) in enumerate(presets):
    pid = new_node_id()
    note = make_note(
        pid,
        (preset_x + (i % 2) * 520, preset_y + (i // 2) * 250),
        (500, 230),
        f"```\n{text}\n```\n\n*↑ Bu metni kopyala, ✍️ Positive Prompt'a yapıştır.*",
        title=title,
        color="#2d3436", bg="#1c2a3d",
    )
    w["nodes"].append(note)

# ============================================================================
# 13. EXTRA GROUPS (Türkçe)
# ============================================================================
w["groups"].extend([
    {"id": 12, "title": "👤 KARAKTER (I2V ref) · 📦 ÜRÜN (Ollama vision için)",
     "bounding": [2270, 5340, 1280, 480], "color": "#a1309b", "font_size": 24, "flags": {}},
    {"id": 13, "title": "🤖 2-ADIM OLLAMA · Llava (ürün vision) → Concat → Gemma4 (İngilizce LTX)",
     "bounding": [3960, 4220, 1820, 760], "color": "#9b4500", "font_size": 24, "flags": {}},
    {"id": 14, "title": "🎬 HAZIR PROMPT PRESETLERİ (8 adet · kopyala-yapıştır)",
     "bounding": [5190, 4220, 1080, 1280], "color": "#26a269", "font_size": 24, "flags": {}},
    {"id": 15, "title": "⏱ SANİYE → KARE (otomatik 8n+1)",
     "bounding": [2270, 4730, 470, 250], "color": "#b58b00", "font_size": 24, "flags": {}},
])

# Move main usage note up
for n in w["nodes"]:
    if n["type"] == "MarkdownNote" and n.get("title", "").startswith("📖"):
        n["pos"] = [2270, 3700]
        n["size"] = [4000, 200]

# Update main usage note with new features
for n in w["nodes"]:
    if n["type"] == "MarkdownNote" and n.get("title", "").startswith("📖"):
        n["widgets_values"] = [
            "# 🎬 FikoYT MVD · Muhteşem Video Üretim Merkezi v3\n\n"
            "**LTX-2.3-22B Distilled** · native ses+video · T2V / I2V / **Vision-Aware Ürün Tanıtım** / 8 preset\n\n"
            "## ⚡ Workflow Adımları\n"
            "1. **⏱ Saniye** kutusuna kaç saniye istediğini yaz (örn. `5.0`, `8.0`). FPS=24 ile otomatik 8n+1 frame'e snap'lenir.\n"
            "2. **Genişlik × Yükseklik** seç (varsayılan 1280×720).\n"
            "3. **👤 KARAKTER görseli** yükle — bu LTX I2V'nin başlangıç frame'i olur (full W×H'a auto-resize). "
            "Her çözünürlükte olabilir.\n"
            "4. **📦 ÜRÜN görseli** yükle — bu LTX'e bağlanmaz, sadece **🤖 Ollama llava:13b vision** modeline gider. "
            "Llava ürünü görür → karakterin sahnesine entegre edilmiş İngilizce LTX prompt yazar (yan yana iki video DEĞİL, "
            "karakter ürünü tutarak/göstererek **tek koherent sahne**).\n"
            "5. **Prompt seçenekleri** (3 yöntem):\n"
            "   - **A) Vision Compose (önerilen ürün tanıtımı için)**: 🇹🇷 TÜRKÇE ANLATIM'a sahne anlatımını yaz "
            "(*\"Karakter elinde ürünü göstererek kameraya gülümsüyor, kafeterya ortamı, akşam ışığı\"*) → "
            "🤖 OLLAMA node'unu UNMUTE et → llava ürünü görerek tek tutarlı sahne prompt'u üretir.\n"
            "   - **B) Hazır preset**: Sağdaki 8 yeşil kartdan birini kopyala → ✍️ Positive Prompt'a yapıştır.\n"
            "   - **C) Doğrudan İngilizce**: ✍️ Positive Prompt'a kendin yaz.\n"
            "6. **Run**. Çıktı: `output/video/FikoYT_MVD/`\n\n"
            "## 🎵 Native Ses\n"
            "LTX-2.3 video+ses'i tek pass'te üretir. Türkçe anlatımının son cümlesinde sesi/müziği tarif et "
            "(*\"Müzik: modern lo-fi caz 90 BPM\"*) — llava bunu da prompt'a koyar.\n\n"
            "## 🖼 Otomatik Çözünürlük\n"
            "👤 KARAKTER: full W×H'a alır (LTX'in beklediği boyut). "
            "📦 ÜRÜN: 512×512 thumbnail'a alır (llava için yeter; orijinal görsel düzeni korunur).\n\n"
            "## 🤖 2-Adım Ollama Pipeline (A yöntemi için zorunlu)\n"
            "**Llava (vision)** + **Gemma4 (text)** → garantili İngilizce LTX prompt.\n"
            "Llava sadece ürünü görür → İngilizce 2-3 cümle tarif. Gemma Türkçeyi anlar → final İngilizce LTX prompt.\n"
            "1. https://ollama.com indir+kur  2. PowerShell:\n"
            "   ```\n   ollama pull llava:13b\n   ollama pull gemma4:12b\n   ```\n"
            "3. `http://127.0.0.1:11434` çalışıyor olmalı.\n"
            "4. Workflow'da turuncu gruptaki **3 node'u da unmute et** (sağ tık → Mode → Always): "
            "🤖 LLAVA, 🔗 Concat, 🤖 GEMMA4.\n\n"
            "**Mod: T2V/I2V boolean** — T2V (true): görselleri kullanmaz, sıfırdan prompt'tan üretir. "
            "I2V (false): KARAKTER görselini başlangıç frame'i alır + ürünü prompt'tan sahneye yerleştirir."
        ]
        break

# ============================================================================
# SAVE
# ============================================================================
DST.write_text(json.dumps(w, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"OK · Nodes: {len(w['nodes'])} · Links: {len(w['links'])} · Groups: {len(w['groups'])}")
print(f"Saved → {DST}")
