Fixing TranscriptsDisabled from youtube-transcript-api
Your script works for a hundred videos, then dies on one:
youtube_transcript_api._errors.TranscriptsDisabled:
Subtitles are disabled for this videoIf you're here from a stack trace, the short version: this is not a bug, a rate limit, or an IP block, and retrying will never fix it. Here's what's actually happening and what your options are.
What the error means
youtube-transcript-api does exactly one thing: it reads caption tracks that
already exist on a video. It doesn't do any speech recognition of its own.
TranscriptsDisabled means the video has no caption track to read — either
the uploader turned captions off, or YouTube never auto-generated them (common
for music, very short clips, low-resource languages, and some region-locked
uploads).
So the library is telling you the truth: there is nothing to fetch.
It's worth separating the errors people conflate:
| Error | Cause | Retrying helps? |
|---|---|---|
TranscriptsDisabled | No caption track exists at all | No — nothing to fetch |
NoTranscriptFound | Captions exist, but not in the language you asked for | Sometimes — try other languages |
IpBlocked / RequestBlocked | YouTube blocked your IP (common on cloud hosts) | Yes — with a residential proxy |
What doesn't fix it
Retries and backoff. The response isn't flaky; it's a definitive "no captions here." Retrying just burns time.
Proxies. Residential proxies are genuinely necessary for a different
problem — YouTube blocks datacenter IPs, so the same code that works on your
laptop can fail on AWS or Vercel with IpBlocked. But a proxy changes who is
asking, not whether the captions exist. It will not fix TranscriptsDisabled.
Trying every language. Worth a shot for NoTranscriptFound, useless here:
from youtube_transcript_api import YouTubeTranscriptApi
api = YouTubeTranscriptApi()
# Listing shows you what actually exists — for a TranscriptsDisabled video,
# this raises too, because there is no track list at all.
transcripts = api.list(video_id)The only real fix: transcribe the audio
If there's no caption track, the only way to get text is to listen to the video. That means: pull the audio, run speech recognition, and format the result.
Doing it yourself
Fully workable, and worth knowing even if you outsource it later:
# 1. Pull audio only
yt-dlp -f bestaudio -x --audio-format mp3 -o audio.mp3 "https://youtu.be/VIDEO_ID"# 2. Transcribe it
from openai import OpenAI
client = OpenAI()
with open("audio.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-1",
file=f,
response_format="verbose_json", # needed for per-segment timestamps
)
for seg in result.segments:
print(seg.start, seg.text)Two details that bite people:
- Ask for
verbose_jsonif you want timestamps. The default response is a flat string, and some newer transcription models won't return segment timestamps at all —whisper-1withverbose_jsonwill. - Mind the file size limit. Long videos need to be chunked (or compressed) before upload, and chunk boundaries have to be stitched back together with their offsets or your timestamps drift.
What it costs you to run
The DIY path is the right call if transcription is your core product. If it isn't, the ongoing costs are: residential proxies (for the caption path and for downloads from cloud IPs), audio storage and cleanup, a queue and workers for jobs that take minutes, chunking and stitching for long videos, retries when YouTube changes something, and a cache so you don't pay twice for the same video.
That's a small service, not a function.
Doing it with an API
This is the problem transcript.land exists for. One call returns captions when they exist and falls back to AI transcription when they don't — the response shape is identical either way, so your code stops caring which path ran:
curl -X POST https://api.transcript.land/v1/transcript \
-H "Authorization: Bearer $TRANSCRIPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/VIDEO_ID", "format": "json"}'Or from the terminal:
brew install ziqorg/tap/transcript
transcript login
transcript get "https://youtu.be/VIDEO_ID"Videos with captions come back in under a second. Videos without return a
job_id you poll while the audio is transcribed. The same works for TikTok, X,
Instagram, Bilibili, Facebook, and RedNote, and you can export to TXT, SRT, VTT,
Markdown, or JSON. There's a free tier if you just need to unblock a
handful of videos.
Summary
TranscriptsDisabledmeans no caption track exists — retries and proxies can't help, because there's nothing to fetch.- Proxies solve a different error (
IpBlocked), which is real but separate. - The only fix is transcribing the audio:
yt-dlp+ a speech model yourself, or an API that falls back automatically.
If you want to skip the pipeline, try it on the video that broke your script — paste the URL and see whether it comes back.