# Audio & Video Transcriber: Speech to Text with Timestamps (`frameprobe/audio-video-transcriber`) Actor

Transcribe audio and video files to text with timestamps. Paste direct MP3, MP4, M4A, WAV or WEBM links and get the full transcript, timed segments, spoken language and duration as clean JSON. Whisper runs inside the Actor: no API key, no signup, nothing to install.

- **URL**: https://apify.com/frameprobe/audio-video-transcriber.md
- **Developed by:** [FrameProbe](https://apify.com/frameprobe) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Audio & Video Transcriber: speech to text with timestamps

Transcribe audio and video files to text. Give it direct links to MP3, MP4, M4A, WAV, WEBM or MOV
files and get one row per file: the full transcript, timed segments, the spoken language and the
duration, as clean JSON. Whisper runs inside the Actor, so there is no API key to bring, no account to
create and nothing to install.

- Podcast and interview transcripts
- Subtitles from the timed segments
- Searchable text from a folder of recorded calls or lectures
- A transcription step inside a pipeline: one URL in, one row out, the same keys every time

### Input

| Field | Required | What it does |
|---|---|---|
| `mediaUrls` | yes | Direct https links to audio or video files |
| `language` | no | A code such as `en`, `es` or `de`. Leave it empty to detect the language |
| `maxFiles` | no | The most files one run transcribes. Default 10, up to 100 |

`mediaUrls` also answers to `urls`, `url`, `mediaUrl`, `videoUrls`, `audioUrls` and `startUrls`, and
takes a single string as well as a list.

```json
{ "mediaUrls": ["https://example.com/interview.mp3"] }
```

### Output

One row per file, with the same keys on every row, including the files that failed.

```json
{
  "source": "https://example.com/interview.mp3",
  "status": "transcribed",
  "reason": null,
  "durationSeconds": 184.32,
  "language": "en",
  "text": "Welcome back to the show. Today we are talking about pricing.",
  "segments": [
    { "startSeconds": 0.0, "endSeconds": 2.4, "text": "Welcome back to the show." },
    { "startSeconds": 2.4, "endSeconds": 5.1, "text": "Today we are talking about pricing." }
  ],
  "wordCount": 12,
  "model": "base"
}
```

A file that could not be transcribed has `status` set to `failed`, `no_audio` or `no_speech`, a
`reason` from a fixed list your pipeline can branch on, and an `error` sentence saying what happened.

### Cost

| Event | Price |
|---|---|
| Actor start | $0.01 per run |
| Minute of audio transcribed | $0.009 per minute, rounded up, minimum one per file |

A 40-second voice note costs $0.019 in a run of its own. Ten 3-minute interviews in one run cost
$0.28. A single 10-minute file costs $0.10. A one-minute file took about 15 seconds end to end on
the platform.

**Not charged:** files that fail to download, have no audio track, or are refused. They still get a
row with the reason. A file with no spoken words **is** charged, because the model listened to all
of it.

**Set a maximum charge and the run stays inside it.** Each file's length is read before any audio is
decoded. A file that does not fit what is left gets a `skipped` row saying so, is not charged, and
the run stops there.

### What it does not do

- **Page links are not files.** A YouTube, TikTok or Instagram page link comes back as a failed row.
  Run a scraper that returns the media file URL, and pass that.
- **Up to 200 MB and 10 minutes per file.** A longer file is refused with a reason, not cut short.
- **Some hosts refuse cloud servers.** A link that plays in your browser but fails here with
  `download-403` is usually the host blocking datacenter addresses.
- **No speaker labels and no translation.** One transcript, in the language spoken.
- **Accuracy is Whisper `base`'s, and it has not been measured on this Actor yet.**

### Security

Only public `https://` links are fetched. A link to a private or internal address is refused before
anything is downloaded, on the first request and again on every redirect. One gap remains: a host
name can give a public address when we check it and a private one when we connect (DNS rebinding).
Nothing a file contains can make the Actor fetch anything else: ffmpeg reads local files only.

### For developers: the package inside

The Actor wraps `transcript_core`, a package that knows nothing about Apify and charges nothing
(`tests/test_isolation.py` fails if it ever does).

```python
from pathlib import Path
from transcript_core import FasterWhisperBackend, transcribe

row = transcribe("https://cdn.example/clip.mp4", kind="url",
                 backend=FasterWhisperBackend("base"),
                 allow=lambda seconds: True if seconds <= 120 else "over this run's limit",
                 temp_root=Path("/tmp"))
```

1. **Every failure is a row with a reason.** `REASONS` maps each reason to one status. A caller bug (a
   `kind` that is not `url` or `path`) raises instead.
2. **The duration is read before any work.** `allow(seconds)` runs after the header probe and before
   the audio is decoded or the model is called. Only a literal `True` proceeds.
3. **No charge call in the core.** The row carries `status`, `durationSeconds` and `audioSeconds`,
   which is what a charge needs.
4. **One backend seam** (`backend.py`). `FasterWhisperBackend` is the only one implemented: CPU,
   int8, model from local disk only. The Dockerfile bakes the model into the image.

`kind` is never guessed from the string, and the Actor always passes `kind="url"`, so a buyer's text
that looks like a path is sent through the SSRF guard as a URL. The hardened download is a copy of
reel-teardown's and is held to it by `../tests/test_core_drift.py`.

**Not known yet:** whether faster-whisper runs fast enough on one core to price (A2 in
`PLAN-transcript-actors-2026-09-12.md`), transcript quality, and the decode and model timeouts in
`CoreCaps`, which are labelled guesses.

```
python -m pytest -q -p no:cacheprovider transcript-core/tests
```

Hermetic: an autouse fixture refuses every connection and hostname lookup. Needs ffmpeg and ffprobe
on PATH.

# Actor input Schema

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

Direct links to audio or video files (MP3, MP4, M4A, WAV, WEBM, MOV and similar), one per line. Each file gets one row: the full transcript, timed segments and the spoken language. Links must start with https://. A YouTube, TikTok or Instagram page link is a web page, not a file, and comes back as a failed row with the reason. Up to 200 MB and 10 minutes per file.

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

Leave empty to detect the spoken language automatically. If you already know it, set a code such as en, es, de, fr, pt or ja and detection is skipped.

## `maxFiles` (type: `integer`):

Hard stop on how many files one run transcribes, so a long list cannot run for longer than you expect. Files past the limit are left for another run.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://api.apify.com/v2/key-value-stores/Bfr82R8zp35dJRkcL/records/transcriber-autotest-speech.mp3"
  ],
  "maxFiles": 10
}
```

# Actor output Schema

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

One row per input file, including the ones that failed. Successful rows carry the transcript as one string and as timed segments (start and end in seconds), the word count, the detected language and its probability, and the duration read from the file. Failed rows carry the reason and what to do about it.

# 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://api.apify.com/v2/key-value-stores/Bfr82R8zp35dJRkcL/records/transcriber-autotest-speech.mp3"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("frameprobe/audio-video-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://api.apify.com/v2/key-value-stores/Bfr82R8zp35dJRkcL/records/transcriber-autotest-speech.mp3"] }

# Run the Actor and wait for it to finish
run = client.actor("frameprobe/audio-video-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://api.apify.com/v2/key-value-stores/Bfr82R8zp35dJRkcL/records/transcriber-autotest-speech.mp3"
  ]
}' |
apify call frameprobe/audio-video-transcriber --silent --output-dataset

```

## MCP server setup

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