
Yt Search Download
- 725 installs
- 113 repo stars
- Updated March 6, 2026
- joeseesun/yt-search-download
yt-search-download is an agent automation skill that searches YouTube via Data API v3 and downloads videos, audio, subtitles, and metadata with yt-dlp for developers building media ingestion workflows.
About
yt-search-download is a Claude agent skill combining YouTube Data API v3 for advanced search with yt-dlp for media extraction. Developers can run keyword search, channel browsing, and sorting by time, views, or relevance, then download full videos, extract MP3 audio, or pull Chinese and English subtitles plus detailed video metadata. The skill triggers on YouTube, YT, or 油管 mentions paired with search, download, subtitle, or channel intents across agent conversations. It centralizes six capability areas—search, channel browse, video download, audio extraction, subtitle capture, and video info—behind one skill entry point. Teams reach for it when agents must fetch reference talks, transcribe conference footage, or archive channel updates without leaving the coding session.
- Combines YouTube Data API v3 for advanced keyword and channel search with yt-dlp for reliable downloads.
- Supports full-site keyword search, channel browsing, sorting by time, view count or relevance.
- Downloads video, extracts MP3 audio, or grabs Chinese/English subtitles in one command.
- Returns rich video details including duration, view stats and metadata.
- Keyword-triggered: activates on any message containing YouTube/YT/油管 plus search, download, subtitle or channel actions.
Yt Search Download by the numbers
- 725 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,401 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joeseesun/yt-search-download --skill yt-search-downloadAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 725 |
|---|---|
| repo stars | ★ 113 |
| Security audit | 2 / 3 scanners passed |
| Last updated | March 6, 2026 |
| Repository | joeseesun/yt-search-download ↗ |
How do you search and download YouTube videos from an agent?
Instantly search YouTube, download videos, extract audio, subtitles, or fetch video metadata directly from Claude-powered agents.
Who is it for?
Developers automating YouTube search, download, subtitle extraction, or metadata lookup inside Claude-powered agent workflows.
Skip if: Production video hosting pipelines, copyright-sensitive bulk archiving without API compliance review, or tasks unrelated to YouTube media ingestion.
When should I use this skill?
A user message mentions YouTube, YT, or 油管 with search, download, subtitle, channel browse, or video metadata requests.
What you get
Downloaded video or audio files, subtitle tracks, channel listings, and structured YouTube metadata from search or direct URLs.
- downloaded media files
- subtitle files
- video metadata JSON
By the numbers
- Covers 6 core capability areas: search, channel browse, video download, audio, subtitles, and metadata
Files
YouTube 搜索 & 下载
基于 YouTube Data API v3 进行高级搜索,配合 yt-dlp 下载。
前置条件
1. YouTube API Key:
echo $YT_BROWSE_API_KEY如果为空:Google Cloud Console → 启用 YouTube Data API v3 → 创建 API Key → 写入 ~/.zshrc:
export YT_BROWSE_API_KEY=your_key2. yt-dlp(下载用):
brew install yt-dlp # macOS
pip install yt-dlp # 或 pip 安装命令说明
脚本路径:~/.claude/skills/yt-search-download/scripts/yt_search.py
全站关键词搜索
python3 scripts/yt_search.py search "关键词" -n 20| 参数 | 说明 |
|---|---|
-n 20 | 最多返回条数(默认 20) |
-o date | 按时间排序(默认 relevance) |
-o viewCount | 按播放量排序(API 级,准确) |
--sort-by views | 本地二次排序(按播放量降序) |
--sort-by duration-asc | 本地排序:时长从短到长 |
--sort-by duration-desc | 本地排序:时长从长到短 |
--min-duration 30m | 过滤:最短时长(支持 30m、1h、1h30m、纯数字=分钟) |
--max-duration 1h | 过滤:最长时长 |
--after 2024-01-01 | 发布时间起 |
--before 2024-12-31 | 发布时间止 |
-c @handle | 限定频道 |
-d | 显示简介 |
--json | JSON 格式输出 |
浏览频道视频
# 频道最新视频(按时间倒序)
python3 scripts/yt_search.py channel @channelHandle -n 10
# 频道内关键词搜索
python3 scripts/yt_search.py channel @channelHandle -q "关键词"
# 频道内按播放量排序
python3 scripts/yt_search.py channel @channelHandle -o viewCount
# 只看长视频(超过 1 小时)
python3 scripts/yt_search.py channel @channelHandle --min-duration 1h
# 只看短视频(30 分钟内),按时长升序
python3 scripts/yt_search.py channel @channelHandle --max-duration 30m --sort-by duration-asc频道格式支持:@handle、https://youtube.com/@handle、频道 ID(UCxxxx)
下载视频
# 最佳画质下载到 ~/Downloads
python3 scripts/yt_search.py download "VIDEO_URL"
# 指定画质
python3 scripts/yt_search.py download "VIDEO_URL" -q 1080p
# 指定目录
python3 scripts/yt_search.py download "VIDEO_URL" --dir ~/Desktop
# 仅下载音频(MP3)
python3 scripts/yt_search.py download "VIDEO_URL" --audio-only视频详情查询
python3 scripts/yt_search.py info "VIDEO_URL"输出格式(Markdown 表格)
脚本已输出 Markdown 表格,AI 必须将每行的 `【译】___` 替换为实际中文翻译后再呈现给用户。
最终呈现效果:
| # | 标题(原文 → 中文译文) | 日期 | 时长 | 播放量 |
|---|---|---|---|---|
| 1 | Rick Beato: Greatest Guitarists...<br>里克·贝阿托:史上最伟大的吉他手 | 2026-03-01 | 2h33m | 302.2K |
| 2 | State of AI in 2026: LLMs, Coding...<br>2026年AI现状:大模型、编程、Scaling法则 | 2026-01-31 | 4h25m | 741.7K |
🔴 强制规范:
- ✅ 保留原英文标题(作为可点击链接)
- ✅
【译】___替换为简洁中文译文(放在链接后同一单元格) - ✅ 所有视频逐行翻译,不得跳过
- ❌ 不询问用户是否需要翻译(直接翻译)
- ❌ 不把
【译】___原样输出给用户
典型工作流
找某频道最新视频并下载: 1. channel @handle -n 10 → 浏览结果 2. 问用户要下载哪个 3. download "URL" → 保存到 ~/Downloads
搜索 + 按播放量筛选: 1. search "关键词" -o viewCount -n 20
提取播客音频: 1. search "播客名" -o date -n 5 2. download "URL" --audio-only
下载字幕(默认同时输出 SRT + TXT)
标准流程:下载字幕转为 SRT,同时生成保留时间戳的 TXT(供 AI 总结用,时间戳有助于定位内容)。
# Step 1:下载字幕并转为 SRT(英文优先,无则用自动字幕)
yt-dlp --cookies-from-browser chrome \
--write-auto-sub --write-sub \
--sub-lang en,zh-Hans \
--convert-subs srt \
--skip-download \
-o "~/Downloads/%(title)s.%(ext)s" \
"VIDEO_URL"
# 输出:~/Downloads/视频标题.en.srt 或 .zh-Hans.srt
# Step 2:从 SRT 生成 TXT(保留时间戳,仅去除序号和空行,供 AI 总结使用)
python3 -c "
import re, sys
srt = open(sys.argv[1]).read()
# 去除序号行(纯数字行),保留时间戳和字幕文本
txt = re.sub(r'^\d+\s*\n', '', srt, flags=re.MULTILINE)
txt = re.sub(r'\n{3,}', '\n\n', txt).strip()
txt_path = sys.argv[1].replace('.srt', '.txt')
open(txt_path, 'w').write(txt)
print(f'已保存:{txt_path}')
" ~/Downloads/视频标题.en.srt
# 输出:~/Downloads/视频标题.en.txt(格式:时间戳 + 字幕文本)TXT 格式示例(保留时间戳,便于 AI 总结时引用具体时间点):
00:00:01,000 --> 00:00:04,000
Welcome to the Lex Fridman podcast.
00:00:05,000 --> 00:00:09,000
Today we're talking about the greatest guitarists of all time.其他场景:
# 仅中文字幕(SRT + TXT)
yt-dlp --cookies-from-browser chrome --write-auto-sub --write-sub \
--sub-lang zh-Hans --convert-subs srt --skip-download \
-o "~/Downloads/%(title)s.%(ext)s" "VIDEO_URL"
# 字幕 + 视频一起下载
yt-dlp --cookies-from-browser chrome --write-auto-sub --write-sub \
--sub-lang en --convert-subs srt \
-o "~/Downloads/%(title)s.%(ext)s" "VIDEO_URL"规范:下载字幕时始终加 `--convert-subs srt`,下载完成后始终执行 Step 2 生成 TXT,让用户同时拿到.srt(带时间轴)和.txt(纯文本)两个文件。
高级用法(直接用 yt-dlp)
# 列出可用格式
yt-dlp --cookies-from-browser chrome -F "VIDEO_URL"
# 下载整个播放列表
yt-dlp --cookies-from-browser chrome -o "~/Downloads/%(playlist_title)s/%(title)s.%(ext)s" "PLAYLIST_URL"MIT License
Copyright (c) 2026 Joe Sun (@joeseesun)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
yt-search-download
YouTube 视频搜索、下载、字幕提取工具 — Claude Code Skill
基于 YouTube Data API v3 进行高级搜索,配合 yt-dlp 下载视频/音频/字幕,自动翻译英文标题为中文。
功能
- 搜索:全站关键词搜索,支持按时间 / 播放量 / 相关度排序
- 频道浏览:浏览指定频道最新视频,支持频道内关键词搜索
- 下载视频:支持多种画质(最高 4K),指定保存目录
- 提取音频:一键下载 MP3
- 下载字幕:自动生成 SRT(带时间轴)+ TXT(供 AI 总结),支持中英双语
- 中文翻译:搜索结果为英文时自动附加中文译名,无需额外操作
- 视频详情:查询时长、播放量、简介等元数据
安装
npx skills add joeseesun/yt-search-download前置条件
1. YouTube API Key:Google Cloud Console → 启用 YouTube Data API v3 → 创建 API Key
export YT_BROWSE_API_KEY=your_key # 写入 ~/.zshrc2. yt-dlp(下载 / 字幕用):
brew install yt-dlp # macOS
pip install yt-dlp # 或 pip使用场景
在 Claude Code 中直接用自然语言描述需求,无需记命令:
🔍 搜索视频
"搜索 Lex Fridman 最近更新的 YouTube"
"YouTube 上有什么关于 Claude 4 的最新视频"
"找 Andrej Karpathy 频道的视频,按播放量排序"
"搜索 AI agent 相关视频,最近一个月发布的"搜索结果示例(英文自动附中文译名):
1 Rick Beato: Greatest Guitarists of All Time
【译】里克·贝阿托:史上最伟大的吉他手、音乐史与创作秘密
2026-03-01 2h34m 301.9K
2 State of AI in 2026: LLMs, Coding, Scaling Laws
【译】2026年AI现状:大模型、编程、Scaling法则与中国AI
2026-01-31 4h25m 741.7K📺 浏览频道
"看看 Lex Fridman 最近发了什么视频"
"浏览 @karpathy 的频道,找讲 GPT 的视频"
"查看 3Blue1Brown 最新上传"⬇️ 下载视频
"下载这个视频 https://youtube.com/watch?v=..."
"把这个视频下载到桌面,要 1080p"
"下载 Lex Fridman 和 Sam Altman 的那期播客"🎵 提取音频
"提取这个视频的音频"
"YouTube 转 MP3,只要音频"
"下载播客音频 https://youtube.com/watch?v=..."📝 下载字幕(SRT + TXT)
字幕下载自动生成两个文件:
.srt:带时间轴,适合视频剪辑、精准定位.txt:保留时间戳,适合交给 AI 总结归纳
"下载这个视频的字幕 https://youtube.com/watch?v=..."
"提取 Lex Fridman 这期节目的英文字幕"
"获取视频文本,我要交给 AI 总结"
"下载中文字幕"TXT 格式示例(保留时间戳,AI 可引用具体时间点):
00:00:01,000 --> 00:00:04,000
Welcome to the Lex Fridman podcast.
00:00:05,000 --> 00:00:09,000
Today we're talking about the greatest guitarists of all time.🔄 典型工作流
① 找频道最新视频并下载字幕: 1. "看看 Lex Fridman 最近更新了什么" 2. 选择感兴趣的视频 3. "下载第2个视频的字幕" 4. 得到 .srt + .txt,丢给 Claude 总结
② 搜索 + 筛选 + 下载: 1. "搜索 AI safety 相关视频,最近半年,按播放量排序" 2. "下载播放量最高那个"
③ 批量提取播客音频: 1. "搜索 Lex Fridman 播客最新5期" 2. "把第1、3期的音频都下载下来"
命令行用法
# 全站搜索
python3 scripts/yt_search.py search "关键词" -n 20 -o date
# 浏览频道(最新视频)
python3 scripts/yt_search.py channel @lexfridman -n 10
# 频道内搜索
python3 scripts/yt_search.py channel @karpathy -q "GPT"
# 下载视频
python3 scripts/yt_search.py download "VIDEO_URL" -q 1080p
# 仅下载音频(MP3)
python3 scripts/yt_search.py download "VIDEO_URL" --audio-only
# 下载字幕(SRT + TXT)
yt-dlp --cookies-from-browser chrome \
--write-auto-sub --write-sub \
--sub-lang en,zh-Hans \
--convert-subs srt \
--skip-download \
-o "~/Downloads/%(title)s.%(ext)s" \
"VIDEO_URL"
# 视频详情
python3 scripts/yt_search.py info "VIDEO_URL"搜索参数
| 参数 | 说明 |
|---|---|
-n 20 | 最多返回条数(默认 20) |
-o date | 按时间排序 |
-o viewCount | 按播放量排序 |
-o relevance | 按相关度排序(默认) |
--after 2025-01-01 | 发布时间起 |
--before 2025-12-31 | 发布时间止 |
-c @handle | 限定频道搜索 |
-d | 显示视频简介 |
License
MIT
📱 关注作者
如果这个项目对你有帮助,欢迎关注我获取更多技术分享:
- X (Twitter): @vista8
- 微信公众号「向阳乔木推荐看」:
<p align="center"> <img src="https://github.com/joeseesun/terminal-boost/raw/main/assets/wechat-qr.jpg?raw=true" alt="向阳乔木推荐看公众号二维码" width="300"> </p>
#!/usr/bin/env python3
"""
YouTube Search & Download Tool
- Search YouTube videos/channels using YouTube Data API v3
- Download via yt-dlp
"""
import os
import sys
import json
import argparse
import urllib.request
import urllib.parse
import urllib.error
import datetime
import re
import subprocess
import shutil
from typing import Optional
API_KEY = os.environ.get("YT_BROWSE_API_KEY") or os.environ.get("YOUTUBE_API_KEY")
BASE_URL = "https://www.googleapis.com/youtube/v3"
def api_get(endpoint: str, params: dict) -> dict:
"""Call YouTube Data API v3 and return parsed JSON response."""
params["key"] = API_KEY
url = f"{BASE_URL}/{endpoint}?" + urllib.parse.urlencode(params)
try:
with urllib.request.urlopen(url, timeout=15) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
body = e.read().decode() if e.fp else ""
try:
err_msg = json.loads(body).get("error", {}).get("message", body[:200])
except Exception:
err_msg = body[:200]
print(f"[API Error] {e.code}: {err_msg}", file=sys.stderr)
sys.exit(1)
except urllib.error.URLError as e:
print(f"[Network Error] {e.reason}", file=sys.stderr)
sys.exit(1)
def parse_duration(iso: str) -> tuple:
"""Convert ISO 8601 duration (e.g. PT3H55M16S) to (readable string, total seconds)."""
m = re.match(r"PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?", iso or "")
if not m:
return "?", 0
h = int(m.group(1) or 0)
mi = int(m.group(2) or 0)
s = int(m.group(3) or 0)
total_secs = h * 3600 + mi * 60 + s
parts = []
if h:
parts.append(f"{h}h")
if mi:
parts.append(f"{mi}m")
if s:
parts.append(f"{s}s")
return "".join(parts) or "0s", total_secs
def parse_duration_input(s: str) -> int:
"""Parse user duration input to seconds. Supports: 30m, 1h, 1h30m, 90 (= 90 min)."""
s = s.strip().lower()
m = re.match(r"^(?:(\d+)h)?(?:(\d+)m)?$", s)
if m and (m.group(1) or m.group(2)):
return int(m.group(1) or 0) * 3600 + int(m.group(2) or 0) * 60
try:
return int(s) * 60 # bare number = minutes
except ValueError:
return 0
def fmt_views(n: int) -> str:
"""Format view count for display (e.g. 1.2M, 35.5K)."""
if n >= 1_000_000:
return f"{n/1_000_000:.1f}M"
if n >= 1_000:
return f"{n/1_000:.1f}K"
return str(n)
def validate_date(date_str: str) -> bool:
"""Validate date string is in YYYY-MM-DD format."""
try:
datetime.datetime.strptime(date_str, "%Y-%m-%d")
return True
except ValueError:
return False
def resolve_channel(query: str) -> Optional[str]:
"""Resolve channel handle/URL/ID to channel ID."""
if re.match(r"^UC[a-zA-Z0-9_-]{22}$", query):
return query
handle = query.strip("/").split("/")[-1].lstrip("@")
try:
data = api_get("channels", {"forHandle": handle, "part": "id", "maxResults": 1})
items = data.get("items", [])
if items:
return items[0]["id"]
except SystemExit:
pass
# Fallback: search for the channel
try:
data = api_get("search", {"q": query, "type": "channel", "part": "id", "maxResults": 1})
items = data.get("items", [])
if items:
return items[0]["id"]["channelId"]
except SystemExit:
pass
return None
def search_videos(
query: str = "",
channel_id: str = None,
max_results: int = 20,
order: str = "relevance",
published_after: str = None,
published_before: str = None,
) -> list:
"""Search videos and return list of video dicts with details."""
params = {
"part": "id",
"type": "video",
"maxResults": min(max_results, 50),
"order": order,
}
if query:
params["q"] = query
if channel_id:
params["channelId"] = channel_id
if published_after:
params["publishedAfter"] = published_after
if published_before:
params["publishedBefore"] = published_before
data = api_get("search", params)
video_ids = [item["id"]["videoId"] for item in data.get("items", [])]
if not video_ids:
return []
details = api_get("videos", {
"part": "snippet,statistics,contentDetails",
"id": ",".join(video_ids),
})
results = []
for item in details.get("items", []):
snip = item["snippet"]
stats = item.get("statistics", {})
cd = item.get("contentDetails", {})
pub = snip.get("publishedAt", "")[:10]
dur_str, dur_secs = parse_duration(cd.get("duration", ""))
results.append({
"id": item["id"],
"title": snip.get("title", ""),
"channel": snip.get("channelTitle", ""),
"published": pub,
"views": int(stats.get("viewCount", 0)),
"duration": dur_str,
"duration_seconds": dur_secs,
"url": f"https://www.youtube.com/watch?v={item['id']}",
"description": snip.get("description", "")[:200],
})
return results
def search_channel_videos(
channel: str,
query: str = "",
max_results: int = 20,
order: str = "date",
) -> list:
"""Search within a specific channel."""
channel_id = resolve_channel(channel)
if not channel_id:
print(f"[错误] 找不到频道: {channel}", file=sys.stderr)
return []
return search_videos(
query=query,
channel_id=channel_id,
max_results=max_results,
order=order,
)
def print_results(results: list, show_desc: bool = False):
"""Print search results as a Markdown table with translation placeholder."""
if not results:
print("未找到结果")
return
print()
print("| # | 标题(原文 → 中文译文) | 日期 | 时长 | 播放量 |")
print("|---|------------------------|------|------|--------|")
for i, v in enumerate(results, 1):
title = v["title"].replace("|", "\\|")
short = title if len(title) <= 55 else title[:52] + "..."
link = f"[{short}]({v['url']})"
print(f"| {i} | {link}<br>【译】___ | {v['published']} | {v['duration']} | {fmt_views(v['views'])} |")
if show_desc and v["description"]:
desc = v["description"][:120].replace("|", "\\|").replace("\n", " ")
print(f"| | _{desc}_ | | | |")
print()
def apply_duration_filter_sort(results: list, min_secs: int = 0, max_secs: int = 0, sort_by: str = "") -> list:
"""Filter by duration and/or sort client-side."""
if min_secs:
results = [r for r in results if r["duration_seconds"] >= min_secs]
if max_secs:
results = [r for r in results if r["duration_seconds"] <= max_secs]
if sort_by == "duration-asc":
results = sorted(results, key=lambda r: r["duration_seconds"])
elif sort_by == "duration-desc":
results = sorted(results, key=lambda r: r["duration_seconds"], reverse=True)
elif sort_by == "views":
results = sorted(results, key=lambda r: r["views"], reverse=True)
return results
def download_video(url: str, output_dir: str = None, quality: str = "best", audio_only: bool = False) -> bool:
"""Download a video via yt-dlp."""
if not shutil.which("yt-dlp"):
print("[错误] 未找到 yt-dlp。安装方式: brew install yt-dlp", file=sys.stderr)
return False
out = output_dir or os.path.expanduser("~/Downloads")
os.makedirs(out, exist_ok=True)
cmd_parts = ["yt-dlp"]
# Try to use browser cookies for age-restricted or region-locked videos
for browser in ["chrome", "firefox", "safari"]:
if shutil.which(browser) or os.path.exists(f"/Applications/{browser.capitalize()}.app"):
cmd_parts += ["--cookies-from-browser", browser]
break
if audio_only:
cmd_parts += ["-x", "--audio-format", "mp3"]
else:
fmt_map = {
"best": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
"1080p": "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080]",
"720p": "bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720]",
"480p": "bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480]",
}
cmd_parts += ["-f", fmt_map.get(quality, fmt_map["best"])]
output_fmt = "mp3" if audio_only else "mp4"
cmd_parts += [
"--merge-output-format", output_fmt,
"-o", f"{out}/%(title)s.%(ext)s",
"--no-playlist",
url,
]
print(f"\n▶ 开始下载: {url}")
print(f" 保存到: {out}\n")
result = subprocess.run(cmd_parts, capture_output=False)
if result.returncode != 0:
print(f"\n[错误] 下载失败 (退出码 {result.returncode})", file=sys.stderr)
return False
print("\n✓ 下载完成")
return True
def main():
if not API_KEY:
print("[错误] 未设置 YouTube API Key。")
print("请设置环境变量: export YT_BROWSE_API_KEY=your_key")
print("获取方式: https://console.cloud.google.com/ → 启用 YouTube Data API v3 → 创建 API Key")
sys.exit(1)
parser = argparse.ArgumentParser(description="YouTube 搜索 & 下载工具")
sub = parser.add_subparsers(dest="cmd")
# search
ps = sub.add_parser("search", help="搜索 YouTube 视频")
ps.add_argument("query", help="搜索关键词")
ps.add_argument("-c", "--channel", help="限定频道 (handle/@name/URL/ID)")
ps.add_argument("-n", "--max", type=int, default=20, help="最多返回条数 (默认20)")
ps.add_argument("-o", "--order", choices=["relevance", "date", "viewCount", "rating"], default="relevance",
help="API 排序: relevance|date|viewCount|rating")
ps.add_argument("--sort-by", choices=["views", "duration-asc", "duration-desc"],
help="本地二次排序: views|duration-asc|duration-desc")
ps.add_argument("--min-duration", metavar="DUR", help="最短时长 (如 30m, 1h, 1h30m, 或纯数字=分钟)")
ps.add_argument("--max-duration", metavar="DUR", help="最长时长 (如 30m, 1h, 1h30m, 或纯数字=分钟)")
ps.add_argument("--after", help="发布时间起 (YYYY-MM-DD)")
ps.add_argument("--before", help="发布时间止 (YYYY-MM-DD)")
ps.add_argument("-d", "--desc", action="store_true", help="显示简介")
ps.add_argument("--json", action="store_true", help="输出 JSON 格式")
# channel
pc = sub.add_parser("channel", help="浏览频道最新视频")
pc.add_argument("channel", help="频道 (handle/@name/URL/ID)")
pc.add_argument("-q", "--query", default="", help="在频道内搜索")
pc.add_argument("-n", "--max", type=int, default=20, help="最多返回条数 (默认20)")
pc.add_argument("-o", "--order", choices=["date", "relevance", "viewCount"], default="date",
help="API 排序: date|relevance|viewCount")
pc.add_argument("--sort-by", choices=["views", "duration-asc", "duration-desc"],
help="本地二次排序: views|duration-asc|duration-desc")
pc.add_argument("--min-duration", metavar="DUR", help="最短时长 (如 30m, 1h, 1h30m, 或纯数字=分钟)")
pc.add_argument("--max-duration", metavar="DUR", help="最长时长 (如 30m, 1h, 1h30m, 或纯数字=分钟)")
pc.add_argument("-d", "--desc", action="store_true", help="显示简介")
pc.add_argument("--json", action="store_true", help="输出 JSON 格式")
# download
pd = sub.add_parser("download", help="下载视频")
pd.add_argument("url", help="YouTube 视频 URL")
pd.add_argument("--dir", default=os.path.expanduser("~/Downloads"), help="下载目录")
pd.add_argument("-q", "--quality", choices=["best", "1080p", "720p", "480p"], default="best")
pd.add_argument("--audio-only", action="store_true", help="仅下载音频 (MP3)")
# info
pi = sub.add_parser("info", help="获取视频信息")
pi.add_argument("url", help="YouTube 视频 URL 或 ID")
args = parser.parse_args()
if args.cmd == "search":
after, before = None, None
if args.after:
if not validate_date(args.after):
print(f"[错误] 日期格式无效: {args.after} (应为 YYYY-MM-DD)", file=sys.stderr)
sys.exit(1)
after = f"{args.after}T00:00:00Z"
if args.before:
if not validate_date(args.before):
print(f"[错误] 日期格式无效: {args.before} (应为 YYYY-MM-DD)", file=sys.stderr)
sys.exit(1)
before = f"{args.before}T23:59:59Z"
channel_id = resolve_channel(args.channel) if args.channel else None
results = search_videos(
query=args.query,
channel_id=channel_id,
max_results=args.max,
order=args.order,
published_after=after,
published_before=before,
)
min_secs = parse_duration_input(args.min_duration) if args.min_duration else 0
max_secs = parse_duration_input(args.max_duration) if args.max_duration else 0
results = apply_duration_filter_sort(results, min_secs, max_secs, args.sort_by or "")
if args.json:
print(json.dumps(results, ensure_ascii=False, indent=2))
else:
print_results(results, show_desc=args.desc)
elif args.cmd == "channel":
results = search_channel_videos(
channel=args.channel,
query=args.query,
max_results=args.max,
order=args.order,
)
min_secs = parse_duration_input(args.min_duration) if args.min_duration else 0
max_secs = parse_duration_input(args.max_duration) if args.max_duration else 0
results = apply_duration_filter_sort(results, min_secs, max_secs, args.sort_by or "")
if hasattr(args, 'json') and args.json:
print(json.dumps(results, ensure_ascii=False, indent=2))
else:
print_results(results, show_desc=getattr(args, 'desc', False))
elif args.cmd == "download":
ok = download_video(args.url, output_dir=args.dir, quality=args.quality, audio_only=args.audio_only)
sys.exit(0 if ok else 1)
elif args.cmd == "info":
vid_id = args.url
m = re.search(r"(?:v=|youtu\.be/)([a-zA-Z0-9_-]{11})", args.url)
if m:
vid_id = m.group(1)
details = api_get("videos", {"part": "snippet,statistics,contentDetails", "id": vid_id})
items = details.get("items", [])
if not items:
print("未找到视频", file=sys.stderr)
sys.exit(1)
item = items[0]
snip = item["snippet"]
stats = item.get("statistics", {})
cd = item.get("contentDetails", {})
print(f"\n标题: {snip.get('title')}")
print(f"频道: {snip.get('channelTitle')}")
print(f"发布: {snip.get('publishedAt', '')[:10]}")
print(f"时长: {parse_duration(cd.get('duration', ''))}")
print(f"播放: {fmt_views(int(stats.get('viewCount', 0)))}")
print(f"点赞: {fmt_views(int(stats.get('likeCount', 0)))}")
print(f"URL: https://www.youtube.com/watch?v={vid_id}")
print(f"\n简介:\n{snip.get('description', '')[:500]}")
else:
parser.print_help()
if __name__ == "__main__":
main()
Related skills
FAQ
What APIs does yt-search-download use?
yt-search-download uses YouTube Data API v3 for search, channel browsing, and sorting, and yt-dlp for downloading videos, extracting MP3 audio, and fetching subtitle tracks.
Can yt-search-download extract subtitles?
yt-search-download can download subtitles, including Chinese and English tracks, and retrieve video text for transcription-style workflows. Subtitle download is one of its core trigger scenarios.
Is Yt Search Download safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.