初始发布: 21 个 skills (Claude Code / Codex / DSH)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# youtube-download — 便携技能包
|
||||
|
||||
把 YouTube 视频下成本地最高画质 MP4(最高 4K,内含最佳音质 Opus 音轨)。默认规格:只下视频版,不单独下音频;除非用户明确要求音频版。已处理好现代 yt-dlp 下 YouTube 的两个坑:JS 运行时 + 机器人检测 cookie。
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
youtube-download/
|
||||
├─ SKILL.md # 技能说明(给 AI agent 读的:何时用、怎么用、坑)
|
||||
├─ README.md # 本文件
|
||||
└─ scripts/
|
||||
├─ yt-download.ps1 # Windows 一键脚本(自动装 yt-dlp/deno)
|
||||
└─ yt-download.sh # Linux/macOS 一键脚本
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
**Windows**
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\yt-download.ps1 "https://www.youtube.com/watch?v=VIDEO_ID"
|
||||
```
|
||||
首次运行会自动把 `yt-dlp.exe` 和 `deno.exe` 下到 `scripts\bin\`。还需要系统装了 `ffmpeg`(`winget install Gyan.FFmpeg`)。
|
||||
|
||||
**Linux/macOS**
|
||||
```bash
|
||||
bash scripts/yt-download.sh "https://www.youtube.com/watch?v=VIDEO_ID"
|
||||
```
|
||||
需先装 `yt-dlp`、`ffmpeg`、以及 JS 运行时 `bun` / `deno` / `node` 任一(命令见 SKILL.md 顶部;脚本默认优先用 bun)。
|
||||
|
||||
## 登录/受限视频要 cookie
|
||||
|
||||
公开视频不用管。如果报 `Sign in to confirm you're not a bot`:
|
||||
|
||||
1. 浏览器(已登录 YouTube)装扩展 **Get cookies.txt LOCALLY**。
|
||||
2. 打开着 youtube.com 标签页,点扩展 → Export,得到 Netscape 格式 cookie 文件。
|
||||
3. 改名为 `cookies.txt` 放进 `scripts/` 目录(脚本会自动带上)。
|
||||
4. 报“cookies no longer valid”时重新导出覆盖即可。
|
||||
|
||||
⚠️ cookie 是你的登录凭证,别外发、别提交到 git。本包**不含** cookie。
|
||||
|
||||
## 在 Codex / 其他 agent 里用
|
||||
|
||||
Codex(OpenAI CLI)没有 Claude 那种正式 skill 装载机制,但这个包是纯文档 + 脚本,任何 agent 都能用:
|
||||
|
||||
- 把整个 `youtube-download/` 文件夹放进项目,或放到 Codex 能读到的位置。
|
||||
- 在项目根的 `AGENTS.md` 里加一句指路,例如:
|
||||
> 需要下载 YouTube 视频时,读 `youtube-download/SKILL.md` 并运行其中的脚本。
|
||||
- 或直接让 Codex “按 youtube-download/SKILL.md 的方法下这个链接”。
|
||||
|
||||
如果目标是 **Claude Code / Claude Agent**:把 `youtube-download/` 整个放进 `~/.claude/skills/`(或项目的 `.claude/skills/`)即可作为技能被自动发现(靠 SKILL.md 的 frontmatter)。
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: youtube-download
|
||||
description: "Download YouTube videos at the highest available quality (up to 4K) as merged MP4 files with the best audio track (native Opus). Default spec: best video + best audio merged into one MP4 only — do not download a separate audio-only file unless the user explicitly asks for 音频/audio-only. Handles the modern yt-dlp requirements — a JS runtime (bun/deno/node) for YouTube's JS challenges, and browser cookies for videos blocked by bot detection / login / age gates. Use when asked to download, save, or grab a YouTube video, playlist item, or a high-quality/HD/4K copy of a YouTube URL."
|
||||
---
|
||||
|
||||
# YouTube 高清下载 (youtube-download)
|
||||
|
||||
用 `yt-dlp` 把 YouTube 视频下成本地最高画质 MP4(最高 4K),也可以只下最高音质音频。已经踩平了 2025/2026 年 yt-dlp 下 YouTube 的两个主要坑:需要 JS 运行时、以及机器人检测要 cookie。
|
||||
|
||||
## 什么时候用
|
||||
|
||||
用户给一个或多个 YouTube 链接,要求“下载 / 保存 / 下高清 / 下 4K / 下最高画质”时。
|
||||
|
||||
## 默认规格(每次照此执行)
|
||||
|
||||
- 默认只下「视频版」:最高画质视频(最高 4K)+ 最佳音质音轨(原生 Opus),合并成单个 MP4,存到用户「下载」文件夹。
|
||||
- 除非用户明确说「只要音频 / 音频版 / 单独下音频」,否则**不要**额外下载 audio-only 文件。
|
||||
- 链接带 `&list=` / `start_radio=1` 时默认只下当前这一个视频(`--no-playlist`),除非用户要求整个播放列表/电台。
|
||||
|
||||
## 核心命令(能跑通的那条)
|
||||
|
||||
```
|
||||
yt-dlp \
|
||||
--cookies "cookies.txt" \ # 仅当视频需要登录/被机器人拦时必需,见下
|
||||
--js-runtimes "bun:$(command -v bun)" \ # 必需:YouTube 现在要求解 JS 挑战(bun/deno/node 任选)
|
||||
-f "bestvideo+bestaudio/best" \
|
||||
--merge-output-format mp4 \
|
||||
--ffmpeg-location "<ffmpeg bin 目录>" \ # 合并视频+音频需要 ffmpeg
|
||||
-o "<输出目录>/%(title)s [%(height)sp].%(ext)s" \
|
||||
--no-playlist \ # 链接带 &list= 时只下当前这一个
|
||||
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||
```
|
||||
|
||||
**推荐直接用打包好的脚本**(自动准备工具、找 ffmpeg、带 cookie):
|
||||
|
||||
- Windows:`scripts/yt-download.ps1`
|
||||
- Linux/macOS:`scripts/yt-download.sh`
|
||||
|
||||
```powershell
|
||||
# Windows 示例:下一个或多个视频到「下载」文件夹
|
||||
powershell -ExecutionPolicy Bypass -File scripts\yt-download.ps1 "https://www.youtube.com/watch?v=xxxx"
|
||||
powershell -ExecutionPolicy Bypass -File scripts\yt-download.ps1 URL1 URL2 URL3
|
||||
```
|
||||
|
||||
```bash
|
||||
# Linux/macOS 示例
|
||||
bash scripts/yt-download.sh "https://www.youtube.com/watch?v=xxxx"
|
||||
```
|
||||
|
||||
脚本行为:
|
||||
- 默认就是最高画质视频 + 最佳音质音轨合并 MP4;不会单独下载音频。
|
||||
- JS 运行时自动选 `bun`(已装);没有则用 `deno`,再退到 `node`。也可用环境变量 `JS_RUNTIME=bun|deno|node` 指定。
|
||||
- 找不到 `yt-dlp`(Windows 版还会找 `deno`)会自动下载到脚本旁的 `bin/` 目录。
|
||||
- 自动探测 `ffmpeg`(PATH 里没有会提示安装)。
|
||||
- 如果脚本目录里存在 `cookies.txt`,会自动带上。
|
||||
- 输出文件名格式:`标题 [1080p].mp4`,默认存到当前用户「下载」文件夹(可用 `-OutDir` / `$2` 改)。
|
||||
|
||||
## 三个关键坑(务必知道)
|
||||
|
||||
1. **必须有 JS 运行时。** 否则报 `No supported JavaScript runtime could be found` 或直接 `Sign in to confirm you're not a bot`。装 `bun` / `deno` / `node` 任一即可(macOS 上已装 bun,脚本默认用 bun),或给 yt-dlp 传 `--js-runtimes "bun:路径"`。
|
||||
- ⚠️ yt-dlp 已把 bun 标记为 deprecated,只支持 bun 1.2.11–1.3.14(本机 1.3.13 可用)。若以后升级 bun 或 yt-dlp 报 bun 不支持,用 `JS_RUNTIME=deno` / `JS_RUNTIME=node` 切换即可(deno 已随 brew 版 yt-dlp 装好)。
|
||||
|
||||
2. **机器人检测 → 需要 cookie。** 如果 API 返回 `LOGIN_REQUIRED` / “Sign in to confirm you're not a bot” 且 0 个格式,就必须提供你 YouTube 账号的 cookie:
|
||||
- 浏览器(登录着 YouTube)装扩展 **“Get cookies.txt LOCALLY”**(开源、纯本地不上传)。
|
||||
- **切到 youtube.com 标签页**(当前激活),点扩展图标 → Export,导出 Netscape 格式的 `www.youtube.com_cookies.txt`。
|
||||
- 把它改名/放成脚本目录下的 `cookies.txt`(或用 `--cookies` 指定路径)。
|
||||
- **公开视频不需要 cookie**,脚本没检测到 cookie 也能下公开视频。
|
||||
|
||||
3. **cookie 会过期/轮换。** 出现 `The provided YouTube account cookies are no longer valid` 时,按第 2 步重新导出覆盖 `cookies.txt` 即可。公开视频不受影响。
|
||||
|
||||
## 输出编码说明
|
||||
|
||||
YouTube 最高画质通常是 **AV1 视频 + Opus 音频**(封装成 mp4)。较新,VLC / PotPlayer / 新版系统播放器都能放;个别老设备放不了。需要更通用时:
|
||||
|
||||
- 想要 H.264(兼容性最好,但 YouTube 上 H.264 最高只到 1080p):把格式改成
|
||||
`-f "bestvideo[vcodec^=avc1]+bestaudio[ext=m4a]/best[vcodec^=avc1]"`
|
||||
- 或下完后用 ffmpeg 转码成 H.264/H.265。
|
||||
|
||||
## 小抄
|
||||
|
||||
- 仅当用户明确要求音频时才用:
|
||||
- 只下音频(m4a):`-f "bestaudio[ext=m4a]/bestaudio" -x --audio-format m4a`
|
||||
- 只下最高音质音频(保留 YouTube 原生 Opus,不转码、音质最好):`-f "bestaudio/best" -o "%(title)s.%(ext)s"`
|
||||
- 限制最高 1080p:`-f "bestvideo[height<=1080]+bestaudio/best[height<=1080]"`
|
||||
- 整个播放列表:去掉 `--no-playlist`
|
||||
- 看有哪些格式:`-F`
|
||||
- 下字幕:`--write-subs --sub-langs "en,zh-Hans" --convert-subs srt`
|
||||
@@ -0,0 +1,109 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Download YouTube video(s) at highest quality as merged MP4.
|
||||
.DESCRIPTION
|
||||
Wraps yt-dlp with the settings needed for modern YouTube:
|
||||
- JS runtime (bun preferred; deno auto-downloaded to .\bin if missing)
|
||||
- yt-dlp (auto-downloaded to .\bin if missing)
|
||||
- ffmpeg (must be on PATH, or set -FfmpegDir)
|
||||
- cookies.txt next to this script is used automatically if present
|
||||
.EXAMPLE
|
||||
.\yt-download.ps1 "https://www.youtube.com/watch?v=xxxx"
|
||||
.EXAMPLE
|
||||
.\yt-download.ps1 URL1 URL2 URL3 -OutDir "D:\Videos"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true)]
|
||||
[string[]] $Urls,
|
||||
|
||||
[string] $OutDir = (Join-Path $env:USERPROFILE 'Downloads'),
|
||||
|
||||
# Override format selection if you want (e.g. H.264 only, or <=1080p)
|
||||
[string] $Format = 'bestvideo+bestaudio/best',
|
||||
|
||||
# Path to ffmpeg's bin dir. Auto-detected from PATH if omitted.
|
||||
[string] $FfmpegDir = '',
|
||||
|
||||
# Path to a Netscape cookies.txt. Defaults to cookies.txt beside this script.
|
||||
[string] $Cookies = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$BinDir = Join-Path $ScriptDir 'bin'
|
||||
New-Item -ItemType Directory -Force $BinDir | Out-Null
|
||||
|
||||
function Get-Tool($name, $url, $outFile) {
|
||||
$local = Join-Path $BinDir $outFile
|
||||
if (Test-Path $local) { return $local }
|
||||
$onPath = (Get-Command $name -ErrorAction SilentlyContinue)
|
||||
if ($onPath) { return $onPath.Source }
|
||||
Write-Host "[setup] downloading $name ..." -ForegroundColor Cyan
|
||||
Invoke-WebRequest -Uri $url -OutFile $local
|
||||
return $local
|
||||
}
|
||||
|
||||
# --- yt-dlp ---
|
||||
$ytdlp = Get-Tool 'yt-dlp' 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe' 'yt-dlp.exe'
|
||||
|
||||
# --- JS runtime (bun preferred; deno fallback; required by YouTube) ---
|
||||
$jsRuntime = ''
|
||||
$bun = Get-Command 'bun' -ErrorAction SilentlyContinue
|
||||
if ($bun) {
|
||||
$jsRuntime = "bun:$($bun.Source)"
|
||||
} else {
|
||||
$deno = Join-Path $BinDir 'deno.exe'
|
||||
if (-not (Test-Path $deno)) {
|
||||
$onPath = Get-Command 'deno' -ErrorAction SilentlyContinue
|
||||
if ($onPath) {
|
||||
$deno = $onPath.Source
|
||||
} else {
|
||||
Write-Host "[setup] downloading deno ..." -ForegroundColor Cyan
|
||||
$zip = Join-Path $BinDir 'deno.zip'
|
||||
Invoke-WebRequest -Uri 'https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip' -OutFile $zip
|
||||
Expand-Archive $zip -DestinationPath $BinDir -Force
|
||||
Remove-Item $zip -Force
|
||||
}
|
||||
}
|
||||
$jsRuntime = "deno:$deno"
|
||||
}
|
||||
|
||||
# --- ffmpeg ---
|
||||
if (-not $FfmpegDir) {
|
||||
$ff = Get-Command 'ffmpeg' -ErrorAction SilentlyContinue
|
||||
if ($ff) {
|
||||
$FfmpegDir = Split-Path -Parent $ff.Source
|
||||
} else {
|
||||
Write-Warning "ffmpeg not found on PATH. Install it (winget install Gyan.FFmpeg) or pass -FfmpegDir. Merging video+audio will fail without it."
|
||||
}
|
||||
}
|
||||
|
||||
# --- cookies ---
|
||||
if (-not $Cookies) {
|
||||
$defaultCookies = Join-Path $ScriptDir 'cookies.txt'
|
||||
if (Test-Path $defaultCookies) { $Cookies = $defaultCookies }
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force $OutDir | Out-Null
|
||||
|
||||
$common = @(
|
||||
'--no-js-runtimes',
|
||||
'--js-runtimes', $jsRuntime,
|
||||
'-f', $Format,
|
||||
'--merge-output-format', 'mp4',
|
||||
'-o', (Join-Path $OutDir '%(title)s [%(height)sp].%(ext)s'),
|
||||
'--no-playlist'
|
||||
)
|
||||
if ($FfmpegDir) { $common += @('--ffmpeg-location', $FfmpegDir) }
|
||||
if ($Cookies) { $common += @('--cookies', $Cookies); Write-Host "[info] using cookies: $Cookies" -ForegroundColor DarkGray }
|
||||
else { Write-Host "[info] no cookies.txt -- public videos only. See SKILL.md if you hit 'Sign in to confirm you're not a bot'." -ForegroundColor DarkGray }
|
||||
|
||||
$fail = 0
|
||||
foreach ($u in $Urls) {
|
||||
Write-Host "`n==== downloading: $u ====" -ForegroundColor Green
|
||||
& $ytdlp @common $u
|
||||
if ($LASTEXITCODE -ne 0) { $fail++; Write-Warning "failed: $u" }
|
||||
}
|
||||
if ($fail) { Write-Warning "$fail download(s) failed."; exit 1 }
|
||||
Write-Host "`nAll done. Saved to: $OutDir" -ForegroundColor Green
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download YouTube video(s) at highest quality as merged MP4 (Linux/macOS).
|
||||
#
|
||||
# ./yt-download.sh "https://www.youtube.com/watch?v=xxxx"
|
||||
# ./yt-download.sh URL1 URL2 URL3
|
||||
#
|
||||
# Env overrides:
|
||||
# OUTDIR=/path/to/dir output directory (default: ~/Downloads)
|
||||
# FORMAT="..." yt-dlp -f selector (default: bestvideo+bestaudio/best)
|
||||
# COOKIES=/path/cookies.txt Netscape cookies file (default: cookies.txt beside this script)
|
||||
#
|
||||
# Requirements: yt-dlp, ffmpeg, and a JS runtime (bun/deno/node) must be installed.
|
||||
# pipx install yt-dlp (or: brew install yt-dlp / apt install yt-dlp)
|
||||
# brew install ffmpeg (or: apt install ffmpeg)
|
||||
# bun / deno / node any one (needed for YouTube JS challenges)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "usage: $0 <youtube-url> [more urls...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUTDIR="${OUTDIR:-$HOME/Downloads}"
|
||||
FORMAT="${FORMAT:-bestvideo+bestaudio/best}"
|
||||
|
||||
# yt-dlp
|
||||
if ! command -v yt-dlp >/dev/null 2>&1; then
|
||||
echo "error: yt-dlp not found. Install it: pipx install yt-dlp" >&2
|
||||
exit 1
|
||||
fi
|
||||
# ffmpeg
|
||||
if ! command -v ffmpeg >/dev/null 2>&1; then
|
||||
echo "warning: ffmpeg not found; merging video+audio will fail. Install ffmpeg." >&2
|
||||
fi
|
||||
|
||||
# JS runtime for YouTube JS challenges. Prefer bun, fall back to deno/node.
|
||||
# Override with JS_RUNTIME=bun|deno|node (must be on PATH).
|
||||
# --no-js-runtimes ensures ONLY the chosen runtime is used (bun won't be
|
||||
# silently shadowed by yt-dlp's built-in deno preference).
|
||||
JS_ARGS=()
|
||||
JS_RUNTIME_NAME=
|
||||
JS_RUNTIME_BIN=
|
||||
if [ -n "${JS_RUNTIME:-}" ] && command -v "$JS_RUNTIME" >/dev/null 2>&1; then
|
||||
JS_RUNTIME_NAME="$JS_RUNTIME"
|
||||
JS_RUNTIME_BIN="$(command -v "$JS_RUNTIME")"
|
||||
elif command -v bun >/dev/null 2>&1; then
|
||||
JS_RUNTIME_NAME="bun"
|
||||
JS_RUNTIME_BIN="$(command -v bun)"
|
||||
elif command -v deno >/dev/null 2>&1; then
|
||||
JS_RUNTIME_NAME="deno"
|
||||
JS_RUNTIME_BIN="$(command -v deno)"
|
||||
elif [ -x "$HOME/.deno/bin/deno" ]; then
|
||||
JS_RUNTIME_NAME="deno"
|
||||
JS_RUNTIME_BIN="$HOME/.deno/bin/deno"
|
||||
elif command -v node >/dev/null 2>&1; then
|
||||
JS_RUNTIME_NAME="node"
|
||||
JS_RUNTIME_BIN="$(command -v node)"
|
||||
fi
|
||||
if [ -n "$JS_RUNTIME_NAME" ]; then
|
||||
JS_ARGS=(--no-js-runtimes --js-runtimes "$JS_RUNTIME_NAME:$JS_RUNTIME_BIN")
|
||||
else
|
||||
echo "warning: no JS runtime (bun/deno/node) found. YouTube may reject the download ('confirm you're not a bot')." >&2
|
||||
fi
|
||||
|
||||
# cookies
|
||||
COOKIES="${COOKIES:-$SCRIPT_DIR/cookies.txt}"
|
||||
COOKIE_ARGS=()
|
||||
if [ -f "$COOKIES" ]; then
|
||||
COOKIE_ARGS=(--cookies "$COOKIES")
|
||||
echo "[info] using cookies: $COOKIES"
|
||||
else
|
||||
echo "[info] no cookies.txt -- public videos only. See SKILL.md for login-required videos."
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
fail=0
|
||||
for url in "$@"; do
|
||||
echo ""
|
||||
echo "==== downloading: $url ===="
|
||||
if ! yt-dlp "${JS_ARGS[@]}" "${COOKIE_ARGS[@]}" \
|
||||
-f "$FORMAT" \
|
||||
--merge-output-format mp4 \
|
||||
-o "$OUTDIR/%(title)s [%(height)sp].%(ext)s" \
|
||||
--no-playlist \
|
||||
"$url"; then
|
||||
fail=$((fail+1))
|
||||
echo "warning: failed: $url" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$fail" -gt 0 ]; then
|
||||
echo "warning: $fail download(s) failed." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo "All done. Saved to: $OUTDIR"
|
||||
Reference in New Issue
Block a user