AutomationFlowsContent & Video › Human Anatomy Shorts — Fully Synced + Visually Consistent (flux Keyframe +…

Human Anatomy Shorts — Fully Synced + Visually Consistent (flux Keyframe +…

Original n8n title: Human Anatomy Shorts — Fully Synced + Visually Consistent (flux Keyframe + Hailuo 2.0 I2v)

Human Anatomy Shorts — Fully Synced + Visually Consistent (FLUX Keyframe + Hailuo 2.0 I2V). Uses googleSheets, httpRequest, telegram, googleDrive. Scheduled trigger; 33 nodes.

Cron / scheduled trigger★★★★★ complexity33 nodesGoogle SheetsHTTP RequestTelegramGoogle DriveYouTube
Content & Video Trigger: Cron / scheduled Nodes: 33 Complexity: ★★★★★ Added:

This workflow follows the Google Drive → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Human Anatomy Shorts \u2014 Fully Synced + Visually Consistent (FLUX Keyframe + Hailuo 2.0 I2V)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1,3,5"
            }
          ]
        }
      },
      "id": "1d803a72-3cd7-49c9-8b96-0148b7a3599d",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -7632,
        464
      ],
      "notes": "5x/minggu = ~22 video/bln (~$38/bln). Untuk 3x/minggu: ganti ke '0 9 * * 1,3,5' (~$26/bln)."
    },
    {
      "parameters": {
        "operation": "getRows",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_SPREADSHEET_ID",
          "mode": "id"
        }
      },
      "id": "f5e7be01-62b2-4970-838e-6a5a390bfa3e",
      "name": "Sheets - Get Topic",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        -7408,
        464
      ]
    },
    {
      "parameters": {
        "operation": "updateRow",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_SPREADSHEET_ID",
          "mode": "id"
        }
      },
      "id": "a9cb0a1c-76a5-42b9-8dfb-75e3546e678b",
      "name": "Sheets - Mark as Processing",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        -7200,
        464
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite-preview-06-17:generateContent?key=REPLACE_GEMINI_API_KEY",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"generationConfig\": {\n    \"temperature\": 0.8,\n    \"maxOutputTokens\": 900,\n    \"responseMimeType\": \"application/json\"\n  },\n  \"systemInstruction\": {\n    \"parts\": [{\n      \"text\": \"You are a science content creator for YouTube Shorts about human anatomy. Respond ONLY with valid JSON. Output exactly these keys:\\n\\n- title: string, max 70 chars, no hashtags\\n- voiceover: string, EXACTLY 3 sentences. Sentence 1=hook (shocking fact or question). Sentence 2=key explanation. Sentence 3=memorable close. Total 40-55 words strictly.\\n- description: string, max 280 chars\\n- hashtags: array of exactly 5 strings, no # symbol\\n- keyframe_prompt: string \u2014 a single detailed text-to-image prompt for FLUX that establishes the MASTER VISUAL of this organ. This image will be used as the starting frame for ALL 3 video clips. Must include: exact organ name, photorealistic 3D medical render, cross-section view showing internal structure, bioluminescent blue-teal color palette, pitch black background, cinematic rim lighting, ultra-detailed surface texture, 4K sharp focus, no text no labels no watermarks. Example: 'Human heart muscle in cross-section, photorealistic 3D medical render, bioluminescent blue-teal surface texture, intricate ventricle chambers visible, cinematic rim light, pitch black background, ultra-detailed 4K sharp focus, no text no labels'\\n- motion_prompts: array of exactly 3 strings. Each is a camera motion instruction for Hailuo 2.0 image-to-video that ANIMATES the keyframe. Must start with [camera command]. Focus ONLY on motion \u2014 the visual style is locked by the keyframe image. Max 20 words each. Example: '[Slow push in] organ surface reveals microscopic cellular detail, bioluminescent structures pulse gently'\"\n    }]\n  },\n  \"contents\": [{\n    \"role\": \"user\",\n    \"parts\": [{\n      \"text\": \"Topic: {{ $('Sheets - Get Topic').first().json['topic'] }}\\nOrgan: {{ $('Sheets - Get Topic').first().json['organ'] }}\\n\\nWrite the script. motion_prompts[0]=hook motion (wide reveal), motion_prompts[1]=detail motion (push in to process), motion_prompts[2]=closing motion (pull back dramatic). All motion_prompts animate the same organ from the same keyframe image.\"\n    }]\n  }]\n}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "1c90c3d9-f021-4b22-a0e0-99e1e33029b7",
      "name": "Gemini - Generate Script",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -6976,
        464
      ],
      "notes": "ARSITEKTUR KONSISTENSI: Gemini menghasilkan (1) keyframe_prompt untuk FLUX \u2014 satu gambar master yang jadi anchor visual seluruh video, dan (2) motion_prompts untuk Hailuo \u2014 instruksi gerak kamera saja, bukan deskripsi visual baru. Visual dikunci oleh keyframe."
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const body = $input.item.json;\nconst raw  = body?.candidates?.[0]?.content?.parts?.[0]?.text || '';\n\nif (!raw) {\n  const block = body?.promptFeedback?.blockReason || '';\n  const err   = body?.error?.message || 'unknown';\n  throw new Error('Empty Gemini response.' + (block ? ' Blocked: ' + block : '') + (err !== 'unknown' ? ' ' + err : ''));\n}\n\nlet p;\ntry { p = JSON.parse(raw); }\ncatch (e) {\n  const clean = raw.replace(/```json\\s*/g,'').replace(/```\\s*/g,'').trim();\n  try { p = JSON.parse(clean); }\n  catch (e2) {\n    const m = clean.match(/\\{[\\s\\S]*\\}/);\n    if (m) p = JSON.parse(m[0]);\n    else throw new Error('Cannot parse Gemini JSON: ' + raw.substring(0,200));\n  }\n}\n\n// Validasi ketat\nconst sentences = (p.voiceover||'').split(/(?<=[.!?])\\s+/).filter(s=>s.trim());\nif (sentences.length !== 3)\n  throw new Error('Voiceover harus tepat 3 kalimat, dapat: ' + sentences.length);\nif (!p.keyframe_prompt || p.keyframe_prompt.length < 20)\n  throw new Error('keyframe_prompt terlalu pendek atau kosong');\nif (!Array.isArray(p.motion_prompts) || p.motion_prompts.length !== 3)\n  throw new Error('motion_prompts harus array 3 elemen');\n\nconst topic = $('Sheets - Get Topic').first().json['topic'] || '';\nconst organ = $('Sheets - Get Topic').first().json['organ'] || '';\n\nreturn {\n  json: {\n    topic, organ,\n    execution_id:     $execution.id,\n    title:            p.title || '',\n    voiceover:        p.voiceover || '',\n    sentence_1:       sentences[0],\n    sentence_2:       sentences[1],\n    sentence_3:       sentences[2],\n    description:      p.description || '',\n    hashtags:         Array.isArray(p.hashtags) ? p.hashtags.join(',') : String(p.hashtags||''),\n    keyframe_prompt:  p.keyframe_prompt,\n    motion_prompt_1:  p.motion_prompts[0],\n    motion_prompt_2:  p.motion_prompts[1],\n    motion_prompt_3:  p.motion_prompts[2]\n  }\n};"
      },
      "id": "168eb925-53b1-4a9e-9a02-0af1e38e6dce",
      "name": "Code - Parse & Validate Script",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6752,
        464
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/flux/schnell",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"prompt\": {{ JSON.stringify($json.keyframe_prompt) }},\n  \"image_size\": \"portrait_4_3\",\n  \"num_inference_steps\": 4,\n  \"num_images\": 1,\n  \"enable_safety_checker\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "2da4b1be-a84e-4021-be79-f0eaa6c7d18c",
      "name": "FLUX - Generate Keyframe Image",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -6528,
        352
      ],
      "notes": "FLUX.1 Schnell: generate keyframe master dalam ~2 detik. Biaya: $0.003/MP. Portrait 4:3 = ~0.75MP = $0.003/gambar. Keyframe ini jadi first_frame untuk semua klip Hailuo \u2014 kunci konsistensi visual."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM/with-timestamps",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "xi-api-key",
              "value": "REPLACE_ELEVENLABS_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": {{ JSON.stringify($('Code - Parse & Validate Script').first().json.voiceover) }},\n  \"model_id\": \"eleven_flash_v2_5\",\n  \"voice_settings\": {\n    \"stability\": 0.5,\n    \"similarity_boost\": 0.75,\n    \"style\": 0.2,\n    \"use_speaker_boost\": true\n  }\n}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "0bd23835-4c96-4b88-8c8c-ffca7aaa1eed",
      "name": "ElevenLabs - TTS with Timestamps",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -6528,
        592
      ],
      "notes": "Berjalan paralel dengan FLUX generate keyframe. /with-timestamps mengembalikan audio_base64 + word-level alignment untuk SRT akurat."
    },
    {
      "parameters": {
        "amount": 10
      },
      "id": "234f10c2-786a-49e9-b311-71f6d66d479e",
      "name": "Wait 10s - FLUX Render",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -6320,
        352
      ],
      "notes": "FLUX Schnell sangat cepat ~2-5 detik. Tunggu 10 detik untuk buffer."
    },
    {
      "parameters": {
        "url": "=https://queue.fal.run/fal-ai/flux/schnell/requests/{{ $('FLUX - Generate Keyframe Image').first().json.request_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {}
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "3d766c23-8920-4728-a2eb-b900cc2e0510",
      "name": "FLUX - Poll Result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -6096,
        352
      ]
    },
    {
      "parameters": {
        "jsCode": "// ============================================================\n// GABUNGKAN: keyframe URL dari FLUX + audio data dari ElevenLabs\n// Hitung durasi aktual, bagi ke 3 klip, buat SRT dari timestamps\n// ============================================================\n\nconst fluxResult  = $('FLUX - Poll Result').first().json;\nconst ttsResult   = $('ElevenLabs - TTS with Timestamps').first().json;\nconst script      = $('Code - Parse & Validate Script').first().json;\n\n// --- Keyframe URL dari FLUX ---\nconst keyframeUrl = fluxResult?.images?.[0]?.url || '';\nif (!keyframeUrl) {\n  throw new Error('FLUX belum selesai atau gagal. Status: ' + (fluxResult?.status || 'unknown') + '. Response: ' + JSON.stringify(fluxResult).substring(0,200));\n}\n\n// --- Audio dari ElevenLabs ---\nconst audioB64  = ttsResult?.audio_base64 || '';\nif (!audioB64) throw new Error('Tidak ada audio dari ElevenLabs /with-timestamps');\n\nconst alignment  = ttsResult?.alignment || {};\nconst charList   = alignment.characters || [];\nconst startTimes = alignment.character_start_times_seconds || [];\nconst endTimes   = alignment.character_end_times_seconds   || [];\nif (charList.length === 0) throw new Error('Tidak ada alignment data dari ElevenLabs');\n\n// --- Durasi audio aktual ---\nconst audioDur   = Math.round((endTimes[endTimes.length-1] || 28) * 100) / 100;\n\n// --- Durasi klip video = audio_duration / 3, min 5 dtk, max 10 dtk ---\n// Hailuo 2.0 I2V mendukung 6 atau 10 detik saja\nconst rawClipDur = audioDur / 3;\nconst clipDur    = rawClipDur <= 7 ? 6 : 10;\nconst totalVidDur = clipDur * 3;\n\n// --- Build SRT dari word-level timestamps ---\nconst toSRT = (idx, s, e, text) => {\n  const fmt = n => {\n    const h  = Math.floor(n/3600);\n    const m  = Math.floor((n%3600)/60);\n    const sc = Math.floor(n%60);\n    const ms = Math.round((n%1)*1000);\n    return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(sc).padStart(2,'0')},${String(ms).padStart(3,'0')}`;\n  };\n  return `${idx}\\n${fmt(s)} --> ${fmt(e)}\\n${text}`;\n};\n\n// Gabungkan chars \u2192 words\nlet words = [], curW = '', curS = 0, curE = 0;\nfor (let i = 0; i < charList.length; i++) {\n  const c = charList[i];\n  if (c === ' ' || c === '\\n') {\n    if (curW.trim()) words.push({ word: curW.trim(), start: curS, end: curE });\n    curW = ''; curS = startTimes[i+1] || curE;\n  } else {\n    if (!curW) curS = startTimes[i];\n    curW += c;\n    curE  = endTimes[i];\n  }\n}\nif (curW.trim()) words.push({ word: curW.trim(), start: curS, end: curE });\n\n// Chunk 5 kata per baris subtitle\nconst CHUNK = 5;\nconst entries = [];\nfor (let i = 0, idx = 1; i < words.length; i += CHUNK, idx++) {\n  const chunk = words.slice(i, i+CHUNK);\n  entries.push(toSRT(idx, chunk[0].start, chunk[chunk.length-1].end, chunk.map(w=>w.word).join(' ')));\n}\nconst srtContent = entries.join('\\n\\n');\n\n// ============================================================\n// STYLE ANCHOR untuk motion_prompts \u2014 ditambahkan otomatis\n// agar Hailuo tidak mengubah estetika visual dari keyframe\n// ============================================================\nconst STYLE_SUFFIX = ', photorealistic 3D medical visualization, bioluminescent blue-teal, pitch black background, cinematic lighting, 4K sharp focus, consistent with first frame';\n\nreturn [{\n  json: {\n    // Script\n    topic:         script.topic,\n    organ:         script.organ,\n    title:         script.title,\n    voiceover:     script.voiceover,\n    description:   script.description,\n    hashtags:      script.hashtags,\n    // Keyframe (anchor visual)\n    keyframe_url:  keyframeUrl,\n    // Motion prompts dengan style anchor\n    motion_1:      script.motion_prompt_1 + STYLE_SUFFIX,\n    motion_2:      script.motion_prompt_2 + STYLE_SUFFIX,\n    motion_3:      script.motion_prompt_3 + STYLE_SUFFIX,\n    // Audio\n    audio_base64:  audioB64,\n    audio_dur:     audioDur,\n    // Durasi klip (6 atau 10 dtk \u2014 nilai valid Hailuo 2.0)\n    clip_dur:      clipDur,\n    total_vid_dur: totalVidDur,\n    // SRT dari timestamps nyata\n    srt_content:   srtContent\n  }\n}];"
      },
      "id": "0225817a-8a84-4814-8232-b334b5915025",
      "name": "Code - Merge & Build Sync Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5872,
        464
      ],
      "notes": "Node penggabung: mengambil keyframe URL dari FLUX dan audio dari ElevenLabs, menghitung durasi klip (6 atau 10 dtk \u2014 satu-satunya nilai valid Hailuo 2.0), membangun SRT dari word timestamps, dan menambahkan style_suffix ke motion prompts agar visual Hailuo tidak menyimpang dari keyframe."
    },
    {
      "parameters": {
        "chatId": "REPLACE_CHAT_ID",
        "text": "=\ud83d\udcdd SCRIPT REVIEW\n\nTopic: {{ $json.topic }} | Organ: {{ $json.organ }}\nTitle: {{ $json.title }}\n\nVoiceover:\n{{ $json.voiceover }}\n\nDurasi audio: {{ $json.audio_dur }}dtk\nDurasi per klip: {{ $json.clip_dur }}dtk\nTotal video: {{ $json.total_vid_dur }}dtk\n\nKeyframe FLUX:\n{{ $json.keyframe_url }}\n\nMotion 1: {{ $json.motion_1 }}\nMotion 2: {{ $json.motion_2 }}\nMotion 3: {{ $json.motion_3 }}\n\nAPPROVE atau REJECT.",
        "additionalFields": {
          "disable_web_page_preview": false
        }
      },
      "id": "bd245ea9-0519-49d4-8380-de368c2adf18",
      "name": "Telegram - Send for Review",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -5648,
        464
      ],
      "notes": "Telegram akan preview gambar keyframe FLUX langsung di chat karena disable_web_page_preview=false. User bisa lihat visual anchor sebelum approve."
    },
    {
      "parameters": {
        "resume": "webhook",
        "options": {}
      },
      "id": "9a93f13d-8f8d-40b7-aa01-16187d1d86b3",
      "name": "Wait - Human Approval",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -5440,
        464
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-approve",
              "leftValue": "={{ $json.body?.callback_query?.data || '' }}",
              "rightValue": "approve",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "09abd1fb-82c6-4b40-a904-09c722c61e70",
      "name": "IF - Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -5216,
        464
      ]
    },
    {
      "parameters": {
        "operation": "updateRow",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_SPREADSHEET_ID",
          "mode": "id"
        }
      },
      "id": "5a94f3e8-c3f7-4b15-97cf-59dc3fcd62ed",
      "name": "Sheets - Mark as Rejected",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        -4992,
        640
      ]
    },
    {
      "parameters": {
        "chatId": "REPLACE_CHAT_ID",
        "text": "=\u274c Rejected: {{ $('Code - Merge & Build Sync Data').first().json.topic }}",
        "additionalFields": {}
      },
      "id": "60602361-5af9-4970-87f3-94d3bf08d6e8",
      "name": "Telegram - Notify Rejected",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -4768,
        640
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"image_url\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.keyframe_url) }},\n  \"prompt\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.motion_1) }},\n  \"duration\": {{ $('Code - Merge & Build Sync Data').first().json.clip_dur }},\n  \"resolution\": \"768P\",\n  \"prompt_optimizer\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "e20a8604-3a21-4a97-b21a-1ad4f0a43259",
      "name": "Hailuo - Submit Clip 1 (I2V)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4992,
        320
      ],
      "notes": "KUNCI KONSISTENSI: image_url = keyframe yang sama untuk ketiga klip. Hailuo 2.0 image-to-video dengan last-frame conditioning akan menjaga visual anchor dari keyframe FLUX. prompt_optimizer=false agar instruksi kamera diikuti persis."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"image_url\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.keyframe_url) }},\n  \"prompt\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.motion_2) }},\n  \"duration\": {{ $('Code - Merge & Build Sync Data').first().json.clip_dur }},\n  \"resolution\": \"768P\",\n  \"prompt_optimizer\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "02af1b76-bf81-444a-b453-6970ff40b402",
      "name": "Hailuo - Submit Clip 2 (I2V)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4768,
        320
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"image_url\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.keyframe_url) }},\n  \"prompt\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.motion_3) }},\n  \"duration\": {{ $('Code - Merge & Build Sync Data').first().json.clip_dur }},\n  \"resolution\": \"768P\",\n  \"prompt_optimizer\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "741a1b92-bda2-4ebc-9764-b5dcd556d1e1",
      "name": "Hailuo - Submit Clip 3 (I2V)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4560,
        320
      ]
    },
    {
      "parameters": {
        "unit": "minutes"
      },
      "id": "44ac9cab-e574-43e1-8207-730e3bb8b3ab",
      "name": "Wait 5min - Hailuo Render",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -4336,
        320
      ]
    },
    {
      "parameters": {
        "url": "=https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video/requests/{{ $('Hailuo - Submit Clip 1 (I2V)').first().json.request_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "172dbcfa-fb7e-45bd-9c33-996cb8b80385",
      "name": "Hailuo - Poll Clip 1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4112,
        240
      ]
    },
    {
      "parameters": {
        "url": "=https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video/requests/{{ $('Hailuo - Submit Clip 2 (I2V)').first().json.request_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "c2cda4ac-d02e-4512-97bd-8b88f37329e7",
      "name": "Hailuo - Poll Clip 2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4112,
        368
      ]
    },
    {
      "parameters": {
        "url": "=https://queue.fal.run/fal-ai/minimax/hailuo-02/standard/image-to-video/requests/{{ $('Hailuo - Submit Clip 3 (I2V)').first().json.request_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Key REPLACE_FAL_API_KEY"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "42c83a54-c020-403c-abf0-dc5c4d300923",
      "name": "Hailuo - Poll Clip 3",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -4112,
        480
      ]
    },
    {
      "parameters": {
        "jsCode": "const c1 = $('Hailuo - Poll Clip 1').first().json;\nconst c2 = $('Hailuo - Poll Clip 2').first().json;\nconst c3 = $('Hailuo - Poll Clip 3').first().json;\nconst u1 = c1?.video?.url || '';\nconst u2 = c2?.video?.url || '';\nconst u3 = c3?.video?.url || '';\nconst done = c1?.status==='COMPLETED' && c2?.status==='COMPLETED' && c3?.status==='COMPLETED';\nreturn [{ json: { all_ready: done && !!u1 && !!u2 && !!u3, clip1_url:u1, clip2_url:u2, clip3_url:u3 } }];"
      },
      "id": "2fe5dee1-13c8-44cd-a3b9-720fddac3994",
      "name": "Code - Check All Clips Ready",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3888,
        368
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-ready",
              "leftValue": "={{ $json.all_ready }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "b1c8e8cf-52f7-4ded-b925-ef32061788e8",
      "name": "IF - All Clips Ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -3680,
        368
      ]
    },
    {
      "parameters": {
        "amount": 2,
        "unit": "minutes"
      },
      "id": "3b9340e3-2e2a-4af6-8a6c-a39ee1ca54f0",
      "name": "Wait 2min - Retry Poll",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -3456,
        512
      ]
    },
    {
      "parameters": {
        "jsCode": "const { execSync } = require('child_process');\nconst fs    = require('fs');\nconst https = require('https');\nconst http  = require('http');\n\nconst sync  = $('Code - Merge & Build Sync Data').first().json;\nconst clips = $('Code - Check All Clips Ready').first().json;\nconst tmp   = '/tmp/synced_' + Date.now();\nfs.mkdirSync(tmp, { recursive: true });\n\nconst dl = (url, dest) => new Promise((res, rej) => {\n  const f = fs.createWriteStream(dest);\n  const c = url.startsWith('https') ? https : http;\n  c.get(url, r => {\n    if (r.statusCode===301||r.statusCode===302) return dl(r.headers.location,dest).then(res).catch(rej);\n    r.pipe(f);\n    f.on('finish',()=>{ f.close(); res(); });\n  }).on('error', e=>{ fs.unlink(dest,()=>{}); rej(e); });\n});\n\n// 1. Audio (dari base64 ElevenLabs)\nfs.writeFileSync(tmp+'/voice.mp3', Buffer.from(sync.audio_base64,'base64'));\n\n// 2. SRT (dari word timestamps nyata)\nfs.writeFileSync(tmp+'/captions.srt', sync.srt_content);\n\n// 3. Download 3 klip video\nawait dl(clips.clip1_url, tmp+'/clip1.mp4');\nawait dl(clips.clip2_url, tmp+'/clip2.mp4');\nawait dl(clips.clip3_url, tmp+'/clip3.mp4');\n\n// 4. FFmpeg:\n//    - Concat 3 klip\n//    - Scale + pad 1080x1920 (9:16)\n//    - Overlay SRT (timing akurat dari ElevenLabs)\n//    - Watermark kecil\n//    - Audio fade out 0.5 dtk terakhir\n//    - -shortest: durasi final = durasi audio (tidak hardcode)\nconst srt = tmp+'/captions.srt';\nconst dur = sync.audio_dur;\nconst cmd = [\n  'ffmpeg -y',\n  '-i '+tmp+'/clip1.mp4',\n  '-i '+tmp+'/clip2.mp4',\n  '-i '+tmp+'/clip3.mp4',\n  '-i '+tmp+'/voice.mp3',\n  '-filter_complex',\n  '\"[0:v][1:v][2:v]concat=n=3:v=1:a=0[cv];'+\n  '[cv]scale=1080:1920:force_original_aspect_ratio=decrease,'+\n  'pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,'+\n  'subtitles='+srt+':force_style=\\'FontName=Arial,FontSize=26,Bold=1,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,Alignment=2,MarginV=90\\','+\n  'drawtext=text=\\'AI-Assisted Content\\':fontcolor=white:fontsize=13:alpha=0.4:x=20:y=20[ov];'+\n  '[3:a]afade=t=out:st='+Math.max(0,dur-0.5)+':d=0.5[ao]\"',\n  '-map \"[ov]\" -map \"[ao]\"',\n  '-c:v libx264 -crf 20 -preset fast',\n  '-c:a aac -b:a 128k',\n  '-shortest',\n  tmp+'/final.mp4'\n].join(' ');\n\ntry {\n  execSync(cmd, { timeout:240000, stdio:'pipe' });\n} catch(e) {\n  throw new Error('FFmpeg: '+(e.stderr?.toString()||e.message).substring(0,500));\n}\n\nconst video = fs.readFileSync(tmp+'/final.mp4');\ntry { execSync('rm -rf '+tmp); } catch(_){}\n\nreturn [{\n  json: {\n    title:       sync.title,\n    description: sync.description,\n    hashtags:    sync.hashtags,\n    topic:       sync.topic,\n    organ:       sync.organ,\n    audio_dur:   sync.audio_dur\n  },\n  binary: {\n    video: {\n      data:     video.toString('base64'),\n      mimeType: 'video/mp4',\n      fileName: (sync.title||'shorts').replace(/[^a-zA-Z0-9]/g,'_').substring(0,80)+'.mp4'\n    }\n  }\n}];"
      },
      "id": "7ab3782c-ddde-4ab0-8116-690c3080246a",
      "name": "Code - FFmpeg Render (Synced)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3456,
        272
      ]
    },
    {
      "parameters": {
        "inputDataFieldName": "video",
        "name": "={{ $json.title }}.mp4",
        "driveId": {
          "__rl": true,
          "value": "MyDrive",
          "mode": "list",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "REPLACE_DRIVE_FOLDER_ID",
          "mode": "id"
        },
        "options": {}
      },
      "id": "aced9d62-d045-47ce-a907-b59ce199ec3d",
      "name": "Google Drive - Upload Backup",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        -3232,
        160
      ]
    },
    {
      "parameters": {
        "operation": "upload"
      },
      "id": "b4ed4330-eb05-42cc-8e97-89fba9cf47f6",
      "name": "YouTube - Upload Shorts",
      "type": "n8n-nodes-base.youTube",
      "typeVersion": 1,
      "position": [
        -3232,
        304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://graph.facebook.com/v19.0/REPLACE_FB_PAGE_ID/video_reels",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\"upload_phase\":\"start\",\"access_token\":\"REPLACE_FB_PAGE_ACCESS_TOKEN\"}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "3565810d-856b-4d32-927c-34460c879cee",
      "name": "Facebook - Start Upload",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -3232,
        448
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.upload_url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "OAuth REPLACE_FB_PAGE_ACCESS_TOKEN"
            },
            {
              "name": "offset",
              "value": "0"
            },
            {
              "name": "file_size",
              "value": "=50000000"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "video",
        "options": {
          "timeout": 300000
        }
      },
      "id": "803e36e3-40d9-4a38-8740-136bb3de9fa9",
      "name": "Facebook - Upload Bytes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -3008,
        448
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://graph.facebook.com/v19.0/REPLACE_FB_PAGE_ID/video_reels",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"upload_phase\": \"finish\",\n  \"access_token\": \"REPLACE_FB_PAGE_ACCESS_TOKEN\",\n  \"video_id\": {{ JSON.stringify($('Facebook - Start Upload').first().json.video_id) }},\n  \"title\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.title) }},\n  \"description\": {{ JSON.stringify($('Code - Merge & Build Sync Data').first().json.description) }},\n  \"published\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "e9944cc5-a684-4d6b-9f0a-0e804b8cef94",
      "name": "Facebook - Finish as Draft",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -2800,
        448
      ]
    },
    {
      "parameters": {
        "operation": "updateRow",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_SPREADSHEET_ID",
          "mode": "id"
        }
      },
      "id": "fc688955-4172-49c2-ae7b-5992ec83d758",
      "name": "Sheets - Mark as Done",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        -2576,
        288
      ]
    },
    {
      "parameters": {
        "chatId": "REPLACE_CHAT_ID",
        "text": "=\u2705 Video Siap!\n\n{{ $('Code - Merge & Build Sync Data').first().json.title }}\nOrgan: {{ $('Code - Merge & Build Sync Data').first().json.organ }}\nDurasi: {{ $('Code - Merge & Build Sync Data').first().json.audio_dur }}dtk (audio-synced)\n\nYouTube (Private):\nhttps://youtube.com/watch?v={{ $('YouTube - Upload Shorts').first().json.id }}\nhttps://studio.youtube.com\n\nFacebook Draft: {{ $('Facebook - Finish as Draft').first().json.video_id }}\nhttps://business.facebook.com/creatorstudio\n\nDrive: {{ $('Google Drive - Upload Backup').first().json.webViewLink }}\n\nLangkah: Review \u2192 Enable AI disclosure \u2192 Publish",
        "additionalFields": {
          "disable_web_page_preview": true
        }
      },
      "id": "966a88de-c763-4f77-87df-3010c52afe3f",
      "name": "Telegram - Final Notify",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -2576,
        448
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Sheets - Get Topic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets - Get Topic": {
      "main": [
        [
          {
            "node": "Sheets - Mark as Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets - Mark as Processing": {
      "main": [
        [
          {
            "node": "Gemini - Generate Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini - Generate Script": {
      "main": [
        [
          {
            "node": "Code - Parse & Validate Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Parse & Validate Script": {
      "main": [
        [
          {
            "node": "FLUX - Generate Keyframe Image",
            "type": "main",
            "index": 0
          },
          {
            "node": "ElevenLabs - TTS with Timestamps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FLUX - Generate Keyframe Image": {
      "main": [
        [
          {
            "node": "Wait 10s - FLUX Render",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 10s - FLUX Render": {
      "main": [
        [
          {
            "node": "FLUX - Poll Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FLUX - Poll Result": {
      "main": [
        [
          {
            "node": "Code - Merge & Build Sync Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ElevenLabs - TTS with Timestamps": {
      "main": [
        [
          {
            "node": "Code - Merge & Build Sync Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Merge & Build Sync Data": {
      "main": [
        [
          {
            "node": "Telegram - Send for Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram - Send for Review": {
      "main": [
        [
          {
            "node": "Wait - Human Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Human Approval": {
      "main": [
        [
          {
            "node": "IF - Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - Approved?": {
      "main": [
        [
          {
            "node": "Hailuo - Submit Clip 1 (I2V)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sheets - Mark as Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets - Mark as Rejected": {
      "main": [
        [
          {
            "node": "Telegram - Notify Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Submit Clip 1 (I2V)": {
      "main": [
        [
          {
            "node": "Hailuo - Submit Clip 2 (I2V)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Submit Clip 2 (I2V)": {
      "main": [
        [
          {
            "node": "Hailuo - Submit Clip 3 (I2V)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Submit Clip 3 (I2V)": {
      "main": [
        [
          {
            "node": "Wait 5min - Hailuo Render",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 5min - Hailuo Render": {
      "main": [
        [
          {
            "node": "Hailuo - Poll Clip 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Hailuo - Poll Clip 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Hailuo - Poll Clip 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Poll Clip 1": {
      "main": [
        [
          {
            "node": "Code - Check All Clips Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Poll Clip 2": {
      "main": [
        [
          {
            "node": "Code - Check All Clips Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hailuo - Poll Clip 3": {
      "main": [
        [
          {
            "node": "Code - Check All Clips Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Check All Clips Ready": {
      "main": [
        [
          {
            "node": "IF - All Clips Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - All Clips Ready?": {
      "main": [
        [
          {
            "node": "Code - FFmpeg Render (Synced)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 2min - Retry Poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 2min - Retry Poll": {
      "main": [
        [
          {
            "node": "Hailuo - Poll Clip 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Hailuo - Poll Clip 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Hailuo - Poll Clip 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - FFmpeg Render (Synced)": {
      "main": [
        [
          {
            "node": "Google Drive - Upload Backup",
            "type": "main",
            "index": 0
          },
          {
            "node": "YouTube - Upload Shorts",
            "type": "main",
            "index": 0
          },
          {
            "node": "Facebook - Start Upload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive - Upload Backup": {
      "main": [
        [
          {
            "node": "Sheets - Mark as Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "YouTube - Upload Shorts": {
      "main": [
        [
          {
            "node": "Sheets - Mark as Done",
            "type": "main",
            "index": 0
          },
          {
            "node": "Telegram - Final Notify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Facebook - Start Upload": {
      "main": [
        [
          {
            "node": "Facebook - Upload Bytes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Facebook - Upload Bytes": {
      "main": [
        [
          {
            "node": "Facebook - Finish as Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Facebook - Finish as Draft": {
      "main": [
        [
          {
            "node": "Sheets - Mark as Done",
            "type": "main",
            "index": 0
          },
          {
            "node": "Telegram - Final Notify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "fd96ea94-98e0-41e6-8c9f-353231745e5a",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "exPvnBHNXWWwg658",
  "tags": []
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Human Anatomy Shorts — Fully Synced + Visually Consistent (FLUX Keyframe + Hailuo 2.0 I2V). Uses googleSheets, httpRequest, telegram, googleDrive. Scheduled trigger; 33 nodes.

Source: https://gist.github.com/obzennn/d52a6abbbce3de02433b74a8d002dec9 — original creator credit. Request a take-down →

More Content & Video workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Content & Video

watch this video

YouTube, Google Sheets, Agent +2
Content & Video

This workflow automates the entire process of creating, managing, and publishing AI-generated videos using OpenAI Sora2 Pro, Google Sheets, Google Drive, and YouTube.

HTTP Request, Google Sheets, Google Drive +1
Content & Video

Content creators, media teams, and bloggers who run a YouTube channel and want to automatically repurpose video content into SEO-ready blog posts — without manual writing. Ideal for anyone publishing

HTTP Request, Google Sheets, Google Docs +7
Content & Video

Every night at 22:00 (Asia/HoChiMinh), the workflow fetches the latest posts from multiple competitor WordPress RSS feeds, upserts them into Google Sheets, captures a dashboard screenshot (via ApiFlas

Google Sheets, Google Gemini, Telegram +1
Content & Video

This n8n workflow automatically syncs your WordPress website content — including posts, categories, tags, and media — into Google Sheets.

HTTP Request, Google Sheets