This workflow follows the Agent → Datatable 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": "Sora 2 Pro Talking-Head Video Generator | AI \u6570\u5b57\u4eba\u53e3\u64ad\u89c6\u9891\u751f\u6210\uff08Sora 2 Pro + MiniMax TTS + FFmpeg \u81ea\u52a8\u5408\u6210\uff09",
"active": false,
"nodes": [
{
"parameters": {
"content": "## Sora 2 Pro \u6570\u5b57\u4eba\u53e3\u64ad\u89c6\u9891\u751f\u6210 | Talking-Head Video Generator\n\n**Built:** 2026-01\u3000|\u3000**Nodes:** 73\n\n**Model stack:** sora-2-pro-text-to-video, sora-2-text-to-video, gemini-3-pro-preview, gemini-3-flash-preview, claude-sonnet-4.5, gpt-5.2, MiniMax speech-2.6-hd\n\n**External services:** api.kie.ai, api.minimaxi.com\n\nAll credentials are `YOUR_*` placeholders \u2014 fill them in before the first run.\nSetup guide and placeholder reference: see the repository README.\n\n---\n\n## \u4e2d\u6587\u8bf4\u660e\n\n**\u6784\u5efa\u65f6\u95f4\uff1a** 2026-01\u3000|\u3000**\u8282\u70b9\u6570\uff1a** 73\n\n**\u6a21\u578b\u6808\uff1a** sora-2-pro-text-to-video, sora-2-text-to-video, gemini-3-pro-preview, gemini-3-flash-preview, claude-sonnet-4.5, gpt-5.2, MiniMax speech-2.6-hd\n\n**\u5916\u90e8\u670d\u52a1\uff1a** api.kie.ai, api.minimaxi.com\n\n\u6240\u6709\u51ed\u8bc1\u90fd\u662f `YOUR_*` \u5360\u4f4d\u7b26\uff0c\u9996\u6b21\u8fd0\u884c\u524d\u8bf7\u5148\u586b\u597d\u3002\n\u90e8\u7f72\u6b65\u9aa4\u548c\u5360\u4f4d\u7b26\u5bf9\u7167\u8868\u89c1\u4ed3\u5e93 README\u3002\n",
"height": 640,
"width": 760,
"color": 4
},
"id": "snapshot-meta-sora2-ta",
"name": "Overview | \u5de5\u4f5c\u6d41\u8bf4\u660e",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-13136,
-292
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "3351ae7a-e4c2-48c8-87ad-dd4c5f380f5c",
"name": "=\u7247\u6bb5",
"type": "string",
"value": "=cap:{{ $runIndex + 1 }}\nfilename:{{ $json.response.filename }}\nfile_url:{{ $json.response.file_url }}"
}
]
},
"options": {}
},
"id": "a292f8d2-fa4f-4a29-bbbb-ab3a11e14fad",
"name": "Edit Fields4",
"type": "n8n-nodes-base.set",
"position": [
-6208,
384
],
"typeVersion": 3.4
},
{
"parameters": {
"method": "POST",
"url": "http://nca-toolkit:8080/v1/s3/upload",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-api-key",
"value": "nca-api-key-2024"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"file_url\": \"{{ $json.videoUrl }}\",\n \"filename\": \"cap{{ $json.index + 1 }}_{{ $('code-5').all()[$json.index].json['\u5206\u955c'].shot_id }}.mp4\",\n \"public\": true\n}",
"options": {
"batching": {
"batch": {
"batchSize": 10
}
}
}
},
"id": "9abd757e-751c-4bee-be7d-53e51955f7d5",
"name": "\u4e0a\u4f20\u5230n8n-storage\u687617",
"type": "n8n-nodes-base.httpRequest",
"position": [
-6448,
384
],
"typeVersion": 4.2
},
{
"parameters": {
"amount": 55
},
"id": "aacd1c06-135e-4199-97ed-ceef2050a494",
"name": "Wait 30s1",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
-8960,
432
]
},
{
"parameters": {
"url": "https://api.kie.ai/api/v1/jobs/recordInfo",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "taskId",
"value": "={{ $json.task_Id }}"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_KIE_API_KEY"
}
]
},
"options": {}
},
"id": "e3f5458c-ef64-49b8-bfe2-58f26e38d473",
"name": "HTTP - Check Video Status1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-8272,
432
]
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\nconst videoUrls = items\n .filter(item => item.json.video_url)\n .map(item => ({\n json: {\n index: item.json.index,\n shot_id: item.json.shot_id,\n videoUrl: item.json.video_url\n }\n }));\n\nif (videoUrls.length === 0) {\n throw new Error('\u6ca1\u6709\u627e\u5230\u4efb\u4f55\u89c6\u9891URL');\n}\n\nconsole.log(`\u6210\u529f\u63d0\u53d6 ${videoUrls.length} \u4e2a\u89c6\u9891URL`);\n\nreturn videoUrls;"
},
"id": "1d6f4280-6f5c-44b1-8afe-e02da5711de7",
"name": "Code - Get Video URL1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-6896,
384
]
},
{
"parameters": {
"jsCode": "// n8n Code Node: Flatten storyboard shots into standard items\n// Compatible with both:\n// 1) storyboard.scenes[].shots[]\n// 2) storyboard.shots[] at top level\n\nfunction stripCodeFences(s) {\n if (typeof s !== \"string\") return s;\n return s\n .replace(/```json\\s*/gi, \"\")\n .replace(/```\\s*/g, \"\")\n .trim();\n}\n\nfunction safeParseJSON(maybeString) {\n if (typeof maybeString === \"object\" && maybeString !== null) return maybeString;\n if (typeof maybeString !== \"string\") {\n throw new Error(\"AI Agent output is neither string nor object.\");\n }\n const cleaned = stripCodeFences(maybeString);\n try {\n return JSON.parse(cleaned);\n } catch (e) {\n // Provide a more helpful error message\n throw new Error(\"Failed to JSON.parse AI Agent output. Error: \" + e.message);\n }\n}\n\nconst input = $input.first().json;\n\n// AI Agent typically returns: { output: \"{...json...}\" }\nconst storyboard = safeParseJSON(input.output ?? input);\n\n// Prepare scene defaults (used if shots are top-level)\nconst scenes = Array.isArray(storyboard.scenes) ? storyboard.scenes : [];\nconst defaultScene = scenes[0] ?? {};\n\nconst defaultSceneName =\n defaultScene.scene_name ||\n defaultScene.sceneName ||\n storyboard.scene_name ||\n storyboard.sceneName ||\n \"\u5c45\u5bb6\u5f55\u64ad\u5de5\u4f5c\u5ba4\";\n\nconst defaultLocation =\n defaultScene.location ||\n storyboard.location ||\n \"\u5c45\u5bb6\u5f55\u64ad\u5de5\u4f5c\u5ba4\";\n\n// Collect shots (from either nested or top-level)\nlet collected = [];\n\n// Case B: top-level shots\nif (Array.isArray(storyboard.shots) && storyboard.shots.length > 0) {\n collected = storyboard.shots.map((shot) => ({\n __scene: defaultScene,\n __shot: shot,\n }));\n} else {\n // Case A: scenes[].shots[]\n for (const scene of scenes) {\n if (Array.isArray(scene.shots)) {\n for (const shot of scene.shots) {\n collected.push({ __scene: scene, __shot: shot });\n }\n }\n }\n}\n\n// Fail fast if nothing found\nif (!collected.length) {\n const keys = Object.keys(storyboard || {}).join(\", \");\n throw new Error(\n `No shots found. Expected storyboard.shots[] or storyboard.scenes[].shots[]. Top-level keys: ${keys}`\n );\n}\n\n// Flatten to the downstream \"standard\" shot object\nreturn collected.map(({ __scene, __shot }, idx) => {\n const scene = __scene || {};\n const shot = __shot || {};\n\n const narrative = shot.narrative || {};\n const visual = shot.visual || {};\n const kve = visual.key_visual_elements || {};\n const camera = visual.camera || {};\n const perf = shot.performance || {};\n\n // Some outputs might not provide shot_id; generate a fallback\n const fallbackShotId = `S${String(idx + 1).padStart(2, \"0\")}`;\n\n return {\n json: {\n // \u57fa\u7840\u4fe1\u606f\n shot_id: shot.shot_id || fallbackShotId,\n time_code: shot.time_code || \"\",\n duration_seconds: shot.duration_seconds ?? 15,\n scene_name: scene.scene_name || scene.sceneName || defaultSceneName,\n location: scene.location || defaultLocation,\n\n // \u6545\u4e8b\u5185\u5bb9\n story_beat: narrative.story_beat || \"\",\n voiceover: narrative.voiceover || \"\",\n dialogue: narrative.dialogue || [],\n sound_fx: narrative.sound_fx || \"\",\n is_opening: narrative.is_opening ?? false,\n is_ending: narrative.is_ending ?? false,\n\n // \u89c6\u89c9\u63cf\u8ff0\n image_prompt: visual.image_prompt || \"\",\n subject: kve.subject || \"\",\n action: kve.action || \"\",\n environment: kve.environment || \"\",\n lighting: kve.lighting || \"\",\n mood: kve.mood || \"\",\n color_scheme: kve.color_scheme || \"\",\n\n // \u955c\u5934\u53c2\u6570\n shot_type: camera.shot_type || \"\",\n angle: camera.angle || \"\",\n movement: camera.movement || \"\",\n focus: camera.focus || \"\",\n\n // \u8868\u6f14\u7ec6\u8282\n expression: perf.expression || \"\",\n gesture: perf.gesture || \"\",\n posture: perf.posture || \"\",\n eye_contact: perf.eye_contact || \"\",\n\n // \u989d\u5916\u4fdd\u7559\uff1a\u7528\u4e8e\u540e\u7eed\u66f4\u5f3a\u63a7\u5236\uff08\u4e0d\u5f71\u54cd\u539f\u6d41\u7a0b\uff09\n overlays: visual.overlays || [],\n continuity: shot.continuity || {},\n timing_plan: shot.timing_plan || [],\n meta: storyboard.meta || {},\n quality_check: storyboard.quality_check || {},\n },\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-10704,
432
],
"id": "47b8bb5a-2681-49f0-ae06-3de697989df1",
"name": "\u8f93\u51fa\u6240\u6709\u5206\u955c"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.allDone }}",
"rightValue": "success",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"id": "115c749b-cbbe-4ed7-baa1-1314e9279fbc"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "4fe86652-6ffb-456d-a955-3291bf8c6ecd",
"leftValue": "={{ $json.hasRetryable }}",
"rightValue": "waiting",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d7478896-711b-44c6-b1d5-708d072aa845",
"leftValue": "={{ $json.shouldContinue }}",
"rightValue": "fail",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d42a71c0-921d-4c7c-b44d-2bb3c78e1aff",
"leftValue": "=",
"rightValue": "",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
-7472,
400
],
"id": "b343a112-ee6b-499a-a41a-83872a376994",
"name": "Switch"
},
{
"parameters": {
"promptType": "define",
"text": "=This shot failed with complex prompt. Generate a simpler but VISUALLY CONSISTENT prompt.\n\n[SHOT DATA]\nShot ID: {{ $json['\u5206\u955c'].shot_id }}\nStory Beat: {{ $json['\u5206\u955c'].story_beat }}\nVoiceover (Mandarin): {{ $json['\u5206\u955c'].voiceover }}\nExpression: {{ $json['\u5206\u955c'].expression || 'confident, natural' }}\nGesture: {{ $json['\u5206\u955c'].gesture || 'subtle hand movements' }}\nIs Opening: {{ $json['\u5206\u955c'].is_opening }}\nIs Ending: {{ $json['\u5206\u955c'].is_ending }}\n\n[CAMERA - MUST MATCH PRIMARY]\nShot Type: {{ $json['\u5206\u955c'].shot_type || 'Medium Shot' }}\nAngle: {{ $json['\u5206\u955c'].angle || 'Eye Level' }}\nMovement: {{ $json['\u5206\u955c'].movement || 'Static' }}\n\n[CONTINUITY - CRITICAL]\nPrevious shot exit state: {{ $json.prev_anchor || 'N/A (first shot)' }}\nNext shot entry state: {{ $json.next_anchor || 'N/A (last shot)' }}\nSeries Lock: {{ $json.series_lock }}\n\n[VOICEOVER ADJUSTMENT]\n- Count characters in voiceover (Chinese chars only, exclude punctuation)\n- If > 55 chars: Simplify to keep core message, target 40-55 chars\n- If < 40 chars: Add natural filler words or keep as-is\n- Speech must complete by 9.5s\n\n[CRITICAL REQUIREMENTS]\n1. Camera: MEDIUM SHOT only - NOT close-up, NOT wide - must match other shots\n2. Position: Character at DEAD CENTER - no drift left/right\n3. Scene: EXACT same background - gray sofa, KAWS figurines, abstract painting, wooden staircase\n4. Hair: CHESTNUT BROWN only - no color change\n5. Props: NO desk, NO table, NO new objects\n6. Continuity: Entry state matches prev_anchor, exit state matches next_anchor\n7. Timing: Voiceover 40-55 chars, ends by 9.5s\n8. Output plain English prompt, video dialogue in Mandarin Chinese\n\n[WHY CONSISTENCY MATTERS]\nThis video will be concatenated with other shots. Any change in:\n- Camera distance/angle \u2192 visible jump cut\n- Character position \u2192 jarring shift\n- Scene elements \u2192 continuity break\n- Hair color \u2192 character inconsistency\n\nGenerate a stable, simplified prompt that maintains VISUAL CONSISTENCY with the series.",
"options": {
"systemMessage": "=## Role\nYou are a \"stability-first\" video prompt generator. Activated when the primary prompt fails. \nYour goal: generate simpler but CONSISTENT prompts that match the primary agent's visual style.\n\n## Core Principle\nConsistency > Complexity | Stability > Detail | Match Primary Agent > Simplify Everything\n\n---\n\n## CRITICAL: INHERIT FROM PRIMARY AGENT\n\nYou MUST maintain visual consistency with the primary agent's output:\n1. SAME camera shot type (medium shot, eye-level, static)\n2. SAME scene composition (exact same background elements)\n3. SAME character position (dead center of frame)\n4. SAME lighting setup (bright soft key light, f/2.8)\n5. SAME anchor timing (0.0-0.5s in, 8.5-9.0s out)\n\nThe only difference: your prompt is SIMPLER in structure, not DIFFERENT in visuals.\n\n---\n\n## ABSOLUTE LOCKS (Never Violate)\n\n### 1. Camera Lock\u3010CRITICAL FOR CONSISTENCY\u3011\n- Shot Type: MEDIUM SHOT (waist to head visible, NOT close-up, NOT wide shot)\n- Angle: EYE-LEVEL (camera at subject's eye height)\n- Movement: COMPLETELY STATIC (no pan, no tilt, no zoom, no dolly, no shake)\n- Framing: Character occupies center 40% of frame width\n- DO NOT change to close-up, DO NOT change to wide shot\n\n### 2. Character Position Lock\n- Character MUST sit at DEAD CENTER of frame\n- NO position drift (no left shift, no right shift, no standing, no leaning out of frame)\n- Distance from camera: FIXED throughout (same as other shots in series)\n- Head position: Centered horizontally, upper third of frame vertically\n\n### 3. Appearance Lock\n- Hair: CHESTNUT BROWN (warm medium brown), long straight, center-parted, no bangs, chest-length\n- Hair color MUST NOT change to blonde, black, red, or any other shade\n- Outfit: Black crew-neck sweatshirt with white shoulder/sleeve panels, white collar trim\n- NO appearance changes (hair color, hairstyle, clothing, accessories)\n\n### 4. Scene Lock\u3010MUST MATCH PRIMARY AGENT EXACTLY\u3011\n- Setting: Home recording studio, modern minimalist style\n- Furniture: Light gray fabric sofa ONLY, character seated centrally\n- Background Left: Wooden staircase with black metal handrail\n- Background Right: White low cabinet with KAWS figurines (black-gray, red, gray) and purple vinyl record\n- Background Wall: Abstract painting with red, yellow, blue, green color blocks\n- Lighting: Bright soft key light from front, slight depth-of-field blur f/2.8\n- FORBIDDEN: NO desk, NO table, NO computer, NO phone, NO cup, NO additional props\n\n### 5. Continuity Lock\n- 0.0-0.5s: ANCHOR IN - static pose, mouth closed, hands resting on lap\n- 0.5-8.5s: Main performance with voiceover\n- 8.5-9.0s: ANCHOR OUT - return to static, mouth closed, hands at rest\n- Voiceover: 40-55 characters, must complete by 8.5s\n- Entry state must match prev_anchor (if provided)\n- Exit state must match next_anchor (if provided)\n\n---\n\n## Output Format\n- Plain text prompt in English, 1800-2500 characters\n- No code blocks, no JSON, no markdown\n- Character speaks Mandarin Chinese in the video\n\n## Output Template\n\n[VIDEO SPECS]\n16:9 landscape | 1920x1080 | 10 seconds | 24fps | Realistic live-action talking-head video\n\n[CAMERA LOCK - MUST MATCH SERIES]\nShot Type: MEDIUM SHOT (waist to head visible)\nAngle: Eye-level (camera at subject's eye height)\nMovement: Completely static, no camera movement whatsoever\nFraming: Character centered, occupying middle 40% of frame width\nDepth of Field: f/2.8, slight background blur\nCRITICAL: Maintain exact same framing as other shots in this series\n\n[SCENE LOCK - MUST MATCH SERIES]\nSetting: Home recording studio, modern minimalist living room corner\nMain Element: Light gray fabric sofa, character @your_persona seated at center\nBackground Left: Wooden staircase with black metal handrail (partially visible)\nBackground Right: White low cabinet displaying KAWS figurines and purple vinyl record\nBackground Wall: Abstract painting with red, yellow, blue, green color blocks\nLighting: Bright soft key light from front-center, warm tone\nFORBIDDEN: NO desk, NO table, NO additional furniture, NO new props\n\n[CHARACTER LOCK]\n@your_persona - young Asian female finance blogger, seated at exact center of frame\nHair: Chestnut brown (warm medium brown), long straight, center-parted, no bangs, chest-length\nCRITICAL: Hair color must stay chestnut brown - NO blonde, NO black, NO red, NO other colors\nOutfit: Black crew-neck sweatshirt with white shoulder/sleeve panels, white collar trim\nSkin: Fair, natural makeup, rose-tinted lips\nDemeanor: Confident, approachable, professional\nEyes: Maintain camera contact throughout\n\n[POSITION LOCK]\nCharacter Position: Dead center of frame, no drift allowed\nHorizontal: Exactly centered\nVertical: Head in upper third of frame\nDistance: Fixed, same as series standard\nCRITICAL: Position must not shift left, right, forward, or backward\n\n[CONTINUITY ANCHORS]\nEntry State (0.0-0.5s): {prev_anchor or \"Static seated pose, mouth closed, hands on lap, looking at camera\"}\nExit State (8.5-9.0s): {next_anchor or \"Return to rest, mouth closed, hands settling, stable pose\"}\n\n[PERFORMANCE - SIMPLIFIED]\n0.0-0.5s ANCHOR IN:\n- Pose: Static, seated, centered\n- Expression: Neutral ready\n- Hands: Resting on lap\n- Mouth: Closed\n- Eyes: Looking at camera\n\n0.5-8.5s MAIN ACTION:\n- Expression: {basic expression from input}\n- Gesture: {basic gesture from input, keep subtle}\n- Voiceover: \u300c{voiceover text, 40-55 chars}\u300d\n- Eyes: Maintain camera contact\n- Position: Remain centered, no drift\n\n8.5-9.0s ANCHOR OUT:\n- Pose: Settling to static\n- Expression: Neutral, relaxed\n- Hands: Returning to rest\n- Mouth: Closing naturally\n- Eyes: Maintained camera contact\n\n[DIALOGUE - MANDARIN CHINESE]\n\u300c{full voiceover text}\u300d\nWord Count: {count} characters (target: 40-55, excluding punctuation)\nTiming: Speech completes by 8.5s, final 0.5s is silent\n\n[NEGATIVE CONSTRAINTS - CRITICAL]\nCamera:\n- NO close-up shots\n- NO wide shots \n- NO camera movement (pan, tilt, zoom, dolly)\n- NO angle changes\n- NO framing changes\n\nScene:\n- NO desk or table\n- NO additional props\n- NO scene transitions\n- NO background changes\n- NO lighting changes\n\nCharacter:\n- NO position drift from center\n- NO hair color changes (must stay chestnut brown)\n- NO outfit changes\n- NO standing up\n- NO exaggerated movements\n\nAudio:\n- NO speech after 8.5s\n- NO added dialogue beyond provided voiceover\n\n---\n\n## Processing Logic\n\n1. Extract camera/scene parameters from input - MUST match primary agent\n2. Extract voiceover - adjust to 40-55 characters if needed\n3. Extract prev_anchor/next_anchor - use for continuity\n4. Simplify performance to 3 phases only (anchor-in / main / anchor-out)\n5. Emphasize ALL visual locks repeatedly\n6. Output plain English prompt, video dialogue in Mandarin Chinese",
"batching": {
"batchSize": 5
}
}
},
"id": "a49ea9eb-1d10-4a57-968a-d1b8466cb6b7",
"name": "\u515c\u5e95",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-8880,
992
],
"typeVersion": 3
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const currentData = $json;\nconst retryCount = (currentData.retryCount || 0) + 1;\n\nreturn {\n json: {\n ...currentData, // \ud83d\udd11 \u4fdd\u7559\u6240\u6709\u5b57\u6bb5\uff0c\u5305\u62ec \u5206\u955c\u3001prev_anchor \u7b49\n retryCount: retryCount,\n retryLevel: retryCount,\n index: currentData.index,\n skipShot: retryCount > 3\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-9296,
912
],
"id": "968ad7a0-e9d7-4a8d-85f6-ed0d94179b75",
"name": "\u91cd\u8bd5\u8ba1\u6570"
},
{
"parameters": {
"method": "POST",
"url": "https://api.kie.ai/api/v1/jobs/createTask",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_KIE_API_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n model: \"sora-2-pro-text-to-video\",\n input: {\n prompt: $json.output,\n aspect_ratio: \"landscape\",\n n_frames: \"15\",\n size: \"standard\", \n remove_watermark: true\n },\n callBackUrl: \"http://YOUR_N8N_HOST:5678/webhook/kie-sora-callback\"\n}) }}",
"options": {}
},
"id": "e356824f-3448-4eb0-9436-bed7e5b30a63",
"name": "HTTP - Create Video Task",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-9552,
432
],
"retryOnFail": true
},
{
"parameters": {
"promptType": "define",
"text": "=This shot failed with complex prompt. Generate a simpler but VISUALLY CONSISTENT prompt.\n\n[SHOT DATA]\nShot ID: {{ $node[\"code-5\"].json['\u5206\u955c'].shot_id }}\nStory Beat: {{ $node[\"code-5\"].json['\u5206\u955c'].story_beat }}\nVoiceover (Mandarin): {{ $node[\"code-5\"].json['\u5206\u955c'].voiceover }}\nExpression: {{ $node[\"code-5\"].json['\u5206\u955c'].expression || 'confident, natural' }}\nGesture: {{ $node[\"code-5\"].json['\u5206\u955c'].gesture || 'subtle hand movements' }}\nIs Opening: {{ $node[\"code-5\"].json['\u5206\u955c'].is_opening }}\nIs Ending: {{ $node[\"code-5\"].json['\u5206\u955c'].is_ending }}\n\n[CAMERA - MUST MATCH PRIMARY]\nShot Type: {{ $node[\"code-5\"].json['\u5206\u955c'].shot_type || 'Medium Shot' }}\nAngle: {{ $node[\"code-5\"].json['\u5206\u955c'].angle || 'Eye Level' }}\nMovement: {{ $node[\"code-5\"].json['\u5206\u955c'].movement || 'Static' }}\n\n[CONTINUITY - CRITICAL]\nPrevious shot exit state: {{ $node[\"code-5\"].json.prev_anchor || 'N/A (first shot)' }}\nNext shot entry state: {{ $node[\"code-5\"].json.next_anchor || 'N/A (last shot)' }}\nSeries Lock: {{ $node[\"code-5\"].json.series_lock }}\n\n[VOICEOVER ADJUSTMENT]\n- Count characters in voiceover (Chinese chars only, exclude punctuation)\n- If > 55 chars: Simplify to keep core message, target 40-55 chars\n- If < 40 chars: Add natural filler words or keep as-is\n- Speech must complete by 9.5s\n\n[CRITICAL REQUIREMENTS]\n1. Camera: MEDIUM SHOT only - NOT close-up, NOT wide - must match other shots\n2. Position: Character at DEAD CENTER - no drift left/right\n3. Scene: EXACT same background - gray sofa, KAWS figurines, abstract painting, wooden staircase\n4. Hair: CHESTNUT BROWN only - no color change\n5. Props: NO desk, NO table, NO new objects\n6. Continuity: Entry state matches prev_anchor, exit state matches next_anchor\n7. Timing: Voiceover 40-55 chars, ends by 9.5s\n\n[WHY CONSISTENCY MATTERS]\nThis video will be concatenated with other shots. Any change in:\n- Camera distance/angle \u2192 visible jump cut\n- Character position \u2192 jarring shift\n- Scene elements \u2192 continuity break\n- Hair color \u2192 character inconsistency\n\nGenerate a stable, simplified prompt that maintains VISUAL CONSISTENCY with the series.",
"options": {
"systemMessage": "=# \u8eab\u4efd\u5b9a\u4e49\n\u4f60\u662f\u4e13\u4e1a\u7684AI\u89c6\u9891\u751f\u6210\u63d0\u793a\u8bcd\u5de5\u7a0b\u5e08\uff0c\u4e13\u7cbe\u4e8e Sora-2-text-to-video \u6a21\u578b\u3002\n\u4f60\u7684\u4efb\u52a1\u662f\u4e3a\u8d22\u7ecf\u79d1\u666e\u535a\u4e3b\u7684\u53e3\u64ad\u89c6\u9891\u751f\u6210\u7cbe\u7b80\u9ad8\u6548\u7684\u63d0\u793a\u8bcd\u3002\n\n# \u6838\u5fc3\u7ea6\u675f\u3010\u6700\u9ad8\u4f18\u5148\u7ea7\u3011\n\n## 1. \u8f93\u51fa\u957f\u5ea6\u9650\u5236\u3010\u6781\u91cd\u8981\u3011\n- \u6700\u7ec8\u8f93\u51fa\u7684prompt\u5fc5\u987b\u63a7\u5236\u57284000\u5b57\u7b26\u4ee5\u5185\n- \u4e0d\u8981\u8f93\u51fa\u5197\u4f59\u5185\u5bb9\uff0c\u6bcf\u53e5\u8bdd\u90fd\u8981\u6709\u4ef7\u503c\n- \u89d2\u8272\u548c\u573a\u666f\u63cf\u8ff0\u53ea\u8f93\u51fa\u4e00\u6b21\uff0c\u4e0d\u91cd\u590d\n\n## 2. \u8f93\u51fa\u683c\u5f0f\u7ea6\u675f\n- \u76f4\u63a5\u8f93\u51fa\u7eaf\u6587\u672c\uff0c\u4e0d\u8981\u7528\u4ee3\u7801\u5757\u5305\u88f9\n- \u7981\u6b62\u4f7f\u7528\u7279\u6b8aUnicode\u7b26\u53f7\uff08\u53cc\u7ebf\u6846\u3001\u5236\u8868\u7b26\u3001emoji\u7b49\uff09\n- \u5206\u9694\u7ebf\u7528 --- \u6216 ===\n- \u7528 [\u91cd\u70b9] \u66ff\u4ee3\u661f\u53f7\u5f3a\u8c03\n\n## 3. \u89d2\u8272\u9501\u5b9a - @your_persona\n\u56fa\u5b9a\u89d2\u8272\uff0c\u5916\u8c8c\u670d\u88c5\u4e0d\u53ef\u53d8\u3002\n\n## 4. \u573a\u666f\u9501\u5b9a - \u5c45\u5bb6\u5f55\u64ad\u5de5\u4f5c\u5ba4\n\u56fa\u5b9a\u573a\u666f\uff0c\u80cc\u666f\u5143\u7d20\u4e0d\u53ef\u53d8\u3002\n\n---\n\n# \u7cbe\u7b80\u8f93\u51fa\u6a21\u677f\u3010\u5fc5\u987b\u9075\u5faa\u3011\n\n\u4f60\u7684\u8f93\u51fa\u5fc5\u987b\u4e25\u683c\u6309\u7167\u4ee5\u4e0b\u7cbe\u7b80\u6a21\u677f\uff0c\u603b\u957f\u5ea6\u63a7\u5236\u57284000\u5b57\u7b26\u4ee5\u5185\uff1a\n\n---\n\n[Sora-2-text-to-video \u751f\u6210\u6307\u4ee4]\n\n\u89c6\u9891\u89c4\u683c\uff1a16:9\u6a2a\u5c4f\uff0c1920x1080\uff0c[X]\u79d2\uff0c24fps\uff0c\u771f\u4eba\u5b9e\u62cd\u98ce\u683c\n\n[\u89d2\u8272 - @your_persona]\n\u5e74\u8f7b\u4e9a\u6d32\u5973\u6027\u8d22\u7ecf\u535a\u4e3b\u3002\u76ae\u80a4\u767d\u7699\uff0c\u7cbe\u81f4\u9e45\u86cb\u8138\uff0c\u5927\u773c\u775b\u914d\u68d5\u8272\u7f8e\u77b3\uff0c\u7c89\u68d5\u8272\u773c\u5986\uff0c\u81ea\u7136\u91ce\u751f\u7709\uff0c\u73ab\u7470\u8272\u9971\u6ee1\u5507\uff0c\u5c0f\u5de7\u633a\u9f3b\u3002\u6d45\u4e9a\u9ebb\u68d5\u957f\u76f4\u53d1\uff0c\u4e2d\u5206\u65e0\u5218\u6d77\u3002\u7a7f\u9ed1\u767d\u62fc\u8272\u5706\u9886\u536b\u8863\uff08\u9ed1\u5e95\u767d\u80a9\u8896\u62fc\u63a5\uff0c\u80f8\u524d\u767d\u8272Logo\uff09\u3002\u795e\u6001\u81ea\u4fe1\u4eb2\u548c\uff0c\u76ee\u5149\u76f4\u89c6\u955c\u5934\u3002\n\n[\u573a\u666f - \u5c45\u5bb6\u5f55\u64ad\u5de5\u4f5c\u5ba4]\n\u73b0\u4ee3\u7b80\u7ea6\u5ba2\u5385\u4e00\u89d2\u3002\u6d45\u7070\u5e03\u827a\u6c99\u53d1\u5c45\u4e2d\uff0c@your_persona\u7aef\u5750\u5176\u4e0a\u3002\u5de6\u4fa7\u53ef\u89c1\u539f\u6728\u697c\u68af\u9ed1\u8272\u6276\u624b\uff0c\u53f3\u4fa7\u767d\u8272\u77ee\u67dc\u4e0a\u6446\u653e3\u4e2aKAWS\u624b\u529e\uff08\u9ed1\u7070\u3001\u7ea2\u3001\u7070\u8272\u6b3e\uff09\u548c\u7d2b\u8272\u9ed1\u80f6\u5531\u7247\u3002\u80cc\u666f\u5899\u6302\u4e00\u5e45\u7ea2\u9ec4\u84dd\u8272\u5757\u62bd\u8c61\u753b\u3002\u660e\u4eae\u67d4\u5149\uff0c\u8f7b\u5fae\u666f\u6df1\u865a\u5316f/2.8\u3002\n\n[\u5206\u955cS01] \u65f6\u957f[X]\u79d2\n\u53d9\u4e8b\uff1a[\u53d9\u4e8b\u8282\u62cd\u63cf\u8ff0]\n\u60c5\u7eea\uff1a[\u8d77\u59cb\u60c5\u7eea] \u5230 [\u8f6c\u6298] \u5230 [\u7ed3\u675f\u60c5\u7eea]\n\u80fd\u91cf\uff1a[\u9ad8\u80fd/\u4e2d\u9ad8/\u4e2d\u7b49/\u4f4e\u8c03]\n\n[\u8868\u6f14 - \u9010\u79d2\u62c6\u89e3]\n\n\u7b2c0-Xs\uff1a[\u9636\u6bb5\u540d\u79f0]\n- \u8868\u60c5\uff1a\u7709[\u72b6\u6001]\uff0c\u773c[\u72b6\u6001]\uff0c\u5634[\u72b6\u6001]\n- \u624b\u52bf\uff1a[\u5177\u4f53\u52a8\u4f5c\u63cf\u8ff0]\n- \u8eab\u4f53\uff1a[\u59ff\u6001\u53d8\u5316]\n- \u53f0\u8bcd\uff1a\u300c[\u5bf9\u5e94\u53f0\u8bcd]\u300d\u8bed\u6c14[\u63cf\u8ff0]\n\n\u7b2cX-Ys\uff1a[\u9636\u6bb5\u540d\u79f0]\n- \u8868\u60c5\uff1a[\u53d8\u5316\u63cf\u8ff0]\n- \u624b\u52bf\uff1a[\u52a8\u4f5c\u63cf\u8ff0]\n- \u8eab\u4f53\uff1a[\u59ff\u6001]\n- \u53f0\u8bcd\uff1a\u300c[\u53f0\u8bcd]\u300d\u8bed\u6c14[\u63cf\u8ff0]\n\n\u7b2cY-Ns\uff1a[\u9636\u6bb5\u540d\u79f0]\n- \u8868\u60c5\uff1a[\u6700\u7ec8\u72b6\u6001]\n- \u624b\u52bf\uff1a[\u6536\u5c3e\u52a8\u4f5c]\n- \u8eab\u4f53\uff1a[\u5b9a\u683c\u59ff\u6001]\n- \u53f0\u8bcd\uff1a\u300c[\u53f0\u8bcd]\u300d\u8bed\u6c14[\u63cf\u8ff0]\n\n[\u91cd\u70b9] \u8bb0\u5fc6\u70b9\uff1a\n1. \u7b2c[X]\u79d2 - [\u89c6\u89c9\u51b2\u51fb\u63cf\u8ff0]\n2. \u7b2c[Y]\u79d2 - [\u60c5\u7eea\u8f6c\u6298\u63cf\u8ff0]\n\n[\u7ec6\u8282\u8865\u5145]\n- \u773c\u795e\uff1a\u5168\u7a0b\u9501\u5b9a\u955c\u5934\uff0c\u7b2c[X]\u79d2\u77ed\u6682\u77a5\u5411\u5de6\u4e0a0.2\u79d2\u540e\u56de\u6b63\n- \u5934\u90e8\uff1a[\u5173\u952e\u65f6\u523b]\u8f7b\u70b9\u5934/\u6b6a\u5934[X]\u5ea6\n- \u53d1\u4e1d\uff1a\u8eab\u4f53\u524d\u503e\u65f6\u80f8\u524d\u957f\u53d1\u56e0\u60ef\u6027\u8f7b\u5fae\u524d\u8361\n\n---\n\n# \u89d2\u8272\u6863\u6848\u53c2\u8003\u3010\u5185\u90e8\u4f7f\u7528\uff0c\u4e0d\u8981\u5b8c\u6574\u8f93\u51fa\u3011\n\n@your_persona\u5b8c\u6574\u63cf\u8ff0\uff08\u4f9b\u4f60\u7406\u89e3\u89d2\u8272\uff0c\u4f46\u8f93\u51fa\u65f6\u8981\u7cbe\u7b80\uff09\uff1a\n- \u57fa\u672c\uff1a\u5e74\u8f7b\u4e9a\u6d32\u5973\u6027\uff0c\u76ae\u80a4\u767d\u7699\u7ec6\u817b\uff0c\u9762\u5bb9\u7cbe\u81f4\n- \u8138\u578b\uff1a\u7cbe\u81f4\u9e45\u86cb\u8138\uff0c\u4e0b\u5df4\u5c16\u4fcf\n- \u773c\u775b\uff1a\u5927\u800c\u6709\u795e\uff0c\u68d5\u8272\u7f8e\u77b3\uff0c\u7c89\u68d5\u8272\u773c\u5986\uff0c\u776b\u6bdb\u7ea4\u957f\n- \u7709\u6bdb\uff1a\u81ea\u7136\u91ce\u751f\u7709\uff0c\u7709\u5f62\u67d4\u548c\n- \u5634\u5507\uff1a\u9971\u6ee1\uff0c\u73ab\u7470\u7ea2\u6da6\u8272\n- \u9f3b\u5b50\uff1a\u5c0f\u5de7\u633a\u62d4\n- \u5986\u611f\uff1a\u4f2a\u7d20\u989c\uff0c\u6d0b\u5a03\u5a03\u822c\u7cbe\u81f4\n- \u53d1\u578b\uff1a\u6d45\u4e9a\u9ebb\u68d5/\u5976\u8336\u68d5\u957f\u76f4\u53d1\uff0c\u4e2d\u5206\u65e0\u5218\u6d77\uff0c\u81ea\u7136\u5782\u843d\u81f3\u80f8\u524d\n- \u670d\u88c5\uff1a\u9ed1\u767d\u62fc\u8272\u5706\u9886\u536b\u8863\uff0c\u5927\u9762\u79ef\u9ed1\u8272\uff0c\u80a9\u8896\u767d\u8272\u62fc\u63a5\uff0c\u80f8\u524d\u767d\u8272Logo\n- \u6c14\u8d28\uff1a\u81ea\u4fe1\u4f46\u4e0d\u5f20\u626c\uff0c\u4e13\u4e1a\u4f46\u63a5\u5730\u6c14\n\n# \u573a\u666f\u6863\u6848\u53c2\u8003\u3010\u5185\u90e8\u4f7f\u7528\uff0c\u4e0d\u8981\u5b8c\u6574\u8f93\u51fa\u3011\n\n\u5c45\u5bb6\u5f55\u64ad\u5de5\u4f5c\u5ba4\u5b8c\u6574\u63cf\u8ff0\uff08\u4f9b\u4f60\u7406\u89e3\u573a\u666f\uff0c\u4f46\u8f93\u51fa\u65f6\u8981\u7cbe\u7b80\uff09\uff1a\n- \u98ce\u683c\uff1a\u73b0\u4ee3\u7b80\u7ea6\uff0c\u5ba2\u5385\u4e00\u89d2\n- \u5149\u7ebf\uff1a\u660e\u4eae\u901a\u900f\uff0c\u9ad8\u8c03\u5e03\u5149\n- \u8272\u8c03\uff1a\u767d\u8272\u3001\u6d45\u7070\u3001\u539f\u6728\u4e3a\u4e3b\n- \u6c99\u53d1\uff1a\u6d45\u7070\u5e03\u827a\u6c99\u53d1\uff0c@your_persona\u5750\u4e2d\u592e\n- \u5de6\u4fa7\uff1a\u539f\u6728\u697c\u68af\uff0c\u9ed1\u8272\u91d1\u5c5e\u6276\u624b\n- \u53f3\u4fa7\uff1a\u767d\u8272\u77ee\u67dc\uff0c3\u4e2aKAWS\u624b\u529e\uff08\u9ed1\u7070\u3001\u7ea2\u3001\u7070\uff09\uff0c\u7d2b\u8272\u9ed1\u80f6\u5531\u7247\n- \u80cc\u666f\uff1a\u7ea2\u9ec4\u84dd\u62bd\u8c61\u88c5\u9970\u753b\n- \u5149\u5f71\uff1a\u6b63\u9762\u67d4\u5149\uff0cf/2.0-2.8\u666f\u6df1\u865a\u5316\n\n---\n\n# \u8d22\u7ecf\u535a\u4e3b\u52a8\u4f5c\u53c2\u8003\n\n\u624b\u52bf\uff1a\n- \u505c/\u6ce8\u610f\uff1a\u638c\u5fc3\u5411\u5916\u63a8\u51fa\uff0c\u4e94\u6307\u4f38\u76f4\n- \u66b4\u6da8\uff1a\u53f3\u624b\u4ece\u4e0b\u5f80\u4e0a\u5feb\u901f\u6325\u8d77\n- \u66b4\u8dcc\uff1a\u53f3\u624b\u4ece\u4e0a\u5f80\u4e0b\u5207\u843d\n- \u5f3a\u8c03\uff1a\u98df\u6307\u70b9\u5411\u955c\u59342-3\u6b21\n- \u6570\u5b57\uff1a\u624b\u6307\u4f9d\u6b21\u4f38\u51fa\n- \u5bf9\u6bd4\uff1a\u53cc\u624b\u5206\u522b\u5411\u5de6\u53f3\u644a\u5f00\n- \u65e0\u5948\uff1a\u53cc\u624b\u644a\u5f00\u638c\u5fc3\u5411\u4e0a\uff0c\u5fae\u8038\u80a9\n- \u52a0\u6cb9\uff1a\u5355\u624b\u63e1\u62f3\u5c0f\u5e45\u6325\u52a8\n\n\u8868\u60c5\u5f69\u86cb\uff1a\n- \u79bb\u8c31\uff1a\u773c\u7403\u5feb\u901f\u4e0a\u7ffb0.3\u79d2\n- \u618b\u7b11\uff1a\u62bf\u5634\uff0c\u5634\u89d2\u62bd\u52a8\n- \u9707\u60ca\uff1a\u773c\u775b\u7741\u5927\uff0c\u5634\u5df4\u5fae\u5f20\n- \u5410\u69fd\uff1a\u5355\u4fa7\u7709\u6bdb\u4e0a\u626c\uff0c\u5634\u89d2\u6b6a\n- \u5f97\u610f\uff1a\u773c\u775b\u5fae\u772f\uff0c\u5634\u89d2\u4e0a\u626c\n\n---\n\n# \u4e25\u683c\u7981\u6b62\n\n- \u7981\u6b62\u8f93\u51fa\u8d85\u8fc74000\u5b57\u7b26\n- \u7981\u6b62\u91cd\u590d\u8f93\u51fa\u89d2\u8272\u6863\u6848\u548c\u573a\u666f\u6863\u6848\u7684\u5b8c\u6574\u7248\u672c\n- \u7981\u6b62\u4f7f\u7528\u7279\u6b8aUnicode\u7b26\u53f7\n- \u7981\u6b62\u4f7f\u7528markdown\u4ee3\u7801\u5757\n- \u7981\u6b62\u8f93\u51fa\u4e0e\u89c6\u9891\u751f\u6210\u65e0\u5173\u7684\u89e3\u91ca\u8bf4\u660e\n\n---\n\n# \u6267\u884c\u6307\u4ee4\n\n\u6839\u636e\u8f93\u5165\u7684\u5206\u955c\u6570\u636e\uff0c\u6309\u7167\u4e0a\u65b9\u7cbe\u7b80\u6a21\u677f\u751f\u6210\u63d0\u793a\u8bcd\u3002\n\n\u6838\u5fc3\u8981\u6c42\uff1a\n1. \u603b\u8f93\u51fa\u63a7\u5236\u57284000\u5b57\u7b26\u4ee5\u5185\n2. \u89d2\u8272\u63cf\u8ff0\u7cbe\u7b80\u4e3a2-3\u53e5\u8bdd\n3. \u573a\u666f\u63cf\u8ff0\u7cbe\u7b80\u4e3a3-4\u53e5\u8bdd\n4. \u8868\u6f14\u7ec6\u8282\u6309\u65f6\u95f4\u6bb5\u5408\u5e76\uff0c\u4e0d\u8981\u90100.5\u79d2\u62c6\u89e3\n5. \u53ea\u4fdd\u7559\u6700\u5173\u952e\u7684\u8bb0\u5fc6\u70b9\u548c\u7ec6\u8282\n6. \u76f4\u63a5\u8f93\u51fa\uff0c\u4e0d\u8981\u4efb\u4f55\u989d\u5916\u8bf4\u660e"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-8880,
848
],
"id": "7f1d1817-8d2c-47c7-8100-265387c8d9d9",
"name": "AI Agent1"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.retryLevel }}",
"rightValue": 1,
"operator": {
"type": "number",
"operation": "equals"
},
"id": "f56b1515-be44-4a8b-8cbe-045c02ff3ea1"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "3db6ab09-d24f-4601-a4b2-dac97fcfb62f",
"leftValue": "={{ $json.retryLevel }}",
"rightValue": 2,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "190bf04f-99ad-4a81-aafd-2549c694c1db",
"leftValue": "={{ $json.retryLevel }}",
"rightValue": 3,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
-9104,
896
],
"id": "c92f333f-0630-42d4-8b24-62f496f00633",
"name": "\u91cd\u8bd5\u8def\u7531"
},
{
"parameters": {
"model": "google/gemini-3-pro-preview",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-10096,
592
],
"id": "31ba35b3-9ee3-4131-9697-82282a278320",
"name": "OpenRouter Chat Model",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const agentOutput = $json.output; \nconst contextData = $(\"\u91cd\u8bd5\u8ba1\u6570\").item.json;\n\nreturn {\n json: {\n output: agentOutput,\n index: contextData.index,\n shotId: contextData.shotId,\n retryCount: contextData.retryCount,\n id: contextData.id\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-8560,
1040
],
"id": "aed7be0f-937f-4651-b101-98c62d321ac9",
"name": "\u4fdd\u7559\u91cd\u8bd5\u4e0a\u4e0b\u6587"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const agentOutput = $json.output; \nconst contextData = $(\"\u91cd\u8bd5\u8ba1\u6570\").item.json;\n\nreturn {\n json: {\n output: agentOutput,\n index: contextData.index,\n shotId: contextData.shotId,\n retryCount: contextData.retryCount,\n id: contextData.id\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-8560,
880
],
"id": "75672518-673a-42eb-a7ea-7e9da794709a",
"name": "\u4fdd\u7559\u91cd\u8bd5\u4e0a\u4e0b\u65871"
},
{
"parameters": {
"jsCode": "// \u5904\u7406\u6240\u6709\u5206\u955c\uff08\u5e76\u884c\u6a21\u5f0f\uff09\nconst allShots = $input.all();\n\n// \u4e3a\u6bcf\u4e2a\u5206\u955c\u6dfb\u52a0\u4e0a\u4e0b\u6587\u4fe1\u606f\nreturn allShots.map((item, index) => {\n const currentShot = item.json;\n const totalShots = allShots.length;\n \n // \u8ba1\u7b97 prev_anchor\uff08\u4e0a\u4e00\u4e2a\u955c\u5934\u7684\u51fa\u573a\u72b6\u6001\uff09\n const prevAnchor = index > 0 \n ? allShots[index - 1].json.continuity?.continuity_out || ''\n : '';\n \n // \u8ba1\u7b97 next_anchor\uff08\u4e0b\u4e00\u4e2a\u955c\u5934\u7684\u5165\u573a\u72b6\u6001\uff09\n const nextAnchor = index < totalShots - 1\n ? allShots[index + 1].json.continuity?.continuity_in || ''\n : '';\n \n // Series Lock \u53c2\u6570\n const seriesLock = \"SeriesLock v1: 16:9 1920x1080 24fps; medium shot eye-level static by default; bright soft key light; fixed home studio set; consistent framing exposure white balance; realistic live-action; no extra on-screen text unless overlays provided; no emojis; no watermarks.\";\n \n // \u63d0\u53d6shot_num\n const shotNum = parseInt(String(currentShot.shot_id || '').replace(/\\D/g, '')) || (index + 1);\n \n return {\n json: {\n '\u5206\u955c': currentShot,\n shot_num: shotNum,\n total_shots: totalShots,\n prev_anchor: prevAnchor,\n next_anchor: nextAnchor,\n series_lock: seriesLock,\n episode_id: currentShot.meta?.chapter_id || ''\n }\n };\n});"
},
"id": "ac38e831-e46a-40df-b1d0-738959f5d44e",
"name": "code-5",
"type": "n8n-nodes-base.code",
"position": [
-10496,
432
],
"typeVersion": 2
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const aiOutput = $json.output;\n\n// \ud83d\udd11 \u5173\u952e\uff1a\u83b7\u53d6\u5f53\u524ditem\u7684\u7d22\u5f15\nconst currentIndex = $itemIndex; // n8n\u5185\u7f6e\u53d8\u91cf\uff0c\u5f53\u524ditem\u7684\u7d22\u5f15\uff080-based\uff09\n\n// \ud83d\udd11 \u4ece\u4e0a\u6e38\u8282\u70b9\u83b7\u53d6\u539f\u59cb\u5206\u955c\u6570\u636e\nconst originalShots = $items('code-5'); // \u66ff\u6362\u6210\u4f60\u7684\u5206\u955c\u6570\u636e\u6765\u6e90\u8282\u70b9\u540d\nconst originalShot = originalShots[currentIndex]?.json;\n\n// \u63d0\u53d6\u4e0a\u4e0b\u6587\nconst shotId = originalShot?.['\u5206\u955c']?.shot_id || `S${String(currentIndex + 1).padStart(2, '0')}`;\n\nreturn {\n json: {\n output: aiOutput,\n index: currentIndex,\n shotId: shotId,\n retryCount: 0\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-9760,
432
],
"id": "ee66fc5d-f195-45e1-9099-4e20aa6a97ac",
"name": "\u643a\u5e26\u9996\u6b21\u521b\u5efa\u4e0a\u4e0b\u6587"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $json;\nconst currentIndex = $itemIndex;\n\nreturn {\n json: {\n ...item,\n _taskIndex: currentIndex,\n _shotId: item['\u5206\u955c']?.shot_id || `S${String(currentIndex + 1).padStart(2, '0')}`\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-10256,
432
],
"id": "49326cc4-bbf7-4bf1-b4e6-0b5d8c6e96d0",
"name": "Code in JavaScript"
},
{
"parameters": {
"jsCode": "const items = $input.all().map(item => item.json);\n\nconst failedTasks = items.filter(t => t.status === 'failed' && (t.retry_count || 0) < 3);\n\nif (failedTasks.length === 0) {\n return [{ json: { retryComplete: true, message: '\u6ca1\u6709\u9700\u8981\u91cd\u8bd5\u7684\u4efb\u52a1' } }];\n}\n\nreturn failedTasks.map(task => {\n // \ud83d\udd11 \u89e3\u6790 shot_data\n let shotInfo = null;\n try {\n if (task.shot_data) {\n shotInfo = typeof task.shot_data === 'string' \n ? JSON.parse(task.shot_data) \n : task.shot_data;\n }\n } catch (e) {\n console.log('\u89e3\u6790\u5931\u8d25:', e.message);\n }\n \n return {\n json: {\n ...task,\n index: task.index,\n shotId: task.shot_id,\n retryCount: task.retry_count || 0,\n id: task.id,\n // \ud83d\udd11 \u89e3\u6790\u540e\u7684\u5206\u955c\u6570\u636e\n \u5206\u955c: shotInfo?.\u5206\u955c || null,\n prev_anchor: shotInfo?.prev_anchor || '',\n next_anchor: shotInfo?.next_anchor || '',\n series_lock: shotInfo?.series_lock || ''\n }\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-9472,
912
],
"id": "ca7124e5-92a1-4b9e-9979-f9eaa877093f",
"name": "\u51c6\u5907\u91cd\u8bd5\u4efb\u52a1"
},
{
"parameters": {
"method": "POST",
"url": "https://api.kie.ai/api/v1/jobs/createTask",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_KIE_API_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n model: \"sora-2-pro-text-to-video\",\n input: {\n prompt: $json.output,\n aspect_ratio: \"landscape\",\n n_frames: \"15\",\n size: \"standard\", \n remove_watermark: true\n },\n callBackUrl: \"http://YOUR_N8N_HOST:5678/webhook/kie-sora-callback\"\n}) }}",
"options": {}
},
"id": "b2200074-35bb-4893-a652-f8fe88fbb760",
"name": "HTTP - Create Video Task1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-8240,
912
],
"retryOnFail": true
},
{
"parameters": {
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "\u7247\u6bb5"
}
]
},
"options": {}
},
"id": "a64475d1-ec98-48b7-9305-af729be49bf3",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
-6000,
384
],
"typeVersion": 1
},
{
"parameters": {
"method": "POST",
"url": "http://nca-toolkit:8080/v1/video/concatenate",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-api-key",
"value": "nca-api-key-2024"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"video_urls\": {{ JSON.stringify($json.video_urls, null, 2) }}\n}",
"options": {}
},
"id": "11395613-1241-4bd5-a447-f8fe1f673006",
"name": "\u591a\u89c6\u9891\u62fc\u63a52",
"type": "n8n-nodes-base.httpRequest",
"position": [
-5632,
384
],
"typeVersion": 4.2
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "4feb0899-bf16-4115-9dd6-2674e85b9d5c",
"name": "\u591a\u89c6\u9891\u62fc\u63a5\u5730\u5740",
"type": "string",
"value": "={{ $json.response }}"
}
]
},
"options": {}
},
"id": "f2ebad13-0122-4b8f-a6fc-8fdbf5977a15",
"name": "Edit Fields11",
"type": "n8n-nodes-base.set",
"position": [
-5456,
384
],
"typeVersion": 3.4
},
{
"parameters": {
"jsCode": "// \u89e3\u6790\u4e0a\u6e38\u7684\u7247\u6bb5\u6570\u636e\u5e76\u8f6c\u6362\u683c\u5f0f\uff08\u4fee\u590dURL\u622a\u65ad\u95ee\u9898\uff09\nconst inputData = $input.first().json;\nconst fragments = inputData['\u7247\u6bb5'] || [];\n\n// \u6784\u5efavideo_urls\uff08\u5b8c\u6574\u63d0\u53d6file_url\uff09\nconst video_urls = fragments.map(fragment => {\n // \u5339\u914dfile_url\u76f4\u5230\u884c\u5c3e\uff08\u5904\u7406\u53ef\u80fd\u7684\u6362\u884c\u6216\u7279\u6b8a\u5b57\u7b26\uff09\n const fileUrlMatch = fragment.match(/file_url:(.*?)(?=\\n|$)/s);\n const videoUrl = fileUrlMatch ? fileUrlMatch[1].trim() : '';\n return {\n video_url: videoUrl\n };\n}).filter(item => item.video_url && item.video_url !== 'http'); // \u8fc7\u6ee4\u65e0\u6548URL\n\n// \u6784\u5efa\u5b8c\u6574\u7684\u7247\u6bb5\u8be6\u60c5\uff08\u4fee\u590dURL\u622a\u65ad\uff09\nconst fragmentDetails = fragments.map(fragment => {\n const detail = {};\n \n // \u5339\u914dcap\n const capMatch = fragment.match(/cap:(.*?)(?=\\n|$)/);\n if (capMatch) detail.cap = capMatch[1].trim();\n \n // \u5339\u914dfilename\n const filenameMatch = fragment.match(/filename:(.*?)(?=\\n|$)/);\n if (filenameMatch) detail.filename = filenameMatch[1].trim();\n \n // \u5339\u914d\u5b8c\u6574\u7684file_url\uff08\u5305\u62ec\u6362\u884c\u7684\u60c5\u51b5\uff09\n const fileUrlMatch = fragment.match(/file_url:(.*)/s);\n if (fileUrlMatch) detail.file_url = fileUrlMatch[1].trim();\n \n return detail;\n}).filter(item => item.file_url && item.file_url !== 'http'); // \u8fc7\u6ee4\u65e0\u6548\u6761\u76ee\n\n// \u6700\u7ec8\u8f93\u51fa\u4e24\u4e2a\u5e76\u5217\u53c2\u6570\nconst output = {\n video_urls: video_urls,\n fragments: fragmentDetails\n};\n\nreturn output;"
},
"id": "2d8719da-3456-4458-bc76-a0076e264285",
"name": "\u591a\u89c6\u9891\u62fc\u63a5URL1",
"type": "n8n-nodes-base.code",
"position": [
-5824,
384
],
"typeVersion": 2
},
{
"parameters": {
"method": "POST",
"url": "http://nca-toolkit:8080/v1/s3/upload",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-api-key",
"value": "nca-api-key-2024"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"file_url\": \"{{ $json[\"\u591a\u89c6\u9891\u62fc\u63a5\u5730\u5740\"] }}\",\n \"filename\": \"{{ $('\u591a\u89c6\u9891\u62fc\u63a5URL1').item.json.fragments[0].filename }}\",\n \"public\": true\n}",
"options": {}
},
"id": "1575b0f4-a387-4d3f-b6fa-d0fb942cc1e1",
"name": "\u4e0a\u4f20\u5230video-factory\u687611",
"type": "n8n-nodes-base.httpRequest",
"position": [
-5248,
384
],
"typeVersion": 4.2
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c625d01a-c6d2-4518-b6cd-f03b308c11ae",
"name": "=\u521d\u526a{{ $json.response.bucket }}-{{ $json.response.filename }}",
"type": "string",
"value": "={{ $json.response.file_url }}"
},
{
"id": "1aed46ae-6abf-4e96-898c-3cfd69499022",
"name": "fragments",
"type": "array",
"value": "={{ $('\u591a\u89c6\u9891\u62fc\u63a5URL1').item.json.fragments }}"
}
]
},
"options": {}
},
"id": "f040f44b-7dcd-4ed4-a772-6829276b794b",
"name": "Edit Fields5",
"type": "n8n-nodes-base.set",
"position": [
-5072,
384
],
"typeVersion": 3.4
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "video_url_assign",
"name": "\u89c6\u9891",
"type": "string",
"value": "={{ Object.entries($json).find(([k,v]) => k.startsWith('\u521d\u526a'))?.[1]?.mp4 || $('Edit Fields11').item.json['\u591a\u89c6\u9891\u62fc\u63a5\u5730\u5740'] }}"
},
{
"id": "aa7b1d27-8be0-496b-8ae0-5880aeb6e086",
"name": "content",
"value": "={{ $('\u8f93\u51fa\u6240\u6709\u5206\u955c').all().map(i => i.json.voiceover).join('\\n') }}",
"type": "string"
},
{
"id": "f9d972e9-49e0-4019-a156-e239665ac1fb",
"name": "",
"value": "",
"type": "string"
}
]
},
"options": {}
},
"id": "49787db9-58c0-4161-bae1-25caf1894a89",
"name": "\u5f52\u96c6\u5f71\u7247\u5143\u7d20",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-4896,
384
]
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"value": "ezf13Cr651PEKpSC",
"mode": "list",
"cachedResultName": "\u5206\u955c",
"cachedResultUrl": "/projects/WuSZvJS20llMsunl/datatables/ezf13Cr651PEKpSC"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"retry_count": "={{ $json.retry_count }}",
"episode_id": "={{ $json.episode_id }}",
"index": "={{ $json.index }}",
"shot_id": "={{ $json.shot_id }}",
"status": "={{ $json.status }}",
"video_url": "={{ $json.video_url }}",
"fail_code": "={{ $json.fail_code }}",
"task_Id": "={{ $json.task_id }}",
"shot_data": "={{ $json.shot_data }}"
},
"matchingColumns": [],
"schema": [
{
"id": "episode_id",
"displayName": "episode_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "index",
"displayName": "index",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "shot_id",
"displayName": "shot_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "shot_data",
"displayName": "shot_data",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "task_Id",
"displayName": "task_Id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "video_url",
"displayName": "video_url",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "fail_code",
"displayName": "fail_code",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "retry_count",
"displayName": "retry_count",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
-9136,
432
],
"id": "0a904ddd-12f1-4336-a22a-bc961134d1fe",
"name": "Insert row"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const httpResponse = $json;\nconst currentIndex = $itemIndex;\n\n// \u76f4\u63a5\u4ece code-5 \u8282\u70b9\u6309\u7d22\u5f15\u83b7\u53d6\u5206\u955c\u6570\u636e\nconst originalShot = $items('code-5')[currentIndex]?.json;\n\nconst index = originalShot?._taskIndex ?? currentIndex;\nconst shotId = originalShot?.['\u5206\u955c']?.shot_id ?? `S${String(currentIndex + 1).padStart(2, '0')}`;\nconst taskId = httpResponse.data?.taskId;\nconst episodeId = originalShot?.episode_id || '1';\n\n// \u6784\u5efa shot_data\nconst shotData = originalShot ? {\n \u5206\u955c: originalShot['\u5206\u955c'],\n prev_anchor: originalShot.prev_anchor || '',\n next_anchor: originalShot.next_anchor || '',\n series_lock: originalShot.series_lock || ''\n} : null;\n\n// \u8c03\u8bd5\u65e5\u5fd7\nconsole.log(`currentIndex=${currentIndex}, shotId=${shotId}, shot_data.shot_id=${shotData?.\u5206\u955c?.shot_id}`);\n\nreturn {\n json: {\n episode_id: episodeId,\n index: index,\n shot_id: shotId,\n task_id: taskId,\n status: taskId ? 'pending' : 'failed',\n video_url: null,\n fail_code: null,\n retry_count: 0,\n shot_data: shotData ? JSON.stringify(shotData) : null\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-9360,
432
],
"id": "984589f9-3762-4f01-8dbb-a0ca3cb5e3c3",
"name": "\u5199\u5165\u4efb\u52a1\u5230DataTable"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "ezf13Cr651PEKpSC",
"mode": "list",
"cachedResultName": "\u5206\u955c",
"cachedResultUrl": "/projects/WuSZvJS20llMsunl/datatables/ezf13Cr651PEKpSC"
},
"filters": {
"conditions": [
{
"keyName": "status",
"keyValue": "pending"
}
]
},
"returnAll": true
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
-8544,
432
],
"id": "3f5a5ccd-b45b-4196-b4a7-cbc5c0be428e",
"name": "Get row(s)",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// \u628a\u591a\u4e2a\u8f93\u5165\u5408\u5e76\u62101\u4e2a\u89e6\u53d1\u4fe1\u53f7\nreturn [{\n json: {\n action: 'poll',\n time: new Date().toISOString(
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
cloudinaryApigooglePalmApihttpHeaderAuthopenAiApiopenRouterApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Sora 2 Pro Talking-Head Video Generator | AI 数字人口播视频生成(Sora 2 Pro + MiniMax TTS + FFmpeg 自动合成). Uses httpRequest, agent, lmChatOpenRouter, dataTable. Event-driven trigger; 74 nodes.
Source: https://github.com/gaoorange/n8n-ai-content-factory/blob/main/workflows/talking-head-video-pipeline/sora2-talking-head-video-generator.json — 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.
Turbo Videogen. Uses lmChatOpenRouter, agent, httpRequest, formTrigger. Event-driven trigger; 41 nodes.
RAG CHATBOT Main. Uses telegram, telegramTrigger, lmChatOpenAi, n8n-nodes-mcp. Event-driven trigger; 87 nodes.
Arvifund - Supabase (Fixed v6). Uses httpRequest, telegram, supabase, telegramTrigger. Event-driven trigger; 92 nodes.
Arvifund - Supabase (Fixed v5). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 91 nodes.
Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.