347 lines
11 KiB
Markdown
347 lines
11 KiB
Markdown
# Voiceover Generation Guide
|
|
|
|
Generate professional AI voiceover using ElevenLabs, precisely timed and content-matched to video scenes.
|
|
|
|
## Requirements
|
|
|
|
- `ELEVEN_LABS_API_KEY` environment variable
|
|
- `ffmpeg` installed for audio processing
|
|
- `whisper` CLI or Python package for timing verification
|
|
- Python 3.x for the generation script
|
|
|
|
## Critical Workflow: Scene-Voiceover Alignment
|
|
|
|
**The #1 problem with promo video voiceovers is misalignment between what's being said and what's on screen.** Follow this precise workflow:
|
|
|
|
### Step 1: Extract Scene Timings from Remotion
|
|
|
|
First, read the main composition file to extract exact scene timings:
|
|
|
|
```javascript
|
|
// Example from AIVoicePromo.jsx - extract these values:
|
|
const OPENING_DURATION = 150; // 5 seconds (frames / 30fps)
|
|
const PAIN_POINT_DURATION = 120; // 4 seconds each
|
|
const SOLUTION_INTRO_DURATION = 120;
|
|
const FEATURE_DURATION = 150; // 5 seconds each
|
|
const RESULTS_DURATION = 180; // 6 seconds
|
|
const CLOSING_DURATION = 240; // 8 seconds
|
|
```
|
|
|
|
Calculate cumulative timestamps:
|
|
```
|
|
Scene | Start | End | Duration
|
|
-------------------|-------|------|----------
|
|
Opening | 0s | 5s | 5s
|
|
Pain Point 1 | 5s | 9s | 4s
|
|
Pain Point 2 | 9s | 13s | 4s
|
|
Pain Point 3 | 13s | 17s | 4s
|
|
Solution Intro | 17s | 21s | 4s
|
|
Feature 1 | 21s | 26s | 5s
|
|
Feature 2 | 26s | 31s | 5s
|
|
Feature 3 | 31s | 36s | 5s
|
|
Feature 4 | 36s | 41s | 5s
|
|
Feature 5 | 41s | 46s | 5s
|
|
Results | 46s | 52s | 6s
|
|
Closing | 52s | 60s | 8s
|
|
```
|
|
|
|
### Step 2: Extract Scene Content for Script Matching
|
|
|
|
Read each scene component to understand what's visually displayed:
|
|
|
|
```bash
|
|
# For each scene, extract:
|
|
# - Title text
|
|
# - Subtitle text
|
|
# - Stats displayed
|
|
# - Key visual elements
|
|
```
|
|
|
|
**The voiceover MUST reference what's on screen.** Example mapping:
|
|
|
|
| Scene | Visual Content | Voiceover Should Say |
|
|
|-------|---------------|---------------------|
|
|
| Opening | "Every Missed Call Costs You Money", stats: $650K, 38%, 4hrs | "Every missed call costs you money. With six hundred and fifty thousand dollars lost annually..." |
|
|
| Pain Point 1 | "Missed Calls = Lost Revenue", stat: 500+ | "Your dealership handles over five hundred service calls every month..." |
|
|
| Feature 1: Smart Voicemail Queue | Shows queue UI mockup | "The Smart Voicemail Queue transcribes every message instantly..." |
|
|
|
|
### Step 3: Write Time-Aligned Script
|
|
|
|
Structure your script with EXACT scene boundaries:
|
|
|
|
```
|
|
[0-5s - Opening - Visual: "Every Missed Call Costs You Money"]
|
|
Every missed call costs you money. With six hundred and fifty thousand dollars lost annually...
|
|
|
|
[5-9s - Pain Point 1 - Visual: "Missed Calls = Lost Revenue", 500+ stat]
|
|
Your dealership handles over five hundred service calls every month...
|
|
|
|
[17-21s - Solution Intro - Visual: ShopLoader logo reveal]
|
|
Introducing ShopLoader AI Voice. Turn voicemails into appointments in minutes.
|
|
```
|
|
|
|
**Content Rules:**
|
|
- Reference the title/stat shown on screen
|
|
- Don't describe something not visible
|
|
- Time the "reveal" moment (e.g., "Introducing ShopLoader" exactly when logo appears)
|
|
|
|
### Step 4: Generate Voiceover Sections
|
|
|
|
Use the generation script with precise start times that leave ~1s buffer before scene transitions:
|
|
|
|
```python
|
|
sections = [
|
|
(1, "Every missed call costs you money."), # Scene starts at 0s, speak at 1s
|
|
(6, "Your dealership handles..."), # Scene starts at 5s, speak at 6s
|
|
(18, "Introducing ShopLoader AI Voice."), # Scene starts at 17s, speak at 18s
|
|
# ...
|
|
]
|
|
```
|
|
|
|
### Step 5: Verify Timing with Whisper Transcription
|
|
|
|
**CRITICAL STEP - Do not skip!**
|
|
|
|
After generating the voiceover, transcribe it to verify actual timing:
|
|
|
|
```bash
|
|
# Using Whisper CLI
|
|
whisper voiceover.mp3 --model tiny --output_format srt
|
|
|
|
# Or using Python
|
|
python3 -c "
|
|
import whisper
|
|
model = whisper.load_model('tiny')
|
|
result = model.transcribe('voiceover.mp3')
|
|
for s in result['segments']:
|
|
print(f\"{s['start']:.1f}s - {s['end']:.1f}s: {s['text']}\")
|
|
"
|
|
```
|
|
|
|
### Step 6: Compare and Validate
|
|
|
|
Compare Whisper output against scene timings:
|
|
|
|
```
|
|
Expected vs Actual Timing Analysis:
|
|
-----------------------------------
|
|
Scene: Solution Intro (17-21s)
|
|
Expected: "Introducing ShopLoader" starts at 18s
|
|
Actual: "Introducing ShopLoader" starts at 17.2s ✓ (within scene)
|
|
|
|
Scene: Feature 1 (21-26s)
|
|
Expected: "Smart Voicemail Queue" at 22s
|
|
Actual: "Smart Voicemail Queue" at 20.8s ✗ (OVERLAPS with previous scene!)
|
|
```
|
|
|
|
**If overlap detected:** Increase delay for that section and regenerate.
|
|
|
|
### Step 7: FIX ALL OVERLAPS (Mandatory)
|
|
|
|
**If ANY overlap is detected, you MUST fix it before proceeding. Do not ask the user.**
|
|
|
|
Overlap fixes (in order of preference):
|
|
1. **Shorten the text** - Make it punchier. Cut filler words. "The Smart Voicemail Queue transcribes every message instantly" → "Voicemails transcribed instantly"
|
|
2. **Add a beat** - Insert "..." in the text to create a natural pause
|
|
3. **Increase gap** - Push the next section's start time 1-2s later
|
|
|
|
Other timing fixes:
|
|
- **Speech ends after scene**: Shorten the text, remove unnecessary words
|
|
- **Too much silence**: Decrease start time or add more content
|
|
- **Wrong content timing**: Adjust start time to match visual reveal
|
|
|
|
**After ANY fix: Regenerate and verify with Whisper again. Repeat until ZERO overlaps.**
|
|
|
|
---
|
|
|
|
## Script Writing Guidelines
|
|
|
|
### Content-Scene Matching Rules
|
|
|
|
1. **Reference what's visible**: If the screen shows "500+ calls", say "five hundred"
|
|
2. **Match reveal timing**: Product name first spoken when logo appears
|
|
3. **Describe features when shown**: Talk about "SMS Quick Response" during SMS mockup scene
|
|
4. **Complete the thought with benefit**: Don't just name features, explain why they matter
|
|
|
|
### DO:
|
|
- Write complete thoughts, not fragments
|
|
- Allow 1s buffer at scene start before speaking
|
|
- Allow 0.5s buffer before scene end
|
|
- Match spoken stats to visual stats
|
|
- Use conversational, natural language
|
|
|
|
### DON'T:
|
|
- Don't reference visuals not yet shown
|
|
- Don't continue speaking into the next scene
|
|
- Don't make it too dense (audio needs breathing room)
|
|
- Don't skip the emotional hook in the opening
|
|
|
|
---
|
|
|
|
## Script Template (60 seconds)
|
|
|
|
```
|
|
[0-5s - Opening - Visual: Hook headline + 3 stats]
|
|
[Start speaking at 1s]
|
|
Every missed call costs you money. With [stat from screen] and [stat from screen], your [consequence].
|
|
|
|
[5-9s - Pain Point 1 - Visual: Title + stat]
|
|
[Start speaking at 6s]
|
|
[Reference the title shown]. [Expand with the stat displayed].
|
|
|
|
[9-13s - Pain Point 2 - Visual: Title + stat]
|
|
[Start speaking at 10s]
|
|
[Reference the title shown]. [Emotional consequence].
|
|
|
|
[13-17s - Pain Point 3 - Visual: Title + stat]
|
|
[Start speaking at 14s]
|
|
[Reference the title shown]. [What happens as a result].
|
|
|
|
[17-21s - Solution Intro - Visual: Product logo reveal]
|
|
[Start speaking at 18s - TIME THIS TO LOGO APPEARANCE]
|
|
Introducing [Product Name]. [One-line value prop].
|
|
|
|
[21-26s - Feature 1 - Visual: Feature title + UI mockup]
|
|
[Start speaking at 22s]
|
|
[Feature name from screen] [what it does], so [benefit to user].
|
|
|
|
[26-31s - Feature 2]
|
|
[Start speaking at 27s]
|
|
[Feature name from screen] [what it does], so [benefit to user].
|
|
|
|
[31-36s - Feature 3]
|
|
[Start speaking at 32s]
|
|
[Feature name from screen] [what it does], so [benefit to user].
|
|
|
|
[36-41s - Feature 4]
|
|
[Start speaking at 37s]
|
|
[Feature name from screen] [what it does], so [benefit to user].
|
|
|
|
[41-46s - Feature 5]
|
|
[Start speaking at 42s]
|
|
[Feature name from screen] [what it does], so [benefit to user].
|
|
|
|
[46-52s - Results - Visual: Outcome stats]
|
|
[Start speaking at 47s]
|
|
[Stat from screen]. [Stat from screen]. [Emotional benefit].
|
|
|
|
[52-60s - Closing - Visual: CTA + branding]
|
|
[Start speaking at 53s]
|
|
[Product Name]. [Tagline from screen]. [CTA].
|
|
```
|
|
|
|
---
|
|
|
|
## ElevenLabs Voice Settings
|
|
|
|
### Recommended Voice: Matilda
|
|
- **Voice ID**: `XrExE9yKIg1WjnnlVkGX`
|
|
- **Characteristics**: American, Professional, Knowledgeable, Clear
|
|
|
|
### Voice Settings for Professional Delivery:
|
|
```json
|
|
{
|
|
"model_id": "eleven_multilingual_v2",
|
|
"voice_settings": {
|
|
"stability": 0.65,
|
|
"similarity_boost": 0.85,
|
|
"style": 0.2,
|
|
"use_speaker_boost": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Adjustments:**
|
|
- If too breathy: Increase `stability` to 0.70-0.75
|
|
- If too robotic: Decrease `stability` to 0.55-0.60
|
|
- If speech too fast: Decrease `style`, or add "..." pauses in text
|
|
|
|
---
|
|
|
|
## Generation & Verification Workflow
|
|
|
|
### Full Pipeline:
|
|
|
|
```bash
|
|
# 1. Generate voiceover sections
|
|
python ${SKILL_DIR}/scripts/generate_voiceover.py
|
|
|
|
# 2. Transcribe to verify timing
|
|
whisper voiceover.mp3 --model tiny --output_format srt
|
|
|
|
# 3. Review SRT file against scene timings
|
|
cat voiceover.srt
|
|
|
|
# 4. If timing issues found, adjust sections[] start times and regenerate
|
|
# 5. Normalize audio
|
|
ffmpeg -y -i voiceover.mp3 -af "loudnorm=I=-16:TP=-1.5:LRA=11" voiceover-normalized.mp3
|
|
|
|
# 6. Add background music
|
|
ffmpeg -y -i voiceover-normalized.mp3 -i music.mp3 \
|
|
-filter_complex "[1:a]volume=0.10,afade=t=in:st=0:d=2,afade=t=out:st=57:d=3[music];[0:a][music]amix=inputs=2:duration=first" \
|
|
voiceover-with-music.mp3
|
|
|
|
# 7. Combine with video
|
|
ffmpeg -y -i video.mp4 -i voiceover-with-music.mp3 -c:v copy -map 0:v:0 -map 1:a:0 final.mp4
|
|
|
|
# 8. Watch final video to confirm alignment
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
**CRITICAL: Never accept overlaps. Fix them immediately and regenerate.**
|
|
|
|
| Issue | Cause | Solution |
|
|
|-------|-------|----------|
|
|
| **Voiceover overlaps itself** | Sections too close | **FIX NOW**: Shorten text OR increase gap, regenerate, verify again |
|
|
| **Even 0.4s overlap** | Text too long for scene | **FIX NOW**: Make text punchier, cut words, regenerate |
|
|
| Speech doesn't match screen | Script not aligned | Re-read scene components, match text to visuals |
|
|
| "Introducing X" before logo | Start time too early | Delay to 1s after scene start |
|
|
| Feature description during wrong scene | Timing drift | Use Whisper to find actual timestamps, adjust |
|
|
| Voice too fast | Too much text | Shorten text or add "..." for pauses |
|
|
| Awkward cuts between scenes | No buffer | Leave 0.5-1s silence before scene transitions |
|
|
|
|
**The loop: Generate → Whisper verify → Fix overlaps → Regenerate → Verify again → Repeat until clean**
|
|
|
|
---
|
|
|
|
## Background Music
|
|
|
|
### Auto-Download Royalty-Free Tracks (Verified Working)
|
|
|
|
```bash
|
|
# Bensound - "Inspire" (corporate/uplifting) - RECOMMENDED
|
|
curl -sL "https://www.bensound.com/bensound-music/bensound-inspire.mp3" -o background-music.mp3
|
|
|
|
# Bensound - "Creative Minds" (light/positive)
|
|
curl -sL "https://www.bensound.com/bensound-music/bensound-creativeminds.mp3" -o background-music.mp3
|
|
|
|
# Pixabay - Corporate background (256kbps, high quality)
|
|
curl -sL "https://cdn.pixabay.com/download/audio/2022/03/15/audio_8cb749d484.mp3" -o background-music.mp3
|
|
|
|
# Verify download worked
|
|
ls -lah background-music.mp3 && file background-music.mp3
|
|
```
|
|
|
|
### Mixing Settings
|
|
- **Volume**: 10% of voice level (`volume=0.10`)
|
|
- **Fade in**: 2 seconds at start (`afade=t=in:st=0:d=2`)
|
|
- **Fade out**: 3 seconds before video end (`afade=t=out:st=57:d=3` for 60s video)
|
|
- **Style**: Subtle corporate/tech underscore, no lyrics
|
|
|
|
### Mix Command
|
|
```bash
|
|
ffmpeg -y -i voiceover-normalized.mp3 -i background-music.mp3 \
|
|
-filter_complex "[1:a]volume=0.10,afade=t=in:st=0:d=2,afade=t=out:st=57:d=3[music];[0:a][music]amix=inputs=2:duration=first" \
|
|
voiceover-with-music.mp3
|
|
```
|
|
|
|
### Other Sources (if auto-download fails)
|
|
- Bensound.com - Free with attribution
|
|
- Mixkit.co - Free, no attribution needed
|
|
- Pixabay.com/music - Free
|
|
- Epidemic Sound (subscription)
|
|
- Artlist (subscription)
|