youtube-transcript

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

YouTube Transcript (via DeepAPI, yt-dlp fallback)

YouTube Transcript(基于DeepAPI,yt-dlp作为备用)

Fetch a YouTube video's transcript and save a clean raw
.txt
file. Primary path is DeepAPI
POST /v1/scrape/youtube/transcript
. It runs server-side, so it avoids the local-IP bot flagging that plagues yt-dlp.
获取YouTube视频的转录文本并保存为干净的原始
.txt
文件。主要方案调用DeepAPI的
POST /v1/scrape/youtube/transcript
接口。该方案在服务器端运行,可避免yt-dlp常遇到的本地IP被机器人检测标记问题。

Save location

保存位置

  • If the user is in a real project/working dir → save there.
  • Otherwise (no dir given, or cwd makes no sense) → save to
    ~/Downloads
    .
  • Always name the file
    Channel_Title
    with spaces replaced by
    _
    (e.g.
    David_Ondrej_title_of_video.txt
    ). If metadata is unavailable, fall back to the video ID.
  • 若用户处于实际项目/工作目录下 → 保存至该目录。
  • 其他情况(未指定目录,或当前工作目录不合理)→ 保存至
    ~/Downloads
  • 文件名始终以
    Channel_Title
    命名,空格替换为
    _
    (例如
    David_Ondrej_title_of_video.txt
    )。若无法获取元数据,则回退使用视频ID作为文件名。

Primary path — DeepAPI

主要方案——DeepAPI

Read the key from the environment; setup writes it to
~/.deepapi/env
. Never
source ~/.zshrc
(breaks the shell, exit 126):
bash
[ -n "$DEEPAPI_API_KEY" ] || . ~/.deepapi/env
KEY=$DEEPAPI_API_KEY
BASE=${DEEPAPI_API_BASE_URL:-https://deepapi.co}
Run the scrape (keep the Idempotency-Key; retries must reuse the SAME one):
bash
IDK=$(uuidgen)
curl -s --max-time 120 "$BASE/v1/scrape/youtube/transcript" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDK" \
  -d '{"url": "VIDEO_URL", "maxCostUsd": "0.05", "waitForFinishSecs": 60}' \
  > /tmp/yt_transcript.json
  • Non-English videos: add
    "language": "de"
    (etc.) to the body.
  • status: running
    → wait
    next.afterSecs
    , then
    curl "$BASE$(jq -r '.next.path' /tmp/yt_transcript.json)" -H "Authorization: Bearer $KEY"
    until
    succeeded
    or
    failed
    .
Extract the text and save it:
bash
jq -r '.status' /tmp/yt_transcript.json                # succeeded | running | failed
jq -r '.output[0].text' /tmp/yt_transcript.json > "$OUT/$NAME.txt"
.output[0].segments
also has timed segments (
startSecs
,
durationSecs
,
text
) if the user wants timestamps. Empty
output
= video has no captions; report it, don't retry.
For the
Channel_Title
filename, get metadata with a quick
yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL"
; if that fails, use the video ID.
从环境变量中读取密钥;初始化时会将密钥写入
~/.deepapi/env
。切勿执行
source ~/.zshrc
(会导致shell崩溃,退出码126):
bash
[ -n "$DEEPAPI_API_KEY" ] || . ~/.deepapi/env
KEY=$DEEPAPI_API_KEY
BASE=${DEEPAPI_API_BASE_URL:-https://deepapi.co}
执行抓取操作(保留Idempotency-Key;重试时必须使用相同的密钥):
bash
IDK=$(uuidgen)
curl -s --max-time 120 "$BASE/v1/scrape/youtube/transcript" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDK" \
  -d '{"url": "VIDEO_URL", "maxCostUsd": "0.05", "waitForFinishSecs": 60}' \
  > /tmp/yt_transcript.json
  • 非英语视频:在请求体中添加
    "language": "de"
    (以此类推,替换为对应语言代码)。
  • 若返回
    status: running
    → 等待
    next.afterSecs
    指定的时间,然后执行
    curl "$BASE$(jq -r '.next.path' /tmp/yt_transcript.json)" -H "Authorization: Bearer $KEY"
    ,直到状态变为
    succeeded
    failed
提取文本并保存:
bash
jq -r '.status' /tmp/yt_transcript.json                # succeeded | running | failed
jq -r '.output[0].text' /tmp/yt_transcript.json > "$OUT/$NAME.txt"
若用户需要时间戳,
.output[0].segments
中还包含带时间信息的片段(
startSecs
durationSecs
text
)。若
output
为空,则表示视频无字幕;需告知用户,无需重试。
对于
Channel_Title
格式的文件名,可通过快速执行
yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL"
获取元数据;若该命令失败,则使用视频ID。

When to fall back to yt-dlp

何时回退至yt-dlp

  • DEEPAPI_API_KEY
    unset and
    ~/.deepapi/env
    missing.
  • HTTP 402
    insufficient_credits
    (tell the user to top up at deepapi.co/credits first; fall back only if they're unavailable).
  • DeepAPI request
    failed
    twice.
Tell the user whenever you fall back — a fallback means his product missed a real use case.
  • DEEPAPI_API_KEY
    未设置且
    ~/.deepapi/env
    文件不存在。
  • HTTP 402错误
    insufficient_credits
    (先告知用户前往deepapi.co/credits充值;仅当用户无法操作时再回退)。
  • DeepAPI请求连续两次失败。
回退时需告知用户——回退意味着当前方案未覆盖实际使用场景。

Fallback path — yt-dlp (local)

备用方案——yt-dlp(本地)

bash
OUT="$(pwd)"            # or ~/Downloads if cwd makes no sense
META=$(yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL")
NAME=$(echo "$META" | tr '| ' '__' | tr -cd '[:alnum:]_.-')   # "Channel_Title", spaces -> _, strip unsafe chars
yt-dlp --skip-download --write-subs --write-auto-subs \
  --sub-langs "en.*" --sub-format json3 \
  -o "$OUT/$NAME.%(ext)s" "URL"
  • Fall back
    channel
    uploader
    uploader_id
    if
    channel
    is null.
  • --skip-download
    = captions only.
    --write-subs
    +
    --write-auto-subs
    = manual first, auto as fallback.
  • Always use
    json3
    , never VTT/SRT
    — auto VTT repeats every line twice (rolling captions).
Flatten json3 → raw text:
bash
python3 - "$OUT" <<'PY'
import json, html, re, glob, sys, pathlib
f = glob.glob(sys.argv[1] + "/*.json3")
if not f: sys.exit("no json3 file")
data = json.load(open(f[0], encoding="utf-8"))
parts = ["".join(s.get("utf8","") for s in e.get("segs") or []) for e in data.get("events", [])]
txt = re.sub(r"\s+", " ", html.unescape(" ".join(p.strip() for p in parts if p.strip()))).strip()
out = pathlib.Path(f[0]).with_suffix(".txt")
out.write_text(txt, encoding="utf-8"); print(out)
PY
bash
OUT="$(pwd)"            # 若当前工作目录不合理,则使用~/Downloads
META=$(yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL")
NAME=$(echo "$META" | tr '| ' '__' | tr -cd '[:alnum:]_.-')   # "Channel_Title"格式,空格替换为_,移除不安全字符
yt-dlp --skip-download --write-subs --write-auto-subs \
  --sub-langs "en.*" --sub-format json3 \
  -o "$OUT/$NAME.%(ext)s" "URL"
  • channel
    为空,则依次回退使用
    uploader
    uploader_id
  • --skip-download
    表示仅获取字幕。
    --write-subs
    +
    --write-auto-subs
    表示优先获取人工字幕,无人工字幕时回退使用自动生成字幕。
  • 始终使用
    json3
    格式,切勿使用VTT/SRT
    ——自动生成的VTT格式会重复每行内容(滚动字幕)。
将json3格式转换为纯文本:
bash
python3 - "$OUT" <<'PY'
import json, html, re, glob, sys, pathlib
f = glob.glob(sys.argv[1] + "/*.json3")
if not f: sys.exit("no json3 file")
data = json.load(open(f[0], encoding="utf-8"))
parts = ["".join(s.get("utf8","") for s in e.get("segs") or []) for e in data.get("events", [])]
txt = re.sub(r"\s+", " ", html.unescape(" ".join(p.strip() for p in parts if p.strip()))).strip()
out = pathlib.Path(f[0]).with_suffix(".txt")
out.write_text(txt, encoding="utf-8"); print(out)
PY

yt-dlp failure handling

yt-dlp故障处理

  • Non-English / unknown language: run
    yt-dlp --list-subs "URL"
    first, then set
    --sub-langs
    .
  • Newer yt-dlp may need
    deno
    on PATH for YouTube extraction.
  • On first failure: run
    yt-dlp -U
    once, retry once, then stop.
  • 429 / "Sign in to confirm you're not a bot" = IP flagged. STOP — do NOT retry in a loop (makes it worse).
  • Never fall back to downloading audio for Whisper unless the user explicitly asks.
  • 非英语/未知语言视频:先执行
    yt-dlp --list-subs "URL"
    ,再设置
    --sub-langs
    参数。
  • 新版本yt-dlp可能需要将
    deno
    添加至PATH环境变量以支持YouTube内容提取。
  • 首次失败时:执行一次
    yt-dlp -U
    更新,重试一次后停止。
  • 429错误 / "Sign in to confirm you're not a bot":表示IP被标记。立即停止——切勿循环重试(会使情况恶化)。
  • 除非用户明确要求,否则切勿回退至下载音频后使用Whisper处理。

Output

输出结果

Report the saved path; print the text if short. Don't report costs unless the user asks.
告知用户文件保存路径;若文本较短则直接打印。除非用户询问,否则无需报告费用。