This workflow corresponds to n8n.io template #12728 — we link there as the canonical source.
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 →
{
"id": "TNf36l4Mq3wVfBMLIVBk-",
"name": "Create AI screencast videos with VEED and automated slides",
"tags": [],
"nodes": [
{
"id": "13577dc8-b30d-4120-81a4-a8280f3a943d",
"name": "When clicking 'Execute workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-8688,
1152
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e8d92c4e-cedd-4698-a3fe-5c9606d176d0",
"name": "\u2699\ufe0f Workflow Configuration",
"type": "n8n-nodes-base.code",
"position": [
-8448,
1152
],
"parameters": {
"jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// \u2699\ufe0f SCREEN RECORDING VIDEO GENERATOR - CONFIGURATION\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nreturn [{\n json: {\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83d\udcdd CONTENT SETTINGS\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n topic: \"How AI is transforming content creation, opening up opportunities for personalization and scale\",\n intention: \"informative\", // Options: informative, lead_generation, disruption\n brand_name: \"YOUR_BRAND_NAME\",\n target_audience: \"sales teams and marketers\",\n trending_hashtags: \"#AIvideo #ContentCreation #VideoMarketing #AItools\",\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83c\udfa8 SLIDE STYLE\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Options:\n // - dark_professional: Dark gradients, white text, sleek look\n // - light_modern: Light backgrounds, dark text, clean\n // - vibrant_colorful: Bold colors, energetic, eye-catching\n // - minimalist: Lots of whitespace, simple, elegant\n // - tech_corporate: Blue tones, geometric shapes, professional\n slide_style: \"vibrant_colorful\",\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83c\udfa5 VIDEO SETTINGS\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n video_resolution: \"720p\", // VEED only supports 720p\n // num_slides is now calculated automatically based on script length\n // Target: 1 slide per 5-7 seconds of audio\n seconds_per_slide: 6, // How long each slide shows (affects slide count)\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83d\uddbc\ufe0f BACKGROUND (Optional)\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Option 1: Gradient colors array e.g. [\"#ff6b6b\", \"#feca57\", \"#48dbfb\"]\n // Option 2: Image URL string e.g. \"https://example.com/background.jpg\"\n // Option 3: Empty string \"\" for no background (full bleed layout)\n background: \"\",\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83d\udd11 API KEYS (Required)\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n anthropic_api_key: \"YOUR_ANTHROPIC_API_KEY\",\n openai_api_key: \"YOUR_OPENAI_API_KEY\",\n elevenlabs_api_key: \"YOUR_ELEVENLABS_API_KEY\",\n creatomate_api_key: \"YOUR_CREATOMATE_API_KEY\",\n fal_api_key: \"YOUR_FAL_API_KEY\",\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83c\udfa4 VOICE SELECTION\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Options: cristina (Spanish female), enrique (Spanish male),\n // susie (English female), jeff (English male),\n // custom (set your voice ID in Parse node)\n voice_selection: \"susie\",\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83c\udfa8 AVATAR OPTIONS (Optional)\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Leave both empty for AI-generated avatar\n custom_avatar_description: \"\", // e.g., \"a woman in her 30s with casual clothing\"\n custom_avatar_image_url: \"\", // Direct URL to an image (publicly accessible)\n \n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // \ud83d\udcdd CUSTOM SCRIPT (Optional)\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n custom_script: \"\" // Your own script (leave empty for AI-generated)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "9fe355e6-8b80-4a17-8d29-b3a32fb34d3e",
"name": "\ud83e\udde0 Build Claude Prompt",
"type": "n8n-nodes-base.code",
"position": [
-8208,
1152
],
"parameters": {
"jsCode": "const config = $input.first().json;\n\n// Slide style definitions with enhanced professional design guidance\nconst slideStyles = {\n \"dark_professional\": {\n background: \"rich dark gradient (deep navy #0a1628 to charcoal #1a1a2e, or midnight purple #16213e to black)\",\n text_color: \"crisp white (#ffffff) or soft off-white (#f8f9fa) text, bold modern sans-serif (Inter, SF Pro, or Montserrat style)\",\n accent: \"subtle neon accents (electric blue #00d4ff or soft purple #a855f7), thin glowing lines, minimal geometric shapes\",\n mood: \"premium, sophisticated, high-end tech keynote aesthetic like Apple or Stripe presentations\",\n layout: \"generous whitespace, text positioned with intentional asymmetry, clean visual hierarchy\"\n },\n \"light_modern\": {\n background: \"clean white (#ffffff) or warm off-white (#fafafa) with subtle texture or grain\",\n text_color: \"rich charcoal (#1a1a1a) or dark gray (#374151) text, elegant sans-serif typography\",\n accent: \"soft pastel accents (blush pink #fecaca, sage green #d1fae5, or sky blue #bae6fd), thin hairlines\",\n mood: \"airy, minimal, Notion or Linear-style design, sophisticated simplicity\",\n layout: \"bold use of negative space, text breathing room, modern asymmetric composition\"\n },\n \"vibrant_colorful\": {\n background: \"bold gradient mesh (coral #ff6b6b to magenta #f472b6, or cyan #22d3ee to violet #8b5cf6)\",\n text_color: \"bold white text with subtle drop shadow for contrast, thick modern typeface\",\n accent: \"organic blob shapes, floating particles, playful geometric elements\",\n mood: \"energetic, social-media native, Canva Pro or Instagram aesthetic, eye-catching\",\n layout: \"dynamic compositions, overlapping elements, bold text placements\"\n },\n \"minimalist\": {\n background: \"pure solid color (warm cream #fef3c7, soft sage #ecfdf5, or clean white #ffffff)\",\n text_color: \"black (#000000) or near-black (#111827) text, generous letter-spacing, refined serif or clean sans\",\n accent: \"single thin rule line, small punctuation mark, or subtle brand color dot\",\n mood: \"ultra-refined, editorial, Pitch.com or premium agency deck style\",\n layout: \"extreme whitespace, single focal point, typography as art\"\n },\n \"tech_corporate\": {\n background: \"professional blue gradient (#1e3a5f to #0f172a) with subtle grid pattern or data visualization hints\",\n text_color: \"white or light blue (#e0f2fe) text, clean professional typeface\",\n accent: \"geometric shapes, circuit-like patterns, subtle glow effects, data viz elements\",\n mood: \"trustworthy, enterprise-grade, McKinsey or Accenture presentation quality\",\n layout: \"structured grid-based composition, clear information hierarchy\"\n }\n};\n\nconst selectedStyle = slideStyles[config.slide_style] || slideStyles.dark_professional;\nconst secondsPerSlide = config.seconds_per_slide || 6;\n\nconst hasCustomAvatar = config.custom_avatar_description && config.custom_avatar_description.trim() !== '';\nconst hasCustomAvatarUrl = config.custom_avatar_image_url && config.custom_avatar_image_url.trim() !== '';\nconst hasCustomScript = config.custom_script && config.custom_script.trim() !== '';\n\n// Calculate number of slides based on custom script length if provided\nlet estimatedSlides = 5; // default\nif (hasCustomScript) {\n const wordCount = config.custom_script.trim().split(/\\s+/).length;\n const estimatedDuration = Math.ceil(wordCount / 2.5); // ~2.5 words per second\n estimatedSlides = Math.max(3, Math.min(8, Math.ceil(estimatedDuration / secondsPerSlide)));\n}\n\nconst intentionGuides = {\n \"informative\": \"Focus on educating the viewer. Provide genuine value and actionable insights. Build trust through expertise.\",\n \"lead_generation\": \"Create curiosity and desire. Include a soft call-to-action. Make them want to learn more.\",\n \"disruption\": \"Challenge conventional thinking. Be bold and provocative. Stand out from the noise.\"\n};\n\n// Build dynamic task instructions\nlet taskInstructions = '';\n\nif (!hasCustomScript) {\n taskInstructions = `### 1. VOICEOVER SCRIPT\nWrite an engaging, punchy script (25-40 seconds when spoken, approximately 60-100 words) that:\n- Opens with an attention-grabbing hook (problem or bold statement)\n- Delivers 2-3 key value points in conversational tone\n- Is optimized for text-to-speech (natural phrasing, no complex words)\n- Builds momentum toward the end\n- Ends with a memorable closing line or soft CTA\\n\\n`;\n}\n\ntaskInstructions += `### ${hasCustomScript ? '1' : '2'}. SLIDE IMAGE PROMPTS\nCreate ${hasCustomScript ? estimatedSlides : '5-7'} slides that sync perfectly with the script flow. Each slide should:\n- Support exactly one key moment/phrase from the script\n- Display 2-4 words maximum as the headline (think billboard, not paragraph)\n- Be timed so slides transition naturally with the spoken content\\n\\n`;\n\nif (!hasCustomAvatarUrl && !hasCustomAvatar) {\n taskInstructions += `### ${hasCustomScript ? '2' : '3'}. AVATAR IMAGE PROMPT\nCreate a prompt for a photorealistic presenter:\n- Head and shoulders framing, direct eye contact with camera\n- Professional but approachable, age 25-40\n- Confident, friendly expression\n- Simple/neutral background\\n\\n`;\n}\n\ntaskInstructions += `### ${hasCustomScript && (hasCustomAvatarUrl || hasCustomAvatar) ? '2' : hasCustomScript ? '3' : (hasCustomAvatarUrl || hasCustomAvatar) ? '3' : '4'}. CAPTION\nWrite a social media caption (under 150 chars) + include the hashtags.`;\n\n// Build response format\nlet formatObj = {};\nif (!hasCustomScript) formatObj.audio_script = \"full voiceover script here\";\nformatObj.slides = [\n { slide_number: 1, image_prompt: \"detailed prompt...\", display_text: \"HEADLINE\" },\n { slide_number: 2, image_prompt: \"...\", display_text: \"...\" }\n];\nif (!hasCustomAvatarUrl && !hasCustomAvatar) formatObj.avatar_prompt = \"detailed avatar prompt\";\nformatObj.caption = \"caption with hashtags\";\nformatObj.content_theme = \"2-3 word theme\";\nformatObj.language = \"en or es\";\n\nconst responseFormat = JSON.stringify(formatObj, null, 2).replace(/\\n/g, '\\\\n');\n\nconst prompt = `You are a world-class presentation designer who creates slides that rival Pitch.com, Apple Keynotes, and top-tier agency decks.\n\n## CONTENT BRIEF:\n- Topic: ${config.topic}\n- Brand: ${config.brand_name}\n- Target Audience: ${config.target_audience}\n- Content Intention: ${config.intention.toUpperCase()}\n- Hashtags: ${config.trending_hashtags}\n${hasCustomScript ? `- CUSTOM SCRIPT PROVIDED: \"${config.custom_script}\"` : ''}\n${hasCustomAvatar ? `- CUSTOM AVATAR DESCRIPTION: ${config.custom_avatar_description}` : ''}\n${hasCustomAvatarUrl ? `- CUSTOM AVATAR IMAGE PROVIDED (no avatar prompt needed)` : ''}\n\n## INTENTION GUIDE:\n${intentionGuides[config.intention] || intentionGuides.informative}\n\n## SLIDE DESIGN SYSTEM (CRITICAL):\nYou are creating prompts for an AI image generator. Each slide must feel like it belongs in a $50,000 pitch deck.\n\n**Visual Style:**\n- Background: ${selectedStyle.background}\n- Typography: ${selectedStyle.text_color}\n- Accents: ${selectedStyle.accent}\n- Mood: ${selectedStyle.mood}\n- Layout: ${selectedStyle.layout}\n\n**Design Principles:**\n1. LESS IS MORE: Maximum 2-4 words per slide, huge and bold\n2. VISUAL HIERARCHY: One clear focal point per slide\n3. BREATHING ROOM: Generous margins and whitespace\n4. CONSISTENCY: Same visual language across all slides\n5. IMPACT: Each slide should work as a standalone image\n\n## YOUR TASK:\n${taskInstructions}\n\n## CRITICAL REQUIREMENTS FOR IMAGE PROMPTS:\nEvery slide prompt MUST include:\n1. \"16:9 aspect ratio, 1920x1080, landscape orientation\" (exact words)\n2. Specific hex color codes for backgrounds and accents\n3. Exact text to display in quotes (e.g., \"THINK BIGGER\")\n4. Text styling: \"large bold centered sans-serif text\"\n5. Composition details: where elements are positioned\n6. Quality markers: \"high resolution, professional design, premium aesthetic\"\n\nExample prompt format:\n\"16:9 aspect ratio, 1920x1080, landscape orientation. Rich gradient background from deep navy #0a1628 to charcoal #1a1a2e. Large bold white centered sans-serif text reading 'THINK BIGGER' with subtle electric blue #00d4ff glow effect. Generous whitespace, minimal composition, premium tech keynote aesthetic, high resolution.\"\n\n## OUTPUT FORMAT:\nRespond ONLY with valid JSON (no markdown, no code blocks):\n${responseFormat}`;\n\nreturn [{\n json: {\n ...config,\n claude_prompt: prompt,\n slide_style_config: selectedStyle,\n seconds_per_slide: secondsPerSlide,\n estimated_slides: estimatedSlides,\n has_custom_avatar: hasCustomAvatar,\n has_custom_avatar_url: hasCustomAvatarUrl,\n has_custom_script: hasCustomScript\n }\n}];"
},
"typeVersion": 2
},
{
"id": "fae078a0-d3f9-4325-94f9-fa9483821ddc",
"name": "\ud83e\udd16 Claude: Generate Content",
"type": "n8n-nodes-base.httpRequest",
"position": [
-7968,
1152
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"claude-sonnet-4-20250514\",\n \"max_tokens\": 3000,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify($json.claude_prompt) }}\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "x-api-key",
"value": "={{ $json.anthropic_api_key }}"
},
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "cd6f7651-edc4-49d1-9116-6ef00097a95b",
"name": "\ud83d\udccb Parse Claude Response",
"type": "n8n-nodes-base.code",
"position": [
-7728,
1152
],
"parameters": {
"jsCode": "const item = $input.first();\nconst claudeResponse = item.json;\nconst config = $('\ud83e\udde0 Build Claude Prompt').item.json;\n\nlet responseText = '';\nif (claudeResponse.content && claudeResponse.content[0]) {\n responseText = claudeResponse.content[0].text;\n}\n\nlet parsed = {};\ntry {\n let cleanText = responseText.trim();\n if (cleanText.startsWith('```json')) {\n cleanText = cleanText.replace(/```json\\n?/, '').replace(/\\n?```$/, '');\n } else if (cleanText.startsWith('```')) {\n cleanText = cleanText.replace(/```\\n?/, '').replace(/\\n?```$/, '');\n }\n parsed = JSON.parse(cleanText.trim());\n} catch (e) {\n // Fallback content\n const defaultSlides = [];\n const slideTexts = [\"AI REVOLUTION\", \"FASTER RESULTS\", \"SMARTER WORK\", \"YOUR EDGE\", \"START NOW\"];\n for (let i = 0; i < 5; i++) {\n defaultSlides.push({\n slide_number: i + 1,\n image_prompt: `16:9 aspect ratio, 1920x1080, landscape orientation. Rich gradient background from deep navy #0a1628 to charcoal #1a1a2e. Large bold white centered sans-serif text reading \"${slideTexts[i]}\" with subtle blue glow. Premium tech aesthetic, high resolution.`,\n display_text: slideTexts[i]\n });\n }\n \n parsed = {\n audio_script: \"AI is revolutionizing how we work. What once took hours now takes minutes. From content to analysis, the possibilities are endless. Are you ready to embrace the future?\",\n slides: defaultSlides,\n avatar_prompt: \"Photorealistic portrait of a confident tech professional in their early 30s, wearing smart casual clothing, warm friendly smile, direct eye contact, soft neutral background, high quality portrait photography\",\n caption: \"AI is changing everything. Are you ready? #AIvideo #ContentCreation\",\n content_theme: \"AI Transformation\",\n language: \"en\"\n };\n}\n\n// Voice selection\nconst voiceMap = {\n \"cristina\": \"CaJslL1xziwefCeTNzHv\",\n \"enrique\": \"iDEmt5MnqUotdwCIVplo\",\n \"susie\": \"gPe4h2IS1C7XHbnizzFa\",\n \"jeff\": \"gs0tAILXbY5DNrJrsM6F\",\n \"custom\": \"YOUR_VOICE_CLONE_ID\"\n};\n\nconst voiceSelection = (config.voice_selection || 'susie').toLowerCase().trim();\nconst selectedVoiceId = voiceMap[voiceSelection] || voiceMap['susie'];\n\nlet language = (parsed.language || 'en').toLowerCase().substring(0, 2);\nif (voiceSelection === 'cristina' || voiceSelection === 'enrique') {\n language = 'es';\n}\n\n// Handle avatar prompt\nlet avatarPrompt = '';\nif (config.has_custom_avatar_url) {\n avatarPrompt = '';\n} else if (config.has_custom_avatar) {\n avatarPrompt = `Photorealistic portrait photo of ${config.custom_avatar_description}. Head and shoulders framing, looking directly at camera, natural confident expression, high quality, portrait orientation, professional lighting, 4K quality.`;\n} else if (parsed.avatar_prompt) {\n avatarPrompt = parsed.avatar_prompt;\n} else {\n avatarPrompt = \"Photorealistic portrait of a confident professional in their early 30s, smart casual attire, warm smile, direct eye contact, soft neutral background, high quality portrait\";\n}\n\n// Handle script\nlet finalScript = '';\nif (config.has_custom_script) {\n finalScript = config.custom_script.trim();\n} else if (parsed.audio_script) {\n finalScript = parsed.audio_script;\n} else {\n finalScript = \"AI is revolutionizing content creation. What once took hours now takes minutes. Are you ready to embrace the future?\";\n}\n\n// Calculate estimated duration from script (approx 2.5 words per second)\nconst wordCount = finalScript.split(/\\s+/).length;\nconst estimatedDuration = Math.ceil(wordCount / 2.5) + 2; // Add 2 seconds buffer\nconst secondsPerSlide = config.seconds_per_slide || 6;\n\n// Get slides from Claude response - use all slides it generated\nlet slides = parsed.slides || [];\n\n// If no slides, create defaults based on estimated duration\nif (slides.length === 0) {\n const numSlides = Math.max(3, Math.ceil(estimatedDuration / secondsPerSlide));\n const defaultTexts = [\"TRANSFORM\", \"ACCELERATE\", \"INNOVATE\", \"SCALE\", \"SUCCEED\", \"LEAD\", \"GROW\", \"WIN\"];\n for (let i = 0; i < numSlides; i++) {\n slides.push({\n slide_number: i + 1,\n image_prompt: `16:9 aspect ratio, 1920x1080, landscape orientation. Rich gradient background from deep navy #0a1628 to charcoal #1a1a2e. Large bold white centered sans-serif text reading \"${defaultTexts[i % defaultTexts.length]}\" with subtle blue glow. Premium aesthetic.`,\n display_text: defaultTexts[i % defaultTexts.length]\n });\n }\n}\n\nconst numSlides = slides.length;\nconst durationPerSlide = estimatedDuration / numSlides;\n\n// Determine background type\nconst bgConfig = config.background;\nlet backgroundType = 'none';\nlet backgroundValue = null;\n\nif (bgConfig) {\n if (typeof bgConfig === 'string' && bgConfig.trim() !== '') {\n // It's a URL\n backgroundType = 'url';\n backgroundValue = bgConfig.trim();\n } else if (Array.isArray(bgConfig) && bgConfig.length > 0) {\n // It's a gradient array\n backgroundType = 'gradient';\n backgroundValue = bgConfig;\n }\n}\n\nreturn [{\n json: {\n // Config passthrough\n topic: config.topic,\n intention: config.intention,\n brand_name: config.brand_name,\n target_audience: config.target_audience,\n trending_hashtags: config.trending_hashtags,\n slide_style: config.slide_style,\n video_resolution: config.video_resolution,\n \n // Calculated values\n num_slides: numSlides,\n estimated_duration: estimatedDuration,\n duration_per_slide: durationPerSlide,\n script_word_count: wordCount,\n \n // API keys\n anthropic_api_key: config.anthropic_api_key,\n openai_api_key: config.openai_api_key,\n elevenlabs_api_key: config.elevenlabs_api_key,\n creatomate_api_key: config.creatomate_api_key,\n fal_api_key: config.fal_api_key,\n \n // Background config\n background_type: backgroundType,\n background_value: backgroundValue,\n \n // Generated content\n script_audio: finalScript,\n avatar_prompt: avatarPrompt,\n slides: slides,\n caption: parsed.caption || \"Check out this AI-generated video! #AIvideo #ContentCreation\",\n content_theme: parsed.content_theme || \"AI Innovation\",\n language: language,\n voice_selection: voiceSelection,\n voice_id: selectedVoiceId,\n \n // Avatar handling flags\n has_custom_avatar_url: config.has_custom_avatar_url,\n custom_avatar_image_url: config.custom_avatar_image_url || ''\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f25fcb40-3760-497f-a1eb-b97ce2153c75",
"name": "\ud83d\udd00 Split into Flows",
"type": "n8n-nodes-base.code",
"position": [
-7488,
1152
],
"parameters": {
"jsCode": "// Split into two parallel flows:\n// Flow A: Avatar (image \u2192 audio \u2192 VEED)\n// Flow B: Slides (Nanobanana)\n\nconst data = $input.first().json;\n\nreturn [\n {\n json: {\n ...data,\n flow: 'avatar'\n }\n },\n {\n json: {\n ...data,\n flow: 'slides'\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "b3311e20-c60a-4861-831f-06dabea2e736",
"name": "\ud83d\udd00 Avatar Flow?",
"type": "n8n-nodes-base.if",
"position": [
-7248,
992
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "flow-avatar-check",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.flow }}",
"rightValue": "avatar"
}
]
}
},
"typeVersion": 2
},
{
"id": "aa0d9826-2a31-4925-96a5-a1d30ad24ffc",
"name": "\ud83d\udd00 Slides Flow?",
"type": "n8n-nodes-base.if",
"position": [
-7248,
1280
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "flow-slides-check",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.flow }}",
"rightValue": "slides"
}
]
}
},
"typeVersion": 2
},
{
"id": "8a9d78e0-6705-47df-87f7-2411678ad536",
"name": "\ud83d\uddbc\ufe0f Has Custom Avatar URL?",
"type": "n8n-nodes-base.if",
"position": [
-7008,
976
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "has-avatar-url",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.has_custom_avatar_url }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "cddbac85-2a6c-4193-bfc0-2f730eeeec81",
"name": "\ud83d\udcf8 Use Custom Avatar URL",
"type": "n8n-nodes-base.code",
"position": [
-6768,
880
],
"parameters": {
"jsCode": "const data = $input.first().json;\nreturn [{\n json: {\n ...data,\n avatar_image_url: data.custom_avatar_image_url\n }\n}];"
},
"typeVersion": 2
},
{
"id": "41801caa-a26a-4947-9e30-5922655eda29",
"name": "\ud83c\udfa8 Generate Avatar (OpenAI)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-6768,
1040
],
"parameters": {
"url": "https://api.openai.com/v1/images/generations",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-image-1\",\n \"prompt\": {{ JSON.stringify($json.avatar_prompt) }},\n \"n\": 1,\n \"size\": \"1024x1536\",\n \"quality\": \"high\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.openai_api_key }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "7d238092-fd81-4a27-95c3-d98546231e5b",
"name": "\ud83d\udcf8 Extract Avatar Image",
"type": "n8n-nodes-base.code",
"position": [
-6528,
1040
],
"parameters": {
"jsCode": "const item = $input.first();\nconst imageResponse = item.json;\nconst previousData = $('\ud83d\uddbc\ufe0f Has Custom Avatar URL?').item.json;\n\nlet imageBase64 = '';\nif (imageResponse.data && imageResponse.data[0] && imageResponse.data[0].b64_json) {\n imageBase64 = imageResponse.data[0].b64_json;\n}\n\nif (!imageBase64) {\n throw new Error('No base64 image data received from OpenAI');\n}\n\nreturn [{\n json: {\n ...previousData\n },\n binary: {\n avatar_image: {\n data: imageBase64,\n mimeType: 'image/png',\n fileName: 'avatar.png'\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "295f2711-5cae-43d3-9a08-22b1361d8e69",
"name": "\u2601\ufe0f Upload Avatar Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
-6288,
1040
],
"parameters": {
"url": "https://tmpfiles.org/api/v1/upload",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "avatar_image"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "14792c9c-7d3a-4c0c-b625-d02f33b2b042",
"name": "\ud83d\udcbe Store Avatar URL",
"type": "n8n-nodes-base.code",
"position": [
-6048,
1040
],
"parameters": {
"jsCode": "const uploadResponse = $input.first().json;\nconst previousData = $('\ud83d\udcf8 Extract Avatar Image').item.json;\n\nconst avatarImageUrl = uploadResponse.data.url.replace(\n /^http:\\/\\/tmpfiles\\.org\\/(\\d+)\\/(.*)$/i,\n 'https://tmpfiles.org/dl/$1/$2'\n);\n\nreturn [{\n json: {\n ...previousData,\n avatar_image_url: avatarImageUrl\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c4117185-27de-451a-9c95-25b79e10719f",
"name": "\ud83d\udd0a Generate Audio (ElevenLabs)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-5808,
880
],
"parameters": {
"url": "=https://api.elevenlabs.io/v1/text-to-speech/{{ $json.voice_id }}",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "file",
"outputPropertyName": "audio"
}
}
},
"jsonBody": "={\n \"text\": {{ JSON.stringify($json.script_audio) }},\n \"model_id\": \"eleven_multilingual_v2\",\n \"voice_settings\": {\n \"stability\": 0.5,\n \"similarity_boost\": 0.75,\n \"style\": 0.0,\n \"use_speaker_boost\": true\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "xi-api-key",
"value": "={{ $json.elevenlabs_api_key }}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "audio/mpeg"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "9d065c26-bc23-424e-ae5c-68b57923beeb",
"name": "\ud83c\udfb5 Convert Audio",
"type": "n8n-nodes-base.code",
"position": [
-5568,
880
],
"parameters": {
"jsCode": "// Get previous data from whichever avatar path we came from\nlet previousData;\ntry {\n previousData = $('\ud83d\udcbe Store Avatar URL').item.json;\n} catch (e) {\n try {\n previousData = $('\ud83d\udcf8 Use Custom Avatar URL').item.json;\n } catch (e2) {\n previousData = $('\ud83d\uddbc\ufe0f Has Custom Avatar URL?').item.json;\n }\n}\n\nreturn items.map(item => {\n const b = item.binary?.audio;\n if (!b) return item;\n\n item.json = { ...previousData };\n item.binary.audio_mp3 = {\n ...b,\n fileName: 'voiceover.mp3',\n mimeType: 'audio/mpeg'\n };\n\n return item;\n});"
},
"typeVersion": 2
},
{
"id": "72856d5c-9ea4-400c-b09b-2cf8b275c868",
"name": "\u2601\ufe0f Upload Audio",
"type": "n8n-nodes-base.httpRequest",
"position": [
-5328,
880
],
"parameters": {
"url": "https://tmpfiles.org/api/v1/upload",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "audio_mp3"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "393b9768-cd46-4d09-836b-3a0e560eb1f7",
"name": "\ud83d\udcbe Store Audio URL",
"type": "n8n-nodes-base.code",
"position": [
-5088,
880
],
"parameters": {
"jsCode": "const uploadResponse = $input.first().json;\nconst previousData = $('\ud83c\udfb5 Convert Audio').item.json;\n\nconst audioUrl = uploadResponse.data.url.replace(\n /^http:\\/\\/tmpfiles\\.org\\/(\\d+)\\/(.*)$/i,\n 'https://tmpfiles.org/dl/$1/$2'\n);\n\nreturn [{\n json: {\n ...previousData,\n audio_url: audioUrl\n }\n}];"
},
"typeVersion": 2
},
{
"id": "be454543-e697-4a13-bfd5-f08973299a79",
"name": "\ud83c\udfac Generate Talking Head (VEED)",
"type": "n8n-nodes-veed.veed",
"position": [
-4848,
880
],
"parameters": {
"options": {
"timeout": 60
},
"audioUrl": "={{ $json.audio_url }}",
"imageUrl": "={{ $json.avatar_image_url }}",
"resolution": "={{ $json.video_resolution }}",
"aspectRatio": "9:16"
},
"typeVersion": 1
},
{
"id": "9473479f-59dd-47f0-b8c6-69e2954c506b",
"name": "\ud83d\udcf9 Extract VEED Video URL",
"type": "n8n-nodes-base.code",
"position": [
-4608,
880
],
"parameters": {
"jsCode": "const veedResult = $input.first().json;\nconst previousData = $('\ud83d\udcbe Store Audio URL').item.json;\n\nlet videoUrl = '';\nif (veedResult.video && veedResult.video.url) {\n videoUrl = veedResult.video.url;\n} else if (veedResult.output && veedResult.output.video_url) {\n videoUrl = veedResult.output.video_url;\n} else if (veedResult.videoUrl) {\n videoUrl = veedResult.videoUrl;\n} else if (veedResult.url) {\n videoUrl = veedResult.url;\n}\n\nreturn [{\n json: {\n ...previousData,\n avatar_video_url: videoUrl,\n asset_type: 'avatar_video'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "11339015-c5f7-4ca3-ab9c-2442cb776d2d",
"name": "\ud83d\udcd1 Expand Slides",
"type": "n8n-nodes-base.code",
"position": [
-7008,
1264
],
"parameters": {
"jsCode": "// Expand slides into individual items for parallel generation\nconst data = $input.first().json;\nconst slides = data.slides || [];\n\nreturn slides.map((slide, index) => ({\n json: {\n ...data,\n current_slide: slide,\n slide_index: index\n }\n}));"
},
"typeVersion": 2
},
{
"id": "17b296a6-06a4-4278-8d2a-7e51041a3c02",
"name": "\ud83d\uddbc\ufe0f Generate Slide (FAL)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-6768,
1264
],
"parameters": {
"url": "https://fal.run/fal-ai/flux-pro/v1.1",
"method": "POST",
"options": {
"batching": {
"batch": {
"batchSize": 1
}
},
"response": {
"response": {}
}
},
"jsonBody": "={\n \"prompt\": {{ JSON.stringify($json.current_slide.image_prompt) }},\n \"image_size\": {\n \"width\": 1920,\n \"height\": 1080\n },\n \"num_images\": 1,\n \"safety_tolerance\": \"5\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Authorization",
"value": "=Key {{ $json.fal_api_key }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "6ae783dd-ec4f-4e56-8df4-23131f91c374",
"name": "\ud83d\udcf8 Extract Slide URL",
"type": "n8n-nodes-base.code",
"position": [
-6528,
1264
],
"parameters": {
"jsCode": "// Process ALL FAL responses and extract URLs\nconst items = $input.all();\nconst expandedItems = $('\ud83d\udcd1 Expand Slides').all();\n\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n const falResponse = items[i].json;\n const slideData = expandedItems[i]?.json || {};\n \n // FAL returns the image URL in the response\n let slideImageUrl = '';\n \n if (falResponse.images && falResponse.images[0]) {\n slideImageUrl = falResponse.images[0].url;\n } else if (falResponse.image && falResponse.image.url) {\n slideImageUrl = falResponse.image.url;\n } else if (falResponse.output && falResponse.output[0]) {\n slideImageUrl = falResponse.output[0];\n }\n \n if (!slideImageUrl) {\n throw new Error('No image URL received from FAL for slide ' + i + '. Response: ' + JSON.stringify(falResponse).substring(0, 500));\n }\n \n results.push({\n json: {\n slide_index: slideData.slide_index,\n slide_image_url: slideImageUrl,\n slide_data: slideData.current_slide,\n duration_seconds: slideData.current_slide?.duration_seconds || slideData.seconds_per_slide || 9,\n asset_type: 'slide'\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "6dbce0fa-fed1-49ef-b493-5596edcbb66f",
"name": "\ud83d\udcda Aggregate Slides",
"type": "n8n-nodes-base.aggregate",
"position": [
-6288,
1264
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "all_slides_data"
},
"typeVersion": 1
},
{
"id": "077c1515-7115-4b97-8cb8-ae81bda24f49",
"name": "\ud83d\udcca Format Slides",
"type": "n8n-nodes-base.code",
"position": [
-6048,
1264
],
"parameters": {
"jsCode": "// Process aggregated slides into final format\nconst aggregatedData = $input.first().json;\nconst allSlides = aggregatedData.all_slides_data || [];\n\n// Sort slides by index\nconst sortedSlides = allSlides\n .sort((a, b) => (a.slide_index || 0) - (b.slide_index || 0))\n .map(slide => ({\n slide_index: slide.slide_index,\n slide_image_url: slide.slide_image_url,\n slide_data: slide.slide_data,\n duration_seconds: slide.duration_seconds\n }));\n\nreturn [{\n json: {\n slides_with_urls: sortedSlides,\n asset_type: 'all_slides'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "cdf6652a-af63-4b25-8a80-cd3a24abf401",
"name": "\ud83d\udd17 Merge Avatar + Slides",
"type": "n8n-nodes-base.merge",
"position": [
-4336,
1248
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineAll"
},
"typeVersion": 3
},
{
"id": "81a456a0-c159-49c4-921d-b0f7f4f56989",
"name": "\ud83d\udce6 Prepare Creatomate Request",
"type": "n8n-nodes-base.code",
"position": [
-4096,
1248
],
"parameters": {
"jsCode": "// Combine avatar video and slides for Creatomate RenderScript\nconst items = $input.all();\n\nlet avatarVideoUrl = '';\nlet slidesWithUrls = [];\nlet baseData = {};\n\nfor (const item of items) {\n const data = item.json;\n \n if (data.avatar_video_url && data.avatar_video_url !== '') {\n avatarVideoUrl = data.avatar_video_url;\n baseData = data;\n }\n \n if (data.slides_with_urls && Array.isArray(data.slides_with_urls) && data.slides_with_urls.length > 0) {\n slidesWithUrls = data.slides_with_urls;\n }\n}\n\n// Use estimated_duration from script calculation\nconst totalDuration = baseData.estimated_duration || 30;\nconst numSlides = slidesWithUrls.length || 1;\nconst durationPerSlide = totalDuration / numSlides;\n\n// Build slide elements\nconst slideElements = slidesWithUrls.map((slide, index) => {\n const element = {\n type: 'image',\n track: 1,\n source: slide.slide_image_url,\n duration: durationPerSlide,\n fit: 'cover'\n };\n \n if (index > 0) {\n element.animations = [\n {\n time: 0,\n duration: 0.5,\n transition: true,\n type: 'fade'\n }\n ];\n }\n \n return element;\n});\n\n// Build the complete Creatomate elements array based on background type\nconst creatomateElements = [];\nconst backgroundType = baseData.background_type || 'none';\nconst backgroundValue = baseData.background_value;\nconst hasBackground = backgroundType !== 'none';\n\nif (backgroundType === 'gradient' && Array.isArray(backgroundValue)) {\n // Gradient background\n const colors = backgroundValue;\n const colorStops = [];\n for (let i = 0; i < Math.min(colors.length, 5); i++) {\n colorStops.push({\n offset: `${Math.round((i / (Math.min(colors.length, 5) - 1)) * 100)}%`,\n color: colors[i]\n });\n }\n \n creatomateElements.push({\n type: 'shape',\n track: 1,\n time: 0,\n duration: totalDuration,\n width: '100%',\n height: '100%',\n fill_color: colorStops,\n fill_mode: 'linear',\n fill_x0: '0%',\n fill_y0: '0%',\n fill_x1: '100%',\n fill_y1: '100%'\n });\n} else if (backgroundType === 'url' && backgroundValue) {\n // Image URL background\n creatomateElements.push({\n type: 'image',\n track: 1,\n time: 0,\n duration: totalDuration,\n source: backgroundValue,\n width: '100%',\n height: '100%',\n fit: 'cover'\n });\n}\n\nif (hasBackground) {\n // With background - add margins and rounded corners\n creatomateElements.push({\n type: 'composition',\n track: 2,\n time: 0,\n duration: totalDuration,\n x: '2%',\n y: '50%',\n width: '74%',\n height: '96%',\n x_anchor: '0%',\n y_anchor: '50%',\n border_radius: '12px',\n clip: true,\n elements: slideElements\n });\n \n creatomateElements.push({\n type: 'video',\n track: 3,\n time: 0,\n source: avatarVideoUrl,\n x: '98%',\n y: '50%',\n width: '20%',\n height: '96%',\n x_anchor: '100%',\n y_anchor: '50%',\n border_radius: '12px',\n fit: 'cover'\n });\n} else {\n // No background - full bleed layout\n creatomateElements.push({\n type: 'composition',\n track: 1,\n time: 0,\n duration: totalDuration,\n x: '0%',\n y: '50%',\n width: '78%',\n height: '100%',\n x_anchor: '0%',\n y_anchor: '50%',\n clip: true,\n elements: slideElements\n });\n \n creatomateElements.push({\n type: 'video',\n track: 2,\n time: 0,\n source: avatarVideoUrl,\n x: '100%',\n y: '50%',\n width: '22%',\n height: '100%',\n x_anchor: '100%',\n y_anchor: '50%',\n fit: 'cover'\n });\n}\n\nreturn [{\n json: {\n ...baseData,\n avatar_video_url: avatarVideoUrl,\n slides_with_urls: slidesWithUrls,\n slide_elements: slideElements,\n creatomate_elements: creatomateElements,\n total_duration: totalDuration,\n duration_per_slide: durationPerSlide,\n num_slides: numSlides,\n has_background: hasBackground,\n background_type: backgroundType\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ccad00db-4d1d-41a5-b8cc-97b5f87feba4",
"name": "\ud83c\udfac Render Video (Creatomate)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-3856,
1248
],
"parameters": {
"url": "https://api.creatomate.com/v2/renders",
"method": "POST",
"options": {},
"jsonBody": "={\n \"output_format\": \"mp4\",\n \"width\": 1920,\n \"height\": 1080,\n \"frame_rate\": 60,\n \"h264_profile\": \"high\",\n \"h264_level\": \"5.2\",\n \"pixel_format\": \"yuv420p\",\n \"h264_crf\": 18,\n \"elements\": {{ JSON.stringify($json.creatomate_elements) }}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.creatomate_api_key }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "4191f759-8483-4afc-858c-b90a2708faed",
"name": "\ud83d\udcca Extract Render Info",
"type": "n8n-nodes-base.code",
"position": [
-3616,
1248
],
"parameters": {
"jsCode": "const renderResponse = $input.first().json;\nconst previousData = $('\ud83d\udce6 Prepare Creatomate Request').item.json;\n\nlet renderId = '';\nlet renderUrl = '';\nlet status = 'pending';\n\nif (Array.isArray(renderResponse)) {\n renderId = renderResponse[0]?.id || '';\n renderUrl = renderResponse[0]?.url || '';\n status = renderResponse[0]?.status || 'pending';\n} else {\n renderId = renderResponse.id || '';\n renderUrl = renderResponse.url || '';\n status = renderResponse.status || 'pending';\n}\n\nreturn [{\n json: {\n ...previousData,\n render_id: renderId,\n render_url: renderUrl,\n render_status: status\n }\n}];"
},
"typeVersion": 2
},
{
"id": "63117ee3-6093-476f-bc71-f04f03a5ace6",
"name": "\u23f3 Wait for Render",
"type": "n8n-nodes-base.wait",
"position": [
-3376,
1248
],
"parameters": {
"amount": 30
},
"typeVersion": 1.1
},
{
"id": "fc9d27bf-161a-4c20-a69e-b57ab2de84a5",
"name": "\ud83d\udd0d Check Render Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
-3136,
1248
],
"parameters": {
"url": "=https://api.creatomate.com/v2/renders/{{ $json.render_id }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('\ud83d\udce6 Prepare Creatomate Request').item.json.creatomate_api_key }}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "9453860e-bd09-4c49-aadd-3fd0265a322f",
"name": "\ud83d\udccb Process Status",
"type": "n8n-nodes-base.code",
"position": [
-2896,
1248
],
"parameters": {
"jsCode": "const statusResponse = $input.first().json;\nconst previousData = $('\ud83d\udcca Extract Render Info').item.json;\n\nconst status = statusResponse.status || 'pending';\nconst finalUrl = statusResponse.url || '';\n\nreturn [{\n json: {\n ...previousData,\n render_status: status,\n final_video_url: finalUrl\n }\n}];"
},
"typeVersion": 2
},
{
"id": "1a4cd2af-d41d-45e4-8919-f7cc2ebd9dc1",
"name": "\u2705 Render Done?",
"type": "n8n-nodes-base.if",
"position": [
-2656,
1248
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "render-done",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.render_status }}",
"rightValue": "succeeded"
}
]
}
},
"typeVersion": 2
},
{
"id": "f187bbfe-113e-401f-afe8-1b3b3b97c43b",
"name": "\u2b07\ufe0f Download Final Video",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2400,
1232
],
"parameters": {
"url": "={{ $json.final_video_url }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"typeVersion": 4.3
},
{
"id": "8ce985b7-d611-432a-aa89-8d1c8dd765e6",
"name": "\ud83d\udce4 Upload to Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
-2160,
1232
],
"parameters": {
"name": "={{ $('\ud83d\udccb Process Status').item.json.topic.replace(/\\s+/g, '_').substring(0, 30) }}_screen_{{ Date.now() }}.mp4",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive",
"cachedResultUrl": "https://drive.google.com/drive/my-drive",
"cachedResultName": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "url",
"value": "YOUR_GOOGLE_DRIVE_FOLDER_URL"
}
},
"typeVersion": 3
},
{
"id": "1e069bb5-bff6-466e-a1ff-c78c1dc66733",
"name": "\u2705 Prepare Final Data",
"type": "n8n-nodes-base.code",
"position": [
-1920,
1232
],
"parameters": {
"jsCode": "const driveResult = $input.first().json;\nconst prevData = $('\ud83d\udccb Process Status').item.json;\n\nconst driveFileUrl = driveResult.webViewLink || \n `https://drive.google.com/file/d/${driveResult.id}/view`;\n\nreturn [{\n json: {\n success: true,\n message: \"Screen recording video generated successfully!\",\n video_url: driveFileUrl,\n topic: prevData.topic,\n intention: prevData.intention,\n brand_name: prevData.brand_name,\n slide_style: prevData.slide_style,\n caption: prevData.caption,\n script: prevData.script_audio,\n content_theme: prevData.content_theme,\n language: prevData.language,\n num_slides: prevData.num_slides,\n avatar_video_url: prevData.avatar_video_url,\n audio_url: prevData.audio_url,\n status: \"done\",\n created_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "74c77f17-0d7e-4d51-a8ee-d8e5604055c1",
"name": "\ud83d\udcdd Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1680,
1232
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "YOUR_GOOGLE_SHEETS_URL"
}
},
"typeVersion": 4.7
},
{
"id": "6db7cf56-8872-47a8-858a-4b5a2b176769",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-9184,
832
],
"parameters": {
"color": 5,
"width": 420,
"height": 860,
"content": "## How it works\n\nThis workflow generates professional screencast-style videos with a talking head avatar and AI-generated slides.\n\n1. You provide a topic and configure your preferences\n2. Claude writes a script and designs slide content\n3. Two parallel processes run:\n - **Avatar path**: Generates an avatar image, creates voiceover audio with ElevenLabs, then uses VEED to create a lip-synced talking head video\n - **Slides path**: Creates 5-7 presentation slides using FAL Flux Pro\n4. Creatomate composites everything: slides as the main background with the avatar in a picture-in-picture overlay\n5. Final video uploads to Google Drive and logs to Sheets\n\n## Setup steps\n\n1. Add your API keys in the Configuration node:\n - Anthropic (Claude) - for script generation\n - OpenAI - for avatar image generation\n - ElevenLabs - for voice synthesis\n - FAL.ai - for slide image generation\n - Creatomate - for video composition\n\n2. Set up n8n credentials:\n - Google Drive (OAuth2) - for video storage\n - Google Sheets (OAuth2) - for logging results\n\n3. Update the Google Drive folder URL and Google Sheets URL in the output nodes\n\n4. Configure your content: topic, brand name, target audience, and slide style"
},
"typeVersion": 1
},
{
"id": "37b73559-9cc9-49dc-bf8a-3c9dae26eec3",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-5808,
608
],
"parameters": {
"color": 4,
"width": 224,
"height": 256,
"content": "### Avatar and audio generation\n\nThis branch creates the talking head presenter.\n\nElevenLabs converts the script to natural speech, then VEED animates the avatar image to lip-sync with the audio. The result is a vertical video of a realistic presenter."
},
"typeVersion": 1
},
{
"id": "fa38ed6f-d9e5-4ffd-a739-f0fdb284b300",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-7248,
1424
],
"parameters": {
"color": 4,
"width": 224,
"height": 256,
"content": "### Slide image generation\n\nThis branch creates the presentation slides.\n\nFAL Flux Pro generates 5-7 high-quality images from Claude's prompts. Each slide is 16:9 landscape format with bold headline text and visual styling based on your chosen theme."
},
"typeVersion": 1
},
{
"id": "9ae4e28a-2b7f-4fb9-8cae-28393bd44e7e",
"name": "Section: Configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
-8448,
1056
],
"parameters": {
"color": 4,
"width": 236,
"height": 80,
"content": "**1. Configuration** \u2014
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n workflow automatically generates presentation-style "screen recording" videos with AI-generated slides and a talking head avatar overlay. You provide a topic and intention, and the workflow handles everything: scriptwriting, slide generation, avatar creation, voiceover,…
Source: https://n8n.io/workflows/12728/ — 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.
Monitor Google Drive folder, parsing PDF, DOCX and image file into a destination folder, ready for further processing (e.g. RAG ingestion, translation, etc.) Keep processing log in Google Sheet and se
This is the final piece of the AI content factory. This workflow takes your text-based video scripts and automatically generates high-quality audio voiceovers for each one, turning your text into read
This workflow turns your n8n into an automated product-video generator powered by Google Sheets. When a new row is added with status = run, it: Downloads the product image from Google Drive. Converts
Generate realistic, high-quality images from text prompts using the Flux AI Text-to-Image Generator API via RapidAPI, and seamlessly store the results in Google Drive and log them in Google Sheets — a
The Problem That it Solves