# Audio Transcriber — Speech to Text (Whisper) (`viralanalyzer/audio-transcriber`) Actor

Transcribe audio to text with Whisper running locally — no API key. Give any media URL (podcast, TikTok, Instagram, YouTube, or a direct audio file) and get text, timestamped segments, SRT subtitles and auto-detected language. Pay per minute transcribed.

- **URL**: https://apify.com/viralanalyzer/audio-transcriber.md
- **Developed by:** [viralanalyzer](https://apify.com/viralanalyzer) (community)
- **Categories:** AI, Videos
- **Features:** Limited permissions
- **Stats:** 82 total users, 4 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 minute of audio transcribeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## 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 Transcriber — Speech to Text (Whisper) for Podcasts, Social & Uploads

> **$0.015 per minute transcribed** (+ Apify platform usage) · 🔒 no API key needed · 🌍 any language (auto-detect) · 📝 text + SRT + timestamps
> 🔗 [View on Apify Store](https://apify.com/viralanalyzer/audio-transcriber) · 🇺🇸 English · [🇧🇷 Português](#-português)

Transcribe **audio to text** using OpenAI **Whisper running locally inside the Actor** (faster-whisper) — **no external API, no key, no rate limits**. Just give any media URL — a podcast, TikTok, Instagram, YouTube, SoundCloud, or a direct link to an audio file — and get accurate **text, timestamped segments, SRT subtitles and auto-detected language**. Audio is extracted automatically from video links too.

**Who it's for:** podcasters & content teams, journalists, researchers, accessibility/SEO teams, and developers building search or summarization on top of spoken audio.

▶️ Click **Try for free** — your first run returns a real transcript in seconds.

### ✨ Features

- 🎯 **Real Whisper transcription** (not just captions) — works on audio that has no subtitles.
- 🌍 **99+ languages**, auto-detected (or force one with the `language` field).
- 🔗 **Any source** — social networks, podcast hosts, or a direct audio-file link (mp3/wav/m4a/…).
- 📝 **Rich output** — full text, timestamped segments, ready-to-use **SRT**, word count, language.
- 🔒 **Self-contained** — Whisper runs in the container; you never need an API key.
- 💸 **Fair pricing** — pay per minute of audio actually transcribed.

### 📥 Input

| Field | Type | Required | Description |
|---|---|---|---|
| `mediaUrls` | array | ✅ | Audio/media URLs (social/podcast link or direct audio file) |
| `language` | string | ❌ | ISO code (`en`, `pt`, `es`…). Empty = auto-detect |
| `model` | string | ❌ | `tiny` | `base` (default) | `small` — speed vs. accuracy |
| `maxMinutesPerItem` | integer | ❌ | Safety cap on minutes per URL (default 120) |
| `proxyConfiguration` | object | ❌ | Datacenter by default; use RESIDENTIAL if a network blocks the download |

### 📤 Output (per item)

```json
{
  "url": "https://…",
  "title": "Episode 12 — …",
  "language": "en",
  "durationSeconds": 612.4,
  "billedMinutes": 11,
  "text": "Full transcript text …",
  "segments": [{ "start": 0.0, "end": 4.2, "text": "…" }],
  "srt": "1\n00:00:00,000 --> 00:00:04,200\n…",
  "wordCount": 1840
}
```

### 🎯 Use cases

- Turn podcasts/interviews into searchable, quotable text.
- Generate SRT subtitles for social clips.
- Feed spoken audio into summarization / RAG / analytics.
- Accessibility transcripts for compliance.

### ✅ Capabilities & Limits

Stated up front, so you do not pay a run to find out.

**Built here:** a podcast or upload URL ending in a media extension skips yt-dlp entirely - plain HTTP GET plus ffmpeg. yt-dlp's generic extractor fetches a "webpage" first and most CDNs answer 403, which is exactly where direct audio links used to fail.

| Input / feature | Supported | Notes |
|---|---|---|
| `mediaUrls` — **required** | ✅ | Audio/media URLs to transcribe — a podcast/social/video link (audio is extracted automatically) or a direct link to an audio file (mp3, wav, m4a, ...). |
| `language` | ✅ | ISO code (e.g. 'en', 'pt', 'es'). Leave empty to auto-detect. |
| `model` | ✅ | Accuracy vs. speed. 'small' (default, more accurate), 'base' (faster) or 'tiny' (fastest). Larger models cost a bit more compute time. |
| Result volume (`maxMinutesPerItem`) | ⚠️ | Safety cap on how many minutes of audio to transcribe per URL. |
| `youtubeCookies` | ✅ | Paste the contents of a Netscape cookies.txt exported from a logged-in YouTube session to reliably download YouTube (avoids the 'Sign in to confirm… |
| Proxy | ⚠️ | Residential proxy by default — social networks (YouTube/TikTok/Instagram) block datacenter IPs when downloading media. Keep RESIDENTIAL unless your… |

### ❓ FAQ

**Q: Do I need an API key (OpenAI, etc.)?** A: No. Whisper runs inside the Actor — you only provide URLs.

**Built here — what YouTube's bot-gate actually does, with both measurements:** YouTube answers a
share of requests with *"Sign in to confirm you're not a bot"*, and it does so **per exit IP**, not
per video. The rate is not stable, and we have measured it twice on the same video over Apify
RESIDENTIAL:

| Date | Condition | Result |
|---|---|---|
| 2026-08-04 | 8 items, full download | 6/8 — the two gated ones were long videos |
| 2026-08-30 | 8 items, partial fetch | **8/8**, no gate at all |

Treat neither number as the rate you will get. What holds across both is the mechanism: a URL gated
on one IP goes through on the next. Since 1.2.1 a gated item is retried on a **fresh residential
session**, up to 3 attempts. The retry costs almost no proxy traffic — the gate fires on the metadata
probe (`yt-dlp --skip-download`), before any media is pulled. Only a confirmed gate is retried; an
unavailable or removed video fails on the first attempt. When every attempt is gated you get a
`BLOCKED` record naming how many IPs were tried, and **nothing is charged**.

**Do not reach for `youtubeCookies` first — measured 2026-08-30.** A controlled pair, same video,
same proxy, 8 items each, minutes apart: with **no cookies, 8/8 transcribed**; with a `cookies.txt`
exported from a logged-in session, **0/8** — every item failed with *"The page needs to be
reloaded."* and the run ended FAILED. The file was accepted by YouTube as a session
(`"LOGGED_IN":true`) but was missing the HttpOnly cookies `__Secure-1PSID`, `__Secure-3PSID`, `HSID`
and `SSID`, because the browser extension used could only read `document.cookie`. A half-authenticated
session breaks the player request outright — worse than sending nothing.

So leave `youtubeCookies` empty unless anonymous access is actually failing for you. If you do use it,
the export must include the HttpOnly cookies; a bad file now returns `COOKIES_REJECTED` naming the
cause instead of a generic download error.

**Q: Which sources work?** A: Anything yt-dlp supports (1800+ sites) plus direct audio-file links. If a network blocks the download, enable RESIDENTIAL proxy.

**Q: How is it billed?** A: **$0.015 per minute** of audio transcribed (rounded up per item), plus the Apify platform usage of the run (compute and proxy, at Apify's standard rates, shown on your run). A 10-minute podcast = $0.15 + usage. Each item is charged right after it is delivered, so a run that stops early bills only what you received.

**Q: What if an audio is longer than the run can handle?** A: The Actor fits the transcription into the remaining run time (and `maxMinutesPerItem`): the transcript is cut, `truncated: true` and `truncatedReason` are set on the record, and only the transcribed minutes are billed. Raise the run timeout (Settings → Timeout, 3600 s recommended for long audio) for full-length transcripts.

**Q: What happens to a URL that cannot be downloaded?** A: You get an error record for that URL (`_dataQuality: "error"`, `errorCode`: `MEDIA_UNAVAILABLE`, `BLOCKED`, `DOWNLOAD_TIMEOUT`, `TRANSCRIPTION_FAILED`, `COOKIES_REJECTED`, `RUN_TIME_BUDGET`) — never charged — and the other URLs in the batch still run.

**Q: What about accuracy?** A: `base` is a good default; switch to `small` for higher accuracy on tough audio.

### 💰 Pricing

**Pay-per-event: $0.015 per minute transcribed + Apify platform usage.** Examples (event price only): 5 min = $0.075 · 30 min = $0.45 · 60 min = $0.90. Platform usage (compute/proxy) is billed separately by Apify at standard rates.

### 🔗 Related actors

- [Video Transcriber](https://apify.com/viralanalyzer/video-transcriber) — transcribe video from any network.
- [YouTube Fast Scraper](https://apify.com/viralanalyzer/youtube-fast-scraper) — metadata + captions.

### 📝 Changelog

- **v1.3.1** (2026-09-02) — a run only fails when something is genuinely broken. A source answering "this is a livestream", "members-only", "still processing" or "log in first" now returns a labelled error item and **succeeds** with nothing charged, instead of failing the run. Real extractor drift still fails, and the fatal yt-dlp line is no longer truncated mid-message.
- **v1.2.0** (2026-08-29) — **Only the audio you pay for is downloaded.** The actor now probes the media first, picks a ≤70 kbps audio stream and, for long sources, fetches only the bytes that cover `maxMinutesPerItem` (or your max charge / remaining run time) — a 19-minute YouTube video capped at 1 minute moves 1.2 MB instead of 17.8 MB. Items cut this way carry `truncated: true`, `truncatedReason` and `sourceDurationSeconds`.
- **v1.1** (2026-08) — per-stage time budgets, `maxMinutesPerItem` enforced, billing right after each item, honest terminal status.
- **v1.0** — local Whisper (faster-whisper) transcription; text + segments + SRT; per-minute pricing.

***

## 🇧🇷 Português

> **$0.015 por minuto transcrito** (+ uso de plataforma Apify) · 🔒 sem chave de API · 🌍 qualquer idioma (detecção automática) · 📝 texto + SRT + timestamps
> [🇺🇸 English](#-audio-transcriber--speech-to-text-whisper-for-podcasts-social--uploads) · 🇧🇷 Português

Transcreva **áudio em texto** com o **Whisper rodando localmente no Actor** (faster-whisper) — **sem API externa, sem chave, sem rate limit**. Passe qualquer URL de mídia — podcast, TikTok, Instagram, YouTube, SoundCloud ou um link direto de arquivo de áudio — e receba **texto, segmentos com timestamps, legendas SRT e idioma detectado**. O áudio é extraído automaticamente também de links de vídeo.

**Para quem é:** podcasters e times de conteúdo, jornalistas, pesquisadores, times de acessibilidade/SEO e desenvolvedores que constroem busca ou resumo sobre áudio falado.

▶️ Clique em **Testar grátis** — sua primeira execução retorna uma transcrição real em segundos.

### ✨ Recursos

- 🎯 **Transcrição Whisper real** (não só legendas) — funciona em áudio sem legendas.
- 🌍 **99+ idiomas**, detectados automaticamente (ou force com `language`).
- 🔗 **Qualquer origem** — redes sociais, hosts de podcast ou link direto de arquivo.
- 📝 **Saída rica** — texto completo, segmentos com tempo, **SRT** pronto, contagem de palavras, idioma.
- 🔒 **Autossuficiente** — o Whisper roda no container; você nunca precisa de chave de API.
- 💸 **Preço justo** — pague por minuto de áudio realmente transcrito.

### 💰 Preço

**Pay-per-event: $0.015 por minuto transcrito + uso de plataforma Apify.** Exemplos (só o evento): 5 min = $0.075 · 30 min = $0.45 · 60 min = $0.90. O uso de plataforma (compute/proxy) é cobrado à parte pela Apify nas tarifas padrão.

### ❓ FAQ

**P: Preciso de chave de API?** R: Não. O Whisper roda dentro do Actor — você só fornece URLs.

**P: Como é cobrado?** R: **$0.015 por minuto** de áudio transcrito (arredondado por item), mais o uso de plataforma Apify do run (compute e proxy, nas tarifas padrão, visível no seu run). Um podcast de 10 min = $0.15 + uso. Cada item é cobrado logo após ser entregue — um run que para antes do fim cobra só o que você recebeu.

**P: E se o áudio for maior do que o run consegue processar?** R: O Actor ajusta a transcrição ao tempo restante do run (e ao `maxMinutesPerItem`): o texto é cortado, o registro recebe `truncated: true` e `truncatedReason`, e só os minutos transcritos são cobrados. Aumente o timeout do run (Settings → Timeout; 3600 s para áudios longos) para transcrições completas.

**Built here — o que o bot-gate do YouTube faz, com as duas medições:** o YouTube responde parte das
requisições com *"Sign in to confirm you're not a bot"*, e faz isso **por IP de saída**, não por
vídeo. A taxa não é estável, e medimos duas vezes no mesmo vídeo sobre RESIDENTIAL da Apify:

| Data | Condição | Resultado |
|---|---|---|
| 2026-08-04 | 8 itens, download completo | 6/8 — os dois barrados eram vídeos longos |
| 2026-08-30 | 8 itens, busca parcial | **8/8**, nenhum gate |

Não tome nenhum dos dois como a taxa que você vai ter. O que vale nos dois é o mecanismo: URL barrada
num IP passa no seguinte. Desde a 1.2.1 o item barrado é retentado em **sessão residencial nova**, até
3 tentativas. Quase não gasta proxy — o gate dispara na sondagem (`yt-dlp --skip-download`), antes de
baixar mídia. Só o gate confirmado é retentado; vídeo removido falha na primeira. Quando todas as
tentativas são barradas você recebe um registro `BLOCKED` dizendo em quantos IPs tentou, e **nada é
cobrado**.

**Não recorra a `youtubeCookies` primeiro — medido em 2026-08-30.** Par controlado, mesmo vídeo, mesmo
proxy, 8 itens cada, minutos de diferença: **sem cookie, 8/8 transcritos**; com um `cookies.txt` de
sessão logada, **0/8** — todos falharam com *"The page needs to be reloaded."* e a execução terminou
FAILED. O arquivo era aceito pelo YouTube como sessão (`"LOGGED_IN":true`), mas faltavam os cookies
HttpOnly `__Secure-1PSID`, `__Secure-3PSID`, `HSID` e `SSID`, porque a extensão usada só lia
`document.cookie`. Sessão pela metade quebra a requisição do player — pior que não mandar nada.

Deixe `youtubeCookies` vazio a menos que o acesso anônimo esteja falhando para você. Se usar, o export
precisa incluir os HttpOnly; arquivo ruim agora devolve `COOKIES_REJECTED` nomeando a causa.

**P: O que acontece com uma URL que não baixa?** R: Você recebe um registro de erro para aquela URL (`_dataQuality: "error"`, `errorCode`: `MEDIA_UNAVAILABLE`, `BLOCKED`, `DOWNLOAD_TIMEOUT`, `TRANSCRIPTION_FAILED`, `COOKIES_REJECTED`, `RUN_TIME_BUDGET`) — nunca cobrado — e as demais URLs do lote continuam.

### 📝 Changelog

- **v1.0** — transcrição local com Whisper (faster-whisper); texto + segmentos + SRT; preço por minuto.

# Actor input Schema

## `mediaUrls` (type: `array`):

Audio/media URLs to transcribe — a podcast/social/video link (audio is extracted automatically) or a direct link to an audio file (mp3, wav, m4a, ...).

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

ISO code (e.g. 'en', 'pt', 'es'). Leave empty to auto-detect.

## `model` (type: `string`):

Accuracy vs. speed. 'small' (default, more accurate), 'base' (faster) or 'tiny' (fastest). Larger models cost a bit more compute time.

## `maxMinutesPerItem` (type: `integer`):

Safety cap on how many minutes of audio to transcribe per URL.

## `youtubeCookies` (type: `string`):

Paste the contents of a Netscape cookies.txt exported from a logged-in YouTube session to reliably download YouTube (avoids the 'Sign in to confirm you're not a bot' gate). Leave empty for other networks / direct files.

## `proxyConfiguration` (type: `object`):

Residential proxy by default — social networks (YouTube/TikTok/Instagram) block datacenter IPs when downloading media. Keep RESIDENTIAL unless your source is a plain audio file that accepts datacenter.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://github.com/openai/whisper/raw/main/tests/jfk.flac"
  ],
  "language": "",
  "model": "small",
  "maxMinutesPerItem": 120,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset with one transcript per input URL (text, segments, SRT, language).

# 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 = {
    "mediaUrls": [
        "https://github.com/openai/whisper/raw/main/tests/jfk.flac"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("viralanalyzer/audio-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 = {
    "mediaUrls": ["https://github.com/openai/whisper/raw/main/tests/jfk.flac"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("viralanalyzer/audio-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 '{
  "mediaUrls": [
    "https://github.com/openai/whisper/raw/main/tests/jfk.flac"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call viralanalyzer/audio-transcriber --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,viralanalyzer/audio-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/P8lwEoQhyndvKVTxi/builds/uWeOtDEGnWLV6mqMR/openapi.json
