DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

How to Build a Voice-Over Tool for YouTube Videos

Why Voice‑Over Automation Matters for YouTube

If you’ve ever tried to produce a YouTube video, you’ll know that the most time‑consuming part isn’t shooting footage or writing the script—it’s the audio. Recording a human voice, cleaning it up, and syncing it to the video can eat up hours of a creator’s day. That’s where text‑to‑speech (TTS) and voice‑cloning come in: they let you generate high‑quality narration with a few lines of code.

In this post I’ll walk you through building a lightweight voice‑over tool that takes a script, turns it into a natural‑sounding voice, and even produces a subtitle file that you can drop straight into your YouTube upload. We’ll use ElevenLabs as our TTS engine because it offers state‑of‑the‑art neural voices and an easy‑to‑use API.

Pro tip: If you’re new to ElevenLabs or want a free trial, use this affiliate link: https://try.elevenlabs.io/kr07zfuqn1bp. It unlocks a generous quota of free synthesis hours.


1. Set Up Your ElevenLabs Account

  1. Visit https://try.elevenlabs.io/kr07zfuqn1bp and sign up.
  2. Once you’re in the dashboard, create a new voice by cloning an existing one or uploading an audio sample.
  3. Copy your API key from the “API” tab. We’ll use that in our code snippets.

2. Pick the Right Voice

ElevenLabs gives you access to a catalog of voices. You can:

  • Clone a real person’s voice by feeding the system a few minutes of clean audio.
  • Choose from pre‑built “human‑like” voices such as “Sam” or “Emma” for quick starts.

When you clone a voice, you’ll get a unique voice ID that you’ll reference later. Make sure the voice you choose matches the tone of your channel (e.g., friendly, professional, energetic).


3. Build the Core Synthesizer

Below is a minimal Python script that sends a script to ElevenLabs, receives back an MP3 file, and writes a subtitle file in SRT format. Feel free to adapt it to your preferred language.

import requests
import json
import os

ELEVEN_API_URL = "https://api.elevenlabs.io/v1/text-to-speech"
API_KEY = "YOUR_ELEVEN_API_KEY"
VOICE_ID = "YOUR_CLONED_VOICE_ID"

# 1️⃣ Read the script
with open("script.txt", "r", encoding="utf-8") as f:
    script = f.read()

# 2️⃣ Build the request payload
payload = {
    "text": script,
    "voice_settings": {
        "stability": 0.75,
        "similarity_boost": 0.8
    }
}

headers = {
    "xi-api-key": API_KEY,
    "Content-Type": "application/json"
}

# 3️⃣ Make the request
response = requests.post(
    f"{ELEVEN_API_URL}/{VOICE_ID}",
    headers=headers,
    data=json.dumps(payload)
)

# 4️⃣ Save the MP3
if response.status_code == 200:
    with open("narration.mp3", "wb") as out:
        out.write(response.content)
    print("✅ Audio generated!")
else:
    print(f"❌ Error {response.status_code}: {response.text}")
Enter fullscreen mode Exit fullscreen mode

What’s happening?

  1. Text is sent as JSON.
  2. The voice settings let you tweak expressiveness (stability) and how closely the voice mimics the source (similarity_boost).
  3. The API streams back raw MP3 bytes; we simply write them to disk.

4. Generate Subtitles

YouTube accepts SRT files, and syncing subtitles is a breeze once you have timestamps. ElevenLabs can return timestamps if you set output_format to json. Let’s tweak our script to capture those timestamps:

payload["output_format"] = "json"

response = requests.post(
    f"{ELEVEN_API_URL}/{VOICE_ID}",
    headers=headers,
    data=json.dumps(payload)
)

# Parse timestamps
if response.status_code == 200:
    data = response.json()
    audio_bytes = requests.get(data["audio_url"]).content
    with open("narration.mp3", "wb") as out:
        out.write(audio_bytes)

    # Build SRT
    srt_lines = []
    for i, segment in enumerate(data["segments"]):
        start = segment["start"]
        end = segment["end"]
        text = segment["text"]
        srt_lines.extend([f"{i+1}", f"{format_time(start)} --> {format_time(end)}", text, ""])

    with open("subtitles.srt", "w", encoding="utf-8") as srt:
        srt.write("\n".join(srt_lines))

    print("✅ Audio + subtitles ready!")
else:
    print(f"❌ Error {response.status_code}: {response.text}")

def format_time(seconds: float) -> str:
    """Convert seconds to SRT timestamp format."""
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)
    ms = int((s - int(s)) * 1000)
    return f"{int(h):02}:{int(m):02}:{int(s):02},{ms:03}"
Enter fullscreen mode Exit fullscreen mode

Result:

  • narration.mp3 – the spoken audio.
  • subtitles.srt – a perfectly synced subtitle file.

5. Automate the Upload to YouTube

While you can still use the YouTube web UI, the YouTube Data API lets you push the video, audio, and subtitles in one go. Below is a quick Node.js snippet that uploads a video and attaches the SRT file:

const fs = require('fs');
const { google } = require('googleapis');
const youtube = google.youtube('v3');

// 1️⃣ Authenticate (OAuth2 flow required)
const auth = new google.auth.OAuth2(
  'YOUR_CLIENT_ID',
  'YOUR_CLIENT_SECRET',
  'YOUR_REDIRECT_URI'
);
// Assume you have a valid refresh token
auth.setCredentials({ refresh_token: 'YOUR_REFRESH_TOKEN' });

async function uploadVideo() {
  const res = await youtube.videos.insert({
    auth,
    part: 'snippet,status',
    requestBody: {
      snippet: {
        title: 'My AI‑Generated Voice‑Over',
        description: 'Generated with ElevenLabs TTS',
        tags: ['AI', 'TTS', 'ElevenLabs'],
        categoryId: '22' // People & Blogs
      },
      status: { privacyStatus: 'public' }
    },
    media: {
      body: fs.createReadStream('narration.mp3')
    }
  });

  const videoId = res.data.id;

  // Upload subtitles
  await youtube.captions.insert({
    auth,
    part: 'snippet',
    requestBody: {
      snippet: {
        videoId,
        language: 'en',
        name: 'English subtitles',
        isDraft: false
      }
    },
    media: {
      body: fs.createReadStream('subtitles.srt')
    }
  });

  console.log(`✅ Video uploaded: https://youtu.be/${videoId}`);
}

uploadVideo().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Heads‑up: Setting up OAuth2 is a bit more involved. Check the Google API docs for a full walkthrough, but once you have the refresh token, the rest is just a few lines of code.


6. Fine‑Tune Your Workflow

Task Tool Why it matters
Voice selection ElevenLabs Natural‑sounding, customizable
Text formatting Markdown or HTML Keeps scripts readable
Subtitle sync ElevenLabs JSON output Accurate timings
Video upload YouTube Data API Fully automated pipeline

If you find yourself generating a lot of videos, consider wrapping this entire flow in a Docker container or a serverless function. That way you can trigger a new voice‑over job from a webhook whenever a new script lands in a GitHub repo.


7. Common Pitfalls & How to Avoid Them

Issue Fix
Audio too short / missing words Use output_format=json to get timestamps; double‑check the script length.
Voice sounding robotic Increase stability and similarity_boost in the payload.
API rate limits Cache the generated MP3s; batch requests if you have many scripts.
Subtitle mis‑alignment Verify timestamps with a tool like ffmpeg -i narration.mp3 -f ffmetadata.

8. Wrap‑Up & Next Steps

You now have a fully automated pipeline:

  1. Write a script.
  2. Send it to ElevenLabs for synthesis.
  3. Pull back audio + subtitles.
  4. Upload to YouTube with the Data API.

You can extend this by adding background music, dynamic pacing, or even generating multiple language tracks.


Ready to get started?

If you haven’t tried ElevenLabs yet, jump on the affiliate link above: https://try.elevenlabs.io/kr07zfuqn1bp. It gives you free credits and a smooth onboarding process, so you can focus on creating content, not on the tech stack. Happy coding—and happy talking!

Top comments (0)