cd /news/ai-tools/how-to-build-a-voice-over-tool-for-y… · home › topics › ai-tools › article
[ARTICLE · art-159123] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

How to Build a Voice-Over Tool for YouTube Videos

A developer published a walkthrough for building a lightweight voice-over tool that uses the ElevenLabs text-to-speech API to convert a script into narration audio and a synced SRT subtitle file. The Python script sends the script to a cloned voice ID with adjustable stability and similarity settings, then parses returned timestamps to generate subtitles, with a Node.js snippet for uploading video and captions via the YouTube Data API. The post also includes an affiliate link promoting ElevenLabs' free trial quota.

by read5 min views1 publishedOct 27, 2026

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.

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

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).

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"

with open("script.txt", "r", encoding="utf-8") as f:
    script = f.read()

payload = {
    "text": script,
    "voice_settings": {
        "stability": 0.75,
        "similarity_boost": 0.8
    }
}

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

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

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}")

stability) and how closely the voice mimics the source ( similarity_boost). 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)
)

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)

    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}"

Result:

narration.mp3 – the spoken audio. subtitles.srt – a perfectly synced subtitle file. 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);

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.

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.

Issue Fix
Audio too short / missing words Use output_format=json to get timestamps; double‑check the script length.
Voice sounding robotic Increase stability andsimilarity_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 .

You now have a fully automated pipeline:

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

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!

── more in #ai-tools 4 stories · sorted by recency
── more on @elevenlabs 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-to-build-a-voice…] indexed:0 read:5min 2026-10-27 · —