TikTok Video Subtitle & Transcript Extractor
Pull every subtitle track off a TikTok video - auto speech recognition plus any machine-translated languages - as timestamped segments and a plain-text transcript.
Input schema
Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.
| Field | Type | Required | Description |
|---|---|---|---|
postUrls | array | optional | TikTok video URLs to process. Full video URLs, @user/photo URLs, short share links, and bare numeric IDs are accepted. Duplicate IDs are processed once. |
postIds | array | optional | Numeric TikTok video IDs as strings, as an alternative to postUrls. |
languages | array | optional | Optional language filter. Accepts TikTok codes (eng-US, spa-ES) and ISO/English aliases (en, English, es, Spanish). Listed languages missing from the video are requested as best-effort TikTok translations. Leave empty for every track on the default page. |
Show all input fields
| Field | Type | Required | Description |
|---|---|---|---|
postUrls | array | optional | TikTok video URLs to process. Full video URLs, @user/photo URLs, short share links, and bare numeric IDs are accepted. Duplicate IDs are processed once. |
postIds | array | optional | Numeric TikTok video IDs as strings, as an alternative to postUrls. |
languages | array | optional | Optional language filter. Accepts TikTok codes (eng-US, spa-ES) and ISO/English aliases (en, English, es, Spanish). Listed languages missing from the video are requested as best-effort TikTok translations. Leave empty for every track on the default page. |
includeAllTranslations | boolean | optional | Best-effort: request TikTok's machine translation for every supported target language. Coverage varies by video and this adds up to 20 extra requests per video. |
includeVtt | boolean | optional | Include the original or generated WebVTT text in each transcript row. |
includeSrt | boolean | optional | Include generated SRT subtitles in each transcript row. |
useWhisperFallback | boolean | optional | Transcribe videos with no native captions using local Whisper. Slower and opt-in; detected language is best-effort, especially on short clips. |
Download the published input schema
View complete JSON
{
"description": "Extract TikTok subtitles and transcripts as timestamped segments and plain text. One row per language per video.",
"properties": {
"includeAllTranslations": {
"default": false,
"description": "Best-effort: request TikTok's machine translation for every supported target language. Coverage varies by video and this adds up to 20 extra requests per video.",
"prefill": false,
"title": "Include All Translations",
"type": "boolean"
},
"includeSrt": {
"default": false,
"description": "Include generated SRT subtitles in each transcript row.",
"prefill": false,
"title": "Include SRT",
"type": "boolean"
},
"includeVtt": {
"default": false,
"description": "Include the original or generated WebVTT text in each transcript row.",
"prefill": false,
"title": "Include WebVTT",
"type": "boolean"
},
"languages": {
"description": "Optional language filter. Accepts TikTok codes (eng-US, spa-ES) and ISO/English aliases (en, English, es, Spanish). Listed languages missing from the video are requested as best-effort TikTok translations. Leave empty for every track on the default page.",
"editor": "stringList",
"items": {
"type": "string"
},
"prefill": [],
"title": "Languages",
"type": "array"
},
"postIds": {
"description": "Numeric TikTok video IDs as strings, as an alternative to postUrls.",
"editor": "stringList",
"items": {
"type": "string"
},
"title": "Video IDs",
"type": "array"
},
"postUrls": {
"description": "TikTok video URLs to process. Full video URLs, @user/photo URLs, short share links, and bare numeric IDs are accepted. Duplicate IDs are processed once.",
"editor": "stringList",
"items": {
"type": "string"
},
"prefill": [
"https://www.tiktok.com/@asapscience/video/7642449638045256978"
],
"title": "Video URLs",
"type": "array"
},
"useWhisperFallback": {
"default": false,
"description": "Transcribe videos with no native captions using local Whisper. Slower and opt-in; detected language is best-effort, especially on short clips.",
"prefill": false,
"title": "Use Whisper Fallback",
"type": "boolean"
}
},
"required": [],
"schemaVersion": 1,
"title": "TikTok Transcript Scraper Input",
"type": "object"
}
Output schema
Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.
| Field | Type | Required | Description |
|---|---|---|---|
transcripts | string | optional | Dataset containing all transcripts extracted from TikTok videos |
Download the published output schema
View complete JSON
{
"actorOutputSchemaVersion": 1,
"description": "One row per video language with timestamped caption segments, full transcript, source flags, available languages and optional VTT/SRT.",
"properties": {
"transcripts": {
"description": "Dataset containing all transcripts extracted from TikTok videos",
"template": "{{links.apiDefaultDatasetUrl}}/items",
"title": "Scraped Transcripts",
"type": "string"
}
},
"title": "TikTok Video Subtitle & Transcript Extractor Output"
}
Dataset schema
Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.
Download the published dataset schema
View complete JSON
{
"actorSpecification": 1,
"fields": {
"authorId": {
"description": "TikTok author ID",
"title": "Author ID",
"type": "string"
},
"authorVerified": {
"description": "Whether the author is verified",
"title": "Author Verified",
"type": "boolean"
},
"availableLanguages": {
"description": "All subtitle language codes obtained for the video",
"title": "Available Languages",
"type": "array"
},
"caption": {
"description": "Video description or caption",
"title": "Caption",
"type": "string"
},
"collectCount": {
"description": "Video save count",
"title": "Saves",
"type": "integer"
},
"commentCount": {
"description": "Video comment count",
"title": "Comments",
"type": "integer"
},
"createdAt": {
"description": "Video publish time (ISO 8601 UTC)",
"title": "Created At",
"type": "string"
},
"displayName": {
"description": "Author's display name",
"title": "Display Name",
"type": "string"
},
"duration": {
"description": "Video duration in seconds",
"title": "Duration",
"type": "integer"
},
"isAutoGenerated": {
"description": "Whether the track is machine-generated",
"title": "Automatic",
"type": "boolean"
},
"isOriginalCaption": {
"description": "Whether the track is the video's original language",
"title": "Original Caption",
"type": "boolean"
},
"isTranslation": {
"description": "Whether the track is a machine translation",
"title": "Translation",
"type": "boolean"
},
"languageCode": {
"description": "TikTok subtitle language code (e.g. eng-US, spa-ES)",
"title": "Language Code",
"type": "string"
},
"languageId": {
"description": "TikTok internal language ID",
"title": "Language ID",
"type": "string"
},
"likeCount": {
"description": "Video like count",
"title": "Likes",
"type": "integer"
},
"originalLanguageCode": {
"description": "TikTok's original spoken-language code (e.g. en, ur, my)",
"title": "Original Language",
"type": "string"
},
"playCount": {
"description": "Video play count",
"title": "Plays",
"type": "integer"
},
"postId": {
"description": "Unique TikTok video ID",
"title": "Post ID",
"type": "string"
},
"postType": {
"description": "video or slideshow",
"title": "Post Type",
"type": "string"
},
"postUrl": {
"description": "Canonical TikTok video URL",
"title": "Post URL",
"type": "string"
},
"scrapedAt": {
"description": "Collection time (ISO 8601 UTC)",
"title": "Scraped At",
"type": "string"
},
"segmentCount": {
"description": "Number of subtitle segments",
"title": "Segment Count",
"type": "integer"
},
"segments": {
"description": "Timestamped subtitle segments",
"items": {
"properties": {
"end": {
"description": "End time in seconds",
"type": "number"
},
"start": {
"description": "Start time in seconds",
"type": "number"
},
"text": {
"description": "Segment text",
"type": "string"
}
},
"type": "object"
},
"title": "Segments",
"type": "array"
},
"shareCount": {
"description": "Video share count",
"title": "Shares",
"type": "integer"
},
"source": {
"description": "Raw source code: ASR, MT, LC, MU or Whisper",
"title": "Source",
"type": "string"
},
"sourceType": {
"description": "automatic, machine_translation, creator or unknown",
"title": "Source Type",
"type": "string"
},
"srt": {
"description": "Generated SRT subtitles (when enabled)",
"title": "SRT",
"type": "string"
},
"subtitleFormat": {
"description": "webvtt, creator_caption, whisper_asr",
"title": "Subtitle Format",
"type": "string"
},
"subtitleUrl": {
"description": "Direct signed subtitle file URL (expires in about 48 hours)",
"title": "Subtitle URL",
"type": "string"
},
"subtitleUrlExpiresAt": {
"description": "Expiry of subtitleUrl (ISO 8601 UTC)",
"title": "Subtitle URL Expires At",
"type": "string"
},
"transcript": {
"description": "Full transcript as plain text",
"title": "Transcript",
"type": "string"
},
"transcriptDuration": {
"description": "End time of the last segment in seconds",
"title": "Transcript Duration",
"type": "number"
},
"username": {
"description": "Author's TikTok username",
"title": "Username",
"type": "string"
},
"variant": {
"description": "TikTok subtitle variant",
"title": "Variant",
"type": "string"
},
"version": {
"description": "TikTok subtitle version string",
"title": "Version",
"type": "string"
},
"vtt": {
"description": "Original or generated WebVTT (when enabled)",
"title": "WebVTT",
"type": "string"
},
"wordCount": {
"description": "Approximate whitespace-delimited word count",
"title": "Word Count",
"type": "integer"
}
},
"title": "TikTok Transcripts",
"views": {
"manualCaptions": {
"display": {
"component": "table",
"properties": {
"languageCode": {
"format": "text",
"label": "Language"
},
"postUrl": {
"format": "link",
"label": "Post URL"
},
"segmentCount": {
"format": "number",
"label": "Segments"
},
"sourceType": {
"format": "text",
"label": "Type"
},
"transcript": {
"format": "text",
"label": "Transcript"
},
"username": {
"format": "text",
"label": "Username"
}
}
},
"title": "Creator Captions",
"transformation": {
"fields": [
"username",
"languageCode",
"sourceType",
"segmentCount",
"transcript",
"postUrl"
]
}
},
"overview": {
"display": {
"component": "table",
"properties": {
"duration": {
"format": "number",
"label": "Duration (s)"
},
"languageCode": {
"format": "text",
"label": "Language"
},
"postUrl": {
"format": "link",
"label": "Post URL"
},
"segmentCount": {
"format": "number",
"label": "Segments"
},
"source": {
"format": "text",
"label": "Source"
},
"transcript": {
"format": "text",
"label": "Transcript"
},
"username": {
"format": "text",
"label": "Username"
}
}
},
"title": "Transcripts",
"transformation": {
"fields": [
"username",
"languageCode",
"source",
"segmentCount",
"duration",
"transcript",
"postUrl"
]
}
},
"translated": {
"display": {
"component": "table",
"properties": {
"isTranslation": {
"format": "text",
"label": "Translation"
},
"languageCode": {
"format": "text",
"label": "Language"
},
"postUrl": {
"format": "link",
"label": "Post URL"
},
"source": {
"format": "text",
"label": "Source"
},
"transcript": {
"format": "text",
"label": "Transcript"
},
"username": {
"format": "text",
"label": "Username"
}
}
},
"title": "Translations",
"transformation": {
"fields": [
"username",
"languageCode",
"source",
"isTranslation",
"transcript",
"postUrl"
]
}
},
"withFiles": {
"display": {
"component": "table",
"properties": {
"languageCode": {
"format": "text",
"label": "Language"
},
"postUrl": {
"format": "link",
"label": "Post URL"
},
"srt": {
"format": "text",
"label": "SRT"
},
"username": {
"format": "text",
"label": "Username"
},
"vtt": {
"format": "text",
"label": "WebVTT"
}
}
},
"title": "With VTT / SRT",
"transformation": {
"fields": [
"username",
"languageCode",
"vtt",
"srt",
"postUrl"
]
}
}
}
}
Key-value schema
Published key-value store definitions, when provided by this build.
This definition is not present in the default published build. See the published README for available documentation.
Published README
Read the full published documentation
TikTok Video Subtitle & Transcript Extractor
Pull the captions TikTok already has for a public video — automatic speech recognition, the creator's own caption track, and TikTok's machine translations — as timestamped segments and a clean plain-text transcript. Paste a batch of video URLs or IDs and get one row per language, with source flags, available languages, video metadata and optional VTT/SRT output. No account, login, cookies, or proxy setup.
What this actor does
- Accepts full TikTok video URLs,
@user/photoURLs, short share links, and bare numeric video IDs; duplicates are processed once - Returns one row per language, not one row per raw caption file
- Reads both WebVTT captions and TikTok's creator-caption JSON format, so manually authored captions are not lost
- Classifies every track from TikTok's raw labels as automatic, machine-translated, or creator-provided, with
isAutoGenerated,isOriginalCaption, andisTranslation - Lists
availableLanguageson every row and keeps the original language first - Includes the video's author, caption, publish date, duration, post type, and engagement counts
- Can request TikTok's machine translations for listed languages (
languages) or all 20 supported targets (includeAllTranslations); coverage varies by video and is best-effort - Can include ready-to-use
vttandsrtfields - Can transcribe videos that have no captions with an optional local Whisper fallback; language detection is best-effort
- Empty fields are omitted; the dataset never contains nulls, empty strings, or placeholder rows
Output per transcript row
postId— TikTok video IDpostUrl— canonical video URLusername— author's TikTok usernamedisplayName— author's display namecaption— video description or captioncreatedAt— publish timestamp (ISO 8601 UTC)duration— video duration in secondspostType—videoorslideshowauthorId— TikTok author IDauthorVerified— whether the author is verifiedplayCount,likeCount,commentCount,shareCount,collectCount— engagement countslanguageCode— TikTok subtitle language code (e.g.eng-US,spa-ES,urd-PK)languageId— TikTok's internal language IDoriginalLanguageCode— the video's original spoken-language code (e.g.en,ur,my)availableLanguages— every subtitle language obtained for the videosubtitleFormat—webvtt,creator_caption, orwhisper_asrsource— raw source code:ASR,MT,LC,MU, orWhispersourceType—automatic,machine_translation,creator, orunknownisAutoGenerated,isOriginalCaption,isTranslation— TikTok's flags, copied per trackversion,variant— TikTok's subtitle version and variant labelssubtitleUrl— direct signed subtitle file URL (expires)subtitleUrlExpiresAt— expiry of that URL (ISO 8601 UTC)segments—[{ "start": 0.0, "end": 2.5, "text": "..." }, ...]with secondssegmentCount— number of cuestranscript— full joined plain-text transcripttranscriptDuration— end time of the last cue in secondswordCount— approximate whitespace-delimited word countvtt,srt— optional subtitle files when those toggles are onscrapedAt— collection time (ISO 8601 UTC)
Input
| Field | Type | Default | Description |
|---|---|---|---|
postUrls |
array of strings | — | TikTok video URLs, photo URLs, short share links, or bare IDs. Duplicates are processed once. |
postIds |
array of strings | — | Numeric video IDs as an alternative to postUrls. |
languages |
array of strings | – | Languages to keep. Accepts TikTok codes (eng-US, spa-ES) and ISO/English aliases (en, English, es). Missing languages that TikTok can translate are requested best-effort (20 targets total); other values only filter. A filter that matches nothing yields no row and is reported as no_transcript. Empty means every track on the default page. |
includeAllTranslations |
boolean | false |
Request TikTok's machine translation for every supported target language. Best-effort and slower; not every target exists for every video. |
includeVtt |
boolean | false |
Include WebVTT text in each row. |
includeSrt |
boolean | false |
Include generated SRT text in each row. |
useWhisperFallback |
boolean | false |
Transcribe videos with no native captions using local Whisper. Roughly 1–3 minutes per short video including model load (longer videos add transcription time); detected language is best-effort. |
Example: all native tracks for a video
{
"postUrls": ["https://www.tiktok.com/@natgeo/video/7637581966396656909"]
}
Example: manual and translated captions
{
"postUrls": [
"https://www.tiktok.com/@asapscience/video/7642449638045256978",
"https://www.tiktok.com/@blingbeauty85/video/7631960233966832918"
],
"includeVtt": true,
"includeSrt": true
}
Example: only Spanish, with a best-effort translation
{
"postUrls": ["https://www.tiktok.com/@natgeo/video/7637581966396656909"],
"languages": ["es"]
}
Example: every available translation
{
"postIds": ["7631960233966832918"],
"includeAllTranslations": true,
"includeVtt": true
}
Example: no-caption video with Whisper
{
"postUrls": ["https://www.tiktok.com/@_readultery_/video/7544740407691791623"],
"useWhisperFallback": true
}
Use cases
- Content repurposing — turn a batch of TikToks into blog posts, newsletters, or scripts using the transcript field
- Multi-language research — compare the original wording with TikTok's translations across markets
- Accessibility and compliance — archive captions and timed segments for audits or caption-quality reviews
- Social listening — search what creators actually say, beyond the text caption
- Translation and dubbing pipelines — feed timestamped segments into translation or voice tools
- Creator research — collect every video's available languages and caption provenance at scale
FAQ
Do I need a TikTok account or cookies? No. The actor reads public pages and public subtitle files without authentication.
Why does one video produce more rows than another? One row is emitted per language obtained. A video with only its original caption produces one row; a video with an original track plus a translation produces two. Slideshows usually have no caption track, and videos without captions produce no rows unless Whisper is enabled. Caption coverage is not universal: in an independent October 2026 sample of 391 public videos across niches, 47.6% had at least one native caption track (51.8% excluding slideshows).
What is the difference between ASR, MT, LC, and MU?
ASR is TikTok's automatic speech recognition. MT is normally a machine translation. LC is TikTok's label on the creator-caption tracks seen in this actor; MU is another label TikTok uses on the converted WebVTT twin of those tracks. Because those raw labels are not perfectly documented, use sourceType, isAutoGenerated, isOriginalCaption, and isTranslation for filtering. A small number of original-language tracks are labelled MT by TikTok (version whisper_lid); the actor preserves that raw label, so isOriginalCaption can be true on the same row as isTranslation. Use originalLanguageCode/languageCode to identify the original.
Why do some translations not appear? TikTok does not precompute every target language for every video. The actor requests English and Spanish through TikTok's translation endpoint and a fixed set of 18 more targets by refetching the video with the requested locale. Targets outside that set (for example German or Japanese) are not requested, even if TikTok's player lists them. The actor reports only the languages it actually obtained and never fabricates a track.
Why is subtitleUrl missing or expired?
Native subtitle files are signed links that usually expire in about 48 hours. Links returned by TikTok's translation endpoint expire in about 6 hours. subtitleUrlExpiresAt gives the exact expiry for each row. The parsed segments and transcript are stored in the dataset permanently; use includeVtt or includeSrt if you need a portable file.
What happens when a video has no captions?
No dataset row is added by default. The run status and the ERRORS key-value record report no_transcript. If a languages filter removes every available track, the video is also reported as no_transcript. Enable useWhisperFallback to transcribe the audio instead. Other ERRORS reasons are invalid, private, unavailable, blocked, fetch_failed, parse_failed, and translation_unavailable.
Does Whisper detect the language reliably? Not always. It is best on clear, longer speech; short or mixed-language clips can be mislabeled. The detected code is reported on the row and should be treated as a hint.
Can I scrape private, deleted, or region-restricted videos? No. Those videos do not expose a public video record, so they are reported as unavailable and no row is added.
How fresh is the data? Captions, metadata, and translation availability are read when the run executes. A video may gain captions or translations later; run it again to pick them up.
Is this affiliated with TikTok? No. This is a third-party actor using publicly available TikTok pages and subtitle files.
Python quickstart
Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.
Source build sV6PG2PO8kfbf5pyj. Documentation is regenerated from the default published build; private drafts are not included.