# Changelog of Audio & Video Transcriber (`humble-echidna/audio-transcriber`) Actor

- **URL**: https://apify.com/humble-echidna/audio-transcriber/changelog.md
- **Full Actor documentation**: https://apify.com/humble-echidna/audio-transcriber.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.

### 1.1.2 (2026-09-26)

- `RUN_STATS.platformLinksRefused`: how many refused links per platform (names only), e.g. {"YouTube": 2}.

### 1.1.1 (2026-09-26)

- README: real screenshots of the input form and the Output table, from our own runs.

### 1.1.0 (2026-09-26)

- New: **word timestamps** (`includeWordTimestamps`, off by default). A `words` field lists every word with its start
  and end in seconds and the model's confidence; `null` when off. faster-whisper's own alignment
  (`word_timestamps=True`), offset per 20-minute window like the segments. Same price per minute; it makes
  transcription slower (measured in COSTS.md). A 300-minute file of fast speech with every output on stays under
  Apify's 9 MB item limit (tested).
- New: **audio links from another actor's dataset** (`datasetId`, with `datasetUrlField`). Each item's link is
  transcribed like a `mediaUrls` line, in the dataset's order, duplicates (and links already in `mediaUrls`)
  dropped; `source` is `dataset`. The field is a name or a dotted path; its value may be a URL, an object with a
  `url` (or href/link/src), or a list of them (the first audio or video one wins, as for podcast enclosures). Left
  empty, it's found among audioUrl, audio, mediaUrl, enclosures (RSS & Atom Feed Reader's output), enclosure,
  videoUrl, fileUrl and downloadUrl; a page link such as `url` is never guessed. Read through Apify's API with the
  run's own client (the user's run token, read-only; locally there is none, and the run says so), only the needed
  field, at most 10,000 items and 1,000 distinct links. A dataset that can't be read, is the run's own output, or
  has no links fails clearly (naming the fields its first item has) without affecting the other inputs; nothing is
  fetched for it. `RUN_STATS.dataset` has the counts.
- Changed: direct links to video and social platforms' **media servers** (googlevideo.com, tiktokcdn.com,
  fbcdn.net, cdninstagram.com, video.twimg.com, sndcdn.com, scdn.co, v.redd.it and the like) are refused before any
  request, like those platforms' page links already were, whether in `mediaUrls` or a dataset: a platform scraper's
  dataset holds exactly these, and the same terms cover them (the legal hold on platform media).
- New: a published accuracy benchmark (`benchmark/`): WER of base and small on a fixed subset of LibriSpeech
  test-clean and test-other (CC BY 4.0), with the actor's own decoding and model settings; results in the README.
- The worker's model settings are one function (`worker.options`), used by the actor and the benchmark alike.

### 1.0.1 (2026-09-25)

- Fixed: the Small model failed every file on a 4 GB run with "the file needs more memory than this run has". The
  worker's memory cap was RLIMIT\_DATA, which counts address space, not memory in use: on x86-64 the small model
  reserves ~3.4 GB of heap for a ~1.2 GB peak, past the 3.7 GB cap. The cap is now on the resident memory of the
  worker and its ffmpeg, sampled by the run every 0.25 s from /proc; over it, the file's process group is stopped
  and the file reported as `outOfMemory`, as before, without affecting the others. The worker also makes itself the
  kernel's first choice if the container runs out of memory between two samples, so a spike costs that file, not
  the run. Windows and per-file deadlines are unchanged.

### 1.0.0 (2026-09-25)

First release.

- Transcribes audio and video files the user supplies by URL, and podcast feeds' episodes (RSS, Atom, JSON Feed,
  or a site that advertises its feed; read by the shared mms\_feeds code), into text, timed segments, SRT and WebVTT.
- Whisper through faster-whisper/CTranslate2 on CPU, int8, beam 5, with the Silero voice-activity filter (skips
  silence and long music before the model sees it) and without conditioning on the previous text (stops a misheard
  passage from repeating for minutes). Two models: base (default) and small. Automatic language detection or one of
  46 languages; transcribe or translate to English.
- ffmpeg decodes the first audio stream (video files: only the sound) to 16 kHz mono; ffprobe reads the container
  first. Each file is decoded and transcribed in its own process with a deadline (the run's timeout) and a memory
  cap; the transcript is written as it's produced, so a file stopped by the run's timeout or a crash still returns
  what was transcribed, charged for what it covers.
- Charged per started audio minute of each file (`audio-minute` for base, `audio-minute-accurate` for small), once
  the transcript is in the dataset; transcripts themselves (`apify-default-dataset-item`) are free. Nothing is
  charged for files that couldn't be downloaded or decoded, have no audio track, or have no speech. Minutes are
  reserved before each file (its first minute before it's even downloaded), so a run stops at the maximum cost per
  run and cuts a file where the budget runs out. Proposed prices: $10.00 and $25.00 per 1,000 minutes (COSTS.md).
- "Only new episodes since the last run": a named key-value store in the user's account, per feed and per model,
  language and task. The first run marks the back catalogue as seen; an episode is remembered only once its
  transcript is in the dataset, so failed episodes are retried.
- Downloads through the shared client (robots.txt incl. AI-crawler opt-outs, private-network guard, ports 80/443),
  streamed to disk, 1 GB per file, 8 GB per run. Files are processed one at a time, in input order (feeds:
  newest episode first), each deleted as soon as it's transcribed. Links to video and social
  platforms' pages (YouTube, TikTok, Spotify, SoundCloud, ...) are refused before any request.
- The input schema's example recordings (the default for `mediaUrls`, which Apify fills in whenever a run leaves the
  field out) are skipped when a run gives only feeds, so an API user asking for feeds doesn't pay for them.

Before building (ACTOR-CHECKLIST.md, section 0), 2026-09-25:

- **Source and terms:** no fixed source. The actor fetches only the media URLs and podcast feeds the user supplies
  (PLAN.md's allowed class "tools that only process URLs or files the user supplies"; feeds are RSS published for
  machines), as a logged-out visitor, honouring robots.txt and AI-crawler opt-outs. No platform links and no
  yt-dlp: YouTube and the other platforms stay on the legal hold. No API key, contact address or other identity of
  ours is used; the only identity is the honest User-Agent every actor sends. The README tells users they need the
  right to transcribe what they submit. The default input is two public-domain files on Wikimedia Commons
  (Armstrong\_Small\_Step.ogg, NASA; The\_New\_Colossus.ogg), upload.wikimedia.org robots.txt: `User-agent: *` /
  `Disallow: /wikipedia/commons/archive/` only.
- **Models:** downloaded at image build time from Hugging Face (Systran/faster-whisper-base and -small, pinned
  revisions in src/models.py, MIT, converted from OpenAI's MIT-licensed Whisper weights); never during a run.
- **Demand (RESEARCH-candidates.md, store\_merged.json, 2026-09-24):** ~850 users/30d across non-platform transcriber
  actors, the largest clean pool found. Leaders: sian.agency 283 (~$300 per 1,000 minutes), steadyfetch 99 ($3),
  kaz\_kakyo 76 ($10), amanatools 49 ($8), sauliusautomatesit 37 ($40, fails 17%).
- **Licences of every component:** faster-whisper MIT; CTranslate2 MIT; Whisper weights MIT; Silero VAD model
  (bundled with faster-whisper) MIT; onnxruntime MIT; tokenizers and huggingface\_hub Apache-2.0; numpy BSD-3; PyAV
  BSD-3 (installed by faster-whisper, not used for decoding; its wheel bundles LGPL FFmpeg libraries); ffmpeg from
  Debian (a GPL build), run as a separate program, never linked.
