Course Intro Video Script Generation

This document explains how course introduction video scripts are generated during the AI content generation workflow in Momentum.

Table of Contents


Overview

When an admin triggers AI course generation, the system can optionally generate an introduction video for the course. This involves:

  1. Script Generation - Using Amazon Bedrock (Claude) to create a 60-90 second video script
  2. Video Creation - Triggering HeyGen API to create an AI-generated video with the script
  3. Video Polling - Monitoring HeyGen until the video is ready
  4. Course Update - Saving the video URL to the course record

The video script is generated from the course outline data (title, description, learning objectives, instructor info) produced in earlier workflow steps.


When Video Generation is Triggered

Video generation occurs during Step 3 of the AI course generation workflow, specifically in the TriggerVideoGeneration state of the Step Functions workflow.

Trigger Conditions

Video generation is enabled when:

  • The admin sets options.generateVideo = true in the generation request
  • The workflow successfully completes steps 1 (Validate Input) and 2 (Generate Outline + Lesson Prompts)

Video generation is skipped when:

  • options.generateVideo = false (default behavior)
  • A previous workflow step has failed

Workflow Position

Admin Input (CourseInput + options.generateVideo=true)
       │
       ▼
┌──────────────────────────────────┐
│  Step 1: Validate Input          │
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  Step 2: Generate Course Outline │
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  Step 2b: Generate Lesson Prompts│
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  CheckVideoGeneration            │ ◄── Choice state checks options.generateVideo
│  (options.generateVideo?)        │
└──────────────────────────────────┘
       │
       ▼ (if true)
┌──────────────────────────────────┐
│  Step 3: TriggerVideoGeneration  │ ◄── VIDEO SCRIPT GENERATED HERE
│  - Generate script via Bedrock   │
│  - Trigger HeyGen API            │
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  Step 4: Save Course             │
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  Video Polling Loop              │ ◄── Polls HeyGen until video ready
│  (WaitForVideo → PollVideoStatus)│
└──────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────┐
│  UpdateCourseWithVideo           │ ◄── Saves video URL to course
└──────────────────────────────────┘

Video Script Generation Flow

The video script generation happens in the trigger-video Lambda handler.

Handler Location

File: backend/functions/ai-generation/src/handlers/trigger-video.ts

Step-by-Step Process

  1. Receive Event Data
    • The handler receives the complete workflow state including:
      • courseInput - Original admin input
      • courseOutline - Generated course outline with title, description, learning objectives, instructor
      • lessonPrompts - Generated lesson prompts
      • options - Generation options including generateVideo flag
  2. Check Video Generation Flag
    if (!options.generateVideo) {
      return {
        videoGeneration: {
          status: 'SKIPPED',
          script: null,
          videoId: null,
        }
      };
    }
    
  3. Build Script Prompt
    const scriptPrompt = getVideoScriptPrompt({
      courseTitle: courseOutline.title,
      courseDescription: courseOutline.description,
      whatYouWillLearn: courseOutline.whatYouWillLearn,
      instructor: courseOutline.instructor,
      duration: courseInput.duration_days,
    });
    
  4. Call Bedrock for Script Generation
    const scriptResponse = await invokeClaudeModel({
      prompt: scriptPrompt,
      maxTokens: 1024,
      temperature: 0.7,
    });
    
  5. Validate and Clean Script
    const scriptErrors = validateVideoScript(videoScript);
    videoScript = cleanVideoScript(videoScript);
    
  6. Trigger HeyGen Video Generation
    const heygenResult = await triggerVideoGeneration({
      script: videoScript,
      avatarId: process.env.HEYGEN_DEFAULT_AVATAR_ID,
      voiceId: process.env.HEYGEN_DEFAULT_VOICE_ID,
    });
    

Script Prompt Details

Prompt File

File: backend/functions/ai-generation/src/prompts/video-script-prompt.ts

Input Parameters

Parameter Type Source Description
courseTitle string courseOutline.title Refined course title from outline generation
courseDescription string courseOutline.description 300-500 word course description
whatYouWillLearn string[] courseOutline.whatYouWillLearn 4-6 learning objectives
instructor InstructorInfo courseOutline.instructor Instructor name and title
duration number courseInput.duration_days Course duration (7, 14, or 21 days)

Full Prompt Template

You are a professional script writer for educational video content. Write a 60-90 second introduction video script.

## Course Information

**Title**: ${input.courseTitle}
**Description**: ${input.courseDescription}
**Duration**: ${input.duration} days
**Instructor**: ${input.instructor.name}, ${input.instructor.title}

**What Students Will Learn**:
- ${input.whatYouWillLearn[0]}
- ${input.whatYouWillLearn[1]}
- ...

## Script Requirements

Write a script that:
1. Opens with an engaging hook (first 5 seconds)
2. Introduces the instructor briefly
3. Explains what the course covers
4. Highlights 2-3 key benefits
5. Mentions the ${input.duration}-day structure
6. Ends with a call to action

## Format

Write the script as plain text, ready to be read aloud. Use natural, conversational language. Include [PAUSE] markers where appropriate for pacing.

## Timing Guidelines

- Total length: 150-200 words (approximately 60-90 seconds when spoken)
- Hook: ~10 words (5 seconds)
- Instructor intro: ~20 words (10 seconds)
- Course overview: ~60 words (30 seconds)
- Benefits: ~40 words (20 seconds)
- Structure mention: ~30 words (15 seconds)
- Call to action: ~20 words (10 seconds)

## Tone

The tone should match the course style:
- Professional but approachable
- Enthusiastic without being over-the-top
- Clear and easy to follow
- Trustworthy and knowledgeable

Write ONLY the script text, no additional commentary or formatting instructions.

Expected Output

Plain text script (not JSON), approximately 150-200 words with optional [PAUSE] markers.

Example Output:

Are you ready to transform your career in just 14 days? [PAUSE]

Hi, I'm Sarah Mitchell, a Senior Data Scientist with over 10 years of experience in the field.

Welcome to "Data Science Fundamentals" - your comprehensive guide to mastering the skills that top companies are looking for. [PAUSE]

In this course, you'll learn how to analyze complex datasets, build predictive models, and communicate insights effectively. We'll cover Python programming, statistical analysis, and machine learning basics.

Here's what makes this course special: every lesson is designed to be completed in just 15-20 minutes, perfect for busy professionals. You'll work on real-world projects and build a portfolio you can show to employers. [PAUSE]

Over the next 14 days, we'll take you from beginner to confident data practitioner, one step at a time.

Ready to start your data science journey? Enroll now and let's begin!

Validation Rules

Rule Requirement
Minimum words 100 words
Maximum words 300 words
Format Plain text

Script Cleaning

The generated script is cleaned before being sent to HeyGen:

function cleanVideoScript(script: string): string {
  return script
    .trim()
    .replace(/\r\n/g, '\n')           // Normalize line breaks
    .replace(/\n{3,}/g, '\n\n')       // Remove excess line breaks
    .replace(/\s*\[PAUSE\]\s*/g, ' [PAUSE] ')  // Format pause markers
    .replace(/  +/g, ' ')             // Clean extra spaces
    .trim();
}

HeyGen Integration

Client Location

File: backend/functions/ai-generation/src/services/heygen-client.ts

API Configuration

Setting Value
API Base URL https://api.heygen.com/v2
Video Endpoint POST /video/generate
Status Endpoint GET /video_status.get?video_id={id}
Video Resolution 1920x1080 (Full HD)

Video Generation Request

const response = await fetch(`${HEYGEN_API_BASE}/video/generate`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': config.apiKey,
  },
  body: JSON.stringify({
    video_inputs: [
      {
        character: {
          type: 'avatar',
          avatar_id: request.avatarId || config.defaultAvatarId,
        },
        voice: {
          type: 'text',
          input_text: request.script,
          voice_id: request.voiceId || config.defaultVoiceId,
        },
      },
    ],
    dimension: {
      width: 1920,
      height: 1080,
    },
  }),
});

Credentials Management

HeyGen API credentials are stored in AWS Secrets Manager:

  • Secret Name: momentum-heygen-api-key-{environment}
  • Secret Contents:
    {
      "api_key": "your-heygen-api-key",
      "default_avatar_id": "avatar-id",
      "default_voice_id": "voice-id"
    }
    

Retry Logic

The HeyGen client implements exponential backoff retry:

  • Max Retries: 3
  • Base Delay: 1000ms
  • Backoff: Exponential with jitter
  • Auth Error Handling: Automatic secret refresh on 401/403 errors

Video Polling and Completion

After triggering video generation, the Step Functions workflow polls HeyGen until the video is ready.

Polling Flow

SaveCourse
    │
    ▼
CheckVideoPolling (Choice)
    │ (if videoJobId exists)
    ▼
WaitForVideo (Wait 60s)
    │
    ▼
PollVideoStatus (Lambda)
    │
    ▼
CheckVideoStatus (Choice)
    │
    ├─► completed → UpdateCourseWithVideo
    │
    ├─► failed → MarkVideoFailed
    │
    ├─► pollCount > 20 → GenerationComplete (timeout)
    │
    └─► pending/processing → WaitForVideoRetry (30s) → PollVideoStatus

Poll Configuration

Setting Value
Initial Wait 60 seconds
Retry Wait 30 seconds
Max Poll Count 20 (total ~11 minutes)

Video Status Updates

When video completes, the course is updated via update-course-video Lambda:

{
  courseId: "uuid",
  videoUrl: "https://heygen.com/video/...",
  videoStatus: "COMPLETED" | "FAILED"
}

This updates the courses table:

  • intro_video_url - URL to the HeyGen video
  • intro_video_status - COMPLETED, FAILED, or PENDING

Error Handling

Script Generation Errors

If Bedrock fails to generate a valid script:

  • Validation warnings are logged but do not stop the workflow
  • Script is cleaned and used even if slightly outside word limits

HeyGen API Errors

If HeyGen API fails:

catch (heygenError) {
  // Continue with script but mark video as failed
  videoGeneration = {
    status: 'FAILED',
    script: videoScript,  // Script is preserved
    videoId: null,
  };
}

The workflow continues to save the course even if video generation fails.

Video Polling Errors

  • Polling errors are caught and logged
  • Workflow completes successfully even if video polling fails
  • Course is saved without video URL if polling times out

Configuration

Environment Variables

Variable Lambda Description
HEYGEN_SECRET_ARN trigger-video, poll-video-status ARN of HeyGen API key secret
HEYGEN_DEFAULT_AVATAR_ID trigger-video Default HeyGen avatar ID
HEYGEN_DEFAULT_VOICE_ID trigger-video Default HeyGen voice ID
BEDROCK_MODEL_ID trigger-video Claude model for script generation
S3_AI_CONTENT_BUCKET trigger-video S3 bucket for AI content

Bedrock Configuration

Setting Value
Model anthropic.claude-3-sonnet-20240229-v1:0
Max Tokens 1024
Temperature 0.7

Cost Estimation

Component Estimated Cost
Script Generation (Bedrock) ~$0.005 per script
HeyGen Video (60-90s) Varies by plan

File Purpose
backend/functions/ai-generation/src/handlers/trigger-video.ts Main video trigger handler
backend/functions/ai-generation/src/prompts/video-script-prompt.ts Script prompt template
backend/functions/ai-generation/src/services/heygen-client.ts HeyGen API client
backend/functions/ai-generation/src/handlers/poll-video-status.ts Video status polling handler
backend/functions/ai-generation/src/handlers/update-course-video.ts Course video update handler
backend/functions/ai-generation/src/types/generation.ts Type definitions
infrastructure/terraform/ai-generation.tf Step Functions workflow definition

Testing

Unit Tests

  • backend/functions/ai-generation/src/__tests__/prompts/video-script-prompt.test.ts
  • backend/functions/ai-generation/src/__tests__/services/heygen-client.test.ts

Manual Testing

To test video generation:

  1. Ensure HeyGen API key is configured in Secrets Manager
  2. Trigger course generation with generateVideo: true:
    {
      "courseInput": {
        "title": "Test Course",
        "category_id": "uuid",
        "duration_days": 7
      },
      "options": {
        "generateVideo": true,
        "isAsync": true
      }
    }
    
  3. Monitor Step Functions execution in AWS Console
  4. Check CloudWatch logs for /aws/lambda/momentum-ai-trigger-video-dev

See Also


Back to top

Momentum LMS © 2025. Distributed under the MIT license.