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 →
{
"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": []
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Content Pipeline: Podcast RSS + Vimeo Intake. Uses httpRequest, googleDrive. Scheduled trigger; 40 nodes.
watch this 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.
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
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