AutomationFlowsAI & RAG › Create AI Screencast Videos with Claude, Veed, Openai and Automated Slides

Create AI Screencast Videos with Claude, Veed, Openai and Automated Slides

ByVEED @veed on n8n.io

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,…

Event trigger★★★★★ complexity46 nodesHTTP RequestN8N Nodes VeedGoogle DriveGoogle Sheets
AI & RAG Trigger: Event Nodes: 46 Complexity: ★★★★★ Added:

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 →

Download .json
{
  "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 
Pro

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 →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

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

Google Drive Trigger, Google Drive, HTTP Request +2
AI & RAG

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

Read Write File, Google Sheets, HTTP Request +2
AI & RAG

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

Google Sheets Trigger, Google Drive, Google Sheets +1
AI & RAG

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

Form Trigger, HTTP Request, Google Sheets +1
AI & RAG

The Problem That it Solves

Google Drive Trigger, OpenAI, Google Drive +5