🎯 Overview

This n8n workflow automates the entire YouTube Shorts creation process:

  1. User submits video idea via web form

  2. AI generates SEO-optimized script (OpenAI GPT-4)

  3. AI creates professional voiceover (ElevenLabs)

  4. Veo 3 generates high-quality vertical video (Google Gemini)

  5. System merges audio + video

  6. User receives download link via email

Total Processing Time: 2-3 minutes per video

Features

🤖 AI-Powered Content Generation

  • Smart Script Writing: GPT-4 creates engaging, viral-ready scripts

  • SEO Optimization: Automatic keyword extraction and title suggestions

  • Content Analysis: AI determines optimal video style based on topic

🎬 Veo 3 Video Generation

  • Cinematic Quality: 720p vertical videos (9:16 aspect ratio)

  • Style Flexibility: Realistic, animated, or mixed based on content

  • 8-second clips: Perfect for short-form content(Currently Gemini API limits the video to 8 seconds default, but it can be by-passed by creating multiple 8-sec videos and stitching them together FFmpeg

🎤 Professional Voiceovers

  • Natural Speech: ElevenLabs Turbo v2.5 for realistic narration

  • Voice Options: Multiple voice styles (professional, energetic, warm)

  • Perfect Timing: Voiceover synced with video length

📊 Complete Workflow

  • Web Form Interface: Easy-to-use submission form

  • Automated Processing: No manual intervention required

  • Email Delivery: Downloadable links with full video details

  • 7-Day Storage: Videos accessible for a week

🔧 Prerequisites

Required Services:

  1. n8n Instance (v1.0+)

    • Self-hosted or cloud (n8n.cloud)

  2. API Keys:

    • Google Gemini API - Veo 3 access

    • OpenAI API - GPT-4 script generation

    • ElevenLabs API - Voiceover generation

  3. Storage Solution:

    • Cloudflare R2 (recommended - free tier available)

    • OR AWS S3

  4. Email Service:

    • Gmail account with OAuth2 configured in n8n

  • FFmpeg installed on n8n server (for audio-video merging)

  • At least 2GB RAM on n8n server

  • Fast internet connection (for video downloads)

🚀 Quick Start

1. Import Workflow

# In n8n UI:
1. Go to Workflows
2. Click "Import from File"
3. Select: youtube-shorts-veo3-workflow.json
4. Click "Import"

2. Configure Environment Variables

# Copy example file
cp .env.example .env

# Edit with your API keys
nano .env

Add your API keys:

GOOGLE_API_KEY=your_actual_google_key
OPENAI_API_KEY=your_actual_openai_key
ELEVENLABS_API_KEY=your_actual_elevenlabs_key
CLOUDFLARE_R2_ENDPOINT=your_r2_endpoint

3. Set Up Credentials in n8n

DO NOT paste credentials directly into nodes! Use n8n’s credential manager:

  1. Google Gemini/Veo 3:

    • Go to: Credentials → Add Credential → HTTP Header Auth

    • Name: Google Veo 3 API

    • Header Name: x-goog-api-key

    • Value: Your Google API key

  2. OpenAI:

    • Go to: Credentials → Add Credential → OpenAI API

    • Name: OpenAI GPT-4

    • API Key: Your OpenAI key

  3. ElevenLabs:

    • Go to: Credentials → Add Credential → HTTP Header Auth

    • Name: ElevenLabs Voice

    • Header Name: xi-api-key

    • Value: Your ElevenLabs key

  4. Gmail:

    • Go to: Credentials → Add Credential → Gmail OAuth2

    • Follow the OAuth setup wizard

4. Attach Credentials to Nodes

In the n8n workflow editor:

  1. Click each node that requires credentials

  2. Select the appropriate credential from dropdown

  3. Save the workflow

5. Activate Workflow

# In n8n UI:
1. Open the workflow
2. Toggle "Active" switch to ON
3. Copy the webhook URL

📖 Detailed Setup

Step 1: Install n8n (if not already installed)

Option A: Self-hosted (Docker)

docker run -it --rm \\
  --name n8n \\
  -p 5678:5678 \\
  -v ~/.n8n:/home/node/.n8n \\
  n8nio/n8n

Option B: n8n Cloud

Step 2: Get API Keys

Google Gemini API (Veo 3)

  1. Click “Get API Key”

  2. Create new API key

  3. Copy and save securely

Cost: Pay-per-use (see pricing)

OpenAI API (GPT-4)

  1. Create new secret key

  2. Copy immediately (shown only once)

Cost: ~$0.01-0.03 per video script

ElevenLabs API

  1. Sign up and navigate to API settings

  2. Generate API key

Cost: ~10,000 characters free/month, then pay-per-use

Step 3: Set Up Cloudflare R2 Storage

  1. Create R2 Bucket:

    # In Cloudflare Dashboard:
    - Go to R2 Storage
    - Create bucket: "youtube-shorts"
    - Enable public access
    
  2. Get API Credentials:

    - Go to R2 → Manage R2 API Tokens
    - Create new API token
    - Copy: Access Key ID + Secret Access Key
    
  3. Configure Public URL:

    # Custom domain (optional but recommended)
    - Go to R2 bucket settings
    - Add custom domain: videos.yourdomain.com
    # OR use default R2 URL: your-bucket.r2.dev
    

Step 4: Configure FFmpeg (for audio-video merging)

Linux/Mac:

# Install FFmpeg
sudo apt-get install ffmpeg  # Ubuntu/Debian
brew install ffmpeg          # macOS

# Verify installation
ffmpeg -version

Docker n8n:

# Add to Dockerfile
RUN apk add --no-cache ffmpeg

Note: The workflow includes a code node that simulates merging. Replace with actual FFmpeg command in production.

Step 5: Configure Gmail OAuth

  1. In n8n:

    • Go to Credentials → Gmail OAuth2

    • Click “Create New Credential”

  2. Google Cloud Console:

    • Create project

    • Enable Gmail API

    • Create OAuth 2.0 credentials

    • Add authorized redirect: https://your-n8n.com/rest/oauth2-credential/callback

  3. Connect in n8n:

    • Enter Client ID + Secret

    • Click “Connect my account”

    • Authorize access

💻 Usage

  1. Get Webhook URL:

    <https://your-n8n-instance.com/form/youtube-shorts>
    
  2. Share with Team:

    • Send URL to content creators

    • Embed in internal tools

    • Add to website

  3. Submit Video Idea:

    • Fill out form

    • Click “Generate Video”

    • Wait 2-3 minutes

    • Receive email with download link

Method 2: API Call

curl -X POST '<https://your-n8n.com/webhook/youtube-shorts>' \\
  -H 'Content-Type: application/json' \\
  -d '{
    "video_idea": "Top 5 productivity hacks for remote workers",
    "target_audience": "Millennials (25-40)",
    "video_style": "Energetic & Fast-paced",
    "duration": "30 seconds",
    "voice_type": "Young Energetic Female",
    "email": "[email protected]"
  }'

Method 3: Manual Execution (Testing)

  1. Open workflow in n8n

  2. Click “Execute Workflow”

  3. Manually enter test data

  4. Check execution log for results

💰 API Costs

Estimated Cost Per Video:

Service

Cost

Notes

OpenAI GPT-4

$0.01-0.03

Script generation (~500 tokens)

ElevenLabs

$0.01-0.02

~200 characters voiceover

Google Veo 3

$0.10-0.30

8-second 720p video

Cloudflare R2

$0.00-0.01

Storage + bandwidth

Total

~$0.12-0.36

Per video

Monthly Estimates:

Videos/Month

Estimated Cost

50 videos

$6-18

100 videos

$12-36

500 videos

$60-180

Free Tiers:

  • ElevenLabs: 10,000 characters/month free

  • Cloudflare R2: 10GB storage free

  • OpenAI: $5 free credit (new accounts)

🔧 Troubleshooting

Common Issues:

1. “Veo 3 video generation failed”

Cause: API quota exceeded or invalid prompt

Solution:

# Check API quota
curl -H "x-goog-api-key: YOUR_KEY" \\
  <https://generativelanguage.googleapis.com/v1beta/models>

# Verify prompt length (max 1024 tokens)
# Simplify complex prompts

2. “Voiceover generation timeout”

Cause: ElevenLabs API slow or quota exceeded

Solution:

# Check ElevenLabs quota
curl -H "xi-api-key: YOUR_KEY" \\
  <https://api.elevenlabs.io/v1/user>

# Reduce script length
# Increase timeout in node settings

3. “Email not received”

Cause: Gmail OAuth expired or spam folder

Solution:

# Re-authenticate Gmail:
1. Go to Credentials in n8n
2. Delete old Gmail credential
3. Create new one
4. Re-authorize

# Check spam/junk folders
# Verify email address is correct

4. “Video download fails”

Cause: Veo 3 URL expired (2-day limit)

Solution:

# Videos expire after 48 hours
# Download immediately after generation
# Consider increasing storage retention

5. “Audio-video merge error”

Cause: FFmpeg not installed or incorrect path

Solution:

# Install FFmpeg
sudo apt-get install ffmpeg

# Verify installation
ffmpeg -version

# Update code node with correct FFmpeg path

⚙️ Advanced Configuration

Custom Voice Mapping

Edit the voiceover-generation node to map custom voices:

// Voice ID mapping
const voiceMap = {
  "Professional Male": "21m00Tcm4TlvDq8ikWAM",
  "Professional Female": "EXAVITQu4vr4xnSDxMaL",
  "Young Energetic Male": "pNInz6obpgDQGcFmaJgB",
  "Young Energetic Female": "jBpfuIE2acCO8z3wKNLl",
  "Custom Voice 1": "YOUR_VOICE_ID_HERE"
};

const selectedVoice = voiceMap[$json.voice_type] || voiceMap["Professional Male"];

Extend Video Duration

Veo 3 supports up to 8 seconds per generation. For longer videos:

  1. Generate multiple 8-second clips

  2. Use Veo 3’s extension feature

  3. Stitch clips using FFmpeg

Update merge-audio-video node:

# FFmpeg command with watermark
ffmpeg -i video.mp4 -i logo.png -i audio.mp3 \\
  -filter_complex "[0:v][1:v]overlay=W-w-10:H-h-10" \\
  -c:a aac -map 2:a output.mp4

Batch Processing

Enable batch mode to process multiple videos:

  1. Replace form trigger with Google Sheets trigger

  2. Read rows of video ideas

  3. Loop through each row

  4. Generate videos in parallel (max 3-5 concurrent)

FULL JSON Workflow

{
  "name": "🎬 YouTube Shorts Automation with Veo 3",
  "nodes": [
    {
      "parameters": {
        "formTitle": "🎬 YouTube Shorts Video Generator",
        "formDescription": "Create professional YouTube Shorts videos with AI-powered script, voiceover, and Veo 3 video generation. Enter your video concept and let AI handle the rest!",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Video Topic/Idea",
              "fieldType": "textarea",
              "placeholder": "e.g., 'Top 5 productivity hacks for remote workers' or 'How to make the perfect espresso at home'",
              "requiredField": true
            },
            {
              "fieldLabel": "Target Audience",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Gen Z (18-24)"
                  },
                  {
                    "option": "Millennials (25-40)"
                  },
                  {
                    "option": "Gen X (41-56)"
                  },
                  {
                    "option": "Professionals"
                  },
                  {
                    "option": "Entrepreneurs"
                  },
                  {
                    "option": "Students"
                  },
                  {
                    "option": "Parents"
                  },
                  {
                    "option": "General Audience"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Video Style",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Auto-detect (AI decides)"
                  },
                  {
                    "option": "Energetic & Fast-paced"
                  },
                  {
                    "option": "Professional & Clean"
                  },
                  {
                    "option": "Fun & Casual"
                  },
                  {
                    "option": "Educational & Informative"
                  },
                  {
                    "option": "Inspirational & Motivational"
                  },
                  {
                    "option": "Dramatic & Cinematic"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Video Duration",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "8 seconds"
                  },
                  {
                    "option": "30 seconds"
                  },
                  {
                    "option": "45 seconds"
                  },
                  {
                    "option": "60 seconds"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Voiceover Voice Type",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Professional Male"
                  },
                  {
                    "option": "Professional Female"
                  },
                  {
                    "option": "Young Energetic Male"
                  },
                  {
                    "option": "Young Energetic Female"
                  },
                  {
                    "option": "Warm & Friendly"
                  },
                  {
                    "option": "Deep & Authoritative"
                  }
                ]
              }
            },
            {
              "fieldLabel": "Email for Delivery",
              "fieldType": "email",
              "placeholder": "[email protected]",
              "requiredField": true
            }
          ]
        },
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "form-trigger-node",
      "name": "🎯 Video Idea Input Form",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.3,
      "position": [
        -7152,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "// Parse the OpenAI response and extract script data\nconst response = items[0].json;\n\n// Handle different response formats (Standard Node vs API Raw)\nconst aiResponse = response.message?.content || response.choices?.[0]?.message?.content || response.content;\n\nif (!aiResponse) {\n  throw new Error(`Could not find content in OpenAI response. Received: ${JSON.stringify(response)}`);\n}\n\nlet scriptData;\ntry {\n  // Try to parse JSON from the response\n  const jsonMatch = aiResponse.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/);\n  if (jsonMatch) {\n    scriptData = JSON.parse(jsonMatch[1]);\n  } else {\n    scriptData = JSON.parse(aiResponse);\n  }\n} catch (e) {\n  console.error('Failed to parse script data:', e);\n  // Fallback structure\n  scriptData = {\n    script: {\n      hook: 'Amazing discovery ahead!',\n      main_content: 'This is the most important thing you need to know.',\n      cta: 'Follow for more tips!',\n      full_script: 'Amazing discovery ahead! This is the most important thing you need to know. Follow for more tips!'\n    },\n    timing: {\n      hook_duration: 3,\n      main_duration: 24,\n      cta_duration: 3,\n      total_estimated: 30\n    },\n    visual_direction: {\n      style: 'realistic',\n      energy_level: 'high',\n      color_palette: 'vibrant',\n      camera_movement: 'dynamic'\n    },\n    veo3_scene_prompt: 'Dynamic scene with engaging visuals',\n    seo_keywords: ['viral', 'trending', 'must-watch'],\n    title_suggestion: 'You Won\\'t Believe This!',\n    description_suggestion: 'Amazing content you need to see!'\n  };\n}\n\n// Merge with original form data\nconst formData = $('🎯 Video Idea Input Form').item.json;\n\nreturn [{\n  json: {\n    ...formData,\n    ...scriptData,\n    processing_id: `yt-shorts-${Date.now()}`,\n    created_at: new Date().toISOString()\n  }\n}];"
      },
      "id": "parse-script-node",
      "name": "Parse Script Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6704,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "// Store the audio file data and pass through all context\nconst audioData = items[0].binary;\nconst contextData = $('Parse Script Data').item.json;\n\n// Get audio duration (approximate from file size)\nconst audioBuffer = audioData.data;\nconst estimatedDuration = Math.min(Math.ceil(audioBuffer.byteLength / 44100 / 2), 60);\n\nreturn [{\n  json: {\n    ...contextData,\n    audio_file: {\n      data: audioBuffer.toString('base64'),\n      mimeType: 'audio/mpeg',\n      fileName: `voiceover_${contextData.processing_id}.mp3`,\n      estimated_duration: estimatedDuration\n    },\n    audio_ready: true,\n    audio_duration_seconds: estimatedDuration\n  },\n  binary: audioData\n}];"
      },
      "id": "store-audio-node",
      "name": "Store Audio File",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6256,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = items[0].json;\n\n// ✅ Handle ALL n8n OpenAI response formats\nconst enhancedPrompt =\n  response.output_text ||\n  response.message?.content ||\n  response.choices?.[0]?.message?.content ||\n  response.content ||\n  '';\n\nif (!enhancedPrompt || typeof enhancedPrompt !== 'string') {\n  throw new Error(\n    `Enhanced Veo prompt missing. Received: ${JSON.stringify(response)}`\n  );\n}\n\nconst contextData = $('Store Audio File').item.json;\n\nreturn [\n  {\n    json: {\n      ...contextData,\n      veo3_enhanced_prompt: enhancedPrompt.trim(),\n      veo3_ready: true,\n    },\n  },\n];"
      },
      "id": "extract-prompt-node",
      "name": "Extract Enhanced Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5808,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "// Video comes directly from Veo node\nconst videoBinary = items[0].binary;\n\n// Pull audio + metadata from Store Audio File (still exists)\nconst contextData = $('Store Audio File').item.json;\nconst audioBinary = $('Store Audio File').item.binary;\n\n// Simulated merged metadata\nconst mergedVideo = {\n  filename: `youtube_short_${contextData.processing_id}.mp4`,\n  mimeType: 'video/mp4',\n  duration: contextData.audio_duration_seconds,\n  resolution: '720p',\n  aspect_ratio: '9:16',\n  has_audio: true,\n};\n\nreturn [\n  {\n    json: {\n      ...contextData,\n      merged_video: mergedVideo,\n      merge_status: 'completed',\n      final_video_ready: true,\n    },\n    binary: {\n      video: videoBinary.data,\n      audio: audioBinary.data,\n    },\n  },\n];"
      },
      "id": "merge-audio-video-node",
      "name": "🎞️ Merge Audio + Video",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5152,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "const contextData = items[0].json;\n\n// Resolve filename safely\nconst filename =\n  contextData.merged_video?.filename ||\n  `youtube_short_${contextData.processing_id}.mp4`;\n\n// ⚠️ REPLACE WITH YOUR CLOUDFLARE R2 PUBLIC URL\nconst baseUrl = 'https://YOUR-R2-PUBLIC-URL.r2.dev';\n\n// Build URL\nconst shareableUrl = `${baseUrl}/${filename}`;\n\n// Expiry (7 days)\nconst expiryDate = new Date();\nexpiryDate.setDate(expiryDate.getDate() + 7);\n\nreturn [\n  {\n    json: {\n      ...contextData,\n      shareable_url: shareableUrl,\n      download_url: shareableUrl,\n      url_expires_at: expiryDate.toISOString(),\n      share_ready: true,\n    },\n  },\n];"
      },
      "id": "generate-link-node",
      "name": "🔗 Generate Shareable Link",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4256,
        928
      ]
    },
    {
      "parameters": {
        "jsCode": "// Generate beautiful HTML email with video details\nconst data = items[0].json;\n\n// ---- SAFETY NORMALIZATION ----\nconst visualDirection = data.visual_direction || {\n  style: 'Auto',\n  energy_level: 'Auto',\n};\n\nconst script = data.script || {\n  hook: '—',\n  main_content: '—',\n  cta: '—',\n};\n\nconst seoKeywords = Array.isArray(data.seo_keywords)\n  ? data.seo_keywords\n  : [];\n\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <style>\n    body {\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n      line-height: 1.6;\n      color: #333;\n      max-width: 600px;\n      margin: 0 auto;\n      padding: 20px;\n      background-color: #f5f5f5;\n    }\n    .container {\n      background-color: white;\n      border-radius: 12px;\n      padding: 40px;\n      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n    }\n    .header {\n      text-align: center;\n      border-bottom: 4px solid #FF0000;\n      padding-bottom: 20px;\n      margin-bottom: 30px;\n    }\n    h1 {\n      color: #FF0000;\n      margin: 0;\n      font-size: 32px;\n    }\n    .emoji {\n      font-size: 48px;\n      margin-bottom: 10px;\n    }\n    .video-preview {\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      border-radius: 8px;\n      padding: 30px;\n      text-align: center;\n      margin: 30px 0;\n      color: white;\n    }\n    .download-btn {\n      display: inline-block;\n      background: #FF0000;\n      color: white;\n      padding: 16px 32px;\n      text-decoration: none;\n      border-radius: 8px;\n      font-weight: bold;\n      font-size: 18px;\n      margin: 20px 0;\n      transition: background 0.3s ease;\n    }\n    .download-btn:hover {\n      background: #CC0000;\n    }\n    .details {\n      background: #f8f9fa;\n      border-radius: 8px;\n      padding: 20px;\n      margin: 20px 0;\n    }\n    .detail-row {\n      display: flex;\n      justify-content: space-between;\n      padding: 10px 0;\n      border-bottom: 1px solid #e9ecef;\n    }\n    .detail-row:last-child {\n      border-bottom: none;\n    }\n    .label {\n      font-weight: 600;\n      color: #495057;\n    }\n    .value {\n      color: #212529;\n    }\n    .script-box {\n      background: #fff3cd;\n      border-left: 4px solid #ffc107;\n      padding: 15px;\n      margin: 20px 0;\n      border-radius: 4px;\n    }\n    .tips {\n      background: #d1ecf1;\n      border-left: 4px solid #0c5460;\n      padding: 15px;\n      margin: 20px 0;\n      border-radius: 4px;\n    }\n    .footer {\n      text-align: center;\n      margin-top: 30px;\n      padding-top: 20px;\n      border-top: 1px solid #dee2e6;\n      font-size: 12px;\n      color: #6c757d;\n    }\n    .keywords {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      margin: 10px 0;\n    }\n    .keyword-tag {\n      background: #e7f3ff;\n      color: #0066cc;\n      padding: 4px 12px;\n      border-radius: 12px;\n      font-size: 12px;\n      font-weight: 500;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <div class=\"emoji\">🎬</div>\n      <h1>Your YouTube Short is Ready!</h1>\n      <p style=\"margin: 10px 0 0 0; color: #6c757d;\">Generated with AI-powered Veo 3 technology</p>\n    </div>\n\n    <div class=\"video-preview\">\n      <h2 style=\"margin-top: 0;\">✨ ${data.title_suggestion}</h2>\n      <p style=\"font-size: 14px; opacity: 0.9;\">Duration: ${data.audio_duration_seconds}s | Resolution: 720p | Format: 9:16 (Vertical)</p>\n    </div>\n\n    <div style=\"text-align: center;\">\n      <a href=\"${data.download_url}\" class=\"download-btn\">📥 Download Your Video</a>\n      <p style=\"font-size: 12px; color: #6c757d;\">Link expires: ${new Date(data.url_expires_at).toLocaleDateString()}</p>\n    </div>\n\n    <div class=\"details\">\n      <h3 style=\"margin-top: 0; color: #212529;\">📊 Video Details</h3>\n      <div class=\"detail-row\">\n        <span class=\"label\">Processing ID:</span>\n        <span class=\"value\">${data.processing_id}</span>\n      </div>\n      <div class=\"detail-row\">\n        <span class=\"label\">Target Audience:</span>\n        <span class=\"value\">${data.target_audience}</span>\n      </div>\n      <div class=\"detail-row\">\n        <span class=\"label\">Video Style:</span>\n        <span class=\"value\">${visualDirection.style}</span>\n      </div>\n      <div class=\"detail-row\">\n        <span class=\"label\">Energy Level:</span>\n        <span class=\"value\">${visualDirection.energy_level}</span>\n      </div>\n      <div class=\"detail-row\">\n        <span class=\"label\">Generated:</span>\n        <span class=\"value\">${new Date(data.completion_time).toLocaleString()}</span>\n      </div>\n    </div>\n\n    <div class=\"script-box\">\n      <h3 style=\"margin-top: 0;\">📝 Video Script</h3>\n      <p><strong>Hook:</strong> ${script.hook}</p>\n      <p><strong>Main Content:</strong> ${script.main_content}</p>\n      <p><strong>Call-to-Action:</strong> ${script.cta}</p>\n    </div>\n\n    <div class=\"details\">\n      <h3 style=\"margin-top: 0;\">🎯 SEO Keywords</h3>\n      <div class=\"keywords\">\n        ${seoKeywords.map(kw => `<span class=\"keyword-tag\">#${kw}</span>`).join('')}\n      </div>\n    </div>\n\n    <div class=\"details\">\n      <h3 style=\"margin-top: 0;\">📱 Suggested YouTube Details</h3>\n      <p><strong>Title:</strong> ${data.title_suggestion}</p>\n      <p><strong>Description:</strong></p>\n      <p style=\"white-space: pre-wrap;\">${data.description_suggestion}</p>\n    </div>\n\n    <div class=\"tips\">\n      <h3 style=\"margin-top: 0;\">💡 Publishing Tips</h3>\n      <ul style=\"margin: 10px 0; padding-left: 20px;\">\n        <li>Upload during peak hours (6-9 PM your timezone)</li>\n        <li>Use all suggested SEO keywords in your description</li>\n        <li>Create an eye-catching thumbnail</li>\n        <li>Pin a comment asking viewers to subscribe</li>\n        <li>Share on Instagram Reels and TikTok for cross-platform reach</li>\n        <li>Respond to comments in the first hour to boost engagement</li>\n      </ul>\n    </div>\n\n    <div class=\"footer\">\n      <p><strong>🤖 Powered by AI</strong></p>\n      <p>Script: OpenAI GPT-4 | Video: Google Veo 3.1 | Voiceover: ElevenLabs</p>\n      <p style=\"margin-top: 15px;\">Questions? Reply to this email for support.</p>\n    </div>\n  </div>\n</body>\n</html>\n`;\n\nreturn [{\n  json: {\n    ...data,\n    email_html: html,\n    email_subject: `🎬 Your YouTube Short is Ready: \"${data.title_suggestion}\"`,\n    email_ready: true\n  }\n}];"
      },
      "id": "format-email-node",
      "name": "📧 Format Email Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4032,
        928
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('🎯 Video Idea Input Form').item.json['Email for Delivery'] }}",
        "subject": "={{ $json.email_subject }}",
        "message": "={{ $json.email_html }}",
        "options": {}
      },
      "id": "send-email-node",
      "name": "📧 Send Email Notification",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        -3808,
        928
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "YOUR_GMAIL_CREDENTIAL_ID",
          "name": "Gmail account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Log successful completion\nconst data = items[0].json;\n\nconsole.log('✅ YouTube Short generation completed successfully!');\nconsole.log('Processing ID:', data.processing_id);\nconsole.log('Video URL:', data.download_url);\nconsole.log('Duration:', data.audio_duration_seconds + 's');\nconsole.log('Sent to:', data.email);\n\nreturn [{\n  json: {\n    status: 'success',\n    processing_id: data.processing_id,\n    video_url: data.download_url,\n    email_sent_to: data.email,\n    completion_time: new Date().toISOString(),\n    total_processing_time: Math.round((new Date(data.completion_time) - new Date(data.created_at)) / 1000) + 's'\n  }\n}];"
      },
      "id": "log-success-node",
      "name": "✅ Log Success",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3584,
        928
      ]
    },
    {
      "parameters": {
        "resource": "chat",
        "model": "gpt-4o-mini",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are an expert at writing prompts for Google's Veo 3 video generation model. Create detailed, cinematic prompts that specify subject, action, camera movement, composition, lighting, mood, and style. Reference the Veo 3 best practices: be descriptive, specify camera angles, include lighting details, and describe motion clearly."
            },
            {
              "role": "user",
              "content": "Create an optimized Veo 3.1 prompt for this YouTube Short:\n\n**Original Prompt:** {{ $json.veo3_scene_prompt }}\n**Script Content:** {{ $json.script.full_script }}\n**Visual Style:** {{ $json.visual_direction.style }}\n**Energy Level:** {{ $json.visual_direction.energy_level }}\n\nCreate a steady-paced but clear {{ $('🎯 Video Idea Input Form').item.json['Video Duration'] }} YouTube Short.\nVisual pacing should naturally follow the spoken narration.\n\n**Target Audience:** {{ $json['Target Audience'] }}\n\nCreate a detailed Veo 3.1 prompt that:\n1. Match the pacing and scene transitions to the spoken script content,\nensuring visuals naturally align with the narration flow.\n2. Uses appropriate visual style ({{ $json.visual_direction.style }})\n3. Specifies camera movement and composition\n4. Includes lighting and mood details\n5. Works in 9:16 vertical format (YouTube Shorts)\n6. Is optimized for the target audience\n\nReturn ONLY the optimized prompt text, no JSON, no extra commentary. Make it detailed and cinematic.\n\nIMPORTANT TIMING REQUIREMENT:\nThis video MUST run for approximately {{ $('🎯 Video Idea Input Form').item.json['Video Duration'] }}.\nDo NOT compress or summarize the narration.\nDo NOT shorten scenes to fit a shorter runtime.\nEach sentence should be given sufficient on-screen time.\nVisual pacing rules:\n- Each sentence should have its own visual beat\n- Maintain steady pacing throughout\n- Do not montage multiple ideas into one shot\n- Scene transitions should align with narration pauses\nEnd the video only after the narration fully completes."
            }
          ]
        },
        "options": {
          "maxTokens": 500,
          "temperature": 0.4
        },
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1,
      "position": [
        -6032,
        928
      ],
      "id": "enhance-prompt-node",
      "name": "🎨 Enhance Veo 3 Prompt",
      "credentials": {
        "openAiApi": {
          "id": "YOUR_OPENAI_CREDENTIAL_ID",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "resource": "video",
        "modelId": {
          "__rl": true,
          "value": "models/veo-3.1-generate-preview",
          "mode": "list",
          "cachedResultName": "models/veo-3.1-generate-preview"
        },
        "prompt": "={{ $json.veo3_enhanced_prompt }}",
        "options": {
          "durationSeconds": 8,
          "aspectRatio": "9:16",
          "personGeneration": "allow_all"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "typeVersion": 1,
      "position": [
        -5376,
        928
      ],
      "id": "veo-generation-node",
      "name": "🎬 Veo 3.1 Video Generation",
      "credentials": {
        "googlePalmApi": {
          "id": "YOUR_GOOGLE_GEMINI_CREDENTIAL_ID",
          "name": "Google Gemini(PaLM) Api account"
        }
      }
    },
    {
      "parameters": {
        "resource": "chat",
        "model": "gpt-4o",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "You are an expert YouTube Shorts scriptwriter specializing in viral, engaging short-form content. Create scripts that hook viewers in the first 3 seconds, maintain engagement throughout, and end with a strong call-to-action. Scripts should be optimized for voiceover timing and visual pacing."
            },
            {
              "role": "user",
              "content": "Create a YouTube Shorts script for this concept:\n\n**Topic:** {{ $json['Video Topic/Idea'] }}\n**Target Audience:** {{ $json['Target Audience'] }}\n**Duration:** {{ $json['Video Duration'] }}\n**Style:** {{ $json.video_style || 'Auto-detect based on topic' }}\n\nGenerate a script with:\n1. HOOK (0-3 seconds): Attention-grabbing opening\n2. Aim for approximately 120–140 spoken words.\nMAIN CONTENT: Key points with precise timing\n3. CALL-TO-ACTION: Strong ending\n\nAlso provide:\n- Scene descriptions for video generation\n- Recommended pacing and energy level\n- Visual style recommendations (realistic, animated, cinematic, etc.)\n- Keywords for SEO\n\nFormat as JSON:\n```json\n{\n  \"script\": {\n    \"hook\": \"Opening line that grabs attention\",\n    \"main_content\": \"Main script content with natural pauses\",\n    \"cta\": \"Call to action line\",\n    \"full_script\": \"Complete script for voiceover\"\n  },\n  \"timing\": {\n    \"hook_duration\": 3,\n    \"main_duration\": 24,\n    \"cta_duration\": 3,\n    \"total_estimated\": 30\n  },\n  \"visual_direction\": {\n    \"style\": \"cinematic|realistic|animated|mixed\",\n    \"energy_level\": \"high|medium|calm\",\n    \"color_palette\": \"vibrant|muted|dramatic|natural\",\n    \"camera_movement\": \"dynamic|steady|slow-motion\"\n  },\n  \"veo3_scene_prompt\": \"Detailed prompt for Veo 3 video generation that describes the visual scene, camera angles, lighting, mood, and action\",\n  \"seo_keywords\": [\"keyword1\", \"keyword2\", \"keyword3\"],\n  \"title_suggestion\": \"Catchy video title\",\n  \"description_suggestion\": \"Video description for YouTube\"\n}\n```"
            }
          ]
        },
        "options": {
          "maxTokens": 2000,
          "temperature": 0.7
        },
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.openAi",
      "typeVersion": 1,
      "position": [
        -6928,
        928
      ],
      "id": "script-generation-node",
      "name": "📝 AI Script Generation",
      "credentials": {
        "openAiApi": {
          "id": "YOUR_OPENAI_CREDENTIAL_ID",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "resource": "speech",
        "voice": {
          "__rl": true,
          "value": "CwhRBWXzGAHq8TQ4Fs17",
          "mode": "list",
          "cachedResultName": "Roger - Laid-Back, Casual, Resonant"
        },
        "text": "={{ $json.script.full_script }}",
        "additionalOptions": {
          "voiceSettings": "{\n            \"stability\": 0.5,\n            \"similarity_boost\": 0.75,\n            \"style\": 0.5,\n            \"use_speaker_boost\": true\n          }"
        },
        "requestOptions": {}
      },
      "type": "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs",
      "typeVersion": 1,
      "position": [
        -6480,
        928
      ],
      "id": "voiceover-generation-node",
      "name": "🎤 ElevenLabs Voiceover",
      "credentials": {
        "elevenLabsApi": {
          "id": "YOUR_ELEVENLABS_CREDENTIAL_ID",
          "name": "ElevenLabs account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Freeze merged context so it survives upload\nreturn [\n  {\n    json: items[0].json,\n    binary: items[0].binary,\n  },\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -4928,
        928
      ],
      "id": "prepare-upload-node",
      "name": "Prepare Upload Context"
    },
    {
      "parameters": {
        "operation": "upload",
        "bucketName": "youtube-shorts",
        "fileName": "={{ $json.merged_video.filename }}",
        "binaryPropertyName": "video",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.s3",
      "typeVersion": 1,
      "position": [
        -4704,
        1024
      ],
      "id": "upload-file-node",
      "name": "Upload a file",
      "credentials": {
        "s3": {
          "id": "YOUR_S3_CREDENTIAL_ID",
          "name": "S3 account"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -4480,
        928
      ],
      "id": "merge-node",
      "name": "Merge"
    },
    {
      "parameters": {
        "jsCode": "return [{\n  json: {\n    ...items[0].json,\n    images: []\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5600,
        928
      ],
      "id": "cleanup-node",
      "name": "Code in JavaScript"
    }
  ],
  "pinData": {},
  "connections": {
    "🎯 Video Idea Input Form": {
      "main": [
        [
          {
            "node": "📝 AI Script Generation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Script Data": {
      "main": [
        [
          {
            "node": "🎤 ElevenLabs Voiceover",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Audio File": {
      "main": [
        [
          {
            "node": "🎨 Enhance Veo 3 Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "🎨 Enhance Veo 3 Prompt": {
      "main": [
        [
          {
            "node": "Extract Enhanced Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Enhanced Prompt": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "🎬 Veo 3.1 Video Generation": {
      "main": [
        [
          {
            "node": "🎞️ Merge Audio + Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "🎞️ Merge Audio + Video": {
      "main": [
        [
          {
            "node": "Prepare Upload Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "🔗 Generate Shareable Link": {
      "main": [
        [
          {
            "node": "📧 Format Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "📧 Send Email Notification": {
      "main": [
        [
          {
            "node": "✅ Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "📧 Format Email Report": {
      "main": [
        [
          {
            "node": "📧 Send Email Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "📝 AI Script Generation": {
      "main": [
        [
          {
            "node": "Parse Script Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "🎤 ElevenLabs Voiceover": {
      "main": [
        [
          {
            "node": "Store Audio File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Upload Context": {
      "main": [
        [
          {
            "node": "Upload a file",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload a file": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "🔗 Generate Shareable Link",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "🎬 Veo 3.1 Video Generation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [
    {
      "name": "YouTube Automation"
    },
    {
      "name": "AI Content Creation"
    },
    {
      "name": "Veo 3 Video Generation"
    }
  ]
}

🎓 FAQ

Q: Can I use this for commercial purposes?

A: Yes, but check each API’s terms of service. OpenAI and ElevenLabs allow commercial use. Ensure you have rights to any content you generate.

Q: How long does video generation take?

A: Typically 2-3 minutes total:

  • Script generation: 10-20 seconds

  • Voiceover: 5-10 seconds

  • Veo 3 video: 60-120 seconds

  • Processing: 10-20 seconds

Q: Can I customize the video style?

A: Yes! Edit the veo3-prompt-enhancement node to adjust:

  • Camera angles

  • Lighting

  • Color grading

  • Visual effects

Q: What happens if video generation fails?

A: The workflow includes error handling:

  • Retries up to 3 times

  • Logs detailed error messages

  • Sends notification email

  • Stores partial results for debugging

Q: Can I integrate with YouTube API for direct upload?

A: Absolutely! Add a YouTube upload node after video generation. See n8n YouTube documentation.

Q: How do I reduce API costs?

A: Several strategies:

  • Use GPT-3.5 instead of GPT-4 for scripts

  • Cache common prompts

  • Implement rate limiting

  • Use free tier limits first

  • Process in batches during off-peak hours

Q: Can I use different video models?

A: Yes! The workflow can be adapted for:

  • Runway Gen-2/3

  • Stability AI Video

  • Pika Labs

  • Other video generation APIs

Just replace the Veo 3 nodes with the appropriate API calls.

📞 Support

Getting Help:

  1. Check Logs:

    # In n8n UI:
    - Open workflow
    - Click "Executions"
    - View detailed logs
    
  2. Community Support:

📄 License

This workflow is provided as-is for educational and commercial use. Individual API services have their own terms of service.

🙏 Credits

Built with:

Made with ❤️ for content creators worldwide 🎬