# Audio & Video Transcriber (Whisper): Speech to Text, SRT, VTT (`madrasco/audio-podcast-transcriber`) Actor

Transcribe audio and video files from direct links, or a podcast RSS feed, to text, SRT, WebVTT and JSON with timestamps. Speech to text by an open Whisper model inside the run, so audio is not sent to an outside AI service. Language auto-detect and a max-minutes limit.

- **URL**: https://apify.com/madrasco/audio-podcast-transcriber.md
- **Developed by:** [Jack Valmadre](https://apify.com/madrasco) (community)
- **Categories:** AI, For creators, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Audio & Podcast Transcriber (Whisper in the run, SRT/VTT)

Turn audio and video files, or the newest episodes of a podcast RSS feed, into plain text, SRT and WebVTT subtitles, and JSON with timestamps. Transcription runs inside the actor with an open Whisper model (faster-whisper, `base`, int8), so your audio is not sent to an outside AI service.

Whisper is an open speech-recognition model released by OpenAI under the MIT licence; this actor is not affiliated with or endorsed by OpenAI.

### What you give it

- **Audio or video file URLs**: direct links that download the file (MP3, M4A, WAV, OGG, FLAC, MP4, WebM and other formats FFmpeg reads).
- **Podcast RSS feed URL** and **Max episodes**: the newest episodes with audio enclosures are transcribed.
- **Max audio minutes per run** (default 60): the run never starts a file that would take the total past this limit; that file and any after it get a `skipped` row.
- **Language**: leave empty to detect it automatically, or give a two-letter code such as `en`.
- **Accuracy mode**: beam search (default) or greedy (faster); **Skip silence** (optional).
- **Label speakers** (optional, off by default): split the transcript by who is speaking, labelled `Speaker 1`, `Speaker 2`... See [Speaker labels](#speaker-labels).

Also accepted, for tools and AI agents: `urls`, `startUrls`, `url`, `feedUrl`.

### What you get

- One dataset row per file: status (`ok`, `error`, `skipped`), detected language and its probability, duration, processing time, full text, and links to the files.
- Four files per transcribed input in the run's key-value store: `.txt`, `.srt`, `.vtt` and `.json` (segments with start and end times).
- An `OUTPUT` record with counts and total audio minutes.

### Speaker labels

Turn on **Label speakers** (`"speakerLabels": true`) to split the transcript by who is speaking. Each speaker gets an anonymous label, `Speaker 1`, `Speaker 2` and so on, numbered in the order they first speak. Labels are never names: the actor does not know or identify who anyone is, and the same person can be `Speaker 1` in one file and `Speaker 2` in another. This step also runs inside the run, so your audio still never leaves it.

What changes in the output when it is on (from a real run on a NASA podcast interview):

- **TXT**: a new paragraph at each change of speaker, starting with its label (`Speaker 1: ...`).
- **SRT and VTT**: each subtitle starts with its label as plain text, so any player shows it:
  ```
  15
  00:01:27,477 --> 00:01:30,920
  Speaker 1: Hi Debbie, thank you so much for joining us today on Houston. We have a podcast.

  16
  00:01:30,920 --> 00:01:32,260
  Speaker 2: Yeah. I'm
  ```
- **JSON**: every segment has a `speaker` field, e.g. `{"start": 90.92, "end": 92.26, "text": "Yeah. I'm", "speaker": "Speaker 2"}`. Segments are split where the speaker changes, so there are more and shorter ones, and when people talk over each other two segments can overlap in time.
- **Dataset row**: `speakerLabels: true`, `speakerCount` (the estimated number of speakers) and `speakerCpuSeconds` (CPU time the speaker step used).
- A stretch the actor can't match to any speaker gets no label: `speaker` is `null`, and the TXT, SRT and VTT show it without a prefix. In one podcast we tested, most of an 80-second sponsor read came out this way.

#### How accurate it is

We measured this version's speaker-labelling code on two public test sets it was never tuned on, both English:

| Test set | What it is | Diarization error rate |
|---|---|---|
| AMI Meeting Corpus, test set | 16 recorded meetings, about 9 hours | 17.85% |
| VoxConverse, test set | 22 news and debate recordings from YouTube (chosen by a rule fixed before scoring), about 4.6 hours | 9.12% (by recording: 1.7% to 42.4%) |

The diarization error rate is the share of speech time that ends up with the wrong speaker, with no speaker, or labelled as speech when nobody is talking. We scored the labelled transcript, i.e. what you get, allowing 0.25 seconds either side of each change of speaker. Lower is better. The two sets differ a lot, so read them as a range for different kinds of audio, not as one number for yours.

One outside example: on the first 10 minutes of the NASA interview above (two speakers; the actor found 2), 93% of the transcribed words that we could match to NASA's published transcript were given the right speaker.

#### Limits

- **The number of speakers is an estimate, and it is often too low.** People who speak only briefly are often folded into another speaker's label. On the VoxConverse test set the count was exactly right for 7 of 22 recordings and too low for 14; on AMI it was right for 11 of 16 meetings. On 4 of the 9 VoxConverse recordings with 2-4 speakers, everyone got the same label (`Speaker 1`). This is most likely when one voice dominates the recording.
- Recordings with many speakers lose the ones who speak little: two VoxConverse news recordings with 15 speakers came out with 7.
- It can also give one person two labels: on AMI it found more speakers than there were in 4 of 16 meetings.
- A label can switch a word or two before or after the real change of speaker, so one person's sentence can be split: `Speaker 1: Well, I want to` / `Speaker 2: talk about` / `Speaker 1: Artemis III...`.
- We have not measured noisy rooms, heavy crosstalk or speech over music separately; expect more errors there than on the test sets above.
- There is no setting for the number of speakers in this version.
- Measured on English recordings only. It will run on other languages, but we have no measurement for them.
- It adds processing time and platform usage (see [Pricing](#pricing)).

### Measured quality and speed

All numbers below were measured by us on 2026-09-25 on a small set of clips; your recordings may differ.

| Clip | Length | Word error rate (local run) | Processing time on Apify (4 GB) |
|---|---|---|---|
| LibriVox read speech, Poe "Alone" (the input-form example) | 78 s | not measured | 10 s |
| NASA "Houston We Have a Podcast" ep. 434 opening, two speakers, pink noise added (about 12 dB signal-to-noise) | 5 min | 6.1% | 53 s |
| LibriVox read speech, "The Gift of the Magi" (clean) | 13.4 min | 3.4% | not run on Apify (84 s on one local core) |

- Word error rate is measured against the published text (LibriVox: Project Gutenberg #7256; NASA: the episode's transcript page, which is lightly edited) on a local run with the same model and settings. Transcripts from the Apify run differed from the local ones by 1-3% of words, mostly punctuation.
- On Apify at 4 GB memory, transcription took about 0.13-0.18 seconds per second of audio (roughly 8-10 minutes of audio per 1.5 minutes of run time), plus about 10 seconds to start. Peak memory was 552 MB.
- Every dataset row reports its own `processingSeconds`, `realTimeFactor` and `cpuSeconds`, so you can check speed on your own files.

### Limitations

- YouTube, TikTok and Instagram links are refused: their terms don't allow downloading their media. If the recording is yours, give a direct link to the file instead.
- Links must download the file itself; web pages that embed a player are reported as unsupported.
- No translation. Speaker labels are optional and have their own limits (see [Speaker labels](#speaker-labels)).
- The `base` model makes more mistakes on heavy accents, crosstalk, music under speech and uncommon names (for example it wrote "Cheshire" for "Cheshier"). Check transcripts before publishing them.
- Files up to 2 GB each.

### Privacy

Files are downloaded into the run, transcribed there and deleted when the run ends. With speaker labels on, the voice analysis also happens inside the run, with models built into the actor; nothing about the voices is kept after the run, only the anonymous labels in your transcript. Transcripts are stored only in the run's own dataset and key-value store, under your account. Only process recordings you have the right to transcribe.

### Use with AI agents and other actors

Input is plain JSON. The smallest useful input is a list of file links:

```json
{"urls": ["https://example.com/episode-12.mp3"], "maxMinutes": 30}
```

`urls` may be plain strings or `{"url": "..."}` objects; `startUrls`, `url` and `audioUrls` work the same way. For a podcast, give `{"feedUrl": "https://example.com/feed.xml", "maxEpisodes": 2}` (`rssUrl` also works). Add `"speakerLabels": true` for speaker labels. Each dataset row has `status`, `text` and links (`txtUrl`, `srtUrl`, `vttUrl`, `jsonUrl`) to the transcript files; rows with `status` `error` or `skipped` explain why in `error`. Set `maxMinutes` to bound how much audio one run transcribes.

### Pricing

No charge from us for now: you pay only Apify's platform usage of your run, which was about US$0.0023 per audio minute plus about US$0.002 per run (at US$0.20 per compute unit, the Free and Starter plan rate) at the default 4 GB memory in our test run. With speaker labels on, the speaker step used roughly half as much CPU time again as the transcription itself: in our Apify runs at 4 GB, two 10-minute podcasts took 49-54 CPU-seconds for speaker labels against 90-112 for transcription, finished in about 3-4 minutes, and used about US$0.044-0.052 of platform usage each. We plan to add a per-audio-minute price in a later release; any price is shown on this page and by Apify before you start a run.

### Credits

Transcription: the open Whisper base model (MIT) run with faster-whisper. Optional speaker labels: pyannote segmentation-3.0 (MIT, (c) 2022 CNRS) and NVIDIA NeMo TitaNet-small (Apache-2.0), both as ONNX exports by sherpa-onnx (Apache-2.0), run with onnxruntime (MIT). Licence notices ship in the actor image (NOTICE, LICENSE-Apache-2.0.txt).

### Support

Please use the **Issues** tab of this actor; we aim to respond within 14 days. This actor is built and supported with AI assistance; a human owner can be reached on request through the Issues tab.

Publisher: Madrasco.

# Actor input Schema

## `audioUrls` (type: `array`):

Direct links to audio or video files (MP3, M4A, WAV, OGG, FLAC, MP4, WebM...). The link must download the file itself, not open a web page. YouTube, TikTok and Instagram links are refused because their terms don't allow downloading.

## `rssUrl` (type: `string`):

A podcast's RSS feed. The newest episodes (up to 'Max episodes') are transcribed.

## `maxEpisodes` (type: `integer`):

How many of the newest feed episodes to transcribe.

## `maxMinutes` (type: `integer`):

The run never starts a file that would take the total transcribed audio past this many minutes; such files get a 'skipped' row. 0 = no limit.

## `language` (type: `string`):

Two-letter language code (e.g. 'en', 'de', 'es'). Leave empty to detect it automatically from the first 30 seconds.

## `beamSize` (type: `string`):

5 = beam search (default, more accurate); 1 = greedy (faster).

## `vadFilter` (type: `boolean`):

Remove long silent stretches before transcribing. Can be faster on recordings with pauses.

## `speakerLabels` (type: `boolean`):

Split the transcript by speaker and label each part with an anonymous 'Speaker 1', 'Speaker 2'... (who spoke when; no names, nobody is identified). Runs inside the run, so the audio still never leaves it. Adds processing time. Measured on English recordings only.

## Actor input object example

```json
{
  "audioUrls": [
    {
      "url": "https://archive.org/download/spc141_1503_librivox/spc141_alone_th_64kb.mp3"
    }
  ],
  "maxEpisodes": 3,
  "maxMinutes": 60,
  "language": "",
  "beamSize": "5",
  "vadFilter": false,
  "speakerLabels": false
}
```

# Actor output Schema

## `transcripts` (type: `string`):

One dataset row per file: status, detected language, duration, full text and links to the TXT, SRT, VTT and JSON files; error rows explain unreachable or unsupported links.

## `files` (type: `string`):

TXT, SRT, VTT and JSON (segments with timestamps) for each transcribed file, plus the OUTPUT run summary.

## `summary` (type: `string`):

Counts of transcribed, failed and skipped files and the audio minutes transcribed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "audioUrls": [
        {
            "url": "https://archive.org/download/spc141_1503_librivox/spc141_alone_th_64kb.mp3"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("madrasco/audio-podcast-transcriber").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "audioUrls": [{ "url": "https://archive.org/download/spc141_1503_librivox/spc141_alone_th_64kb.mp3" }] }

# Run the Actor and wait for it to finish
run = client.actor("madrasco/audio-podcast-transcriber").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "audioUrls": [
    {
      "url": "https://archive.org/download/spc141_1503_librivox/spc141_alone_th_64kb.mp3"
    }
  ]
}' |
apify call madrasco/audio-podcast-transcriber --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,madrasco/audio-podcast-transcriber"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/XbicUJ7VniLYOTIZY/builds/ZS4srKBsom1Qb62zv/openapi.json
