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.
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
This n8n workflow automatically syncs your WordPress website content — including posts, categories, tags, and media — into Google Sheets.