
Youtube Ai Digest
- 2 installs
- 54 repo stars
- Updated April 12, 2026
- yizhiyanhua-ai/youtube-ai-digest
Helps with ai & agent building tasks during AI-assisted development.
About
youtube-ai-digest is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- youtube-ai-digest
- AI & Agent Building
- AI-coding skill
Youtube Ai Digest by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,958 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yizhiyanhua-ai/youtube-ai-digest --skill youtube-ai-digestAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 54 |
| Last updated | April 12, 2026 |
| Repository | yizhiyanhua-ai/youtube-ai-digest ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
YouTube AI Digest
Browse AI-related YouTube videos from subscribed channels, fetch transcripts, and generate Chinese summaries as Markdown digests.
Installation
npx skills add yizhiyanhua-ai/youtube-ai-digest -g快速开始
Skill 根目录:~/.claude/skills/youtube-ai-digest/
标准工作流(推荐)
# 第一步:获取最近 3 天的 AI 相关视频
cd ~/.claude/skills/youtube-ai-digest
python scripts/fetch_videos.py --days 3
# 第二步:展示列表,让用户选择感兴趣的视频(编号选择)
# 第三步(单视频):获取字幕并由 Claude 生成摘要
python scripts/get_transcript.py --video-id VIDEO_ID
# 然后读取字幕文件,Claude 对内容做中文总结
# 第三步(批量日报):批量处理所有视频
python scripts/digest_all.py --days 3 --limit 10
# 然后读取生成的日报文件,Claude 对每段字幕生成中文摘要Claude 的 AI 摘要职责
当获取到字幕内容后,Claude 应主动完成以下工作,无需用户再次指示:
1. 阅读字幕全文(或 digest_all.py 生成的日报文件) 2. 对每个视频用中文输出:
- 核心观点(2-3 句话,直接说结论)
- 关键要点(3-5 条 bullet,具体内容而非泛泛描述)
- 值得关注的原因(为什么这个视频重要)
3. 如果字幕质量差或内容重复,如实告知用户
摘要风格:简洁、有信息量,避免"视频介绍了……"这类废话式开头,直接给出内容。
配置频道
编辑 ~/.claude/skills/youtube-ai-digest/data/channels.json:
{
"channels": [
{"name": "Two Minute Papers", "id": "UCbfYPyITQ-7l4upoX8nvctg"},
{"name": "Yannic Kilcher", "id": "UCZHmQk67mN31gbHey6BVyNw"},
{"name": "AI Explained", "id": "UCNJ1Ymd5yFuUPtn21xtRbbw"}
]
}Scripts 说明
fetch_videos.py
获取关注频道最近 N 天的 AI 相关视频(已修复时间过滤,使用 --dateafter)
python scripts/fetch_videos.py --days 3 # 最近 3 天
python scripts/fetch_videos.py --days 7 # 最近 7 天
python scripts/fetch_videos.py --days 3 --all # 不过滤,返回所有视频
python scripts/fetch_videos.py --keyword "GPT" # 自定义关键词输出:data/videos.json(含上传日期、时长、频道等元信息)
get_transcript.py
获取单个视频的字幕
python scripts/get_transcript.py --video-id VIDEO_ID输出:data/transcript_VIDEO_ID.txt(带时间戳)和 .json
digest_all.py(新增)
批量处理所有视频,生成带字幕的日报 Markdown,Claude 读取后完成摘要填写
python scripts/digest_all.py --days 3 # 处理最近 3 天,最多 10 个
python scripts/digest_all.py --days 7 --limit 5 # 只处理 5 个
python scripts/digest_all.py --no-transcript # 只生成列表,不获取字幕输出:data/output/ai_digest_YYYYMMDD.md
generate_report.py
生成单个视频的详细报告(含封面图下载)
python scripts/generate_report.py --video-id VIDEO_ID --output ~/reports/Output Format(日报)
# AI 视频日报 2026-03-22
> 最近 3 天,共 8 个 AI 相关视频
---
## 1. [视频标题]
- **频道**: Two Minute Papers
- **日期**: 2026-03-21
- **链接**: https://youtube.com/watch?v=...
### 内容摘要(Claude 填写)
NVIDIA 的新模型在自动驾驶感知任务上取得突破,核心是……
**关键要点:**
- 使用了 X 架构,解决了 Y 问题
- 在 Z 数据集上超越 SOTA 15%
- 实际部署挑战在于……
**值得关注:** 这是首次在……常见使用场景
| 用户说 | Claude 应该做 |
|---|---|
| "找最近 AI 视频" | fetch_videos.py --days 3,列出结果 |
| "帮我整理成日报" | digest_all.py,然后读文件生成摘要 |
| "总结第 3 个视频" | get_transcript.py 获取字幕,Claude 总结 |
| "最近 Anthropic 相关的" | fetch_videos.py --keyword "anthropic" |
{
"metadata": {
"name": "youtube-ai-digest",
"description": "Claude Code skill for browsing AI-related YouTube videos, fetching transcripts, and generating Markdown reports",
"owner": "yizhiyanhua-ai",
"version": "1.0.0",
"homepage": "https://github.com/yizhiyanhua-ai/youtube-ai-digest"
},
"plugins": [
{
"name": "youtube-ai-digest",
"description": "Browses AI-related YouTube videos from subscribed channels, fetches transcripts, generates summaries, and creates Markdown reports. Use when the user mentions YouTube AI videos, video summaries, channel subscriptions, or asks about recent AI content from YouTube creators.",
"source": "./",
"strict": false,
"version": "1.0.0",
"category": "productivity",
"keywords": [
"youtube",
"ai",
"video",
"transcript",
"summary",
"digest",
"markdown",
"report"
],
"skills": [
"./"
]
}
]
}
# youtube-ai-digest data
data/
!data/channels.json
__pycache__/
*.pyc
.DS_Store
*.png
*.jpg
*.webp
{
"channels": [
{"name": "Two Minute Papers", "id": "UCbfYPyITQ-7l4upoX8nvctg", "handle": "@TwoMinutePapers"},
{"name": "Yannic Kilcher", "id": "UCZHmQk67mN31gbHey6BVyNw", "handle": "@YannicKilcher"},
{"name": "AI Explained", "id": "UCNJ1Ymd5yFuUPtn21xtRbbw", "handle": "@aiexplained-official"},
{"name": "Matt Wolfe", "id": "UCJMQpUgSRkRb5M2MqN2BxSg", "handle": "@mreflow"},
{"name": "The AI Advantage", "id": "UCFg-CGLLDOOsPjnOsMBG14A", "handle": "@theaiadvantage"},
{"name": "Matthew Berman", "id": "UCRI-Ds5eY90nMJjIEi-67JA", "handle": "@matthewberman"},
{"name": "Fireship", "id": "UCsBjURrPoezykLs9EqgamOA", "handle": "@Fireship"},
{"name": "Wes Roth", "id": "UC-jFiJJrIBeb8G0P9MaXJtg", "handle": "@WesRoth"},
{"name": "David Shapiro", "id": "UCnm23dZMHiAfnLwBiPrGaiQ", "handle": "@DavidShapiroAutomator"},
{"name": "TheAIGRID", "id": "UCkGRxjibCl-TzpxPMl4bZjg", "handle": "@TheAIGRID"},
{"name": "WorldofAI", "id": "UCCy7QB8FVDQQJv5IC3uy_HA", "handle": "@WorldofAI"},
{"name": "Prompt Engineering", "id": "UCDq7SjbgRKty5TgGafW8Clg", "handle": "@engineerprompt"},
{"name": "All About AI", "id": "UCVc7zR-zw0H3eyzidN-VXlA", "handle": "@AllAboutAI"},
{"name": "Jeff Su", "id": "UCrXn9a9GXuqWJAMwCHJWQiA", "handle": "@JeffSu"},
{"name": "NetworkChuck", "id": "UC9x0AN7kwET-E1Me2u-ejXA", "handle": "@NetworkChuck"}
]
}
{
"name": "youtube-ai-digest",
"version": "1.0.0",
"description": "Browse, summarize and capture AI-related YouTube videos from subscribed channels. Generates Markdown reports with thumbnails and screenshots.",
"author": "User",
"dependencies": {
"python": ">=3.9",
"pip_packages": ["yt-dlp", "youtube-transcript-api"]
},
"platforms": ["macOS", "Linux"],
"tags": ["youtube", "ai", "summary", "video", "automation"],
"triggers": [
"youtube ai digest",
"summarize youtube",
"browse youtube ai",
"youtube ai summary"
]
}
<div align="center">
📺 YouTube AI Digest
Let Claude track the latest AI developments for you
  
English | 简体中文
---
Auto-browse subscribed channels, fetch transcripts, generate summary reports — never miss any AI highlights
</div>
✨ Features
- 🔍 Smart Fetching — Get latest AI-related videos from subscribed channels
- 📝 Transcript Extraction — Auto-download video subtitles (including auto-generated)
- 📊 Report Generation — Generate structured Markdown reports
- 🖼️ Thumbnail Download — Auto-save video thumbnails
🚀 Quick Start
Installation
Option 1: Install via Claude Code Plugin (Recommended)
# Add marketplace
claude plugin marketplace add https://github.com/yizhiyanhua-ai/youtube-ai-digest
# Install plugin
claude plugin install youtube-ai-digest@youtube-ai-digestOption 2: Manual Clone
# Clone to Claude Code skills directory
git clone https://github.com/yizhiyanhua-ai/youtube-ai-digest.git \
~/.claude/skills/youtube-ai-digestInstall Dependencies
pip install yt-dlpConfigure Channels
Edit data/channels.json to add your subscribed YouTube channels:
{
"channels": [
{"name": "Two Minute Papers", "id": "UCbfYPyITQ-7l4upoX8nvctg"},
{"name": "AI Explained", "id": "UCNJ1Ymd5yFuUPtn21xtRbbw"},
{"name": "Yannic Kilcher", "id": "UCZHmQk67mN31gbHey6BVyNw"}
]
}💡 How to find Channel ID? Open a YouTube channel page, the URL format is youtube.com/channel/{CHANNEL_ID}Usage
Chat with Claude Code directly:
User: What are the latest AI videos?
User: Summarize the first video
User: Create a report with the key takeaways📖 Manual Usage
# 1. Fetch videos from the past 7 days
python scripts/fetch_videos.py --days 7 --keyword AI
# 2. Get transcript for a specific video
python scripts/get_transcript.py --video-id dQw4w9WgXcQ
# 3. Generate Markdown report
python scripts/generate_report.py --video-id dQw4w9WgXcQ --summary "Your summary here"📁 Directory Structure
youtube-ai-digest/
├── .claude-plugin/
│ └── marketplace.json # Plugin marketplace config
├── SKILL.md # Claude Code skill definition
├── README.md # Documentation (Chinese)
├── README.en.md # Documentation (English)
├── scripts/
│ ├── fetch_videos.py # Fetch channel video list
│ ├── get_transcript.py # Download video transcripts
│ └── generate_report.py# Generate Markdown reports
└── data/
├── channels.json # Subscribed channels config
├── videos.json # Video list cache (auto-generated)
└── output/ # Report output directory (auto-generated)📋 Output Example
# Understanding GPT-4's Reasoning

## Video Info
- Channel: AI Explained
- Published: 2024-01-15
- Duration: 12:34
- Link: https://youtube.com/watch?v=...
## Summary
This video provides an in-depth analysis of GPT-4's reasoning capabilities...
## Transcript
[00:00] Welcome back to AI Explained...
[01:30] Today we're going to discuss...🔧 Requirements
| Dependency | Version | Description |
|---|---|---|
| Python | 3.9+ | Runtime environment |
| yt-dlp | latest | YouTube video/subtitle download |
🤝 Contributing
Issues and Pull Requests are welcome!
📄 License
MIT License
---
<div align="center">
If you find this project helpful, please give it a ⭐ Star!
</div>
<div align="center">
📺 YouTube AI Digest
让 Claude 帮你追踪 AI 领域最新动态
  
English | 简体中文
---
自动浏览订阅频道、获取字幕、生成摘要报告 — 再也不错过任何 AI 热点
</div>
✨ 功能特性
- 🔍 智能抓取 — 从订阅频道获取最新 AI 相关视频
- 📝 字幕提取 — 自动下载视频字幕(支持自动生成字幕)
- 📊 报告生成 — 生成结构化的 Markdown 报告
- 🖼️ 缩略图下载 — 自动保存视频封面
🚀 快速开始
安装
方式一:通过 Claude Code Plugin 安装(推荐)
# 添加 marketplace
claude plugin marketplace add https://github.com/yizhiyanhua-ai/youtube-ai-digest
# 安装插件
claude plugin install youtube-ai-digest@youtube-ai-digest方式二:手动克隆
# 克隆到 Claude Code 技能目录
git clone https://github.com/yizhiyanhua-ai/youtube-ai-digest.git \
~/.claude/skills/youtube-ai-digest安装依赖
pip install yt-dlp配置频道
编辑 data/channels.json 添加你关注的 YouTube 频道:
{
"channels": [
{"name": "Two Minute Papers", "id": "UCbfYPyITQ-7l4upoX8nvctg"},
{"name": "AI Explained", "id": "UCNJ1Ymd5yFuUPtn21xtRbbw"},
{"name": "Yannic Kilcher", "id": "UCZHmQk67mN31gbHey6BVyNw"}
]
}💡 如何获取频道 ID? 打开 YouTube 频道页面,URL 格式为 youtube.com/channel/{CHANNEL_ID}使用方式
在 Claude Code 中直接对话:
用户: 今天有什么 AI 新视频?
用户: 总结一下第一个视频
用户: 把这个视频的要点整理成报告📖 手动使用
# 1. 获取最近 7 天的视频列表
python scripts/fetch_videos.py --days 7 --keyword AI
# 2. 获取指定视频的字幕
python scripts/get_transcript.py --video-id dQw4w9WgXcQ
# 3. 生成 Markdown 报告
python scripts/generate_report.py --video-id dQw4w9WgXcQ --summary "视频摘要内容"📁 目录结构
youtube-ai-digest/
├── .claude-plugin/
│ └── marketplace.json # Plugin marketplace 配置
├── SKILL.md # Claude Code 技能定义
├── README.md # 说明文档(中文)
├── README.en.md # 说明文档(英文)
├── scripts/
│ ├── fetch_videos.py # 获取频道视频列表
│ ├── get_transcript.py # 下载视频字幕
│ └── generate_report.py# 生成 Markdown 报告
└── data/
├── channels.json # 订阅频道配置
├── videos.json # 视频列表缓存(自动生成)
└── output/ # 报告输出目录(自动生成)📋 输出示例
# Understanding GPT-4's Reasoning

## 视频信息
- 频道: AI Explained
- 发布时间: 2024-01-15
- 时长: 12:34
- 链接: https://youtube.com/watch?v=...
## 内容摘要
本视频深入分析了 GPT-4 的推理能力...
## 字幕内容
[00:00] Welcome back to AI Explained...
[01:30] Today we're going to discuss...🔧 依赖要求
| 依赖 | 版本 | 说明 |
|---|---|---|
| Python | 3.9+ | 运行环境 |
| yt-dlp | latest | YouTube 视频/字幕下载 |
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
MIT License
---
<div align="center">
如果这个项目对你有帮助,请给个 ⭐ Star!
</div>
#!/usr/bin/env python3
"""获取关注频道的最新视频列表"""
import json
import os
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
import argparse
DATA_DIR = Path(__file__).parent.parent / "data"
CHANNELS_FILE = DATA_DIR / "channels.json"
OUTPUT_FILE = DATA_DIR / "videos.json"
AI_KEYWORDS = [
"ai", "artificial intelligence", "machine learning", "deep learning",
"llm", "gpt", "claude", "gemini", "neural", "openai", "anthropic",
"transformer", "diffusion", "reinforcement", "chatbot", "agent",
"deepmind", "nvidia", "autonomous", "robot", "cursor", "copilot",
"sora", "midjourney", "stable diffusion", "vibe coding"
]
# 传递代理到子进程的环境变量
def get_env():
"""继承当前环境(含 HTTPS_PROXY 等代理变量)"""
return {**os.environ}
def load_channels():
if not CHANNELS_FILE.exists():
return []
with open(CHANNELS_FILE) as f:
return json.load(f).get("channels", [])\
def fetch_channel_videos(channel, days=3):
"""
使用 yt-dlp flat-playlist + approximate_date 获取频道最近视频。
优先用 @handle 格式(能获取到 upload_date),回退到 channel_id。
"""
date_after = (datetime.now() - timedelta(days=days)).strftime("%Y%m%d")
# 优先用 @handle,回退到 channel_id URL
handle = channel.get("handle")
channel_id = channel.get("id")
if handle:
url = f"https://www.youtube.com/{handle}/videos"
else:
url = f"https://www.youtube.com/channel/{channel_id}/videos"
cmd = [
"yt-dlp",
"--flat-playlist",
"--dump-json",
"--playlist-end", "10",
"--extractor-args", "youtubetab:approximate_date", # 关键:让 flat-playlist 返回 upload_date
url
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60,
env=get_env() # 传递代理环境变量
)
videos = []
for line in result.stdout.strip().split("\n"):
if not line:
continue
try:
v = json.loads(line)
except json.JSONDecodeError:
continue
upload_date = v.get("upload_date") or ""
# 日期过滤(approximate_date 是近似值,允许多1天容差)
if upload_date and upload_date < date_after:
continue
videos.append({
"id": v.get("id"),
"title": v.get("title"),
"url": f"https://www.youtube.com/watch?v={v.get('id')}",
"channel_id": channel_id,
"channel_name": channel.get("name"),
"upload_date": upload_date,
"duration": v.get("duration_string", ""),
"view_count": v.get("view_count", 0),
"description": v.get("description", "")[:300] if v.get("description") else "",
})
return videos
except subprocess.TimeoutExpired:
print(f" Timeout: {channel.get('name')}")
return []
except Exception as e:
print(f" Error fetching {channel.get('name')}: {e}")
return []
def is_ai_related(video, extra_keyword=None):
"""判断视频是否与 AI 相关"""
text = (video.get("title", "") + " " + video.get("description", "")).lower()
if extra_keyword:
return extra_keyword.lower() in text
return any(kw in text for kw in AI_KEYWORDS)
def format_date(upload_date):
if len(upload_date) == 8:
return f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:]}"
return upload_date
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--days", type=int, default=3, help="获取最近 N 天的视频")
parser.add_argument("--keyword", default=None, help="额外关键词过滤")
parser.add_argument("--all", action="store_true", help="不过滤,返回所有视频")
args = parser.parse_args()
channels = load_channels()
if not channels:
print("No channels configured. Edit data/channels.json")
return
all_videos = []
for ch in channels:
print(f"Fetching: {ch['name']}...", flush=True)
videos = fetch_channel_videos(ch, args.days)
all_videos.extend(videos)
if videos:
print(f" -> {len(videos)} video(s)")
# 过滤 AI 相关
if args.all:
filtered = all_videos
else:
filtered = [v for v in all_videos if is_ai_related(v, args.keyword)]
# 按上传日期+播放量排序(最新优先,同日按播放量降序)
filtered.sort(key=lambda v: (v.get("upload_date", ""), v.get("view_count", 0)), reverse=True)
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_FILE, "w") as f:
json.dump({
"videos": filtered,
"fetched_at": datetime.now().isoformat(),
"days": args.days,
"total": len(filtered)
}, f, indent=2, ensure_ascii=False)
print(f"\nFound {len(filtered)} AI-related videos (last {args.days} days)")
for i, v in enumerate(filtered, 1):
date = format_date(v.get("upload_date", ""))
vc = f"{v['view_count']:,}" if v.get("view_count") else "?"
print(f" {i}. [{date}] 👁 {vc} | {v['title']} ({v['channel_name']}) {v['duration']}")
print(f" {v['url']}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""生成 Markdown 报告"""
import json
import argparse
import subprocess
from datetime import datetime
from pathlib import Path
DATA_DIR = Path(__file__).parent.parent / "data"
OUTPUT_DIR = DATA_DIR / "output"
def get_video_info(video_id):
"""使用 yt-dlp 获取视频信息"""
cmd = ["yt-dlp", "--dump-json", "--no-download", f"https://www.youtube.com/watch?v={video_id}"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return json.loads(result.stdout)
except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError) as e:
print(f"Error fetching video info: {e}")
return {}
def download_thumbnail(video_id, output_path):
"""下载视频封面"""
cmd = ["yt-dlp", "--write-thumbnail", "--skip-download", "-o", str(output_path / "thumbnail"),
f"https://www.youtube.com/watch?v={video_id}"]
subprocess.run(cmd, capture_output=True)
def generate_markdown(video_id, info, transcript_file, screenshots=None, summary=None):
"""生成 Markdown 报告"""
title = info.get("title", "Unknown")
channel = info.get("channel", "Unknown")
upload_date = info.get("upload_date", "")
duration = info.get("duration_string", "")
url = f"https://www.youtube.com/watch?v={video_id}"
md = f"""# {title}

## 视频信息
- 频道: {channel}
- 发布时间: {upload_date}
- 时长: {duration}
- 链接: {url}
## 内容摘要
{summary or "[请使用 Claude 根据字幕生成摘要]"}
"""
# 添加字幕内容
if transcript_file and Path(transcript_file).exists():
md += "## 字幕内容\n\n"
md += "```\n"
md += Path(transcript_file).read_text()[:3000] # 限制长度
md += "\n```\n\n"
# 添加截图
if screenshots:
md += "## 关键截图\n\n"
for i, ss in enumerate(screenshots, 1):
md += f"\n\n"
return md
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--video-id", required=True)
parser.add_argument("--output", default=str(OUTPUT_DIR))
parser.add_argument("--summary", help="摘要内容")
args = parser.parse_args()
output_dir = Path(args.output) / args.video_id
output_dir.mkdir(parents=True, exist_ok=True)
print(f"获取视频信息: {args.video_id}")
info = get_video_info(args.video_id)
print("下载封面...")
download_thumbnail(args.video_id, output_dir)
transcript_file = DATA_DIR / f"transcript_{args.video_id}.txt"
md = generate_markdown(args.video_id, info, transcript_file, summary=args.summary)
report_file = output_dir / "report.md"
report_file.write_text(md, encoding="utf-8")
print(f"报告已生成: {report_file}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""获取视频字幕 - 使用 yt-dlp,支持代理和 Cookie"""
import json
import os
import argparse
import subprocess
from pathlib import Path
DATA_DIR = Path(__file__).parent.parent / "data"
def get_env():
"""继承当前环境(含 HTTPS_PROXY 等代理变量)"""
return {**os.environ}
def get_transcript_ytdlp(video_id):
"""
使用 yt-dlp 获取字幕。
策略:
1. 先不带 cookie 尝试(部分视频无需登录)
2. 失败后自动加 --cookies-from-browser chrome 重试
"""
url = f"https://www.youtube.com/watch?v={video_id}"
output_template = str(DATA_DIR / f"sub_{video_id}")
base_cmd = [
"yt-dlp", "--skip-download",
"--write-auto-sub", "--write-sub",
"--sub-lang", "en,zh",
"--sub-format", "vtt",
"-o", output_template,
]
# 第一次尝试:不带 cookie
print(" 尝试无 cookie 获取字幕...", flush=True)
subprocess.run(base_cmd + [url], capture_output=True, env=get_env(), timeout=60)
result = _find_vtt(video_id)
if result[0]:
return result
# 第二次尝试:带 Chrome cookie
print(" 重试:使用 Chrome cookie...", flush=True)
subprocess.run(
base_cmd + ["--cookies-from-browser", "chrome", url],
capture_output=True,
env=get_env(),
timeout=60
)
result = _find_vtt(video_id)
if result[0]:
return result
# 第三次尝试:带 Firefox cookie(部分用户用 Firefox 登录 YouTube)
print(" 重试:使用 Firefox cookie...", flush=True)
subprocess.run(
base_cmd + ["--cookies-from-browser", "firefox", url],
capture_output=True,
env=get_env(),
timeout=60
)
return _find_vtt(video_id)
def _find_vtt(video_id):
"""在 data 目录下查找已生成的字幕文件"""
for suffix in [".en.vtt", ".zh.vtt", ".en-orig.vtt", ".zh-Hans.vtt"]:
sub_file = DATA_DIR / f"sub_{video_id}{suffix}"
if sub_file.exists():
lang = suffix.split(".")[1] # "en" / "zh"
return parse_vtt(sub_file), lang
return None, None
def parse_vtt(vtt_file):
"""解析 VTT 字幕文件,去重并合并"""
content = vtt_file.read_text(encoding="utf-8")
lines = content.split("\n")
transcript = []
seen_texts = set()
i = 0
while i < len(lines):
line = lines[i].strip()
if "-->" in line:
parts = line.split("-->")
start_time = parts[0].strip()
time_parts = start_time.replace(",", ".").split(":")
if len(time_parts) == 3:
h, m, s = time_parts
start_seconds = int(h) * 3600 + int(m) * 60 + float(s.split(".")[0])
else:
start_seconds = 0
i += 1
text_lines = []
while i < len(lines) and lines[i].strip() and "-->" not in lines[i]:
text = lines[i].strip()
if not text.isdigit() and "<" not in text and text not in seen_texts:
text_lines.append(text)
seen_texts.add(text)
i += 1
if text_lines:
transcript.append({"start": start_seconds, "text": " ".join(text_lines)})
else:
i += 1
return transcript
def format_transcript(transcript):
"""格式化字幕为带时间戳的纯文本"""
lines = []
for entry in transcript:
start = int(entry["start"])
mins, secs = divmod(start, 60)
lines.append(f"[{mins:02d}:{secs:02d}] {entry['text']}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="获取 YouTube 视频字幕")
parser.add_argument("--video-id", required=True, help="YouTube 视频 ID")
parser.add_argument("--output", help="自定义输出文件路径")
args = parser.parse_args()
print(f"获取字幕: {args.video_id}", flush=True)
transcript, lang = get_transcript_ytdlp(args.video_id)
if not transcript:
print("❌ 无法获取字幕(可能需要登录 YouTube 或视频无字幕)")
return
formatted = format_transcript(transcript)
print(f"✅ 字幕语言: {lang},共 {len(transcript)} 条")
output_file = Path(args.output) if args.output else DATA_DIR / f"transcript_{args.video_id}.txt"
output_file.write_text(formatted, encoding="utf-8")
print(f"已保存到: {output_file}")
json_file = DATA_DIR / f"transcript_{args.video_id}.json"
with open(json_file, "w", encoding="utf-8") as f:
json.dump(transcript, f, ensure_ascii=False, indent=2)
if __name__ == "__main__":
main()