{
  "data": {
    "id": "Q5AGTxVSf5y446qRf",
    "actId": "SVs5jybOGSsqVgvbs",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-11-09T06:06:16.156Z",
    "finishedAt": "2026-11-09T06:06:26.325Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 10007,
      "runTimeSecs": 10.007,
      "computeUnits": 0.011118888888888888,
      "imageSizeBytes": 1251302912
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Twitter / X Video Transcript Scraper\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"postUrls\": {\n      \"title\": \"Post links or IDs\",\n      \"type\": \"array\",\n      \"description\": \"Post links or bare post IDs that contain a video or GIF. Accepts x.com and twitter.com links, /i/status and /i/web/status links, /video/N links, and bare numeric IDs. Profile links or usernames are accepted too and are treated like profile inputs. Provide this or Profile links — at least one of the two is required.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [\n        \"https://x.com/firesidealpha/status/2118737977824395524\"\n      ],\n      \"default\": []\n    },\n    \"profileUrls\": {\n      \"title\": \"Profile links or usernames\",\n      \"type\": \"array\",\n      \"description\": \"Optional. Transcribe recent video posts from these profiles. Paste x.com profile links, @handles or bare usernames. Works without Post links.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": []\n    },\n    \"maxVideosPerProfile\": {\n      \"title\": \"Max video posts per profile\",\n      \"type\": \"integer\",\n      \"description\": \"How many recent video posts to transcribe per profile input. Defaults to 10.\",\n      \"default\": 10,\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"sectionCaption\": \"Limits\"\n    },\n    \"maxDurationSeconds\": {\n      \"title\": \"Max video duration (seconds)\",\n      \"type\": \"integer\",\n      \"description\": \"Videos longer than this are skipped for AI transcription and get a media_too_large status row. Built-in captions are still returned when available. Defaults to 1200 seconds (20 minutes).\",\n      \"default\": 1200,\n      \"minimum\": 1,\n      \"maximum\": 43200\n    },\n    \"maxDownloadMb\": {\n      \"title\": \"Max download size (MB)\",\n      \"type\": \"integer\",\n      \"description\": \"Largest media download allowed for AI transcription. The actor picks the audio-only or lowest-bitrate rendition first, then stops with a media_too_large status row if the limit is exceeded. Defaults to 200 MB.\",\n      \"default\": 200,\n      \"minimum\": 1,\n      \"maximum\": 2048\n    },\n    \"transcriptionMethod\": {\n      \"title\": \"How to transcribe\",\n      \"type\": \"string\",\n      \"description\": \"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.\",\n      \"editor\": \"select\",\n      \"default\": \"auto\",\n      \"enum\": [\n        \"auto\",\n        \"captions\",\n        \"ai\"\n      ],\n      \"enumTitles\": [\n        \"Auto — built-in captions first, AI fallback\",\n        \"Built-in captions only — skip videos without captions\",\n        \"AI (Whisper) only — always transcribe the audio\"\n      ],\n      \"prefill\": \"captions\"\n    },\n    \"whisperModel\": {\n      \"title\": \"AI model size\",\n      \"type\": \"string\",\n      \"description\": \"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.\",\n      \"editor\": \"select\",\n      \"default\": \"base\",\n      \"enum\": [\n        \"tiny\",\n        \"base\",\n        \"small\",\n        \"medium\",\n        \"large-v2\"\n      ],\n      \"enumTitles\": [\n        \"Tiny — fastest, lowest accuracy\",\n        \"Base — fast and accurate (recommended)\",\n        \"Small — more accurate, slower\",\n        \"Medium — high accuracy, much slower\",\n        \"Large-v2 — best accuracy, slowest\"\n      ],\n      \"prefill\": \"base\"\n    },\n    \"language\": {\n      \"title\": \"Spoken/caption language hint\",\n      \"type\": \"string\",\n      \"description\": \"Optional language code, for example en, es, fr or ur. Native captions prefer this language when several tracks exist; AI transcription uses it as a spoken-language hint. Leave empty to detect automatically.\",\n      \"editor\": \"textfield\",\n      \"nullable\": true\n    },\n    \"outputFormat\": {\n      \"title\": \"Output format\",\n      \"type\": \"string\",\n      \"description\": \"Segments gives timestamped lines, full text gives one transcript string, and both gives both plus SRT and WebVTT text and download links. SRT and WebVTT modes return the subtitle text and a signed download link.\",\n      \"editor\": \"select\",\n      \"default\": \"segments\",\n      \"enum\": [\n        \"segments\",\n        \"fullText\",\n        \"both\",\n        \"srt\",\n        \"vtt\"\n      ],\n      \"enumTitles\": [\n        \"Timestamped segments\",\n        \"Full transcript text\",\n        \"Segments + full text + SRT/VTT\",\n        \"SRT subtitle file text (downloadable)\",\n        \"WebVTT subtitle file text (downloadable)\"\n      ],\n      \"prefill\": \"segments\"\n    },\n    \"includeTweetMetadata\": {\n      \"title\": \"Include post details\",\n      \"type\": \"boolean\",\n      \"description\": \"Attach the full post and author metadata (text, counts, bio, followers, quote/repost details) to every row.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"includeQuotedVideo\": {\n      \"title\": \"Include quoted video\",\n      \"type\": \"boolean\",\n      \"description\": \"When a quote post has no video of its own, transcribe the video of the quoted post. The row keeps the quote post's metadata and records quotedTweetId.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"segmentGranularity\": {\n      \"title\": \"Segment granularity\",\n      \"type\": \"string\",\n      \"description\": \"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.\",\n      \"editor\": \"select\",\n      \"default\": \"segment\",\n      \"enum\": [\n        \"segment\",\n        \"sentence\",\n        \"word\"\n      ],\n      \"enumTitles\": [\n        \"Segment (raw caption/Whisper cues)\",\n        \"Sentence\",\n        \"Word (when per-word timings exist)\"\n      ],\n      \"prefill\": \"segment\"\n    },\n    \"includeWordTimestamps\": {\n      \"title\": \"Include word timestamps\",\n      \"type\": \"boolean\",\n      \"description\": \"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.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"enableYtdlpFallback\": {\n      \"title\": \"Enable yt-dlp fallback\",\n      \"type\": \"boolean\",\n      \"description\": \"When the direct HTTP media download or Whisper transcription fails, try yt-dlp (requires ffmpeg). Off by default so runs stay fast, deterministic and HTTP-only.\",\n      \"default\": false,\n      \"prefill\": false,\n      \"sectionCaption\": \"Advanced\",\n      \"sectionDescription\": \"Optional settings for large or repeated runs.\"\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy configuration\",\n      \"type\": \"object\",\n      \"description\": \"Optional. Use an Apify proxy if you plan large runs or see frequent rate limits.\",\n      \"editor\": \"proxy\"\n    }\n  }\n}",
    "readme": "# Twitter (X) Video Transcript Scraper — SRT/VTT, Captions & AI\r\n\r\nPublic 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.\r\n\r\n## What this actor does\r\n\r\n- **Many input forms:** post links, `/video/N` links, bare post IDs, profile links and `@handles`\r\n- **Captions first:** uses the video's own captions when the uploader or X provides them, and labels them `manual` or `auto-generated`\r\n- **Whisper AI fallback:** automatically transcribes videos without captions, in any language\r\n- **Multi-video posts:** one row per video with `videoIndex`\r\n- **Quotes and reposts:** transcribes the quoted video when a quote post has none, and the original video of a repost while preserving `quotedTweetId` / `retweetedTweetId`\r\n- **Audio-aware AI path:** prefers an audio-only HLS rendition, then the lowest-bitrate MP4, with byte and duration caps\r\n- **Three granularities:** raw segments, sentences, or real per-word timings when available\r\n- **Five output formats:** timestamped segments, full text, both (which now also includes SRT + VTT), SRT or WebVTT — with SRT/VTT files saved for download\r\n- **Transcript extras:** `wordCount`, `timestampedText` and RAG-ready `chunks`\r\n- **Typed outcomes:** every input produces a row; failures carry `errorCode` and `retryable`, and a run summary is written to the key-value store\r\n- **Empty fields are omitted**\r\n\r\n## Output per transcript\r\n\r\nEach row has a `type`: `video transcript` when a transcript was produced, otherwise `status`.\r\n\r\n| Field | Description |\r\n|---|---|\r\n| `tweetUrl`, `tweetId` | Post URL and ID (the requested post) |\r\n| `videoIndex` | 0-based index of the video inside the post |\r\n| `videoSource` | `post`, `quotedTweet` or `retweetedTweet` |\r\n| `transcriptTweetId` | Post that actually owns the transcribed video (present when it differs) |\r\n| `mediaId`, `mediaDurationSeconds`, `mediaSource` | Media identity, duration and the rendition used for AI (`mp4`, `hls`, `hls-audio`, `yt-dlp`) |\r\n| `metadataSource` | Richest resolver that supplied the post metadata (`rsc`, `graphql`, `syndication`, `fxtwitter`) |\r\n| `authorUsername`, `authorName`, `authorId`, `authorFollowers` | Post author details (when Include post details is on) |\r\n| `authorBio`, `authorProfileImageUrl`, `authorVerified`, `authorVerifiedType`, `authorFollowing`, `authorPostCount`, `authorLocation`, `authorProtected`, `authorCreatedAt` | Full author metadata (when Include post details is on) |\r\n| `tweetText`, `publishedAt` | Full post text and publish time (when Include post details is on) |\r\n| `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `bookmarkCount`, `viewCount` | Engagement counters |\r\n| `conversationId`, `inReplyToTweetId`, `inReplyToUsername`, `isReply`, `isQuote`, `isRetweet`, `isNoteTweet` | Conversation/quote/repost metadata |\r\n| `hashtags`, `mentions`, `urls`, `possiblySensitive`, `place`, `poll`, `card`, `postedFrom` | Entities and optional post metadata |\r\n| `quotedTweetId`, `quotedTweet`, `retweetedTweetId`, `retweetedTweet` | Quoted/reposted post details |\r\n| `engagementRate` | Derived engagement in percent (when Include post details is on) |\r\n| `language` | Caption language or detected/requested spoken language |\r\n| `captionSource`, `captionLanguage`, `captionTrackName` | Native caption transparency: `manual` / `auto-generated`, track language and name |\r\n| `transcriptionMethod` | `captions`, `ai` or `none` |\r\n| `transcriptAvailable` | `true` when a transcript was produced |\r\n| `modelUsed` | Whisper model actually used for AI transcripts |\r\n| `modelNote` | Explanation when a smaller model was substituted for memory reasons |\r\n| `segmentGranularity` | `segment`, `sentence` or `word` — granularity actually applied |\r\n| `granularityRequested` | Present when the requested granularity could not be applied |\r\n| `granularityNote` | Explanation when a granularity fell back to raw segments |\r\n| `segmentCount`, `wordCount` | Number of timed segments and words |\r\n| `segments` | `[{index, start, end, text}]`; includes real `words[{word, start, end}]` timings when available |\r\n| `fullTranscript` | Joined transcript text |\r\n| `timestampedText` | One line per segment: `[HH:MM:SS.mmm --> HH:MM:SS.mmm] text` |\r\n| `chunks` | RAG-ready chunks: `[{index, start, end, text, wordCount}]` |\r\n| `srt`, `srtUrl` | SRT subtitle text and a signed download link for the `.srt` file |\r\n| `vtt`, `vttUrl` | WebVTT subtitle text and a signed download link for the `.vtt` file |\r\n| `errorCode`, `retryable`, `message` | Typed status reason for `status` rows |\r\n| `spaceId` | Present on `spaces_not_supported` status rows |\r\n| `type` | `video transcript` or `status` |\r\n| `recordType` | Standard alias: `transcript` or `status` |\r\n| `sourceUrl` | Canonical post URL (omitted when the input had no valid URL) |\r\n| `collectedAt`, `scrapedAt` | UTC timestamp |\r\n\r\nUnavailable transcripts are `status` rows with `transcriptAvailable: false`, `transcriptionMethod: \"none\"` and a typed `errorCode`. 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`.\r\n\r\n### Error codes\r\n\r\n| `errorCode` | Meaning | Retryable |\r\n|---|---|---|\r\n| `no_input` | No post or profile input was provided | no |\r\n| `invalid_url` | The value is not a post link/ID | no |\r\n| `invalid_profile` | The value is not a profile link/username | no |\r\n| `not_found` | Post deleted, hidden or never existed | no |\r\n| `protected` | Post is protected; only approved followers can view it | no |\r\n| `spaces_not_supported` | Input is an X Space; audio Spaces are not transcribed | no |\r\n| `blocked` | X temporarily limited the request | yes |\r\n| `no_video` | Post, quote and repost have no video/GIF | no |\r\n| `no_captions` | Captions-only mode but no native captions exist | no |\r\n| `no_audio` | GIF/silent media with no audio stream | no |\r\n| `no_speech` | Audio is music-only or contains no speech | no |\r\n| `media_too_large` | Video exceeds `maxDurationSeconds` or `maxDownloadMb` | no |\r\n| `transcription_failed` | Download/transcription failed for another reason | yes |\r\n| `profile_not_found` | Profile could not be loaded | yes |\r\n| `profile_no_video` | Profile's recent posts have no video | no |\r\n| `profile_protected` | Profile is protected; its posts are follower-only | no |\r\n\r\nThe actor also writes a `RUN_SUMMARY` key-value record (and sets the status message) with requested/transcript/status/caption/AI counts and counts per `errorCode`.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `postUrls` | array | – | Post links or bare post IDs. Accepts `x.com`/`twitter.com`, `/i/status`, `/i/web/status`, `/video/N` and numeric IDs. Profile links/usernames here are treated as profile inputs |\r\n| `profileUrls` | array | – | Profiles to transcribe (`NASA`, `@NASA`, or `https://x.com/NASA`). Works without `postUrls` |\r\n| `maxVideosPerProfile` | integer | `10` | Recent video posts to transcribe per profile |\r\n| `maxDurationSeconds` | integer | `1200` | AI transcription duration cap; longer videos get `media_too_large` (native captions still work) |\r\n| `maxDownloadMb` | integer | `200` | Media download cap for AI transcription |\r\n| `transcriptionMethod` | string | `auto` | `auto` (captions first, AI fallback), `captions` (skip videos without captions), `ai` (always Whisper) |\r\n| `whisperModel` | string | `base` | `tiny`, `base`, `small`, `medium`, `large-v2` |\r\n| `language` | string | – | Language hint; native captions prefer this track, AI uses it as a spoken-language hint |\r\n| `outputFormat` | string | `segments` | `segments`, `fullText`, `both`, `srt`, `vtt` |\r\n| `segmentGranularity` | string | `segment` | `segment` (raw cues), `sentence`, `word` (only when real word timings exist) |\r\n| `includeWordTimestamps` | boolean | `false` | Add real per-word start/end times to AI segments |\r\n| `includeTweetMetadata` | boolean | `true` | Attach full post and author metadata to each row |\r\n| `includeQuotedVideo` | boolean | `true` | Transcribe the quoted video when the post itself has none |\r\n| `enableYtdlpFallback` | boolean | `false` | Optional yt-dlp fallback when direct HTTP download/transcription fails |\r\n| `proxyConfiguration` | object | – | Optional, for large or repeated runs |\r\n\r\nAt least one of `postUrls` or `profileUrls` is required at runtime; neither is required by the schema so profile-only runs work.\r\n\r\n### Example: captions first, AI fallback, segments + SRT/VTT\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\r\n  \"transcriptionMethod\": \"auto\",\r\n  \"outputFormat\": \"both\"\r\n}\r\n```\r\n\r\n### Example: profiles only, capped\r\n\r\n```json\r\n{\r\n  \"profileUrls\": [\"NASA\", \"https://x.com/SpaceX\"],\r\n  \"maxVideosPerProfile\": 5,\r\n  \"maxDurationSeconds\": 600,\r\n  \"maxDownloadMb\": 100\r\n}\r\n```\r\n\r\n### Example: bare IDs and `/video/N`\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\r\n    \"2107095768821576019\",\r\n    \"https://x.com/elonmusk/status/1519480761749016577/video/1\"\r\n  ],\r\n  \"transcriptionMethod\": \"captions\"\r\n}\r\n```\r\n\r\n### Example: SRT subtitle file\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\r\n  \"outputFormat\": \"srt\",\r\n  \"segmentGranularity\": \"sentence\"\r\n}\r\n```\r\n\r\n### Example record\r\n\r\n```json\r\n{\r\n  \"type\": \"video transcript\",\r\n  \"tweetUrl\": \"https://x.com/NASA/status/2107095768821576019\",\r\n  \"tweetId\": \"2107095768821576019\",\r\n  \"videoIndex\": 0,\r\n  \"videoSource\": \"post\",\r\n  \"authorUsername\": \"NASA\",\r\n  \"authorName\": \"NASA\",\r\n  \"authorFollowers\": 92276527,\r\n  \"tweetText\": \"It's World Space Week!\",\r\n  \"publishedAt\": \"2026-10-05T13:09:13+00:00\",\r\n  \"likeCount\": 1234,\r\n  \"replyCount\": 56,\r\n  \"retweetCount\": 78,\r\n  \"quoteCount\": 9,\r\n  \"bookmarkCount\": 10,\r\n  \"viewCount\": 100000,\r\n  \"language\": \"en\",\r\n  \"captionSource\": \"manual\",\r\n  \"captionLanguage\": \"en\",\r\n  \"transcriptionMethod\": \"captions\",\r\n  \"transcriptAvailable\": true,\r\n  \"segmentGranularity\": \"segment\",\r\n  \"segmentCount\": 1,\r\n  \"wordCount\": 21,\r\n  \"segments\": [\r\n    {\r\n      \"index\": 0,\r\n      \"start\": 0.0,\r\n      \"end\": 11.0,\r\n      \"text\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\"\r\n    }\r\n  ],\r\n  \"fullTranscript\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n  \"timestampedText\": \"[00:00:00.000 --> 00:00:11.000] One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n  \"chunks\": [\r\n    {\r\n      \"index\": 0,\r\n      \"start\": 0.0,\r\n      \"end\": 11.0,\r\n      \"text\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n      \"wordCount\": 21\r\n    }\r\n  ],\r\n  \"collectedAt\": \"2026-10-06T18:54:33+00:00\"\r\n}\r\n```\r\n\r\n## SRT/VTT downloads and multi-video posts\r\n\r\nSRT and VTT exports are stored in the run's key-value store as `transcript_<tweetId>_<videoIndex>.<fmt>`, so multiple videos in one post never overwrite each other. The row carries both the subtitle text (`srt`/`vtt`) and a signed download URL (`srtUrl`/`vttUrl`) when the storage returns one. In local runs the URL may be a `file://` path; cloud runs return a signed public URL.\r\n\r\nThe `srtUrl`/`vttUrl` links point at the run's default (unnamed) key-value store, so they follow that run store's retention policy and are convenience links rather than permanent hosting. The complete SRT/VTT text always remains in the dataset row (`srt`, `vtt`); persist the dataset if you need durable copies, or copy the exports to a named store.\r\n\r\n## Resolution and reliability\r\n\r\nThe actor tries the richest cookieless source first and degrades gracefully:\r\n\r\n1. **RSC SSR** on `x.com` (full text, all counts, author bio/followers, quote/repost originals)\r\n2. **Guest GraphQL** with the exact query id and variables the RSC page used (including the relay-provider variables X validates)\r\n3. **Syndication CDN** (fast, no auth, but truncated long text and missing counts — used only to fill gaps)\r\n4. **FxTwitter** (third-party last resort for identity/text basics)\r\n\r\nNo cookies, login, browser or proxy are required. Proxies are optional for large or repeated runs.\r\n\r\n## Whisper models and memory\r\n\r\n| Model | Speed | Accuracy | Recommended run memory |\r\n|---|---|---|---|\r\n| `tiny` | Fastest | Lowest | 1024 MB |\r\n| `base` | Fast | Good (default) | 1024 MB |\r\n| `small` | Medium | Better | 2048 MB |\r\n| `medium` | Slow | High | 4096 MB |\r\n| `large-v2` | Slowest | Best | 8192 MB |\r\n\r\nLong videos are split into 10-minute chunks automatically. 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.\r\n\r\n## Use cases\r\n\r\n- **Content accessibility** — produce subtitles (SRT/VTT) for public videos\r\n- **Media monitoring** — transcribe interviews, press clips and announcements into searchable text\r\n- **Research** — build text corpora from public video content for analysis\r\n- **Newsrooms** — quote-check and index video statements quickly\r\n- **Podcast and clip workflows** — repurpose spoken content into articles or show notes\r\n- **RAG / AI pipelines** — use `chunks` and `timestampedText` directly\r\n- **Social listening** — combine transcripts with engagement data to track narratives\r\n\r\n## FAQ\r\n\r\n**Do I need an account or cookies?** No. There is no cookie input; public posts, captions and media are read without login.\r\n\r\n**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 `no_captions` status row.\r\n\r\n**Can I transcribe multiple videos in one run?** Yes — add links/IDs to `postUrls`, or point the actor at profiles. Multi-video posts produce one row per video with `videoIndex`.\r\n\r\n**Does it work with quote posts and reposts?** Yes. A quote post with no video of its own transcribes the quoted video (`includeQuotedVideo`, default on) and records `quotedTweetId`. A repost transcribes the original video and records `retweetedTweetId`.\r\n\r\n**Which output format should I choose?** `segments` for timestamped lines, `fullText` for one clean block of text, `both` for segments + full text + SRT/VTT, and `srt`/`vtt` when you only need subtitle files. Subtitle text and a download link are returned.\r\n\r\n**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.\r\n\r\n**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.\r\n\r\n**What are the limits?** AI transcription skips videos longer than `maxDurationSeconds` (default 20 minutes) and stops downloads above `maxDownloadMb` (default 200 MB) with a `media_too_large` status. Native captions are still returned for long videos.\r\n\r\n**Does this support Twitter Spaces?** No — Space links produce a typed `spaces_not_supported` status row. This actor handles video and GIF posts only, not live or replay audio Spaces.\r\n\r\n**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 an `errorCode` and message.\r\n\r\n**Can a deleted or protected post be transcribed?** No. Deleted or never-existing posts return `not_found`; protected posts and profiles return `protected` / `profile_protected` because only approved followers can read them.\r\n\r\n**Is this affiliated with X?** No. This is an independent tool and is not affiliated with, endorsed by, or sponsored by X Corp.\r\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "x-video-subtitle-and-caption-transcript-converter",
      "version": "1.0",
      "buildTag": "latest",
      "defaultMemoryMbytes": 8192,
      "minMemoryMbytes": 2048,
      "dockerfile": "./Dockerfile",
      "input": {
        "title": "Twitter / X Video Transcript Scraper",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "postUrls": {
            "title": "Post links or IDs",
            "type": "array",
            "description": "Post links or bare post IDs that contain a video or GIF. Accepts x.com and twitter.com links, /i/status and /i/web/status links, /video/N links, and bare numeric IDs. Profile links or usernames are accepted too and are treated like profile inputs. Provide this or Profile links — at least one of the two is required.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [
              "https://x.com/firesidealpha/status/2118737977824395524"
            ],
            "default": []
          },
          "profileUrls": {
            "title": "Profile links or usernames",
            "type": "array",
            "description": "Optional. Transcribe recent video posts from these profiles. Paste x.com profile links, @handles or bare usernames. Works without Post links.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": []
          },
          "maxVideosPerProfile": {
            "title": "Max video posts per profile",
            "type": "integer",
            "description": "How many recent video posts to transcribe per profile input. Defaults to 10.",
            "default": 10,
            "minimum": 1,
            "maximum": 100,
            "sectionCaption": "Limits"
          },
          "maxDurationSeconds": {
            "title": "Max video duration (seconds)",
            "type": "integer",
            "description": "Videos longer than this are skipped for AI transcription and get a media_too_large status row. Built-in captions are still returned when available. Defaults to 1200 seconds (20 minutes).",
            "default": 1200,
            "minimum": 1,
            "maximum": 43200
          },
          "maxDownloadMb": {
            "title": "Max download size (MB)",
            "type": "integer",
            "description": "Largest media download allowed for AI transcription. The actor picks the audio-only or lowest-bitrate rendition first, then stops with a media_too_large status row if the limit is exceeded. Defaults to 200 MB.",
            "default": 200,
            "minimum": 1,
            "maximum": 2048
          },
          "transcriptionMethod": {
            "title": "How to transcribe",
            "type": "string",
            "description": "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.",
            "editor": "select",
            "default": "auto",
            "enum": [
              "auto",
              "captions",
              "ai"
            ],
            "enumTitles": [
              "Auto — built-in captions first, AI fallback",
              "Built-in captions only — skip videos without captions",
              "AI (Whisper) only — always transcribe the audio"
            ],
            "prefill": "captions"
          },
          "whisperModel": {
            "title": "AI model size",
            "type": "string",
            "description": "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.",
            "editor": "select",
            "default": "base",
            "enum": [
              "tiny",
              "base",
              "small",
              "medium",
              "large-v2"
            ],
            "enumTitles": [
              "Tiny — fastest, lowest accuracy",
              "Base — fast and accurate (recommended)",
              "Small — more accurate, slower",
              "Medium — high accuracy, much slower",
              "Large-v2 — best accuracy, slowest"
            ],
            "prefill": "base"
          },
          "language": {
            "title": "Spoken/caption language hint",
            "type": "string",
            "description": "Optional language code, for example en, es, fr or ur. Native captions prefer this language when several tracks exist; AI transcription uses it as a spoken-language hint. Leave empty to detect automatically.",
            "editor": "textfield",
            "nullable": true
          },
          "outputFormat": {
            "title": "Output format",
            "type": "string",
            "description": "Segments gives timestamped lines, full text gives one transcript string, and both gives both plus SRT and WebVTT text and download links. SRT and WebVTT modes return the subtitle text and a signed download link.",
            "editor": "select",
            "default": "segments",
            "enum": [
              "segments",
              "fullText",
              "both",
              "srt",
              "vtt"
            ],
            "enumTitles": [
              "Timestamped segments",
              "Full transcript text",
              "Segments + full text + SRT/VTT",
              "SRT subtitle file text (downloadable)",
              "WebVTT subtitle file text (downloadable)"
            ],
            "prefill": "segments"
          },
          "includeTweetMetadata": {
            "title": "Include post details",
            "type": "boolean",
            "description": "Attach the full post and author metadata (text, counts, bio, followers, quote/repost details) to every row.",
            "default": true,
            "prefill": true
          },
          "includeQuotedVideo": {
            "title": "Include quoted video",
            "type": "boolean",
            "description": "When a quote post has no video of its own, transcribe the video of the quoted post. The row keeps the quote post's metadata and records quotedTweetId.",
            "default": true,
            "prefill": true
          },
          "segmentGranularity": {
            "title": "Segment granularity",
            "type": "string",
            "description": "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.",
            "editor": "select",
            "default": "segment",
            "enum": [
              "segment",
              "sentence",
              "word"
            ],
            "enumTitles": [
              "Segment (raw caption/Whisper cues)",
              "Sentence",
              "Word (when per-word timings exist)"
            ],
            "prefill": "segment"
          },
          "includeWordTimestamps": {
            "title": "Include word timestamps",
            "type": "boolean",
            "description": "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.",
            "default": false,
            "prefill": false
          },
          "enableYtdlpFallback": {
            "title": "Enable yt-dlp fallback",
            "type": "boolean",
            "description": "When the direct HTTP media download or Whisper transcription fails, try yt-dlp (requires ffmpeg). Off by default so runs stay fast, deterministic and HTTP-only.",
            "default": false,
            "prefill": false,
            "sectionCaption": "Advanced",
            "sectionDescription": "Optional settings for large or repeated runs."
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Optional. Use an Apify proxy if you plan large runs or see frequent rate limits.",
            "editor": "proxy"
          }
        }
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Twitter (X) Video Transcript Scraper Output",
        "description": "Per-video transcript records with timestamped segments, full transcript text, timestamped text, RAG chunks, SRT/VTT subtitle output and complete post/author metadata. Status records carry a typed errorCode.",
        "type": "object",
        "properties": {
          "transcripts": {
            "type": "string",
            "title": "Transcripts",
            "description": "One row per video (and typed status rows), with transcript segments, full transcript, SRT/VTT text and metadata.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          },
          "runSummary": {
            "type": "string",
            "title": "Run summary",
            "description": "Requested/transcript/status/caption/AI counts for the run.",
            "template": "{{links.apiDefaultKeyValueStoreUrl}}/records/RUN_SUMMARY"
          }
        }
      },
      "readme": "# Twitter (X) Video Transcript Scraper — SRT/VTT, Captions & AI\r\n\r\nPublic 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.\r\n\r\n## What this actor does\r\n\r\n- **Many input forms:** post links, `/video/N` links, bare post IDs, profile links and `@handles`\r\n- **Captions first:** uses the video's own captions when the uploader or X provides them, and labels them `manual` or `auto-generated`\r\n- **Whisper AI fallback:** automatically transcribes videos without captions, in any language\r\n- **Multi-video posts:** one row per video with `videoIndex`\r\n- **Quotes and reposts:** transcribes the quoted video when a quote post has none, and the original video of a repost while preserving `quotedTweetId` / `retweetedTweetId`\r\n- **Audio-aware AI path:** prefers an audio-only HLS rendition, then the lowest-bitrate MP4, with byte and duration caps\r\n- **Three granularities:** raw segments, sentences, or real per-word timings when available\r\n- **Five output formats:** timestamped segments, full text, both (which now also includes SRT + VTT), SRT or WebVTT — with SRT/VTT files saved for download\r\n- **Transcript extras:** `wordCount`, `timestampedText` and RAG-ready `chunks`\r\n- **Typed outcomes:** every input produces a row; failures carry `errorCode` and `retryable`, and a run summary is written to the key-value store\r\n- **Empty fields are omitted**\r\n\r\n## Output per transcript\r\n\r\nEach row has a `type`: `video transcript` when a transcript was produced, otherwise `status`.\r\n\r\n| Field | Description |\r\n|---|---|\r\n| `tweetUrl`, `tweetId` | Post URL and ID (the requested post) |\r\n| `videoIndex` | 0-based index of the video inside the post |\r\n| `videoSource` | `post`, `quotedTweet` or `retweetedTweet` |\r\n| `transcriptTweetId` | Post that actually owns the transcribed video (present when it differs) |\r\n| `mediaId`, `mediaDurationSeconds`, `mediaSource` | Media identity, duration and the rendition used for AI (`mp4`, `hls`, `hls-audio`, `yt-dlp`) |\r\n| `metadataSource` | Richest resolver that supplied the post metadata (`rsc`, `graphql`, `syndication`, `fxtwitter`) |\r\n| `authorUsername`, `authorName`, `authorId`, `authorFollowers` | Post author details (when Include post details is on) |\r\n| `authorBio`, `authorProfileImageUrl`, `authorVerified`, `authorVerifiedType`, `authorFollowing`, `authorPostCount`, `authorLocation`, `authorProtected`, `authorCreatedAt` | Full author metadata (when Include post details is on) |\r\n| `tweetText`, `publishedAt` | Full post text and publish time (when Include post details is on) |\r\n| `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `bookmarkCount`, `viewCount` | Engagement counters |\r\n| `conversationId`, `inReplyToTweetId`, `inReplyToUsername`, `isReply`, `isQuote`, `isRetweet`, `isNoteTweet` | Conversation/quote/repost metadata |\r\n| `hashtags`, `mentions`, `urls`, `possiblySensitive`, `place`, `poll`, `card`, `postedFrom` | Entities and optional post metadata |\r\n| `quotedTweetId`, `quotedTweet`, `retweetedTweetId`, `retweetedTweet` | Quoted/reposted post details |\r\n| `engagementRate` | Derived engagement in percent (when Include post details is on) |\r\n| `language` | Caption language or detected/requested spoken language |\r\n| `captionSource`, `captionLanguage`, `captionTrackName` | Native caption transparency: `manual` / `auto-generated`, track language and name |\r\n| `transcriptionMethod` | `captions`, `ai` or `none` |\r\n| `transcriptAvailable` | `true` when a transcript was produced |\r\n| `modelUsed` | Whisper model actually used for AI transcripts |\r\n| `modelNote` | Explanation when a smaller model was substituted for memory reasons |\r\n| `segmentGranularity` | `segment`, `sentence` or `word` — granularity actually applied |\r\n| `granularityRequested` | Present when the requested granularity could not be applied |\r\n| `granularityNote` | Explanation when a granularity fell back to raw segments |\r\n| `segmentCount`, `wordCount` | Number of timed segments and words |\r\n| `segments` | `[{index, start, end, text}]`; includes real `words[{word, start, end}]` timings when available |\r\n| `fullTranscript` | Joined transcript text |\r\n| `timestampedText` | One line per segment: `[HH:MM:SS.mmm --> HH:MM:SS.mmm] text` |\r\n| `chunks` | RAG-ready chunks: `[{index, start, end, text, wordCount}]` |\r\n| `srt`, `srtUrl` | SRT subtitle text and a signed download link for the `.srt` file |\r\n| `vtt`, `vttUrl` | WebVTT subtitle text and a signed download link for the `.vtt` file |\r\n| `errorCode`, `retryable`, `message` | Typed status reason for `status` rows |\r\n| `spaceId` | Present on `spaces_not_supported` status rows |\r\n| `type` | `video transcript` or `status` |\r\n| `recordType` | Standard alias: `transcript` or `status` |\r\n| `sourceUrl` | Canonical post URL (omitted when the input had no valid URL) |\r\n| `collectedAt`, `scrapedAt` | UTC timestamp |\r\n\r\nUnavailable transcripts are `status` rows with `transcriptAvailable: false`, `transcriptionMethod: \"none\"` and a typed `errorCode`. 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`.\r\n\r\n### Error codes\r\n\r\n| `errorCode` | Meaning | Retryable |\r\n|---|---|---|\r\n| `no_input` | No post or profile input was provided | no |\r\n| `invalid_url` | The value is not a post link/ID | no |\r\n| `invalid_profile` | The value is not a profile link/username | no |\r\n| `not_found` | Post deleted, hidden or never existed | no |\r\n| `protected` | Post is protected; only approved followers can view it | no |\r\n| `spaces_not_supported` | Input is an X Space; audio Spaces are not transcribed | no |\r\n| `blocked` | X temporarily limited the request | yes |\r\n| `no_video` | Post, quote and repost have no video/GIF | no |\r\n| `no_captions` | Captions-only mode but no native captions exist | no |\r\n| `no_audio` | GIF/silent media with no audio stream | no |\r\n| `no_speech` | Audio is music-only or contains no speech | no |\r\n| `media_too_large` | Video exceeds `maxDurationSeconds` or `maxDownloadMb` | no |\r\n| `transcription_failed` | Download/transcription failed for another reason | yes |\r\n| `profile_not_found` | Profile could not be loaded | yes |\r\n| `profile_no_video` | Profile's recent posts have no video | no |\r\n| `profile_protected` | Profile is protected; its posts are follower-only | no |\r\n\r\nThe actor also writes a `RUN_SUMMARY` key-value record (and sets the status message) with requested/transcript/status/caption/AI counts and counts per `errorCode`.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `postUrls` | array | – | Post links or bare post IDs. Accepts `x.com`/`twitter.com`, `/i/status`, `/i/web/status`, `/video/N` and numeric IDs. Profile links/usernames here are treated as profile inputs |\r\n| `profileUrls` | array | – | Profiles to transcribe (`NASA`, `@NASA`, or `https://x.com/NASA`). Works without `postUrls` |\r\n| `maxVideosPerProfile` | integer | `10` | Recent video posts to transcribe per profile |\r\n| `maxDurationSeconds` | integer | `1200` | AI transcription duration cap; longer videos get `media_too_large` (native captions still work) |\r\n| `maxDownloadMb` | integer | `200` | Media download cap for AI transcription |\r\n| `transcriptionMethod` | string | `auto` | `auto` (captions first, AI fallback), `captions` (skip videos without captions), `ai` (always Whisper) |\r\n| `whisperModel` | string | `base` | `tiny`, `base`, `small`, `medium`, `large-v2` |\r\n| `language` | string | – | Language hint; native captions prefer this track, AI uses it as a spoken-language hint |\r\n| `outputFormat` | string | `segments` | `segments`, `fullText`, `both`, `srt`, `vtt` |\r\n| `segmentGranularity` | string | `segment` | `segment` (raw cues), `sentence`, `word` (only when real word timings exist) |\r\n| `includeWordTimestamps` | boolean | `false` | Add real per-word start/end times to AI segments |\r\n| `includeTweetMetadata` | boolean | `true` | Attach full post and author metadata to each row |\r\n| `includeQuotedVideo` | boolean | `true` | Transcribe the quoted video when the post itself has none |\r\n| `enableYtdlpFallback` | boolean | `false` | Optional yt-dlp fallback when direct HTTP download/transcription fails |\r\n| `proxyConfiguration` | object | – | Optional, for large or repeated runs |\r\n\r\nAt least one of `postUrls` or `profileUrls` is required at runtime; neither is required by the schema so profile-only runs work.\r\n\r\n### Example: captions first, AI fallback, segments + SRT/VTT\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\r\n  \"transcriptionMethod\": \"auto\",\r\n  \"outputFormat\": \"both\"\r\n}\r\n```\r\n\r\n### Example: profiles only, capped\r\n\r\n```json\r\n{\r\n  \"profileUrls\": [\"NASA\", \"https://x.com/SpaceX\"],\r\n  \"maxVideosPerProfile\": 5,\r\n  \"maxDurationSeconds\": 600,\r\n  \"maxDownloadMb\": 100\r\n}\r\n```\r\n\r\n### Example: bare IDs and `/video/N`\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\r\n    \"2107095768821576019\",\r\n    \"https://x.com/elonmusk/status/1519480761749016577/video/1\"\r\n  ],\r\n  \"transcriptionMethod\": \"captions\"\r\n}\r\n```\r\n\r\n### Example: SRT subtitle file\r\n\r\n```json\r\n{\r\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\r\n  \"outputFormat\": \"srt\",\r\n  \"segmentGranularity\": \"sentence\"\r\n}\r\n```\r\n\r\n### Example record\r\n\r\n```json\r\n{\r\n  \"type\": \"video transcript\",\r\n  \"tweetUrl\": \"https://x.com/NASA/status/2107095768821576019\",\r\n  \"tweetId\": \"2107095768821576019\",\r\n  \"videoIndex\": 0,\r\n  \"videoSource\": \"post\",\r\n  \"authorUsername\": \"NASA\",\r\n  \"authorName\": \"NASA\",\r\n  \"authorFollowers\": 92276527,\r\n  \"tweetText\": \"It's World Space Week!\",\r\n  \"publishedAt\": \"2026-10-05T13:09:13+00:00\",\r\n  \"likeCount\": 1234,\r\n  \"replyCount\": 56,\r\n  \"retweetCount\": 78,\r\n  \"quoteCount\": 9,\r\n  \"bookmarkCount\": 10,\r\n  \"viewCount\": 100000,\r\n  \"language\": \"en\",\r\n  \"captionSource\": \"manual\",\r\n  \"captionLanguage\": \"en\",\r\n  \"transcriptionMethod\": \"captions\",\r\n  \"transcriptAvailable\": true,\r\n  \"segmentGranularity\": \"segment\",\r\n  \"segmentCount\": 1,\r\n  \"wordCount\": 21,\r\n  \"segments\": [\r\n    {\r\n      \"index\": 0,\r\n      \"start\": 0.0,\r\n      \"end\": 11.0,\r\n      \"text\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\"\r\n    }\r\n  ],\r\n  \"fullTranscript\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n  \"timestampedText\": \"[00:00:00.000 --> 00:00:11.000] One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n  \"chunks\": [\r\n    {\r\n      \"index\": 0,\r\n      \"start\": 0.0,\r\n      \"end\": 11.0,\r\n      \"text\": \"One, booster ignition, and liftoff, the crew of Artemis II, now bound for the moon, humanity's next great voyage begins.\",\r\n      \"wordCount\": 21\r\n    }\r\n  ],\r\n  \"collectedAt\": \"2026-10-06T18:54:33+00:00\"\r\n}\r\n```\r\n\r\n## SRT/VTT downloads and multi-video posts\r\n\r\nSRT and VTT exports are stored in the run's key-value store as `transcript_<tweetId>_<videoIndex>.<fmt>`, so multiple videos in one post never overwrite each other. The row carries both the subtitle text (`srt`/`vtt`) and a signed download URL (`srtUrl`/`vttUrl`) when the storage returns one. In local runs the URL may be a `file://` path; cloud runs return a signed public URL.\r\n\r\nThe `srtUrl`/`vttUrl` links point at the run's default (unnamed) key-value store, so they follow that run store's retention policy and are convenience links rather than permanent hosting. The complete SRT/VTT text always remains in the dataset row (`srt`, `vtt`); persist the dataset if you need durable copies, or copy the exports to a named store.\r\n\r\n## Resolution and reliability\r\n\r\nThe actor tries the richest cookieless source first and degrades gracefully:\r\n\r\n1. **RSC SSR** on `x.com` (full text, all counts, author bio/followers, quote/repost originals)\r\n2. **Guest GraphQL** with the exact query id and variables the RSC page used (including the relay-provider variables X validates)\r\n3. **Syndication CDN** (fast, no auth, but truncated long text and missing counts — used only to fill gaps)\r\n4. **FxTwitter** (third-party last resort for identity/text basics)\r\n\r\nNo cookies, login, browser or proxy are required. Proxies are optional for large or repeated runs.\r\n\r\n## Whisper models and memory\r\n\r\n| Model | Speed | Accuracy | Recommended run memory |\r\n|---|---|---|---|\r\n| `tiny` | Fastest | Lowest | 1024 MB |\r\n| `base` | Fast | Good (default) | 1024 MB |\r\n| `small` | Medium | Better | 2048 MB |\r\n| `medium` | Slow | High | 4096 MB |\r\n| `large-v2` | Slowest | Best | 8192 MB |\r\n\r\nLong videos are split into 10-minute chunks automatically. 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.\r\n\r\n## Use cases\r\n\r\n- **Content accessibility** — produce subtitles (SRT/VTT) for public videos\r\n- **Media monitoring** — transcribe interviews, press clips and announcements into searchable text\r\n- **Research** — build text corpora from public video content for analysis\r\n- **Newsrooms** — quote-check and index video statements quickly\r\n- **Podcast and clip workflows** — repurpose spoken content into articles or show notes\r\n- **RAG / AI pipelines** — use `chunks` and `timestampedText` directly\r\n- **Social listening** — combine transcripts with engagement data to track narratives\r\n\r\n## FAQ\r\n\r\n**Do I need an account or cookies?** No. There is no cookie input; public posts, captions and media are read without login.\r\n\r\n**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 `no_captions` status row.\r\n\r\n**Can I transcribe multiple videos in one run?** Yes — add links/IDs to `postUrls`, or point the actor at profiles. Multi-video posts produce one row per video with `videoIndex`.\r\n\r\n**Does it work with quote posts and reposts?** Yes. A quote post with no video of its own transcribes the quoted video (`includeQuotedVideo`, default on) and records `quotedTweetId`. A repost transcribes the original video and records `retweetedTweetId`.\r\n\r\n**Which output format should I choose?** `segments` for timestamped lines, `fullText` for one clean block of text, `both` for segments + full text + SRT/VTT, and `srt`/`vtt` when you only need subtitle files. Subtitle text and a download link are returned.\r\n\r\n**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.\r\n\r\n**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.\r\n\r\n**What are the limits?** AI transcription skips videos longer than `maxDurationSeconds` (default 20 minutes) and stops downloads above `maxDownloadMb` (default 200 MB) with a `media_too_large` status. Native captions are still returned for long videos.\r\n\r\n**Does this support Twitter Spaces?** No — Space links produce a typed `spaces_not_supported` status row. This actor handles video and GIF posts only, not live or replay audio Spaces.\r\n\r\n**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 an `errorCode` and message.\r\n\r\n**Can a deleted or protected post be transcribed?** No. Deleted or never-existing posts return `not_found`; protected posts and profiles return `protected` / `profile_protected` because only approved followers can read them.\r\n\r\n**Is this affiliated with X?** No. This is an independent tool and is not affiliated with, endorsed by, or sponsored by X Corp.\r\n",
      "title": "Twitter (X) Video Transcript Scraper — SRT/VTT, Captions & AI",
      "description": "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. Supports post IDs, profiles, multi-video posts, quotes and reposts.",
      "seoTitle": "Twitter (X) Video Transcript Scraper — SRT/VTT & AI",
      "seoDescription": "Convert public Twitter/X videos into timestamped transcripts and SRT/VTT subtitle files. Native captions first, AI fallback, full post metadata, profiles, multi-video and quote posts.",
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "transcripts": {
              "title": "Transcripts",
              "transformation": {
                "fields": [
                  "type",
                  "tweetUrl",
                  "tweetId",
                  "videoIndex",
                  "videoSource",
                  "authorUsername",
                  "authorName",
                  "language",
                  "captionSource",
                  "captionLanguage",
                  "metadataSource",
                  "mediaSource",
                  "transcriptionMethod",
                  "transcriptAvailable",
                  "transcriptTweetId",
                  "quotedTweetId",
                  "retweetedTweetId",
                  "wordCount",
                  "mediaDurationSeconds",
                  "fullTranscript",
                  "modelUsed",
                  "errorCode",
                  "message",
                  "publishedAt",
                  "collectedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "type": {
                    "label": "Row Type"
                  },
                  "tweetUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "tweetId": {
                    "label": "Post ID"
                  },
                  "videoIndex": {
                    "label": "Video #",
                    "format": "number"
                  },
                  "videoSource": {
                    "label": "Video Source"
                  },
                  "authorUsername": {
                    "label": "Author"
                  },
                  "authorName": {
                    "label": "Author Name"
                  },
                  "language": {
                    "label": "Language"
                  },
                  "captionSource": {
                    "label": "Caption Source"
                  },
                  "captionLanguage": {
                    "label": "Caption Language"
                  },
                  "metadataSource": {
                    "label": "Metadata Source"
                  },
                  "mediaSource": {
                    "label": "Media Source"
                  },
                  "transcriptionMethod": {
                    "label": "Method"
                  },
                  "transcriptAvailable": {
                    "label": "Available",
                    "format": "boolean"
                  },
                  "transcriptTweetId": {
                    "label": "Transcribed Post ID"
                  },
                  "quotedTweetId": {
                    "label": "Quoted Post ID"
                  },
                  "retweetedTweetId": {
                    "label": "Retweeted Post ID"
                  },
                  "wordCount": {
                    "label": "Words",
                    "format": "number"
                  },
                  "mediaDurationSeconds": {
                    "label": "Video Duration (s)",
                    "format": "number"
                  },
                  "fullTranscript": {
                    "label": "Full Transcript"
                  },
                  "modelUsed": {
                    "label": "Whisper Model"
                  },
                  "errorCode": {
                    "label": "Error Code"
                  },
                  "message": {
                    "label": "Message"
                  },
                  "publishedAt": {
                    "label": "Published"
                  },
                  "collectedAt": {
                    "label": "Data collected at",
                    "format": "date"
                  }
                }
              }
            },
            "segments": {
              "title": "Segments",
              "transformation": {
                "fields": [
                  "tweetUrl",
                  "videoIndex",
                  "authorUsername",
                  "transcriptionMethod",
                  "captionSource",
                  "language",
                  "index",
                  "start",
                  "end",
                  "text"
                ],
                "unwind": [
                  "segments"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "tweetUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "videoIndex": {
                    "label": "Video #",
                    "format": "number"
                  },
                  "authorUsername": {
                    "label": "Author"
                  },
                  "transcriptionMethod": {
                    "label": "Method"
                  },
                  "captionSource": {
                    "label": "Caption Source"
                  },
                  "language": {
                    "label": "Language"
                  },
                  "index": {
                    "label": "Segment #",
                    "format": "number"
                  },
                  "start": {
                    "label": "Start (s)",
                    "format": "number"
                  },
                  "end": {
                    "label": "End (s)",
                    "format": "number"
                  },
                  "text": {
                    "label": "Text"
                  }
                }
              }
            }
          }
        }
      }
    },
    "buildNumber": "1.0.4",
    "imageDigest": "12add783cc2f093ca3c533d4eb0400efbf196ab9270d66059b23225a27abf205",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.011118888888888888
    }
  }
}