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 Video Generation is Triggered
- Video Script Generation Flow
- Script Prompt Details
- HeyGen Integration
- Video Polling and Completion
- Error Handling
- Configuration
- Related Files
Overview
When an admin triggers AI course generation, the system can optionally generate an introduction video for the course. This involves:
- Script Generation - Using Amazon Bedrock (Claude) to create a 60-90 second video script
- Video Creation - Triggering HeyGen API to create an AI-generated video with the script
- Video Polling - Monitoring HeyGen until the video is ready
- 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 = truein 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
- Receive Event Data
- The handler receives the complete workflow state including:
courseInput- Original admin inputcourseOutline- Generated course outline with title, description, learning objectives, instructorlessonPrompts- Generated lesson promptsoptions- Generation options includinggenerateVideoflag
- The handler receives the complete workflow state including:
- Check Video Generation Flag
if (!options.generateVideo) { return { videoGeneration: { status: 'SKIPPED', script: null, videoId: null, } }; } - Build Script Prompt
const scriptPrompt = getVideoScriptPrompt({ courseTitle: courseOutline.title, courseDescription: courseOutline.description, whatYouWillLearn: courseOutline.whatYouWillLearn, instructor: courseOutline.instructor, duration: courseInput.duration_days, }); - Call Bedrock for Script Generation
const scriptResponse = await invokeClaudeModel({ prompt: scriptPrompt, maxTokens: 1024, temperature: 0.7, }); - Validate and Clean Script
const scriptErrors = validateVideoScript(videoScript); videoScript = cleanVideoScript(videoScript); - 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 videointro_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 |
Related Files
| 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.tsbackend/functions/ai-generation/src/__tests__/services/heygen-client.test.ts
Manual Testing
To test video generation:
- Ensure HeyGen API key is configured in Secrets Manager
- Trigger course generation with
generateVideo: true:{ "courseInput": { "title": "Test Course", "category_id": "uuid", "duration_days": 7 }, "options": { "generateVideo": true, "isAsync": true } } - Monitor Step Functions execution in AWS Console
- Check CloudWatch logs for
/aws/lambda/momentum-ai-trigger-video-dev
See Also
- Prompts Reference - Complete reference for all AI prompts
- Technical Architecture - System architecture overview