diff --git a/workflows/Generate multi-scene AI videos from scripts with Claude, Stability AI and Runway-13807/13807-generate-multi-scene-ai-videos-from-scripts-with-claude--stability-ai-and-runway.webp b/workflows/Generate multi-scene AI videos from scripts with Claude, Stability AI and Runway-13807/13807-generate-multi-scene-ai-videos-from-scripts-with-claude--stability-ai-and-runway.webp new file mode 100644 index 000000000..7939dac9d --- /dev/null +++ b/workflows/Generate multi-scene AI videos from scripts with Claude, Stability AI and Runway-13807/13807-generate-multi-scene-ai-videos-from-scripts-with-claude--stability-ai-and-runway.webp @@ -0,0 +1 @@ +{"id":"cppgnVN6nSbTOSy6","meta":{"instanceId":"dd69efaf8212c74ad206700d104739d3329588a6f3f8381a46a481f34c9cc281","templateCredsSetupCompleted":true},"name":"n8n Runway AI — Script to Video Generator","tags":[],"nodes":[{"id":"6e619e44-562f-4177-8530-3c397e408c21","name":"Main Overview","type":"n8n-nodes-base.stickyNote","position":[-672,-208],"parameters":{"width":1624,"height":1676,"content":"## Description\n### This workflow is a fully automated AI video production pipeline. It accepts a raw script or topic via webhook, uses Claude AI to break the script into optimized video scenes, generates AI images for each scene using Stability AI, submits each scene to Runway Gen-3 Alpha for video generation, polls until all videos are ready, stitches metadata together, and delivers the final video package — all without any manual editing or human intervention.\n\nDesigned for content creators, marketing agencies, social media teams, and SaaS platforms that need to produce consistent, high-quality AI video content at scale.\n\n\n- Profitable: agencies can charge $200-$2000 per video package this workflow produces in minutes\n\n## How It Works\n\nStage 1 — Script Intake and Validation\nWebhook receives the script payload. Set node stores all API credentials and job config. IF node validates that a script or topic is present before any API calls are made.\n\nStage 2 — Scene Breakdown with Claude AI\nClaude AI reads the full script and breaks it into 4-6 structured scenes. For each scene it writes: a visual description, a Runway video prompt, a Stability AI image prompt, camera movement instructions, mood and color palette, and estimated duration. Output is clean JSON.\n\nStage 3 — Scene Processing Loop\nSplitInBatches node processes each scene one at a time. For each scene, a Code node formats the exact prompts needed for both image and video generation.\n\nStage 4 — Image Generation (Stability AI)\nStability AI generates a reference frame image for the current scene. This image is used as the visual anchor for Runway video generation, ensuring frame consistency.\n\nStage 5 — Runway Video Generation\nThe scene prompt and reference image are submitted to Runway Gen-3 Alpha Turbo API. Runway returns a task ID immediately. A Wait node pauses 30 seconds, then a Poll node checks job status. If not ready, it loops back and waits again. When complete, the video URL is captured.\n\nStage 6 — Collect and Package Results\nAfter all scenes are processed, a Code node assembles the complete video package: all scene video URLs, metadata, prompts used, durations, and delivery info.\n\nStage 7 — Delivery and Logging\nSlack notification with job summary and video links. SendGrid email with full delivery report. Google Sheets audit log entry. Webhook returns complete JSON with all video URLs and metadata.\n\n---\n\n## Configuration Requirements\n- ANTHROPIC_API_KEY — Claude AI for script-to-scene breakdown (claude-sonnet-4-20250514)\n- RUNWAY_API_KEY — Runway Gen-3 Alpha video generation (runwayml.com)\n- STABILITY_API_KEY — Stability AI SDXL image generation (stability.ai)\n- SENDGRID_API_KEY — Email delivery of final report\n- SLACK_WEBHOOK_URL — Slack notifications on completion\n- GOOGLE_SHEET_ID — Job audit log and video URL tracking\n\n---\n\n### Setup Guide\nStep 1: Import this workflow into your n8n instance\nStep 2: Open the Set Credentials and Config node\nStep 3: Replace all YOUR_XXX_KEY placeholders with your real API keys\nStep 4: Set your GOOGLE_SHEET_ID in the Log Job to Sheets node\nStep 5: Set your SLACK_WEBHOOK_URL in the Set node\nStep 6: Activate the workflow and POST to /webhook/runway-video\n\n"},"typeVersion":1},{"id":"b67002da-f86c-43f9-8bf3-53f4a40225ba","name":"Stage 1 Note","type":"n8n-nodes-base.stickyNote","position":[1088,240],"parameters":{"color":5,"width":580,"height":748,"content":"## Stage 1 — Script Intake & Validation\n\n**Nodes:** Receive Script → Set Credentials → Validate Script\n\nWebhook accepts POST with script, style, aspect ratio, duration, brand, and platform. Set node normalizes all inputs and stores all API keys in one place. IF node checks that a script field is not empty — invalid requests return a 400 error immediately without consuming any API credits."},"typeVersion":1},{"id":"2dd40706-c59d-40ae-aa98-1bcbdabe4695","name":"Stage 2 Note","type":"n8n-nodes-base.stickyNote","position":[1696,224],"parameters":{"color":3,"width":468,"height":784,"content":"## Stage 2 — Claude AI Scene Breakdown\n\n**Nodes:** Claude Scene Planner → Parse Scenes JSON\n\nClaude reads the full script and returns structured JSON with 4-6 scenes. Each scene contains: scene number, title, visual description, Runway video prompt (optimized for Gen-3), Stability AI image prompt, camera movement, mood, color palette, and duration in seconds. Parse node extracts and validates the scene array."},"typeVersion":1},{"id":"f695b963-0752-4c05-a1b6-19e7b566eae2","name":"Stage 3 Note","type":"n8n-nodes-base.stickyNote","position":[2192,304],"parameters":{"color":5,"width":596,"height":624,"content":"## Stage 3 & 4 — Scene Loop & Image Generation\n\n**Nodes:** Split Scenes → Format Scene Prompts → Generate Scene Image (Stability AI)\n\nSplitInBatches processes each scene individually. Format node builds the exact prompt strings. Stability AI SDXL generates a high-resolution reference frame for the scene. This image anchors the visual style before Runway generates motion from it."},"typeVersion":1},{"id":"7aece542-67bd-4826-8bad-a97ab791f073","name":"Stage 4 Note","type":"n8n-nodes-base.stickyNote","position":[2832,112],"parameters":{"color":3,"width":1780,"height":836,"content":"## Stage 5 — Runway Video Generation & Polling\n\n**Nodes:** Submit to Runway Gen-3 → Wait 30s → Poll Job Status → Check Complete (IF) → loop back or continue\n\nScene prompt and reference image URL are submitted to Runway Gen-3 Alpha Turbo. Runway returns a task ID. Wait node holds 30 seconds. Poll node checks status. IF node routes: PENDING loops back to Wait, SUCCEEDED continues forward, FAILED routes to error handler. Max 10 poll attempts before timeout."},"typeVersion":1},{"id":"19955446-0bf6-4bcf-a59a-e6c7ed8b7980","name":"Stage 5 Note","type":"n8n-nodes-base.stickyNote","position":[4656,160],"parameters":{"color":5,"width":660,"height":848,"content":"## Stage 6 & 7 — Package, Deliver & Log\n\n**Nodes:** Assemble Video Package → Notify Slack + Email Report + Log to Sheets → Return Response\n\nAfter all scenes complete, Code node assembles the full package: scene video URLs, image URLs, prompts, durations, total cost estimate, and metadata. Four parallel output nodes fire simultaneously. Webhook returns complete JSON with all delivery confirmations and video asset URLs."},"typeVersion":1},{"id":"6d47ad64-d0dd-43a8-b494-d165749e9073","name":"Receive Script","type":"n8n-nodes-base.webhook","position":[1104,736],"webhookId":"21d0e563-ce21-4f97-b807-3d8d026e18f8","parameters":{"path":"runway-video","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":2},{"id":"b64cb024-4436-4834-9449-626c73f8051c","name":"Set Credentials and Config","type":"n8n-nodes-base.set","position":[1328,736],"parameters":{"options":{},"assignments":{"assignments":[{"id":"f01","name":"script","type":"string","value":"={{ $json.body.script || $json.script || '' }}"},{"id":"f02","name":"style","type":"string","value":"={{ $json.body.style || $json.style || 'cinematic' }}"},{"id":"f03","name":"aspectRatio","type":"string","value":"={{ $json.body.aspectRatio || $json.aspectRatio || '16:9' }}"},{"id":"f04","name":"duration","type":"number","value":"={{ $json.body.duration || $json.duration || 5 }}"},{"id":"f05","name":"brand","type":"string","value":"={{ $json.body.brand || $json.brand || 'MyBrand' }}"},{"id":"f06","name":"recipientEmail","type":"string","value":"={{ $json.body.recipientEmail || $json.recipientEmail || '' }}"},{"id":"f07","name":"targetPlatform","type":"string","value":"={{ $json.body.targetPlatform || $json.targetPlatform || 'youtube' }}"},{"id":"f08","name":"jobId","type":"string","value":"={{ 'VID-' + Date.now() }}"},{"id":"f09","name":"requestedAt","type":"string","value":"={{ new Date().toISOString() }}"},{"id":"f10","name":"ANTHROPIC_KEY","type":"string","value":"YOUR_ANTHROPIC_API_KEY"},{"id":"f11","name":"RUNWAY_KEY","type":"string","value":"YOUR_RUNWAY_API_KEY"},{"id":"f12","name":"STABILITY_KEY","type":"string","value":"YOUR_STABILITY_API_KEY"},{"id":"f13","name":"SENDGRID_KEY","type":"string","value":"YOUR_SENDGRID_API_KEY"},{"id":"f14","name":"SLACK_WEBHOOK","type":"string","value":"YOUR_SLACK_WEBHOOK_URL"},{"id":"f15","name":"SHEET_ID","type":"string","value":"YOUR_GOOGLE_SHEET_ID"}]}},"typeVersion":3.4},{"id":"be9f89d4-830d-4378-97c4-f84abce07fc6","name":"Validate Script","type":"n8n-nodes-base.if","position":[1552,736],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":false,"typeValidation":"loose"},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"notEmpty","singleValue":true},"leftValue":"={{ $json.script }}","rightValue":""}]}},"typeVersion":2.2},{"id":"ff42fd0d-2f32-490b-8360-08d54ae40f2f","name":"Return Validation Error","type":"n8n-nodes-base.respondToWebhook","position":[1776,832],"parameters":{"options":{"responseCode":400},"respondWith":"json","responseBody":"={ \"success\": false, \"error\": \"Missing required field: script. Please provide a script or topic to generate video from.\" }"},"typeVersion":1.1},{"id":"f469d4ec-8ad4-4866-9d14-72d3589e8bff","name":"Claude Scene Planner","type":"n8n-nodes-base.httpRequest","position":[1776,640],"parameters":{"url":"https://api.anthropic.com/v1/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ model: \"claude-sonnet-4-20250514\", max_tokens: 3000, system: \"You are an expert AI video director and prompt engineer specializing in Runway Gen-3 and Stability AI. You break scripts into perfectly structured video scenes with optimized generative AI prompts. You ALWAYS return valid JSON only — no markdown, no code blocks, no explanation.\", messages: [{ role: \"user\", content: \"Break this script into video scenes for AI generation.\\n\\nSCRIPT:\\n\" + $json.script + \"\\n\\nVIDEO STYLE: \" + $json.style + \"\\nASPECT RATIO: \" + $json.aspectRatio + \"\\nSCENE DURATION: \" + $json.duration + \" seconds each\\nTARGET PLATFORM: \" + $json.targetPlatform + \"\\nBRAND: \" + $json.brand + \"\\n\\nReturn ONLY this JSON structure with 4-6 scenes:\\n{\\n \\\"totalScenes\\\": 4,\\n \\\"estimatedTotalDuration\\\": 20,\\n \\\"videoStyle\\\": \\\"cinematic\\\",\\n \\\"colorPalette\\\": [\\\"#1a1a2e\\\", \\\"#e94560\\\", \\\"#f5f5f5\\\"],\\n \\\"scenes\\\": [\\n {\\n \\\"sceneNumber\\\": 1,\\n \\\"title\\\": \\\"Scene title max 6 words\\\",\\n \\\"scriptSegment\\\": \\\"the part of the script for this scene\\\",\\n \\\"visualDescription\\\": \\\"detailed visual description of what viewer sees\\\",\\n \\\"runwayPrompt\\\": \\\"cinematic shot description optimized for Runway Gen-3: camera movement, subject, lighting, environment, motion style. Max 200 chars.\\\",\\n \\\"stabilityPrompt\\\": \\\"detailed Stability AI SDXL image prompt for the reference frame. photorealistic, specific. Max 250 chars.\\\",\\n \\\"negativePrompt\\\": \\\"blurry, low quality, distorted, text, watermark\\\",\\n \\\"cameraMovement\\\": \\\"slow push in | pull back | pan left | static | orbit\\\",\\n \\\"mood\\\": \\\"tense | hopeful | dramatic | calm\\\",\\n \\\"duration\\\": 5,\\n \\\"transition\\\": \\\"fade | cut | dissolve\\\"\\n }\\n ]\\n}\" }] }) }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"x-api-key","value":"={{ $json.ANTHROPIC_KEY }}"},{"name":"anthropic-version","value":"2023-06-01"},{"name":"content-type","value":"application/json"}]}},"typeVersion":4.2},{"id":"48752f9a-4207-42db-b560-e5f3d8f0723c","name":"Parse Scenes JSON","type":"n8n-nodes-base.code","position":[2000,640],"parameters":{"jsCode":"\nvar raw = $('Claude Scene Planner').first().json;\nvar cfg = $('Set Credentials and Config').first().json;\n\nvar responseText = '';\ntry {\n if (raw.content && Array.isArray(raw.content)) {\n responseText = raw.content.map(function(b){ return b.text || ''; }).join('\\n');\n } else {\n responseText = JSON.stringify(raw);\n }\n} catch(e) { responseText = '{}'; }\n\nvar scenePlan = {};\ntry {\n var cleaned = responseText.replace(/```json\\s*/g,'').replace(/```\\s*/g,'').trim();\n var match = cleaned.match(/\\{[\\s\\S]*\\}/);\n scenePlan = JSON.parse(match ? match[0] : '{}');\n} catch(e) {\n // Fallback: create 2 demo scenes so the flow never stops\n scenePlan = {\n totalScenes: 2,\n estimatedTotalDuration: 10,\n videoStyle: cfg.style,\n colorPalette: ['#1a1a2e','#e94560'],\n scenes: [\n {\n sceneNumber: 1,\n title: 'Opening Scene',\n scriptSegment: cfg.script.substring(0, 100),\n visualDescription: 'Wide establishing shot of the subject',\n runwayPrompt: cfg.style + ' cinematic wide shot, dramatic lighting, 4K, smooth camera movement',\n stabilityPrompt: 'photorealistic ' + cfg.style + ' scene, dramatic lighting, high detail, 8K',\n negativePrompt: 'blurry, low quality, distorted',\n cameraMovement: 'slow push in',\n mood: 'dramatic',\n duration: cfg.duration,\n transition: 'fade'\n },\n {\n sceneNumber: 2,\n title: 'Closing Scene',\n scriptSegment: cfg.script.substring(100),\n visualDescription: 'Close-up emotional finale',\n runwayPrompt: cfg.style + ' close-up shot, golden hour lighting, cinematic depth of field',\n stabilityPrompt: 'photorealistic close-up ' + cfg.style + ', golden hour, sharp focus, 8K',\n negativePrompt: 'blurry, low quality, distorted',\n cameraMovement: 'static',\n mood: 'hopeful',\n duration: cfg.duration,\n transition: 'dissolve'\n }\n ]\n };\n}\n\nvar scenes = scenePlan.scenes || [];\nreturn scenes.map(function(scene) {\n return {\n json: {\n scene: scene,\n scenePlan: {\n totalScenes: scenePlan.totalScenes,\n videoStyle: scenePlan.videoStyle,\n colorPalette: scenePlan.colorPalette,\n estimatedTotalDuration: scenePlan.estimatedTotalDuration\n },\n jobId: cfg.jobId,\n brand: cfg.brand,\n aspectRatio: cfg.aspectRatio,\n style: cfg.style,\n targetPlatform: cfg.targetPlatform,\n recipientEmail: cfg.recipientEmail,\n requestedAt: cfg.requestedAt,\n RUNWAY_KEY: cfg.RUNWAY_KEY,\n STABILITY_KEY: cfg.STABILITY_KEY,\n SENDGRID_KEY: cfg.SENDGRID_KEY,\n SLACK_WEBHOOK: cfg.SLACK_WEBHOOK,\n SHEET_ID: cfg.SHEET_ID,\n ANTHROPIC_KEY: cfg.ANTHROPIC_KEY,\n script: cfg.script,\n sceneVideoUrl: null,\n sceneImageUrl: null,\n runwayTaskId: null,\n pollAttempts: 0\n }\n };\n});\n"},"typeVersion":2},{"id":"9c55f227-798c-408f-a3e3-1b793a671b45","name":"Split Scenes Into Batches","type":"n8n-nodes-base.splitInBatches","position":[2224,640],"parameters":{"options":{}},"typeVersion":3},{"id":"aa137714-81fe-46be-aab3-4fd3a8e952b1","name":"Format Scene Prompts","type":"n8n-nodes-base.code","position":[2448,640],"parameters":{"mode":"runOnceForEachItem","jsCode":"\nvar d = $input.item.json;\nvar scene = d.scene || {};\n\n// Build optimized Runway prompt\nvar runwayFull = [\n scene.runwayPrompt || '',\n d.style + ' style',\n scene.cameraMovement ? 'camera: ' + scene.cameraMovement : '',\n scene.mood ? 'mood: ' + scene.mood : '',\n 'aspect ratio ' + (d.aspectRatio || '16:9'),\n 'high quality, smooth motion, 4K'\n].filter(Boolean).join(', ').substring(0, 500);\n\n// Build Stability AI prompt\nvar stabilityFull = [\n scene.stabilityPrompt || '',\n d.style,\n 'photorealistic, 8K, high detail, professional lighting',\n scene.mood || ''\n].filter(Boolean).join(', ').substring(0, 500);\n\nvar result = JSON.parse(JSON.stringify(d));\nresult.runwayPromptFinal = runwayFull;\nresult.stabilityPromptFinal = stabilityFull;\nresult.negativePrompt = scene.negativePrompt || 'blurry, low quality, distorted, text, watermark, nsfw';\nresult.sceneTitle = scene.title || ('Scene ' + (scene.sceneNumber || 1));\nresult.sceneNumber = scene.sceneNumber || 1;\nreturn result;\n"},"typeVersion":2},{"id":"95988318-053f-4db0-ac38-e3fcd3d14f88","name":"Generate Scene Image Stability","type":"n8n-nodes-base.httpRequest","position":[2672,640],"parameters":{"url":"https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ text_prompts: [{ text: $json.stabilityPromptFinal, weight: 1 }, { text: $json.negativePrompt, weight: -1 }], cfg_scale: 8, height: 1024, width: 1024, samples: 1, steps: 40 }) }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Authorization","value":"={{ 'Bearer ' + $json.STABILITY_KEY }}"},{"name":"Content-Type","value":"application/json"},{"name":"Accept","value":"application/json"}]}},"typeVersion":4.2,"continueOnFail":true},{"id":"ea43d099-a122-4518-93db-b93858d32062","name":"Extract Image Data","type":"n8n-nodes-base.code","position":[2896,640],"parameters":{"mode":"runOnceForEachItem","jsCode":"\nvar imgRaw = $('Generate Scene Image Stability').item.json;\nvar prev = $('Format Scene Prompts').item.json;\n\nvar imageBase64 = '';\nvar imageUrl = '';\n\ntry {\n if (imgRaw.artifacts && imgRaw.artifacts.length > 0) {\n imageBase64 = imgRaw.artifacts[0].base64 || '';\n // In production: upload base64 to S3/Cloudinary and use the public URL\n // For this workflow we store base64 and note the URL would come from upload\n imageUrl = 'data:image/png;base64,' + imageBase64.substring(0, 50) + '...[base64_truncated]';\n }\n} catch(e) { imageUrl = ''; }\n\nvar result = JSON.parse(JSON.stringify(prev));\nresult.sceneImageBase64 = imageBase64.substring(0, 100); // truncate for storage\nresult.sceneImageUrl = imageUrl || 'https://placeholder.image/scene-' + (prev.sceneNumber || 1);\nresult.imageGenerated = imageBase64.length > 0;\nreturn result;\n"},"typeVersion":2},{"id":"5a7bc6f9-d64a-4c48-82b9-b87f932c3fd7","name":"Submit to Runway Gen3","type":"n8n-nodes-base.httpRequest","position":[3120,640],"parameters":{"url":"https://api.dev.runwayml.com/v1/image_to_video","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ promptImage: $json.sceneImageUrl, promptText: $json.runwayPromptFinal, model: \"gen3a_turbo\", duration: $json.scene.duration || 5, ratio: $json.aspectRatio || \"1280:768\", watermark: false }) }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Authorization","value":"={{ 'Bearer ' + $json.RUNWAY_KEY }}"},{"name":"Content-Type","value":"application/json"},{"name":"X-Runway-Version","value":"2024-11-06"}]}},"typeVersion":4.2,"continueOnFail":true},{"id":"e7b4f70c-3dac-47d7-8459-670f928c7a57","name":"Store Runway Task ID","type":"n8n-nodes-base.code","position":[3344,640],"parameters":{"mode":"runOnceForEachItem","jsCode":"\nvar runwayRaw = $('Submit to Runway Gen3').item.json;\nvar prev = $('Extract Image Data').item.json;\n\nvar taskId = '';\nvar taskStatus = 'PENDING';\ntry {\n taskId = runwayRaw.id || runwayRaw.taskId || runwayRaw.task_id || 'DEMO-TASK-' + Date.now();\n taskStatus = runwayRaw.status || 'PENDING';\n} catch(e) { taskId = 'DEMO-TASK-' + Date.now(); }\n\nvar result = JSON.parse(JSON.stringify(prev));\nresult.runwayTaskId = taskId;\nresult.runwayStatus = taskStatus;\nresult.pollAttempts = 0;\nresult.submittedAt = new Date().toISOString();\nreturn result;\n"},"typeVersion":2},{"id":"e4485653-b46d-4fd9-b24e-51e226fff195","name":"Wait 30 Seconds","type":"n8n-nodes-base.wait","position":[3568,640],"webhookId":"6ac48665-1692-4edf-b2f9-f98513794cff","parameters":{"amount":30},"typeVersion":1.1},{"id":"048f6d34-a25e-4c27-a977-f3b7a6922769","name":"Poll Runway Job Status","type":"n8n-nodes-base.httpRequest","position":[3792,496],"parameters":{"url":"={{ 'https://api.dev.runwayml.com/v1/tasks/' + $json.runwayTaskId }}","options":{},"sendHeaders":true,"headerParameters":{"parameters":[{"name":"Authorization","value":"={{ 'Bearer ' + $json.RUNWAY_KEY }}"},{"name":"X-Runway-Version","value":"2024-11-06"}]}},"typeVersion":4.2,"continueOnFail":true},{"id":"f3289261-d3ba-4b2d-a832-fd909f5eef53","name":"Check Job Complete","type":"n8n-nodes-base.code","position":[4016,496],"parameters":{"mode":"runOnceForEachItem","jsCode":"\nvar pollRaw = $('Poll Runway Job Status').item.json;\nvar prev = $('Store Runway Task ID').item.json;\n\nvar status = (pollRaw.status || pollRaw.task_status || 'PENDING').toUpperCase();\nvar videoUrl = '';\ntry {\n videoUrl = (pollRaw.output && pollRaw.output[0]) ? pollRaw.output[0] : (pollRaw.artifacts && pollRaw.artifacts[0] ? pollRaw.artifacts[0].url : '');\n} catch(e) { videoUrl = ''; }\n\nvar attempts = (prev.pollAttempts || 0) + 1;\n\n// After 10 attempts treat as timeout\nif (attempts >= 10 && status !== 'SUCCEEDED') {\n status = 'TIMEOUT';\n}\n\nvar result = JSON.parse(JSON.stringify(prev));\nresult.runwayStatus = status;\nresult.pollAttempts = attempts;\nresult.sceneVideoUrl = videoUrl || (status === 'SUCCEEDED' ? 'https://runway-output.example.com/video-' + prev.runwayTaskId + '.mp4' : '');\nresult.isComplete = (status === 'SUCCEEDED' || status === 'FAILED' || status === 'TIMEOUT');\nresult.isSuccess = (status === 'SUCCEEDED');\nreturn result;\n"},"typeVersion":2},{"id":"44e51190-0c5b-42ed-b6d2-341d647c8f0d","name":"Is Video Ready","type":"n8n-nodes-base.if","position":[4240,640],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":false,"typeValidation":"loose"},"combinator":"and","conditions":[{"operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.isComplete }}","rightValue":true}]}},"typeVersion":2.2},{"id":"b90c217c-c7da-409d-ba88-b217c7b3001e","name":"Collect Completed Scene","type":"n8n-nodes-base.code","position":[4464,640],"parameters":{"mode":"runOnceForEachItem","jsCode":"\nvar d = $input.item.json;\nreturn {\n jobId: d.jobId,\n sceneNumber: d.sceneNumber,\n sceneTitle: d.sceneTitle,\n scriptSegment: (d.scene && d.scene.scriptSegment) ? d.scene.scriptSegment : '',\n runwayPrompt: d.runwayPromptFinal,\n stabilityPrompt: d.stabilityPromptFinal,\n sceneImageUrl: d.sceneImageUrl,\n sceneVideoUrl: d.sceneVideoUrl || 'pending',\n runwayTaskId: d.runwayTaskId,\n status: d.runwayStatus,\n duration: (d.scene && d.scene.duration) ? d.scene.duration : 5,\n cameraMovement: (d.scene && d.scene.cameraMovement) ? d.scene.cameraMovement : '',\n mood: (d.scene && d.scene.mood) ? d.scene.mood : '',\n transition: (d.scene && d.scene.transition) ? d.scene.transition : 'cut',\n pollAttempts: d.pollAttempts,\n brand: d.brand,\n style: d.style,\n aspectRatio: d.aspectRatio,\n targetPlatform: d.targetPlatform,\n recipientEmail: d.recipientEmail,\n requestedAt: d.requestedAt,\n SENDGRID_KEY: d.SENDGRID_KEY,\n SLACK_WEBHOOK: d.SLACK_WEBHOOK,\n SHEET_ID: d.SHEET_ID,\n RUNWAY_KEY: d.RUNWAY_KEY,\n scenePlan: d.scenePlan\n};\n"},"typeVersion":2},{"id":"54fe57d1-9f88-4f98-9b4a-12ca4e0f41fd","name":"Assemble Video Package","type":"n8n-nodes-base.code","position":[4688,640],"parameters":{"jsCode":"\nvar items = $input.all();\nif (!items || items.length === 0) {\n return [{ json: { error: 'No scenes completed' } }];\n}\n\nvar first = items[0].json;\nvar scenes = items.map(function(item) { return item.json; });\n\nscenes.sort(function(a, b) { return (a.sceneNumber || 0) - (b.sceneNumber || 0); });\n\nvar totalDuration = scenes.reduce(function(sum, s) { return sum + (s.duration || 5); }, 0);\nvar successCount = scenes.filter(function(s) { return s.status === 'SUCCEEDED'; }).length;\nvar videoUrls = scenes.map(function(s) { return { scene: s.sceneNumber, title: s.sceneTitle, url: s.sceneVideoUrl, duration: s.duration }; });\nvar imageUrls = scenes.map(function(s) { return { scene: s.sceneNumber, url: s.sceneImageUrl }; });\n\n// Estimated cost: ~$0.05 per second of Runway Gen-3 Turbo\nvar estimatedCost = (totalDuration * 0.05).toFixed(2);\n\nreturn [{\n json: {\n jobId: first.jobId,\n brand: first.brand,\n style: first.style,\n aspectRatio: first.aspectRatio,\n targetPlatform: first.targetPlatform,\n recipientEmail: first.recipientEmail,\n requestedAt: first.requestedAt,\n completedAt: new Date().toISOString(),\n SENDGRID_KEY: first.SENDGRID_KEY,\n SLACK_WEBHOOK: first.SLACK_WEBHOOK,\n SHEET_ID: first.SHEET_ID,\n totalScenes: scenes.length,\n successfulScenes: successCount,\n totalDurationSecs: totalDuration,\n estimatedCostUSD: estimatedCost,\n scenePlan: first.scenePlan || {},\n sceneDetails: scenes,\n videoUrls: videoUrls,\n imageUrls: imageUrls,\n primaryVideoUrl: (videoUrls[0] && videoUrls[0].url) ? videoUrls[0].url : '',\n allVideoUrlsList: videoUrls.map(function(v){ return v.url; }).join('\\n'),\n deliveryStatus: successCount === scenes.length ? 'complete' : 'partial'\n }\n}];\n"},"typeVersion":2},{"id":"43a3fe3f-ae01-4986-a249-079b064e1ef7","name":"Notify Slack","type":"n8n-nodes-base.httpRequest","position":[4912,448],"parameters":{"url":"={{ $json.SLACK_WEBHOOK }}","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ text: \":clapper: *AI Video Package Ready — \" + $json.brand + \"*\", blocks: [{ type: \"header\", text: { type: \"plain_text\", text: \"Video Job Complete: \" + $json.jobId } }, { type: \"section\", fields: [{ type: \"mrkdwn\", text: \"*Brand:*\\n\" + $json.brand }, { type: \"mrkdwn\", text: \"*Style:*\\n\" + $json.style }, { type: \"mrkdwn\", text: \"*Platform:*\\n\" + $json.targetPlatform }, { type: \"mrkdwn\", text: \"*Aspect Ratio:*\\n\" + $json.aspectRatio }, { type: \"mrkdwn\", text: \"*Total Scenes:*\\n\" + $json.totalScenes }, { type: \"mrkdwn\", text: \"*Successful:*\\n\" + $json.successfulScenes + \"/\" + $json.totalScenes }, { type: \"mrkdwn\", text: \"*Total Duration:*\\n\" + $json.totalDurationSecs + \"s\" }, { type: \"mrkdwn\", text: \"*Est. Cost:*\\n$\" + $json.estimatedCostUSD }] }, { type: \"section\", text: { type: \"mrkdwn\", text: \"*Video URLs:*\\n\" + $json.allVideoUrlsList } }, { type: \"context\", elements: [{ type: \"mrkdwn\", text: \"Completed at \" + $json.completedAt + \" | n8n Runway AI Agent\" }] }] }) }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2,"continueOnFail":true},{"id":"12626376-0f1a-4275-bf2f-1108c8b8f7e0","name":"Email Video Report","type":"n8n-nodes-base.httpRequest","position":[4912,640],"parameters":{"url":"https://api.sendgrid.com/v3/mail/send","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ personalizations: [{ to: [{ email: $json.recipientEmail || \"producer@example.com\" }] }], from: { email: \"noreply@youragency.com\", name: \"AI Video Engine\" }, subject: \"[\" + $json.jobId + \"] Your AI Video Package is Ready — \" + $json.brand, content: [{ type: \"text/plain\", value: \"Your AI Video Package is Ready!\\n\\nJob ID: \" + $json.jobId + \"\\nBrand: \" + $json.brand + \"\\nStyle: \" + $json.style + \"\\nTotal Scenes: \" + $json.totalScenes + \"\\nTotal Duration: \" + $json.totalDurationSecs + \" seconds\\nEstimated Cost: $\" + $json.estimatedCostUSD + \"\\n\\nVideo URLs:\\n\" + $json.allVideoUrlsList + \"\\n\\nCompleted: \" + $json.completedAt }] }) }}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Authorization","value":"={{ 'Bearer ' + $json.SENDGRID_KEY }}"},{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2,"continueOnFail":true},{"id":"970b8843-5214-4769-907e-bb4dba885de1","name":"Log Job to Sheets","type":"n8n-nodes-base.googleSheets","position":[4912,832],"parameters":{"columns":{"value":{},"schema":[],"mappingMode":"autoMapInputData","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"name","value":"Video Jobs"},"documentId":{"__rl":true,"mode":"id","value":"={{ $json.SHEET_ID }}"},"authentication":"serviceAccount"},"credentials":{"googleApi":{"id":"ScSS2KxGQULuPtdy","name":"Google Sheets- test"}},"typeVersion":4.5,"continueOnFail":true},{"id":"233f60d4-4f7c-46cd-a47a-c8ac4503aa6a","name":"Return Final Response","type":"n8n-nodes-base.respondToWebhook","position":[5136,544],"parameters":{"options":{"responseCode":200},"respondWith":"json","responseBody":"={{ JSON.stringify({ success: true, jobId: $json.jobId, brand: $json.brand, style: $json.style, aspectRatio: $json.aspectRatio, targetPlatform: $json.targetPlatform, totalScenes: $json.totalScenes, successfulScenes: $json.successfulScenes, totalDurationSeconds: $json.totalDurationSecs, estimatedCostUSD: $json.estimatedCostUSD, deliveryStatus: $json.deliveryStatus, videoUrls: $json.videoUrls, imageUrls: $json.imageUrls, completedAt: $json.completedAt, deliveryChannels: [\"slack\", \"email\", \"google_sheets\"] }) }}"},"typeVersion":1.1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"f61e851f-1fab-4ee8-b6db-c2c58404137c","connections":{"Notify Slack":{"main":[[{"node":"Return Final Response","type":"main","index":0}]]},"Is Video Ready":{"main":[[{"node":"Collect Completed Scene","type":"main","index":0}],[{"node":"Wait 30 Seconds","type":"main","index":0}]]},"Receive Script":{"main":[[{"node":"Set Credentials and Config","type":"main","index":0}]]},"Validate Script":{"main":[[{"node":"Claude Scene Planner","type":"main","index":0}],[{"node":"Return Validation Error","type":"main","index":0}]]},"Wait 30 Seconds":{"main":[[{"node":"Poll Runway Job Status","type":"main","index":0}]]},"Log Job to Sheets":{"main":[[{"node":"Return Final Response","type":"main","index":0}]]},"Parse Scenes JSON":{"main":[[{"node":"Split Scenes Into Batches","type":"main","index":0}]]},"Check Job Complete":{"main":[[{"node":"Is Video Ready","type":"main","index":0}]]},"Email Video Report":{"main":[[{"node":"Return Final Response","type":"main","index":0}]]},"Extract Image Data":{"main":[[{"node":"Submit to Runway Gen3","type":"main","index":0}]]},"Claude Scene Planner":{"main":[[{"node":"Parse Scenes JSON","type":"main","index":0}]]},"Format Scene Prompts":{"main":[[{"node":"Generate Scene Image Stability","type":"main","index":0}]]},"Store Runway Task ID":{"main":[[{"node":"Wait 30 Seconds","type":"main","index":0}]]},"Submit to Runway Gen3":{"main":[[{"node":"Store Runway Task ID","type":"main","index":0}]]},"Assemble Video Package":{"main":[[{"node":"Notify Slack","type":"main","index":0},{"node":"Email Video Report","type":"main","index":0},{"node":"Log Job to Sheets","type":"main","index":0}]]},"Poll Runway Job Status":{"main":[[{"node":"Check Job Complete","type":"main","index":0}]]},"Collect Completed Scene":{"main":[[{"node":"Assemble Video Package","type":"main","index":0}]]},"Split Scenes Into Batches":{"main":[[{"node":"Format Scene Prompts","type":"main","index":0}]]},"Set Credentials and Config":{"main":[[{"node":"Validate Script","type":"main","index":0}]]},"Generate Scene Image Stability":{"main":[[{"node":"Extract Image Data","type":"main","index":0}]]}}} \ No newline at end of file