DEV Community

meng xipeng
meng xipeng

Posted on Edited on

How to Transcribe Douyin (抖音) Videos to Chinese Text Without an API Key

Douyin (抖音) doesn't publish subtitles in its web data, so if you want the words spoken in a video you have to run speech recognition yourself. Most open-source Douyin "video to text" projects chain together a video downloader, ffmpeg and a paid speech API (DashScope, SiliconFlow and so on), and they tend to break whenever Douyin changes its cookies.

This tutorial shows two ways to get timestamped Chinese transcripts with punctuation without any of that: a no-code run and a few lines of Python. Then a do-it-yourself section on running the speech model on your own machine, with the accuracy comparison I measured between Whisper and SenseVoice on real Douyin narration.

Disclosure: I built the Apify actor used in options 1 and 2 (Douyin Transcript Scraper). The DIY section works without it.

What you get per video

One row per video. Real values from a run on 26 Oct 2026, an 11-minute movie-explainer video (电影解说) by 心影说:

Field Example
url, videoId https://www.douyin.com/video/7696934197226786058
authorNickname 心影说
description 法律迟迟不肯给出公道,八旬老兵该如何讨回尊严? #伏罗希洛夫射手 #电影解说
createdAt 2026-10-17T09:00:00Z
durationSeconds 664.7
likeCount, commentCount, shareCount, collectCount 163, 2, 10, 21
language zh
characterCount 4031
hook the opening sentence (segments starting in the first 3 seconds)
transcript timed segments [{"start": 0.07, "end": 17.94, "text": "老人架起狙击步枪,…"}]
musicTitle, audioSource @心影说创作的原声, original_sound_mp3

Option 1: No code (Apify Console)

  1. Open the actor page and click Try for free. Apify's free plan includes monthly platform credit; the actor itself is pay-per-event (see the Store page for the current price), and videos without speech aren't charged.
  2. Paste Douyin videos (douyin.com/video/… links, v.douyin.com/… share links or numeric IDs), or add creators by profile link (douyin.com/user/MS4wLjAB…).
  3. Pick the Transcript format: timed segments, plain text or both.
  4. Click Start and export the results as CSV, JSON or Excel.

For creators, the actor transcribes their newest videos up to Max videos per creator. Douyin shows logged-out visitors roughly the 40 newest videos of an account, so for older videos paste their links directly.

Option 2: Python

pip install apify-client, then take your API token from Apify Console → Settings → Integrations.

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("axlymxp/douyin-transcript-scraper").call(run_input={
    "videoUrls": ["https://www.douyin.com/video/7696934197226786058"],
    "profileUrls": [
        "https://www.douyin.com/user/MS4wLjABAAAAOzRPcYBhm0xO0mXI5UGjHc2udHifSV-6xuI5mpo_UNKFN6qLVzfWb4QMT8BTOk2n",
    ],
    "maxVideosPerProfile": 5,
    "outputFormat": "both",        # segments in `transcript`, joined text in `transcriptText`
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["error"]:              # no_speech, not_available, too_long, ...
        print("skip", item["videoId"], item["error"])
        continue
    print(item["authorNickname"], item["likeCount"], item["characterCount"], "chars")
    print("  ", item["transcriptText"][:80])
Enter fullscreen mode Exit fullscreen mode

A natural next step is to feed transcriptText to an LLM for an English summary or translation.

Example output

One real row (trimmed to two segments; coverUrl removed):

{
  "videoId": "7696934197226786058",
  "url": "https://www.douyin.com/video/7696934197226786058",
  "authorNickname": "心影说",
  "description": "法律迟迟不肯给出公道,八旬老兵该如何讨回尊严? #伏罗希洛夫射手 #电影解说",
  "createdAt": "2026-10-17T09:00:00Z",
  "durationSeconds": 664.7,
  "likeCount": 163,
  "language": "zh",
  "audioSource": "original_sound_mp3",
  "characterCount": 4031,
  "transcript": [
    {"start": 0.07, "end": 17.94, "text": "老人架起狙击步枪,枪口牢牢锁定了对面的三个败类,但一枪爆头的死法显然只会便宜了这几个人渣。他随即调整枪口的角度,对准了坐在正中间的那人。那小子正用双腿夹着香槟酒瓶。就在他拔出瓶塞的瞬间,老人猛的扣下了扳机。"},
    {"start": 19.3, "end": 41.44, "text": "玻璃碎片混着鲜血,四下飞溅,不但将现场伪装成了意外爆炸,还顺带废掉了这家伙的作案工具。随后,老人迅速把狙击枪锁进柜子,套上外套,捡起地上的弹壳,神色自若的走下楼,悄无声息的混进了人群之中。…"}
  ],
  "error": null
}
Enter fullscreen mode Exit fullscreen mode

DIY: transcribe Mandarin audio yourself (no API key)

If you already have the audio (your own videos, or files you downloaded), you can run a strong Mandarin speech model on a normal CPU.

Whisper or SenseVoice?

I ran the same 60-second clip of that video's narration through three models on a single cloud vCPU:

Model Time for 60 s What came out
Whisper base 11.5 s Garbled homophones: 扳机 (trigger) became 半斤, 狙击步枪 (sniper rifle) became 嫁起居及不强; traditional and simplified characters mixed
Whisper small 37 s Much better, but in traditional characters, and on another clip it inserted a Korean phrase that nobody said
SenseVoice-Small 9.3 s Simplified Chinese with punctuation; 扳机, 狙击步枪, 神色自若, 悄无声息 all correct

SenseVoice (from Alibaba's FunAudioLLM team) also covers Cantonese, English, Japanese and Korean. The easiest way to run it on CPU is sherpa-onnx: pip install sherpa-onnx av numpy, then download sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2 and silero_vad.onnx from the sherpa-onnx asr-models release.

import av, numpy as np, sherpa_onnx

SV = "sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17"

def load_audio(path, rate=16000):
    """Any audio/video file -> 16 kHz mono float32 (PyAV, no ffmpeg binary needed)."""
    chunks = []
    with av.open(path) as c:
        rs = av.AudioResampler(format="s16", layout="mono", rate=rate)
        for frame in c.decode(audio=0):
            chunks += [f.to_ndarray().reshape(-1) for f in rs.resample(frame)]
        chunks += [f.to_ndarray().reshape(-1) for f in rs.resample(None)]
    return np.concatenate(chunks).astype(np.float32) / 32768.0

recognizer = sherpa_onnx.OfflineRecognizer.from_sense_voice(
    model=f"{SV}/model.int8.onnx", tokens=f"{SV}/tokens.txt",
    num_threads=1, use_itn=True, language="auto")   # use_itn adds punctuation

vad_cfg = sherpa_onnx.VadModelConfig()
vad_cfg.silero_vad.model = "silero_vad.onnx"
vad_cfg.silero_vad.max_speech_duration = 20        # target length; it cuts at the next pause, so some run longer
vad_cfg.sample_rate = 16000
vad = sherpa_onnx.VoiceActivityDetector(vad_cfg, buffer_size_in_seconds=60)

samples = load_audio("my_video.mp3")
window = vad_cfg.silero_vad.window_size
segments = []
for i in range(0, len(samples) + window, window):
    if i < len(samples):
        vad.accept_waveform(samples[i:i + window])
    else:
        vad.flush()
    while not vad.empty():
        segments.append((vad.front.start / 16000, np.array(vad.front.samples, dtype=np.float32)))
        vad.pop()

for start, seg in segments:                         # one segment per decode call (see below)
    stream = recognizer.create_stream()
    stream.accept_waveform(16000, seg)
    recognizer.decode_stream(stream)
    print(f"[{start:7.2f}] {stream.result.text}")
Enter fullscreen mode Exit fullscreen mode

Two things that cost me time:

  • Cut the audio into short segments. On a single 60-second chunk, SenseVoice dropped words in one of three clips. Splitting with voice-activity detection into shorter pieces (most of mine ended up 15-25 seconds) fixed that, and gives you timestamps for free.
  • Don't batch segments of different lengths. sherpa-onnx can decode a list of streams at once (decode_streams), but the batch is padded to its longest segment. On one vCPU that made transcription about 2.5x slower than decoding one segment at a time: roughly 19 s of CPU per audio minute instead of 7.7 s.

Getting the audio out of Douyin is the other half of the job: the video and its download links come from Douyin's signed web API, and the files are big (an 11-minute explainer is 150-210 MB as video). When a video uses the creator's original sound, its audio-only MP3 is much smaller (17-29 MB for 11-17 minutes), which is what the actor downloads.

FAQ

Do I need a Douyin account? No. Only public videos are read, logged out.

Does Douyin provide subtitles? Not in its public web data; every transcript is generated from the audio.

How accurate is it? Clear Mandarin narration comes out very well. Background music, dialects, heavy slang or several people talking at once lower accuracy, and song lyrics are transcribed like speech.

Traditional characters? SenseVoice outputs simplified Chinese; convert with a library such as OpenCC if you need traditional.

Is it legal? These are public videos read without logging in. How you use the transcripts is on you: respect Douyin's terms, copyright and privacy law.

Questions or a video that won't work? Open an issue on the actor page and I'll take a look.

Top comments (0)