# Changelog of YouTube Transcript Scraper: Subtitles, Channels & Whisper (`humble-echidna/youtube-transcripts`) Actor

- **URL**: https://apify.com/humble-echidna/youtube-transcripts/changelog.md
- **Full Actor documentation**: https://apify.com/humble-echidna/youtube-transcripts.md

## Changelog

Versions follow MAJOR.MINOR.PATCH (`src/version.py`); Apify shows MAJOR.MINOR from `.actor/actor.json`.
Every run logs its version and records it in the `RUN_STATS` key-value record.

### 0.2.4 (2026-10-16)

- A run that returns nothing (every link private, removed or blocked, an empty input, a date window that can't
  match) now ends SUCCEEDED with an empty dataset and the reason as its status message and in the log, instead of
  FAILED. Only an unexpected error still fails the run. `RUN_STATS.failure` keeps the reason as before.
- No change to output fields or price.

### 0.2.3 (2026-10-15)

- Run outside Apify (a local run, as with the other actors), the default input reads YouTube directly instead of
  failing at the start for want of an Apify Proxy password. On Apify nothing changes: the proxy setting is used as
  before.
- No change to output fields or price.

### 0.2.2 (2026-10-15)

- A live stream, or one that has just ended and isn't processed yet, is an answer like a video without captions:
  a run of only such links no longer fails. Before, it failed with `whisperFallback` on (the default) and succeeded
  with it off; now both report `live` with the same reason.
- A run that gives only `searchQueries` no longer also reads, and charges for, the example video Apify fills into
  `urls` by default (an API call that leaves `urls` out). A link you give yourself is still read.
- No change to output fields or price.

### 0.2.1 (2026-10-14)

- Caption tracks of very long videos (a 10-hour course: over 10 MB of json3) are read; they failed at the shared
  10 MB body cap before (cap now 80 MB; the tracks come from the run's own address, not the proxy).
- An item that would pass Apify's 9 MB dataset item limit leaves out WebVTT, then SRT, then the segments, keeping the
  text, and says so in `notes` (a 10-hour course with every format on is ~6 MB).

### 0.2.0 (2026-10-14)

- **Search keywords** (`searchQueries`) and YouTube search result links in `urls`: the top results in YouTube's
  order, up to `maxVideosPerSource` each (`inputType: "search"`).
- **Date window** (`publishedAfter`, `publishedBefore`: a day or a span such as "30 days") for videos from channels,
  playlists and searches. A list's rough dates skip videos certainly outside it before they're read, a channel's list
  ends at its first video older than the window, and each video's exact day decides; skipped videos are free
  (`outsideWindow` in RUN_STATS). Pasted video links are always read.
- **Language list**: `language` takes several codes in order of preference (`es, en`).
- `maxVideosPerSource` up to 5,000 (was 1,000).
- New fields: `publishedAt` (exact UTC publish time) and `availability` (public or unlisted).
- **About half the proxy traffic per video**: one anonymous visitor id per run (YouTube's service-worker data, ~3 KB)
  lets each video be read from YouTube's API without the watch page, and YouTube's player script (~760 KB, needed
  only for media links) is never downloaded for a transcript. Any light read that goes wrong is redone the full way
  at once, so it can't cost a transcript (`lightReads`, `lightFallbacks` in RUN_STATS).
- Every request is pinned to English (`hl=en`, `Accept-Language: en-US`): residential addresses are worldwide and
  YouTube localizes by address. Numbers and dates come from YouTube's fixed fields.
- A bot check that yt-dlp reports only as a warning (a read without media links) is a bot check again: retried from a
  new address and reported as `blocked`, never as "no captions".
- Not added: translation. YouTube answered every machine-translated caption request (`tlang`) with HTTP 429, direct and
  through residential addresses, in json3, srv3 and vtt (2026-10-14, 12 tries on 4 videos).

### 0.1.0 (2026-10-14)

First release.

- Transcripts of YouTube videos, Shorts, channels (`@handle`, `/channel/`, `/c/`, `/user/`, a `/videos`, `/shorts`
  or `/streams` tab) and playlists, newest first per channel or playlist (`maxVideosPerSource`), each video once.
- Caption choice: the uploader's subtitles first, then YouTube's automatic captions of the spoken language (the
  "-orig" track), never a machine translation; `language` picks a language, with a flagged fallback to the
  video's own.
- Videos without captions are transcribed from their smallest audio stream with Whisper (faster-whisper base or
  small, the engine and worker of audio-transcriber), charged per started audio minute (`whisper-minute`,
  `whisper-minute-accurate`) after the transcript is in the dataset; capped by `maxWhisperMinutesPerVideo` and by
  the maximum cost per run.
- Formats per item: plain text, timestamped segments, SRT, WebVTT; video details (channel, date, duration, views,
  likes, comments, tags, categories, description, thumbnail, chapters) optional, free.
- Charged per transcript through Apify's `apify-default-dataset-item` event; videos without a transcript are not
  written or charged; each is listed with its reason in `RUN_STATS`.
- Whisper gets a time budget per video (30 s per audio minute at 4 GB, 60 s at the default 2 GB, 3x for small);
  a transcript cut there says so in `notes` and only its minutes are charged.
- Apify's residential proxy by default (datacenter addresses got a bot check on every try); caption tracks are
  fetched without the proxy first (their links aren't tied to an address), through it if refused.
- A bot check or HTTP 429 is retried twice from a new proxy IP. One failing video or input line never affects the
  others.

Sources and terms (ACTOR-CHECKLIST 0 and 6), read 2026-10-14:

- Data: youtube.com's public, logged-out video pages, channel and playlist pages and caption tracks, read with
  yt-dlp (https://github.com/yt-dlp/yt-dlp), the maintained open-source client for them; caption tracks fetched
  through mms_common from www.youtube.com only. No official API serves other people's captions (the YouTube Data API
  `captions.download` needs the video owner's authorization).
- robots.txt: https://www.youtube.com/robots.txt disallows `/api/` (caption tracks) and `/youtubei/` (the player
  and list API) for every crawler; googlevideo.com's (the audio) disallows everything. Read under Michael's platform
  exception of 2026-10-14 (ACTOR-CHECKLIST 2b, `robots_exempt_hosts`).
- Terms: https://www.youtube.com/t/terms forbids "access\[ing] the Service using any automated means (such as robots,
  botnets or scrapers) except (a) in the case of public search engines, in accordance with YouTube's robots.txt
  file; or (b) with YouTube's prior written permission". Michael's decision (2026-10-14) is to read the logged-out
  public data anyway, like the Store's leading actors; the README says so plainly and offers takedown on request.
