# X Video Subtitle & Caption Transcript Converter (`nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter`) Actor

Convert public X (Twitter) videos into timestamped transcripts and downloadable SRT/VTT subtitle files, using native captions when available and AI speech-to-text otherwise.

- **URL**: https://apify.com/nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter.md
- **Developed by:** [Nimbryxo-studio](https://apify.com/nimbryxo-studio) (community)
- **Categories:** Social media, Videos, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## X Video Subtitle & Caption Transcript Converter

Public X (Twitter) videos either already carry captions or need speech-to-text to become searchable text. This actor turns either case into a clean, timestamped transcript — reusing the video's own captions when they exist, and running AI speech-to-text when they don't — and can hand back ready-to-use SRT or WebVTT subtitle files, not just raw text. No login or cookies are needed.

### What this actor does

- **Captions first:** uses the video's own captions when the uploader or X provides them
- **Whisper AI fallback:** automatically transcribes videos without captions, in any language
- **Three granularities:** raw segments, sentences, or real per-word timings when available
- **Five output formats:** timestamped segments, full text, both, SRT or WebVTT — with SRT/VTT files saved for download
- **Explicit outcomes:** every post produces a row; posts with no transcript get `transcriptAvailable: false` and a clear message
- **Empty fields are omitted**

### Output per transcript

Each row has a `type`: `video transcript` when a transcript was produced, otherwise `status`.

| Field | Description |
|---|---|
| `tweetUrl`, `tweetId` | Post URL and ID |
| `authorUsername`, `authorName`, `authorFollowers` | Post author details (when Include post details is on) |
| `tweetText`, `publishedAt` | Post text and publish time (when Include post details is on) |
| `engagementRate` | Derived engagement in percent (when Include post details is on) |
| `language` | Detected or requested language code |
| `transcriptionMethod` | `captions`, `ai` or `none` |
| `transcriptAvailable` | `true` when a transcript was produced |
| `modelUsed` | Whisper model actually used for AI transcripts |
| `modelNote` | Explanation when a smaller model was substituted for memory reasons |
| `segmentGranularity` | `segment`, `sentence` or `word` — granularity actually applied |
| `granularityRequested` | Present when the requested granularity could not be applied |
| `granularityNote` | Explanation when a granularity fell back to raw segments |
| `segmentCount` | Number of timed segments |
| `segments` | `[{index, start, end, text}]`; includes real `words[{word, start, end}]` timings when available |
| `fullTranscript` | Joined transcript text |
| `srt`, `srtUrl` | SRT subtitle text and a signed download link for the `.srt` file |
| `vtt`, `vttUrl` | WebVTT subtitle text and a signed download link for the `.vtt` file |
| `message` | Explanation when no transcript could be produced |
| `collectedAt` | UTC timestamp |

Unavailable transcripts are `status` rows with `transcriptAvailable: false`, `transcriptionMethod: "none"` and a `message` explaining the reason. When the failure happens while resolving a profile input (instead of a post), the row carries `profileUrl` (and `profileUsername`, when known) instead of `tweetUrl`/`tweetId`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `postUrls` | array | – | Required. Post links (`x.com` or `twitter.com`) that contain a video or GIF. Profile links or usernames are accepted too — the actor transcribes the profile's latest video post |
| `profileUrls` | array | – | Additional profiles to transcribe (latest video post each) |
| `transcriptionMethod` | string | `auto` | `auto` (captions first, AI fallback), `captions` (skip videos without captions), `ai` (always Whisper) |
| `whisperModel` | string | `base` | `tiny`, `base`, `small`, `medium`, `large-v2` |
| `language` | string | – | Optional spoken-language hint for AI (e.g. `en`, `es`, `ur`); ignored for native captions |
| `outputFormat` | string | `segments` | `segments`, `fullText`, `both`, `srt`, `vtt` |
| `segmentGranularity` | string | `segment` | `segment` (raw cues), `sentence`, `word` (only when real word timings exist) |
| `includeWordTimestamps` | boolean | `false` | Add real per-word start/end times to AI segments |
| `includeTweetMetadata` | boolean | `true` | Attach author, post text and publish date to each row |
| `proxyConfiguration` | object | – | Optional, for large or repeated runs |

#### Example: captions first, AI fallback

```json
{
  "postUrls": ["https://x.com/NASA/status/2107095768821576019"],
  "transcriptionMethod": "auto",
  "outputFormat": "both"
}
```

#### Example: always use AI with an English hint

```json
{
  "postUrls": [
    "https://x.com/elonmusk/status/1519480761749016577",
    "NASA"
  ],
  "transcriptionMethod": "ai",
  "whisperModel": "small",
  "language": "en"
}
```

#### Example: SRT subtitle file

```json
{
  "postUrls": ["https://x.com/NASA/status/2107095768821576019"],
  "outputFormat": "srt",
  "segmentGranularity": "sentence"
}
```

#### Example: word-level timestamps

```json
{
  "postUrls": ["https://x.com/NASA/status/2107095768821576019"],
  "transcriptionMethod": "ai",
  "segmentGranularity": "word",
  "includeWordTimestamps": true
}
```

#### Example record

```json
{
  "type": "video transcript",
  "tweetUrl": "https://x.com/NASA/status/2107095768821576019",
  "tweetId": "2107095768821576019",
  "authorUsername": "NASA",
  "authorName": "NASA",
  "language": "en",
  "transcriptionMethod": "ai",
  "transcriptAvailable": true,
  "modelUsed": "base",
  "segmentGranularity": "segment",
  "segmentCount": 1,
  "segments": [
    {
      "index": 0,
      "start": 0.0,
      "end": 11.0,
      "text": "One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins."
    }
  ],
  "fullTranscript": "One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.",
  "publishedAt": "2026-10-05T13:09:13+00:00",
  "collectedAt": "2026-10-06T18:54:33+00:00"
}
```

### Whisper models and memory

| Model | Speed | Accuracy | Recommended run memory |
|---|---|---|---|
| `tiny` | Fastest | Lowest | 1024 MB |
| `base` | Fast | Good (default) | 1024 MB |
| `small` | Medium | Better | 2048 MB |
| `medium` | Slow | High | 4096 MB |
| `large-v2` | Slowest | Best | 8192 MB |

Long videos are split into 10-minute chunks automatically, so there is no video length limit. The actor runs with 8192 MB by default and never below 2048 MB. If the selected model does not fit the available memory, it automatically uses the next smaller model and records the substitution in `modelNote` instead of crashing. Caption-only runs have no meaningful memory requirement.

### Use cases

- **Content accessibility** — produce subtitles (SRT/VTT) for public videos
- **Media monitoring** — transcribe interviews, press clips and announcements into searchable text
- **Research** — build text corpora from public video content for analysis
- **Newsrooms** — quote-check and index video statements quickly
- **Podcast and clip workflows** — repurpose spoken content into articles or show notes
- **Social listening** — combine transcripts with engagement data to track narratives

### FAQ

**Do I need an account or cookies?** No. There is no cookie input; public posts, captions and media are read without login.

**What happens when a video has no captions?** With `transcriptionMethod: auto` (the default) the actor transcribes the audio with Whisper, so you still get a transcript. With `captions` it emits a row with `transcriptAvailable: false` and a message.

**Can I transcribe multiple videos in one run?** Yes — add links to `postUrls`, or point the actor at profiles and it transcribes the latest video post of each.

**Which output format should I choose?** `segments` for timestamped lines, `fullText` for one clean block of text, `both` for both, and `srt`/`vtt` when you need subtitle files (both the subtitle text and a signed download link are returned).

**When does word-level granularity work?** Word rows are emitted only when real per-word timings exist — Whisper can produce them, and some X captions include them. When they are missing, the row falls back to raw segments and explains why in `granularityNote`. Timings are never invented.

**Can I transcribe videos in other languages?** Yes — native captions work in any language X provides them; for AI, add a language hint (for example `es`, `fr`, `ur`) or leave it empty to detect automatically.

**Does this support Twitter Spaces?** No — this actor handles video and GIF posts only, not live audio Spaces.

**Why is a row missing `segments` or `fullTranscript`?** `outputFormat` controls which fields are filled, and unavailable transcripts never contain transcript fields — they are `status` rows with a message.

**Can a deleted or protected post be transcribed?** No, only videos on public, currently-live posts can be read.

**Is this affiliated with X?** No. This is an independent tool and is not affiliated with, endorsed by, or sponsored by X Corp.

# Actor input Schema

## `postUrls` (type: `array`):

One or more Twitter/X post links that contain a video or GIF. Both x.com and twitter.com links are accepted. Profile links or usernames also work: the actor transcribes the profile's latest video post.

## `profileUrls` (type: `array`):

Optional. Transcribe the latest video post from these profiles. Paste x.com profile links or bare usernames.

## `transcriptionMethod` (type: `string`):

Auto tries the video's built-in captions first and only falls back to Whisper AI when none exist. Captions-only skips videos without captions. AI-only always uses Whisper speech-to-text.

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

Whisper model used for AI transcription. Larger models are more accurate but slower and need more memory (medium and large-v2 need a run memory of at least 4096 MB and 8192 MB respectively). Base is a good balance for most videos. If the selected model does not fit the run memory, the actor automatically falls back to the next smaller model. Long videos are split into 10-minute chunks automatically, so there is no length limit.

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

Optional language code for AI transcription, for example en, es, fr or ur. Improves accuracy when the spoken language is known. Leave empty to detect it automatically. Ignored when native captions are used.

## `outputFormat` (type: `string`):

Segments gives timestamped lines, full text gives one transcript string, and both gives you both in the same row.

## `includeTweetMetadata` (type: `boolean`):

Attach the post's author, text and publish date to every transcript row.

## `segmentGranularity` (type: `string`):

Level of the timestamped segments. Segment keeps the raw caption/Whisper cues. Sentence groups real word timings into sentences (or splits cue text, sharing the cue's real time range). Word emits one entry per real word timestamp and is used only when X captions or Whisper provide per-word timings — it is never fabricated; when word timings are missing, the row says so and keeps the raw segments.

## `includeWordTimestamps` (type: `boolean`):

Add per-word start/end times to AI (Whisper) transcript segments. Automatically enabled when Segment granularity is Sentence or Word. Native captions only carry words when X included inline timing markers.

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

Optional. Use an Apify proxy if you plan large runs or see frequent rate limits.

## Actor input object example

```json
{
  "postUrls": [
    "https://x.com/NASA/status/2107095768821576019",
    "https://twitter.com/elonmusk/status/1519480761749016577",
    "NASA"
  ],
  "profileUrls": [
    "NASA",
    "https://x.com/elonmusk"
  ],
  "transcriptionMethod": "auto",
  "whisperModel": "base",
  "language": "en",
  "outputFormat": "segments",
  "includeTweetMetadata": true,
  "segmentGranularity": "segment",
  "includeWordTimestamps": false
}
```

# Actor output Schema

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

One row per post, with transcript segments and/or the full transcript text.

# 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 = {
    "postUrls": [
        "https://x.com/NASA/status/2107095768821576019"
    ],
    "transcriptionMethod": "auto",
    "whisperModel": "base",
    "outputFormat": "segments",
    "includeTweetMetadata": true,
    "segmentGranularity": "segment",
    "includeWordTimestamps": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter").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 = {
    "postUrls": ["https://x.com/NASA/status/2107095768821576019"],
    "transcriptionMethod": "auto",
    "whisperModel": "base",
    "outputFormat": "segments",
    "includeTweetMetadata": True,
    "segmentGranularity": "segment",
    "includeWordTimestamps": False,
}

# Run the Actor and wait for it to finish
run = client.actor("nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter").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 '{
  "postUrls": [
    "https://x.com/NASA/status/2107095768821576019"
  ],
  "transcriptionMethod": "auto",
  "whisperModel": "base",
  "outputFormat": "segments",
  "includeTweetMetadata": true,
  "segmentGranularity": "segment",
  "includeWordTimestamps": false
}' |
apify call nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nimbryxo-studio/x-video-subtitle-and-caption-transcript-converter"
        }
    }
}
```

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/SVs5jybOGSsqVgvbs/builds/IeVVIA2c2aj2ljt2y/openapi.json
