"Does this video have captions?" turns out to be the wrong question.
I measured 176 YouTube videos on 2026-08-29. 68.2% of them offer machine transcription and nothing else, and within this sample the split is not random — it is almost entirely determined by which channel the video is on.
What I measured, and what fell out of the sample
I aimed at 20 channels. For each one I resolved the handle to a channel ID from the channel page, pulled its public RSS feed (https://www.youtube.com/feeds/videos.xml?channel_id=...), took up to the 15 most recent video IDs, and asked YouTube's player endpoint what caption tracks each video has.
Two things you should know before reading the numbers:
- 8 of the 20 channels dropped out. Four of them 404'd on the handle I used — which may simply mean I had the wrong handle rather than anything about the channel — and four returned a page I could not extract a channel ID from. Six of the eight were non-English or music channels (five non-English, two music, one both), so the dropouts fell disproportionately on the categories that matter most to the two findings below. What survived is skewed toward English-language education and tech.
- The 12 channels are hand-picked. The videos are not. Video IDs come straight from each feed in publication order, so there is no cherry-picking inside a channel, but there is nothing random about the channel list itself. It is also recency-biased by construction: newest 15, nothing older. One feed returned only 11 videos, which is why the total is 176 rather than 180.
The result
| Channel | Human captions | Auto-generated only | No captions at all |
|---|---|---|---|
| NASA | 15 | 0 | 0 |
| 3Blue1Brown | 14 | 1 | 0 |
| Kurzgesagt | 11 | 0 | 0 |
| Billie Eilish | 2 | 0 | 13 |
| TED | 1 | 14 | 0 |
| Veritasium | 0 | 15 | 0 |
| MKBHD | 0 | 15 | 0 |
| Linus Tech Tips | 0 | 15 | 0 |
| Computerphile | 0 | 15 | 0 |
| HIKAKIN | 0 | 15 | 0 |
| Fireship | 0 | 15 | 0 |
| ThePrimeagen | 0 | 15 | 0 |
| Total | 43 (24.4%) | 120 (68.2%) | 13 (7.4%) |
Three channels account for 40 of the 43 human-captioned videos. Seven channels have zero across all 15 videos I sampled from each of them, and the remaining two contribute three videos between them.
All 13 videos with no captions came from the music channel. For those, the player response has no captions object whatsoever — not an empty array, not a track list with nothing useful in it. The key is absent, while playabilityStatus is still OK. I checked that on all 13.
For the 163 that returned something, the track I was given ran from 9 lines to 10,593, median 263. That is one track per video — whichever one the picker chose — so it mixes human and machine tracks and several languages.
Why the clustering matters more than the percentage
If you build a transcript pipeline and test it against NASA and Kurzgesagt, every video in your test set has human captions. Point the same code at the rest of this sample and seven of the remaining ten channels hand you machine transcription instead. Same endpoint, same playabilityStatus: OK, same shape of JSON. Nothing in the response says the quality of what you just received changed.
I did not measure caption quality here, and I am not going to assert a difference I did not test. What I can say is that the two are different products — one is a file a human wrote, one is the output of speech recognition — and your code currently cannot tell them apart unless you look.
How to tell them apart, in one field
The distinction is in the caption track object:
{"name":{"runs":[{"text":"French (auto-generated)"}]},"vssId":"a.fr","languageCode":"fr","kind":"asr","isTranslatable":true,"trackName":""}
(verbatim from a live response, with the long baseUrl removed)
kind: "asr" means automatic speech recognition. A human-written track has no kind field at all — not kind: "manual", not kind: null. It is simply absent. So:
const isAutoGenerated = track.kind === 'asr';
Do not write if (track.kind) and treat the falsy branch as human. An absent key and some future unknown value would land in the same place.
The second finding: the track you get may not be in the language you asked for
I ran the same 176 videos again, this time asking for English.
| Outcome | Videos |
|---|---|
| Returned a caption track | 163 |
| ...in English | 127 (77.9% of the 163) |
| ...in another language | 36 (22.1% of the 163) |
| Of those 36, how many had an English track available that got skipped | 0 |
That last row rules out the boring explanation. This is not a selection bug in my code: in all 36 cases YouTube offered exactly one track, and it was not English.
| Channel | Videos | Language returned |
|---|---|---|
| HIKAKIN | 15 | Japanese (auto) |
| Veritasium | 15 | French (auto) |
| TED | 6 | Bulgarian (auto) |
HIKAKIN is a Japanese-language channel, so Japanese is the right answer there. The other 21 are recent uploads from English-language channels whose only caption track was auto-generated in some other language.
I checked three of these videos — one per channel, so only two of them are the surprising case — three separate ways: the iOS player client, the Android player client, and the plain watch?v= HTML a browser receives, all with hl=en&gl=US. All nine requests returned the same single non-English track, so this is not an artefact of how I asked. I did not repeat that cross-check on the other 33.
I have not established why this happens. Both channels are known to publish multi-language audio, which is the obvious suspect, but I could not confirm it from these responses — my field mask only asks YouTube for the caption tracks, playability status and basic video details, so the audio track list never came back. I am not going to claim a cause I did not measure.
You can still get English out of it — and that is its own trap
The French track shown earlier has isTranslatable: true, and appending &tlang=en to its baseUrl returns English:
GET <baseUrl>&fmt=json3 → 73 lines, "Il y a cette fameuse question..."
GET <baseUrl>&fmt=json3&tlang=en → 73 lines, "There's that famous..."
The Bulgarian track from one of the TED videos behaves the same way: 759 lines either way, "Сега, както предполагам много от вас..." untranslated and "Now, as I assume many of you, and I..." with tlang=en. I confirmed this on those two videos, not on all 36 — and timedtext starts returning 429 if you hammer it, so expect to retry.
So "no English track" does not mean "no English text". It means the English you can get is a machine translation of a machine transcription — two lossy steps stacked, from a source language that may itself be wrong for the video.
That is a perfectly reasonable fallback to build. What is not reasonable is doing it silently. If your pipeline falls back to tlang, put it in the row: which language the source track was, whether it was asr, and that the text was translated. Otherwise a downstream consumer sees English text from an English channel and has no way to know it went through two models to get there.
The short version
- Human captions vary channel by channel, not video by video. Three channels produced 40 of the 43 human-captioned videos; seven had none at all.
- 68.2% of the 176 videos offered machine transcription and nothing else.
- 7.4% had no caption track at all — all of them from the music channel, and the response has no
captionsobject whatsoever. -
kind: "asr"is the flag. A human track has nokindkey. Don't test truthiness. - Asking for English returned another language 22.1% of the time, and in none of those cases was there an English track to pick instead.
-
&tlang=enwill get you English anyway, as a machine translation of a machine transcription. Record that fact on the row instead of hiding it. - Testing against a well-captioned channel tells you nothing about the rest of YouTube.
The tool these numbers came from
The measurements ran on the same code that backs YouTube Transcript Scraper API, which returns isAutoGenerated, the language it actually gave you, and availableLanguages on every row — so both findings above are visible in the output rather than something you have to infer. It does not do the tlang fallback; it hands you the track that exists and tells you what it is. Worked example: a video transcript as plain text.
Written with AI assistance. Every count, ratio and response above came from live requests executed on 2026-08-29 before publishing; the JSON payload is verbatim apart from the truncated baseUrl.