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. 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 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. python 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}" 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 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}" 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: js 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 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 . | 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 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