# Subtitle Generator - SRT/VTT (Whisper, bilingual, CJK-aware) (`mocking_bird/whisper-subtitles`) Actor

Turn audio or video files into clean SRT/VTT subtitles with Whisper on CPU. Readable cue splitting (line length, CJK-aware), word timestamps, English translation and bilingual subtitles. No API key needed.

- **URL**: https://apify.com/mocking_bird/whisper-subtitles.md
- **Developed by:** [Aoi](https://apify.com/mocking_bird) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 transcribed minutes

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?

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

## Subtitle Generator - SRT / VTT from audio & video (Whisper)

Turn any audio or video file into **clean, readable subtitles** with OpenAI's Whisper running on CPU inside Apify.
No API key, no sign-up with a third party, no file ever leaves the Apify platform.

What makes this one different from a plain transcriber:

- **Broadcast-style cues.** Subtitles are rebuilt from word timestamps into cues that respect a maximum line
  length, a maximum number of lines and a maximum duration, and break at sentence ends and pauses, instead of
  dumping Whisper's raw segments.
- **CJK-aware.** Japanese, Chinese and Korean have no spaces; wide characters are counted properly, lines break
  after punctuation, and cues follow Whisper's phrase boundaries. `maxCharsPerLine: 32` gives tidy 16-character
  Japanese lines for vertical video.
- **Bilingual subtitles.** One click adds an English translation under every original line - for language
  learning content, travel vlogs and interviews.
- **Word-level timestamps** on request, for karaoke-style or word-highlight captions.
- **Whisper large-v3-turbo on the Accurate tier**: near large-v3 accuracy at a fraction of its cost. Japanese narration comes out with correct proper nouns and punctuation (a punctuation prompt is applied automatically for Japanese).
- **Three quality levels** (small greedy / small / large-v3-turbo) so you can trade speed for accuracy per job.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mediaUrls` | array | - | Direct links to mp3, wav, m4a, ogg, flac, mp4, mov, webm, mkv... (anything FFmpeg decodes). YouTube / TikTok page links are **not** supported; pass a direct file URL. |
| `language` | string | auto | ISO 639-1 code (`en`, `ja`, `es`...). Empty = auto-detect. |
| `quality` | enum | `balanced` | `fast` (small, greedy), `balanced` (small, default), `accurate` (large-v3-turbo, ~3x slower, best for Japanese/Chinese/accents). |
| `translateToEnglish` | boolean | false | Adds English transcript + English SRT/VTT (second pass). |
| `bilingualSubtitles` | boolean | false | Extra SRT with original line + English line in every cue. |
| `maxCharsPerLine` | integer | 42 | Wide (CJK) characters count double. 32 for mobile/vertical. |
| `maxLinesPerCue` | integer | 2 | |
| `maxCueDurationSec` | integer | 6 | |
| `wordTimestamps` | boolean | false | Adds a `words` array per file. |
| `vocabularyHint` | string | - | Names and jargon, comma separated. |
| `vadFilter` | boolean | true | Skip silence; avoids hallucinated text. |
| `saveFilesToKeyValueStore` | boolean | true | Store `.srt`, `.vtt`, `.txt` files and return their URLs. |
| `maxDurationMinutes` / `maxFileSizeMb` / `downloadTimeoutSec` | integer | 180 / 500 / 120 | Safety limits; rejected files are not charged. |

#### Example

```json
{
  "mediaUrls": ["https://example.com/interview.mp4"],
  "language": "ja",
  "quality": "accurate",
  "bilingualSubtitles": true,
  "maxCharsPerLine": 32
}
```

### Output

One dataset record per file:

| Field | Description |
|---|---|
| `url`, `fileName`, `ok`, `error` | Source and status. Failed files get `ok: false` and an `error` message. |
| `language`, `languageProbability` | Detected language. |
| `durationSeconds`, `tookMs` | Media length and processing time. |
| `text` | Full transcript. |
| `segments` | Whisper's raw segments (`start`, `end`, `text`). |
| `cues` | The subtitle cues after re-flow (`start`, `end`, `text`). |
| `srt`, `vtt` | Subtitle files as text. |
| `srtUrl`, `vttUrl`, `txtUrl` | Download links (key-value store). |
| `words` | Word-level timestamps (when `wordTimestamps` is on). |
| `translationText`, `translationSrt`, `translationVtt`, `translationSrtUrl` | English translation (when requested). |
| `bilingualSrt`, `bilingualSrtUrl` | Original + English cues (when requested). |

### Pricing

You pay per started minute of audio that was transcribed successfully. `accurate` quality costs more per minute,
and translation / bilingual output adds a second per-minute charge. Downloads that fail, files above the size or
duration limits and undecodable inputs are **not** charged. Platform usage is included.

### Speed and limits

- `fast` runs at roughly 3x realtime on the default 4 GB memory (a 30-minute podcast in about 10 minutes); `balanced` about 1.5x; `accurate` about 1x realtime. Give long files more memory (8 GB = 2 CPUs) to finish sooner at the same price.
- English translation uses Whisper medium (large-v3-turbo cannot translate), so translated runs take about twice as long.
- Timestamps are word-level for Latin scripts and character-level for CJK scripts.
- No speaker diarization. Overlapping speech and heavily compressed phone audio reduce accuracy.
- Links must be public. Signed URLs that expire in seconds may fail to download.

### Tips

- For vertical videos (Shorts, Reels, TikTok) use `maxCharsPerLine: 32` and `maxLinesPerCue: 2`.
- Put proper nouns in `vocabularyHint`; Whisper spells them far better with a hint.
- For Japanese use `quality: "accurate"`, set `language: "ja"` explicitly and put names in `vocabularyHint`.
- Chain this Actor with a video downloader Actor if your source is a YouTube or TikTok page.

### Support

Open an issue on the **Issues** tab with the media URL (or its format and length) and the input you used.

# Actor input Schema

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

Direct links to audio or video files (mp3, wav, m4a, ogg, flac, mp4, mov, webm, mkv...). One subtitle set per file. Video pages such as YouTube links are not supported - use a direct file link.

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

ISO 639-1 code such as <code>en</code>, <code>ja</code>, <code>es</code>. Leave empty to auto-detect.

## `quality` (type: `string`):

<b>Balanced</b> (Whisper small, beam search) suits clean English and most podcasts. <b>Fast</b> is the same model with greedy decoding: quickest and cheapest. <b>Accurate</b> (Whisper large-v3-turbo) is about 3x slower and costs more per minute, but is far better for Japanese, Chinese, accents, names and noisy audio.

## `translateToEnglish` (type: `boolean`):

Adds an English transcript and English SRT/VTT (second pass, charged per minute).

## `bilingualSubtitles` (type: `boolean`):

Produces an extra SRT where every cue shows the original line followed by its English translation. Great for language learning and travel videos. Implies translation.

## `maxCharsPerLine` (type: `integer`):

Line width of a subtitle. 42 is the broadcast standard for Latin scripts; wide (CJK) characters count double, so 42 means about 21 Japanese characters. Use 32 for vertical / mobile video.

## `maxLinesPerCue` (type: `integer`):

How many lines one subtitle may have. 2 is standard.

## `maxCueDurationSec` (type: `integer`):

Cues longer than this are split, even mid-sentence.

## `wordTimestamps` (type: `boolean`):

Adds a <code>words</code> array (start, end, text) to each record - for karaoke-style captions or word highlighting.

## `vocabularyHint` (type: `string`):

Names, brands or jargon that appear in the audio, comma separated. Helps spelling.

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

Removes long silences before transcription. Faster and avoids hallucinated text in quiet parts.

## `saveFilesToKeyValueStore` (type: `boolean`):

Stores downloadable files in the run's key-value store and adds their URLs to each record.

## `maxDurationMinutes` (type: `integer`):

Files longer than this are rejected before transcription (not charged).

## `maxFileSizeMb` (type: `integer`):

Downloads are aborted above this size (not charged).

## `downloadTimeoutSec` (type: `integer`):

Raise it for very large files on slow hosts.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://github.com/openai/whisper/raw/main/tests/jfk.flac"
  ],
  "language": "",
  "quality": "balanced",
  "translateToEnglish": false,
  "bilingualSubtitles": false,
  "maxCharsPerLine": 42,
  "maxLinesPerCue": 2,
  "maxCueDurationSec": 6,
  "wordTimestamps": false,
  "vadFilter": true,
  "saveFilesToKeyValueStore": true,
  "maxDurationMinutes": 180,
  "maxFileSizeMb": 500,
  "downloadTimeoutSec": 120
}
```

# Actor output Schema

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

No description

# 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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mocking_bird/whisper-subtitles").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"] }

# Run the Actor and wait for it to finish
run = client.actor("mocking_bird/whisper-subtitles").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"
  ]
}' |
apify call mocking_bird/whisper-subtitles --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mocking_bird/whisper-subtitles"
        }
    }
}
```

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/eWQPxwl1q6jG2aFJx/builds/EB3eqeXjVTp526cg8/openapi.json
