{
  "name": "Shorts Clip Extractor (MinIO Source)",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        16,
        96
      ],
      "id": "ff8e0d15-9b3b-465d-9a0f-e2b49187400b",
      "name": "When clicking 'Execute workflow'"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "src-video",
              "name": "source_video_url",
              "value": "http://host.docker.internal:9000/raw-videos/microsoft.mp4",
              "type": "string"
            },
            {
              "id": "base_url",
              "name": "base_url",
              "value": "http://host.docker.internal:8080",
              "type": "string"
            },
            {
              "id": "api_key",
              "name": "api_key",
              "value": "thekey",
              "type": "string"
            },
            {
              "id": "lm_studio_url",
              "name": "lm_studio_url",
              "value": "http://host.docker.internal:1234/api/v1/chat",
              "type": "string"
            },
            {
              "id": "lm_model",
              "name": "lm_model",
              "value": "google/gemma-4-e4b:2",
              "type": "string"
            },
            {
              "id": "clip_count",
              "name": "clip_count",
              "value": 3,
              "type": "number"
            },
            {
              "id": "clip_duration",
              "name": "clip_duration",
              "value": 60,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        320,
        96
      ],
      "id": "ffd82f79-0f97-45d7-a939-805b9070c131",
      "name": "Set Variables"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Variables').first().json.base_url }}/v1/media/transcribe",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set Variables').first().json.api_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"media_url\": \"{{ $('Set Variables').first().json.source_video_url }}\", \"language\": \"tr\", \"include_segments\": true, \"word_timestamps\": false, \"response_type\": \"direct\" }",
        "options": {
          "timeout": 1800000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        800,
        96
      ],
      "id": "d2ca2bfe-fd0b-415f-8d59-6c3fc0c8f638",
      "name": "Transcribe (Turkish)"
    },
    {
      "parameters": {
        "jsCode": "const segments = $('Transcribe (Turkish)').first().json.response.segments;\nconst videoUrl = $('Set Variables').first().json.source_video_url;\n\n// Use all segments directly, no chunking - compact format for large transcripts\nconst transcriptText = segments.map((s, i) => `[${i}]${s.start.toFixed(0)}s: ${s.text}`).join(' ');\n\nconst prompt = `Viral video uzman\u0131s\u0131n. Transkripsiyonu analiz et, en viral potansiyelli k\u0131s\u0131mlar\u0131 se\u00e7.\n\nKRITER: Duygusal etki, hikaye, payla\u015f\u0131labilirlik.\n\nHer se\u00e7im i\u00e7in JSON objesi:\n{\"chunk_index\":id,\"start\":saniye,\"end\":saniye(start+60 max),\"viral_score\":1-10,\"reason\":\"k\u0131sa\",\"hook\":\"3sn\"}\n\nTRANSCRIPSYON:\n${transcriptText}\n\nSADECE JSON array d\u00f6nd\u00fcr, ba\u015fka hi\u00e7bir \u015fey yazma.`;\n\n// Create 60s chunks for post-analysis reference\nconst chunkSize = 60;\nconst chunks = [];\nlet currentChunk = { start: null, end: null, texts: [] };\nsegments.forEach(seg => {\n  if (currentChunk.start === null) {\n    currentChunk.start = seg.start;\n    currentChunk.end = seg.end;\n    currentChunk.texts.push(seg.text);\n  } else if (seg.end - currentChunk.start <= chunkSize) {\n    currentChunk.end = seg.end;\n    currentChunk.texts.push(seg.text);\n  } else {\n    chunks.push({ start: currentChunk.start, end: currentChunk.end, text: currentChunk.texts.join(' ').trim() });\n    currentChunk = { start: seg.start, end: seg.end, texts: [seg.text] };\n  }\n});\nif (currentChunk.texts.length > 0) chunks.push({ start: currentChunk.start, end: currentChunk.end, text: currentChunk.texts.join(' ').trim() });\n\nreturn [{ json: { prompt, chunks, video_url: videoUrl } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        96
      ],
      "id": "3264e21c-506f-47bf-8f60-dbed5579fb98",
      "name": "Prepare AI Analysis Prompt"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Variables').first().json.lm_studio_url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $('Set Variables').first().json.lm_model, system_prompt: 'You are a YouTube viral content expert. Output ONLY valid JSON.', input: $json.prompt, stream: false}) }}",
        "options": {
          "timeout": 300000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1280,
        96
      ],
      "id": "55bdd894-f250-4ac7-8ced-b079aa6ff12f",
      "name": "\ud83e\udd16 LM Studio - Viral Clip Analysis"
    },
    {
      "parameters": {
        "jsCode": "const lmResponse = $('\ud83e\udd16 LM Studio - Viral Clip Analysis').first().json;\n\n// Extract AI response text from Gemma format\nlet aiResponse = '';\nif (lmResponse.output && Array.isArray(lmResponse.output)) {\n  const msgObj = lmResponse.output.find(o => o.type === 'message');\n  if (msgObj) aiResponse = msgObj.content;\n}\nif (!aiResponse) {\n  aiResponse = lmResponse.message?.content || lmResponse.choices?.[0]?.message?.content || lmResponse.response || lmResponse.content || '';\n}\n\n// Strip markdown code fences if present\naiResponse = aiResponse.replace(/^```(?:json)?\\n?([\\s\\S]*?)\\n?```$/gm, '$1').trim();\n\nconst videoUrl = $('Prepare AI Analysis Prompt').first().json.video_url;\nconst segments = $('Transcribe (Turkish)').first().json.response.segments;\nconst clipCount = $('Set Variables').first().json.clip_count;\nconst clipDuration = $('Set Variables').first().json.clip_duration;\n\nlet scoredClips;\ntry {\n  const jsonMatch = aiResponse.match(/\\[[\\s\\S]*\\]/);\n  if (jsonMatch) {\n    scoredClips = JSON.parse(jsonMatch[0]);\n  } else {\n    const parsed = JSON.parse(aiResponse);\n    scoredClips = parsed.clips || parsed.results || [parsed];\n  }\n} catch (e) {\n  throw new Error('AI response parsing failed: ' + e.message + '\\n\\nRaw response: ' + aiResponse);\n}\n\nconst topClips = scoredClips\n  .sort((a, b) => (b.viral_score || 0) - (a.viral_score || 0))\n  .slice(0, clipCount)\n  .map(clip => {\n    const startTime = clip.start;\n    const actualEnd = Math.min(clip.end || startTime + clipDuration, startTime + clipDuration);\n    const closestSeg = segments.find(s => Math.abs(s.start - startTime) < 2);\n    const textPreview = closestSeg?.text?.substring(0, 100) || 'N/A';\n    return {\n      start_time: startTime,\n      end_time: actualEnd,\n      viral_score: clip.viral_score,\n      reason: clip.reason,\n      hook: clip.hook,\n      text_preview: textPreview\n    };\n  });\n\nreturn [{ json: { clips: topClips, video_url: videoUrl, total_analyzed: segments.length, ai_recommendations: topClips.length } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        96
      ],
      "id": "9c069345-c7c8-46b3-9438-e3f38c9cbb88",
      "name": "Parse AI Results & Select Clips"
    },
    {
      "parameters": {
        "fieldToSplitOut": "clips",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1760,
        96
      ],
      "id": "5f74d6a4-0469-402b-86f5-60d5dc4d4b9d",
      "name": "Split Clips"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Variables').first().json.base_url }}/v1/ffmpeg/compose",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set Variables').first().json.api_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ inputs: [{ file_url: $('Set Variables').first().json.source_video_url }], outputs: [{ options: [{ option: '-ss', argument: String($json.start_time) }, { option: '-t', argument: String($json.end_time - $json.start_time) }, { option: '-c:v', argument: 'libx264' }, { option: '-preset', argument: 'fast' }, { option: '-crf', argument: '23' }, { option: '-c:a', argument: 'aac' }] }], id: 'extract-clip-' + $json.start_time.toFixed(0) }) }}",
        "options": {
          "timeout": 600000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        96
      ],
      "id": "c025a7ae-b234-4ee0-907f-2622e65b535a",
      "name": "Extract AI-Selected Clip"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Variables').first().json.base_url }}/v1/ffmpeg/compose",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set Variables').first().json.api_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"inputs\": [\n    {\n      \"file_url\": \"{{ $json.response[0].file_url }}\"\n    }\n  ],\n  \"filters\": [\n    {\n      \"filter\": \"crop=w=ih*9/16:h=ih\"\n    }\n  ],\n  \"outputs\": [\n    {\n      \"options\": [\n        {\n          \"option\": \"-c:v\",\n          \"argument\": \"libx264\"\n        },\n        {\n          \"option\": \"-preset\",\n          \"argument\": \"fast\"\n        },\n        {\n          \"option\": \"-crf\",\n          \"argument\": \"23\"\n        },\n        {\n          \"option\": \"-c:a\",\n          \"argument\": \"aac\"\n        }\n      ]\n    }\n  ],\n  \"id\": \"resize-clip-{{ $now.format('x') }}\"\n}",
        "options": {
          "timeout": 600000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        96
      ],
      "id": "3075182b-f738-45c6-a671-fcb92513e46b",
      "name": "Resize to 9:16 (Shorts)"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Variables').first().json.base_url }}/v1/video/caption",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set Variables').first().json.api_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"video_url\": \"{{ $('Resize to 9:16 (Shorts)').first().json.response[0].file_url }}\",\n  \"language\": \"tr\",\n  \"settings\": {\n    \"line_color\": \"#FFFFFF\",\n    \"word_color\": \"#FFD700\",\n    \"all_caps\": false,\n    \"max_words_per_line\": 3,\n    \"font_size\": 50,\n    \"bold\": true,\n    \"italic\": false,\n    \"underline\": false,\n    \"strikeout\": false,\n    \"outline_width\": 3,\n    \"shadow_offset\": 4,\n    \"style\": \"highlight\",\n    \"font_family\": \"The Bold Font\",\n    \"position\": \"bottom_center\"\n  },\n  \"id\": \"ai-caption-{{ $now.format('x') }}\"\n}\n",
        "options": {
          "timeout": 600000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2480,
        96
      ],
      "id": "6e153e6b-f539-4e3c-828c-54aeb9c65187",
      "name": "Add Turkish Captions"
    },
    {
      "parameters": {
        "jsCode": "const allClips = $('Split Clips').all();\nconst finalClips = allClips.map((clip, idx) => ({\n  clip_number: idx + 1,\n  start_time: clip.json.start_time.toFixed(1) + 's',\n  end_time: clip.json.end_time.toFixed(1) + 's',\n  duration: (clip.json.end_time - clip.json.start_time).toFixed(1) + 's',\n  viral_score: clip.json.viral_score,\n  ai_reason: clip.json.reason,\n  ai_hook: clip.json.hook,\n  final_url: clip.json.response\n}));\n\nreturn [{\n  json: {\n    total_clips: finalClips.length,\n    clips: finalClips,\n    summary: `\ud83e\udd16 AI ${finalClips.length} viral clip se\u00e7ti ve i\u015fledi`,\n    ai_analysis_complete: true\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2720,
        96
      ],
      "id": "5f327ec5-4ac0-4b2b-a0a9-ff28f8ff502b",
      "name": "Compile Final Results"
    },
    {
      "parameters": {
        "content": "## 1\ufe0f\u20e3 Configuration",
        "height": 300,
        "width": 480,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        -16
      ],
      "typeVersion": 1,
      "id": "25e042c1-d7ac-418e-8b32-c7a79d48966d",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## 2\ufe0f\u20e3 Transcribe & AI Analysis",
        "height": 300,
        "width": 480,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        -16
      ],
      "typeVersion": 1,
      "id": "d6583361-cd80-482d-adf0-2a1a4e3a5799",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## 3\ufe0f\u20e3 AI Analysis (LM Studio)",
        "height": 300,
        "width": 720,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        -16
      ],
      "typeVersion": 1,
      "id": "08eda9fd-4d97-4f80-b15c-83907d19a8b1",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## 4\ufe0f\u20e3 Cut & Resize",
        "height": 300,
        "width": 480,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        -16
      ],
      "typeVersion": 1,
      "id": "6e57ae8e-4950-42e0-ab48-746982446d8f",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## 5\ufe0f\u20e3 Caption & Export",
        "height": 300,
        "width": 720,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        -16
      ],
      "typeVersion": 1,
      "id": "9c74c44f-d3a9-4bf0-afe6-f1955fb341c0",
      "name": "Sticky Note4"
    }
  ],
  "connections": {
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Transcribe (Turkish)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe (Turkish)": {
      "main": [
        [
          {
            "node": "Prepare AI Analysis Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare AI Analysis Prompt": {
      "main": [
        [
          {
            "node": "\ud83e\udd16 LM Studio - Viral Clip Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udd16 LM Studio - Viral Clip Analysis": {
      "main": [
        [
          {
            "node": "Parse AI Results & Select Clips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Results & Select Clips": {
      "main": [
        [
          {
            "node": "Split Clips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Clips": {
      "main": [
        [
          {
            "node": "Extract AI-Selected Clip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract AI-Selected Clip": {
      "main": [
        [
          {
            "node": "Resize to 9:16 (Shorts)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resize to 9:16 (Shorts)": {
      "main": [
        [
          {
            "node": "Add Turkish Captions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Turkish Captions": {
      "main": [
        [
          {
            "node": "Compile Final Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "71c5ff7a-5398-4ccc-a34e-53de75a3deaa",
  "id": "xBgTD4eDTXe2FUau",
  "tags": []
}