{
  "name": "AI Product Video Generator (Windows)",
  "nodes": [
    {
      "parameters": {
        "path": "ai-product-video",
        "httpMethod": "POST",
        "responseMode": "lastNode",
        "responseData": "allEntries",
        "options": {
          "binaryData": true,
          "binaryPropertyName": "productImage"
        }
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// \u2705 \u062a\u062d\u0642\u0642 \u0645\u0646 \u0627\u0644\u0645\u062f\u062e\u0644\u0627\u062a \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629\nconst body = $json;\nif (!body.product_name) throw new Error('\u26a0\ufe0f product_name \u0645\u0641\u0642\u0648\u062f');\nif (!body.lang) throw new Error('\u26a0\ufe0f lang (\u0627\u0644\u0644\u063a\u0629) \u0645\u0641\u0642\u0648\u062f\u0629');\nif (!body.tone) throw new Error('\u26a0\ufe0f tone (\u0646\u0628\u0631\u0629 \u0627\u0644\u0646\u0635) \u0645\u0641\u0642\u0648\u062f\u0629');\nconst aspect = body.aspect === '16:9' ? '16:9' : '9:16';\nconst addSubs = !!body.add_subtitles;\nreturn [{ json: { ...body, aspect, add_subtitles: addSubs }, binary: $binary }];"
      },
      "name": "Validate Input",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "responseFormat": "json",
        "jsonParameters": true,
        "sendHeaders": true,
        "headerParametersJson": "={\"Authorization\":\"Bearer {{$env.OPENAI_API_KEY}}\",\"Content-Type\":\"application/json\"}",
        "bodyParametersJson": "={\"model\":\"gpt-4o-mini\",\"temperature\":0.8,\"messages\":[{\"role\":\"system\",\"content\":\"\u0627\u0643\u062a\u0628 \u0633\u0643\u0631\u064a\u0628\u062a \u062a\u0633\u0648\u064a\u0642\u064a \u0642\u0635\u064a\u0631 \u0648\u0645\u0642\u0646\u0639 \u0644\u0645\u0646\u062a\u062c\u060c \u0645\u0646 35 \u0625\u0644\u0649 65 \u0643\u0644\u0645\u0629\u060c \u0628\u0627\u0644\u0644\u063a\u0629 {{$json.lang}} \u0648\u0628\u0646\u0628\u0631\u0629 {{$json.tone}}.\"},{\"role\":\"user\",\"content\":\"\u0627\u0633\u0645 \u0627\u0644\u0645\u0646\u062a\u062c: {{$json.product_name}}.\\n\u0627\u0644\u0645\u064a\u0632\u0627\u062a: {{$json.features}}.\"}]}"
      },
      "name": "Generate Script",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        720,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const script = $json.choices?.[0]?.message?.content || ''; if(!script) throw new Error('\u0641\u0634\u0644 \u062a\u0648\u0644\u064a\u062f \u0627\u0644\u0646\u0635'); return [{ json: { ...$json, voice_script: script } }];"
      },
      "name": "Extract Script",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        980,
        300
      ]
    },
    {
      "parameters": {
        "url": "https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM",
        "method": "POST",
        "responseFormat": "file",
        "jsonParameters": true,
        "sendHeaders": true,
        "headerParametersJson": "={\"xi-api-key\":\"{{$env.ELEVEN_API_KEY}}\",\"Content-Type\":\"application/json\",\"Accept\":\"audio/mpeg\"}",
        "bodyParametersJson": "={\"text\":\"{{$json.voice_script}}\",\"model_id\":\"eleven_multilingual_v2\",\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.75},\"output_format\":\"mp3_44100_128\"}"
      },
      "name": "Generate Voice (TTS)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 3,
      "position": [
        1240,
        300
      ]
    },
    {
      "parameters": {
        "fileName": "C:\\\\n8n_temp\\\\voice.mp3",
        "binaryPropertyName": "data"
      },
      "name": "Save Voice",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        1500,
        300
      ]
    },
    {
      "parameters": {
        "command": "ffprobe -i C:\\\\n8n_temp\\\\voice.mp3 -show_entries format=duration -v quiet -of csv=p=0"
      },
      "name": "Get Duration",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1760,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const dur = parseFloat(($json.data || '').trim()); return [{ json: { ...$json, duration: isFinite(dur)?dur:20 } }];"
      },
      "name": "Parse Duration",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        2020,
        300
      ]
    },
    {
      "parameters": {
        "command": "bash -lc \"ASPECT='{{$json.aspect}}'; D={{$json.duration}}; if [ \\\"$ASPECT\\\" = \\\"16:9\\\" ]; then SIZE=1920:1080; else SIZE=1080:1920; fi; ffmpeg -y -loop 1 -i C:\\\\n8n_temp\\\\input.jpg -t $D -vf \\\"scale=${SIZE},zoompan=z='min(zoom+0.0008,1.2)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=25*$D,fps=25\\\" -c:v libx264 -pix_fmt yuv420p C:\\\\n8n_temp\\\\kb.mp4\""
      },
      "name": "Create Video (Ken Burns)",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        2280,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const text = $json.voice_script || ''; const dur = $json.duration || 20; if(!$json.add_subtitles) return [{json:{...$json, skipSubs:true}}]; const lines = text.split(/(?<=[.!\u061f?\u060c])\\s+/).filter(Boolean); const seg = dur/lines.length; const fmt = t=>{const ms=Math.floor((t%1)*1000);const s=Math.floor(t)%60;const m=Math.floor(t/60);return `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')},${String(ms).padStart(3,'0')}`;}; let srt=''; for(let i=0;i<lines.length;i++){srt+=`${i+1}\\n00:${fmt(i*seg)} --> 00:${fmt((i+1)*seg)}\\n${lines[i]}\\n\\n`;} const data = Buffer.from(srt).toString('base64'); return [{json:{...$json}, binary:{srt:{data,fileName:'subs.srt',mimeType:'text/plain'}}}];"
      },
      "name": "Generate SRT",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        2540,
        300
      ]
    },
    {
      "parameters": {
        "fileName": "C:\\\\n8n_temp\\\\subs.srt",
        "binaryPropertyName": "srt"
      },
      "name": "Save SRT",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        2800,
        300
      ]
    },
    {
      "parameters": {
        "command": "bash -lc \"if [ -f C:\\\\n8n_temp\\\\bgm.mp3 ]; then ffmpeg -y -i C:\\\\n8n_temp\\\\voice.mp3 -i C:\\\\n8n_temp\\\\bgm.mp3 -filter_complex 'volume=1[a];[1:a]volume=0.2[b];[a][b]amix=inputs=2:duration=first' -c:a aac C:\\\\n8n_temp\\\\mixaudio.aac; else ffmpeg -y -i C:\\\\n8n_temp\\\\voice.mp3 -c:a aac C:\\\\n8n_temp\\\\mixaudio.aac; fi\""
      },
      "name": "Mix Audio",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        3060,
        300
      ]
    },
    {
      "parameters": {
        "command": "bash -lc \"if [ -f C:\\\\n8n_temp\\\\subs.srt ] && [ $(stat -c%s C:\\\\n8n_temp\\\\subs.srt) -gt 0 ]; then ffmpeg -y -i C:\\\\n8n_temp\\\\kb.mp4 -i C:\\\\n8n_temp\\\\mixaudio.aac -vf subtitles=C:\\\\n8n_temp\\\\subs.srt -c:a aac C:\\\\n8n_temp\\\\burned.mp4; else ffmpeg -y -i C:\\\\n8n_temp\\\\kb.mp4 -i C:\\\\n8n_temp\\\\mixaudio.aac -c:a aac C:\\\\n8n_temp\\\\burned.mp4; fi\""
      },
      "name": "Combine Video + Audio + Subtitles",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        3320,
        300
      ]
    },
    {
      "parameters": {
        "command": "bash -lc \"if [ -f C:\\\\n8n_temp\\\\logo.png ]; then ffmpeg -y -i C:\\\\n8n_temp\\\\burned.mp4 -i C:\\\\n8n_temp\\\\logo.png -filter_complex 'overlay=main_w-overlay_w-40:40' -c:a copy C:\\\\n8n_temp\\\\final.mp4; else copy C:\\\\n8n_temp\\\\burned.mp4 C:\\\\n8n_temp\\\\final.mp4; fi\""
      },
      "name": "Overlay Logo (optional)",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        3580,
        300
      ]
    },
    {
      "parameters": {
        "filePath": "C:\\\\n8n_temp\\\\final.mp4",
        "property": "video"
      },
      "name": "Read Final Video",
      "type": "n8n-nodes-base.readBinaryFile",
      "typeVersion": 1,
      "position": [
        3840,
        300
      ]
    },
    {
      "parameters": {
        "responseData": "binary",
        "binaryPropertyName": "video",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "Content-Type": "video/mp4",
            "Content-Disposition": "attachment; filename=ai_product_video.mp4"
          }
        }
      },
      "name": "Respond to Webhook (Video)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        4100,
        300
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input": {
      "main": [
        [
          {
            "node": "Generate Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Script": {
      "main": [
        [
          {
            "node": "Extract Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Script": {
      "main": [
        [
          {
            "node": "Generate Voice (TTS)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Voice (TTS)": {
      "main": [
        [
          {
            "node": "Save Voice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Voice": {
      "main": [
        [
          {
            "node": "Get Duration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Duration": {
      "main": [
        [
          {
            "node": "Parse Duration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Duration": {
      "main": [
        [
          {
            "node": "Create Video (Ken Burns)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Video (Ken Burns)": {
      "main": [
        [
          {
            "node": "Generate SRT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate SRT": {
      "main": [
        [
          {
            "node": "Save SRT",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save SRT": {
      "main": [
        [
          {
            "node": "Mix Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mix Audio": {
      "main": [
        [
          {
            "node": "Combine Video + Audio + Subtitles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Video + Audio + Subtitles": {
      "main": [
        [
          {
            "node": "Overlay Logo (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Overlay Logo (optional)": {
      "main": [
        [
          {
            "node": "Read Final Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Final Video": {
      "main": [
        [
          {
            "node": "Respond to Webhook (Video)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}