# Video/Audio Transcript + SRT — Bilibili, TED, Podcasts (`960w/video-to-transcript`) Actor

Turn public Mandarin & English videos or podcast audio into timestamped JSON, plain text, and .srt files. Captions-first; CPU faster-whisper fallback (no OpenAI key). Supports Bilibili, TED, Vimeo, direct MP3. YouTube & Douyin/TikTok NOT supported — fail fast ≤2 min, no charge on failures.

- **URL**: https://apify.com/960w/video-to-transcript.md
- **Developed by:** [TG960 T](https://apify.com/960w) (community)
- **Categories:** AI, Videos
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 captions transcribeds

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

## Chinese & English Video/Audio to Transcript & SRT

Turn public Chinese (Mandarin) and English videos or podcast audio files into timestamped JSON segments, clean plain-text transcripts, and ready-to-download `.srt` subtitle files—automatically extracting existing captions when available and falling back to built-in CPU `faster-whisper` transcription with no external API key required.

***

### 1. What It Does

Pass a list of public video or audio URLs and get back:

- **Timestamped `segments`** (`[{start, end, text}]`) for search indexing, AI summarization, or subtitle alignment.
- **Full plain-text transcript (`text`)** ready for LLM pipelines or content analysis.
- **Standard SubRip (`.srt`) subtitles** both inline in the Dataset and saved as individual `.srt` files in the default Key-Value Store.

It uses a two-stage hybrid pipeline to keep your costs low:

1. **Captions Path (`source: "captions"`)**: Checks metadata first and extracts existing manual or auto-generated subtitles in seconds without downloading media.
2. **Local Whisper Fallback (`source: "whisper"`)**: When no captions exist (such as direct podcast `.mp3` files or uncaptioned Bilibili videos), downloads only the audio stream and transcribes it on CPU using `faster-whisper` (`compute_type="int8"`, `vad_filter=True`). You do not need to bring an OpenAI API key.

***

### 2. Supported Platforms (Cloud-Verified)

Every platform listed below has been tested and verified on Apify Cloud without cookies or account logins:

| Platform | URL Pattern | How It Works on Apify Cloud | Verified Languages |
| :--- | :--- | :--- | :--- |
| **Bilibili (B站公开视频)** | `https://www.bilibili.com/video/BV...` | Resolves metadata and audio via Bilibili public player API and transcribes Mandarin speech via local `faster-whisper` (`source: "whisper"`), or extracts public JSON subtitles when exposed without login. | Chinese (Mandarin `zh`) |
| **Direct Audio & Podcasts** | `https://.../*.mp3` (e.g. Archive.org, podcast RSS audio CDNs, `.m4a`, `.wav`) | Downloads the audio stream directly, enforces `maxDurationMinutes` truncation, and transcribes speech via `faster-whisper` (`source: "whisper"`). | Chinese (`zh`), English (`en`), and 90+ languages |
| **TED Talks** | `https://www.ted.com/talks/...` | Extracts official human-translated WebVTT subtitles directly (`source: "captions"`) in ~1–2 seconds per talk. | English (`en`), Chinese (`zh`), and 40+ subtitle languages |
| **Vimeo (Public Videos)** | `https://vimeo.com/<id>` or `https://player.vimeo.com/video/<id>` | Extracts existing VTT captions directly (`source: "captions"`), or falls back to `faster-whisper` audio transcription when uncaptioned. | English (`en`), Spanish, German, French, Japanese, Korean, etc. |

***

### 3. Input Example

```json
{
  "videoUrls": [
    "https://www.bilibili.com/video/BV1zUnZ67EMY",
    "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3",
    "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
    "https://vimeo.com/1064773380"
  ],
  "languageHint": "auto",
  "useWhisperFallback": true,
  "whisperModel": "small",
  "maxDurationMinutes": 60,
  "maxVideos": 20
}
```

#### Input Parameters

| Field | Type | Required | Default | Description |
| :--- | :--- | :---: | :---: | :--- |
| `videoUrls` | `array<string>` | Yes | — | Publicly accessible URLs from Bilibili (`bilibili.com/video/BV...`), direct audio links (`.mp3`/`.m4a`/`.wav`), TED Talks (`ted.com/talks/...`), or public Vimeo videos (`vimeo.com/...`). |
| `languageHint` | `string` | No | `"auto"` | Preferred language code (`"zh"`, `"en"`, `"ja"`, etc.) or `"auto"` for automatic detection. |
| `useWhisperFallback` | `boolean` | No | `true` | When `true`, downloads audio and transcribes locally with `faster-whisper` if no captions exist. |
| `whisperModel` | `string` | No | `"small"` | Model size for CPU `int8` transcription (`"base"`, `"small"`, or `"medium"`). |
| `maxDurationMinutes` | `integer` | No | `60` | Maximum duration per item in minutes. Videos with metadata duration above this limit are skipped (`status: "skipped"`), and direct audio files are truncated at this limit. |
| `maxVideos` | `integer` | No | `20` | Maximum number of URLs processed in one run. |

***

### 4. Output Examples (Real Apify Cloud Outputs)

#### Example A — Chinese Audio Direct Link (`source: "whisper"`, Archive.org Mandarin Audiobook)

**Dataset JSON Record (Excerpt from real cloud run `luw3C2TgcZvDlKLOn`):**

```json
{
  "url": "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3",
  "title": "calltoarms_06_lu",
  "durationSec": 262.88,
  "source": "whisper",
  "language": "zh",
  "segments": [
    {
      "start": 0.08,
      "end": 2.64,
      "text": "鲁迅小说集,呐喊"
    },
    {
      "start": 2.9,
      "end": 4.44,
      "text": "一件小事"
    },
    {
      "start": 4.68,
      "end": 7.5,
      "text": "此次 LibriVox 录音由公众所有"
    },
    {
      "start": 9.04,
      "end": 13.4,
      "text": "我从乡下跑到京城里,一转眼已经六年了"
    },
    {
      "start": 13.64,
      "end": 18.52,
      "text": "期间耳闻目睹的所谓国家大事算起来也很不少"
    }
  ],
  "text": "鲁迅小说集,呐喊 一件小事 此次 LibriVox 录音由公众所有 我从乡下跑到京城里,一转眼已经六年了 期间耳闻目睹的所谓国家大事算起来也很不少 ...",
  "srt": "1\n00:00:00,080 --> 00:00:02,640\n鲁迅小说集,呐喊\n\n2\n00:00:02,900 --> 00:00:04,440\n一件小事\n\n3\n00:00:04,680 --> 00:00:07,500\n此次 LibriVox 录音由公众所有\n",
  "srtKey": "SRT_001_calltoarms_06_lu.srt",
  "status": "succeeded",
  "error": null
}
```

**Saved `.srt` File in Key-Value Store (`SRT_001_calltoarms_06_lu.srt`):**

```srt
1
00:00:00,080 --> 00:00:02,640
鲁迅小说集,呐喊

2
00:00:02,900 --> 00:00:04,440
一件小事

3
00:00:04,680 --> 00:00:07,500
此次 LibriVox 录音由公众所有

4
00:00:09,040 --> 00:00:13,400
我从乡下跑到京城里,一转眼已经六年了

5
00:00:13,640 --> 00:00:18,520
期间耳闻目睹的所谓国家大事算起来也很不少
```

***

#### Example B — Bilibili Public Mandarin Video (`source: "whisper"`, Real Cloud Run `NkJl91DvzPCxh591Y`)

```json
{
  "url": "https://www.bilibili.com/video/BV1zUnZ67EMY",
  "title": "陕视评：判决未至，定论先入教材 武大图书馆事件反思：公共出版物不能越司法“断案” （策划：李彤 任超 评论员：刁坤 摄像：姬佩瑶）",
  "durationSec": 176,
  "source": "whisper",
  "language": "zh",
  "segments": [
    {
      "start": 0.02,
      "end": 2.32,
      "text": "一本教大学生心理健康的小采"
    },
    {
      "start": 2.32,
      "end": 4.62,
      "text": "凭什么让一个法院认定清白的男生"
    },
    {
      "start": 4.62,
      "end": 7.42,
      "text": "在白纸黑字上继续当性骚扰犯呢"
    },
    {
      "start": 7.42,
      "end": 9.32,
      "text": "2023年武汉大学图书馆内"
    }
  ],
  "srtKey": "SRT_001_BV1zUnZ67EMY.srt",
  "status": "succeeded",
  "error": null
}
```

***

#### Example C — TED Talk / Vimeo Captions Extraction (`source: "captions"`, Real Cloud Run `pJBxyhsCk1XEPUI1I`)

```json
{
  "url": "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
  "title": "Try something new for 30 days",
  "durationSec": 190,
  "source": "captions",
  "language": "en",
  "segments": [
    {
      "start": 0,
      "end": 4.976,
      "text": "A few years ago, I felt like I was stuck in a rut,"
    },
    {
      "start": 5,
      "end": 6.976,
      "text": "so I decided to follow in the footsteps"
    },
    {
      "start": 7,
      "end": 10.536,
      "text": "of the great American philosopher, Morgan Spurlock,"
    }
  ],
  "srtKey": "SRT_001_1183.srt",
  "status": "succeeded",
  "error": null
}
```

***

### 5. Pricing & Cost Examples (Pay-Per-Event)

You only pay for succeeded items based on which path was used:

| Event Name | When It Is Charged | Price |
| :--- | :--- | :--- |
| **`captions-transcribed`** | Charged per video transcribed using existing platform captions or subtitles (`source: "captions"`) | **$0.002 / video** ($2.00 per 1,000 captioned videos) |
| **`whisper-transcribed-minute`** | Charged per started minute of audio (`ceil(durationSec / 60)`) transcribed locally via `faster-whisper` (`source: "whisper"`) | **$0.025 / minute** |

#### Concrete Cost Examples

- **10 TED Talks or Vimeo videos with existing captions**: `10 × $0.002` = **$0.020 total** (completes in ~10 seconds).
- **A 3-minute Bilibili Mandarin video without public captions**: `3 minutes × $0.025` = **$0.075**.
- **A 5-minute Chinese audiobook / podcast MP3**: `5 minutes × $0.025` = **$0.125**.
- **A 30-minute Chinese podcast episode (MP3)**: `30 minutes × $0.025` = **about $0.75**.
- **Failed or skipped URLs**: **$0.00** (never charged).

***

### 6. Supported vs Unsupported Links

#### Supported (cloud-verified)

| Link type | Example | Expected path | Notes |
| :--- | :--- | :--- | :--- |
| **Bilibili public BV** | `https://www.bilibili.com/video/BV...` | captions (rare) or whisper | Public API audio download; no cookies |
| **Direct audio / podcast** | `https://.../*.mp3` (Archive.org, RSS CDN, `.m4a`, `.wav`) | whisper | Truncated at `maxDurationMinutes` |
| **TED Talks** | `https://www.ted.com/talks/...` | captions | Official WebVTT / full.vtt |
| **Public Vimeo** | `https://vimeo.com/<id>` | captions or whisper | Rewritten to `player.vimeo.com` |

#### NOT supported (fail-fast ≤ 2 minutes, **no charge**)

| Link type | Why | Actor behavior |
| :--- | :--- | :--- |
| **YouTube** (with or without captions) | Datacenter IP blocked (`Sign in to confirm you're not a bot`) | Immediate clear `status=failed` error — never hangs to TIMED-OUT |
| **Douyin / TikTok share links** | Login wall / anti-bot; Actor never uses cookies or paid proxies | Immediate clear `status=failed` error |
| Private / VIP / password / DRM | No login, no cookies | `failed` or `skipped` with reason |

#### Other limits

- **Public Content Only (No Login / No Cookies)**: Does not support private videos, login-walled pages, VIP/paid Bilibili episodes, password-protected Vimeo videos, or DRM-protected streams.
- **Bilibili Unauthenticated Subtitle Restriction**: Bilibili requires account login to access AI-generated closed captions on most videos. Since this Actor never logs in or uses cookies, Bilibili videos usually take the `whisper` audio transcription path.
- **CPU Transcription Speed / 10-minute SLA**: Whisper runs on Apify Cloud CPU (`faster-whisper` `int8`, `vad_filter=True`, `beam_size=1`). For audio longer than ~12 minutes the Actor auto-selects `base`, and above ~25 minutes it selects `tiny`, so ≤60-minute items can finish within ~10 minutes. Multi-hour files are ffmpeg-cut to `maxDurationMinutes` before decode to avoid OOM. Caption paths usually finish in seconds.
- **Fail-fast timeouts**: Metadata / captions / download / whisper each have hard asyncio deadlines; unsupported hosts never call yt-dlp. Per-URL wall clock is capped at 10 minutes.
- **Mandarin Homophones in Fast Speech**: For rapid news commentary or domain-specific proper nouns without context, `whisperModel="small"` may occasionally produce same-sound character substitutions (e.g., `统战` → `统政`, `小册` → `小采`). You can select `whisperModel="medium"` for higher Mandarin accuracy on complex audio (may miss the 10-minute SLA on long clips).
- **Memory & Duration Limits**: Requires `2048 MB–4096 MB` RAM. Audio exceeding `maxDurationMinutes` (default `60` minutes) is automatically skipped or truncated at `maxDurationMinutes`.
- **Billing**: `Actor.charge` runs **only** when `status == "succeeded"`. Failed / skipped / timed-out-internally items cost **$0** PPE.

***

### 7. Frequently Asked Questions (FAQ)

#### Do I need an OpenAI API key?

No. Transcription runs locally inside the Actor container using `faster-whisper` (`MIT` license). No external API key is needed.

#### Why is YouTube not supported?

YouTube actively blocks requests from cloud datacenter IP addresses unless authenticated cookies or residential proxies are supplied. To keep pricing predictable and compliant, this Actor focuses on Bilibili, podcast/audio direct links, TED Talks, and public Vimeo videos. YouTube URLs fail immediately with a clear error (no TIMED-OUT hang, no PPE charge).

#### Are Douyin / TikTok links supported?

No. Douyin and TikTok share links require login or anti-bot challenges. They fail within 2 minutes with a clear unsupported-platform message and are never charged.

#### How can I cap my maximum spend for a run?

Set the **Maximum cost per run (`ACTOR_MAX_TOTAL_CHARGE_USD`)** in Apify Console or via the API. The Actor checks your remaining budget before and after each item and stops gracefully as soon as the limit is reached.

#### Where do I download the `.srt` subtitle files?

Every succeeded item includes the full `.srt` text inside the Dataset JSON (`srt` field) and also saves a standalone `.srt` file (such as `SRT_001_BV1zUnZ67EMY.srt`) in the run's **Default Key-Value Store**.

#### What open-source licenses are used?

- `apify` Python SDK: Apache-2.0
- `yt-dlp`: The Unlicense
- `faster-whisper`: MIT License
- `ffmpeg` (system package installed via `apt`): License (LGPL v2.1+ or GPL v2+/v3) depends on the distribution's compile-time configuration.
- Users are responsible for complying with the Terms of Service of the target platforms they query.

# Actor input Schema

## `videoUrls` (type: `array`):

List of publicly accessible video or audio URLs to transcribe. Supported: Bilibili BV, direct MP3/podcast audio, TED Talks, public Vimeo. NOT supported (fail-fast, no charge): YouTube, Douyin/TikTok share links, private/VIP/login-walled content.

## `languageHint` (type: `string`):

Preferred ISO language code (e.g. 'zh', 'zh-TW', 'zh-Hans', 'en') or 'auto' for automatic selection/detection.

## `useWhisperFallback` (type: `boolean`):

If true, downloads audio and transcribes locally on CPU with faster-whisper when no subtitles/captions exist.

## `whisperModel` (type: `string`):

faster-whisper model size to run on CPU (int8) when falling back to audio transcription.

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

Maximum allowed duration per item in minutes. Metadata durations above this limit are skipped, and direct audio streams are truncated at this limit.

## `maxVideos` (type: `integer`):

Maximum number of URLs to process in a single Actor run.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
    "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3"
  ],
  "languageHint": "auto",
  "useWhisperFallback": true,
  "whisperModel": "small",
  "maxDurationMinutes": 60,
  "maxVideos": 20
}
```

# Actor output Schema

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

Timestamped segments, plain text, and SRT strings for all processed URLs.

## `srtFiles` (type: `string`):

Default Key-Value Store containing standalone .srt subtitle files.

# 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 = {
    "videoUrls": [
        "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
        "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("960w/video-to-transcript").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 = { "videoUrls": [
        "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
        "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("960w/video-to-transcript").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 '{
  "videoUrls": [
    "https://www.ted.com/talks/matt_cutts_try_something_new_for_30_days",
    "https://archive.org/download/call_to_arms_jl_librivox/calltoarms_06_lu.mp3"
  ]
}' |
apify call 960w/video-to-transcript --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,960w/video-to-transcript"
        }
    }
}
```

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/1pkm7IRhs7dzqbdN4/builds/EEXRax3ZMPT58GFq6/openapi.json
