mcpField
Y
youtube

youtube

github.com/acedatacloud创意与媒体17213 次安装今天更新
代码仓库

关于 youtube

通过 curl 和 jq 调用 YouTube Data API v3 的技能,使用已保存的 OAuth Bearer 令牌。可查询频道和视频,也支持上传;发布需要相应权限并先取得用户确认。

主要功能

  1. 01检查已连接的频道
  2. 02搜索视频、频道和播放列表
  3. 03读取上传记录、统计数据和评论
  4. 04通过可恢复请求上传视频
  5. 05处理 OAuth 权限与 API 错误

适用场景

  1. 01查看频道信息和上传历史
  2. 02查找视频并读取互动数据
  3. 03确认视频详情后发布

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/acedatacloud/skills --skill youtube

安装后在 Claude Code 中输入/youtube确认已加载。

附带文件scripts/list_uploads.py

详细介绍

使用 curl + jq 调用 YouTube Data API v3。用户的 OAuth Bearer 令牌位于 $GOOGLE_YOUTUBE_TOKEN;每次调用都必须将其作为 Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN 发送。基础 URL: https://www.googleapis.com/youtube/v3。

令牌始终包含 youtube.readonly 和身份验证作用域 (openid email profile);如果用户在安装时选择启用,还会包含 youtube.upload(发布视频)。

响应均为标准 JSON;失败时会返回 {"error": {"code": 401|403|..., "message": "..."}} — 请原样显示该错误。 401 → 令牌已过期,用户必须重新连接 YouTube 连接器。上传时出现 403 insufficientPermissions → 用户未授予 youtube.upload;请让用户重新连接,并勾选上传选项。

务必先检查频道,以确认连接有效,并了解当前操作的是哪个频道。

curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  "https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,contentDetails&mine=true" \
  | jq '.items[0] | {title: .snippet.title, subs: .statistics.subscriberCount, views: .statistics.viewCount, uploads: .contentDetails.relatedPlaylists.uploads}'

搜索 YouTube

# Public search (any video). type can be video|channel|playlist.
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  --data-urlencode "q=ai video automation" \
  --data-urlencode "part=snippet" \
  --data-urlencode "type=video" \
  --data-urlencode "maxResults=10" \
  -G "https://www.googleapis.com/youtube/v3/search" \
  | jq '.items[] | {videoId: .id.videoId, title: .snippet.title, channel: .snippet.channelTitle, published: .snippet.publishedAt}'

添加 --data-urlencode "order=date|viewCount|rating|relevance" 可排序, 或添加 --data-urlencode "publishedAfter=2026-01-01T00:00:00Z" 可设置时间范围。

查看我上传的视频

YouTube 没有直接“列出我的视频”的调用 — 先读取频道的 上传内容播放列表,再逐页读取其中的条目。

python3 skills/youtube/scripts/list_uploads.py --max-pages 20

此辅助程序会跟进每个 nextPageToken,直到上传历史记录全部读取完毕 (或达到明确的安全上限)。它返回精简的 JSON,其中每个条目包含 videoId、title 和 published,此外还有 complete、pages_fetched 和 next_page_token。如果 complete 为 false,切勿声称某个视频不存在。

分页时,将上一个响应中的 .nextPageToken 作为参数传入: --data-urlencode "pageToken=$PAGE_TOKEN"。

视频统计数据(观看次数 / 点赞数 / 评论数)

# Accepts a comma-separated id list.
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/videos" \
  --data-urlencode "part=snippet,statistics,status" \
  --data-urlencode "id=VIDEO_ID_1,VIDEO_ID_2" \
  | jq '.items[] | {title: .snippet.title, views: .statistics.viewCount, likes: .statistics.likeCount, comments: .statistics.commentCount, privacy: .status.privacyStatus}'

查看视频评论

curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/commentThreads" \
  --data-urlencode "part=snippet" \
  --data-urlencode "videoId=VIDEO_ID" \
  --data-urlencode "maxResults=20" \
  --data-urlencode "order=relevance" \
  | jq '.items[] | .snippet.topLevelComment.snippet | {author: .authorDisplayName, text: .textDisplay, likes: .likeCount}'

上传视频(需要 youtube.upload)

发布前先向用户确认 — 展示即将上传的视频标题、隐私设置和 文件。上传分为两步,采用可恢复流程:先使用元数据初始化 → 再 PUT 文件内容。

如果源文件是 URL,先下载

通过生成 API(Maestro、Seedance、Veo、……)制作的视频会以 CDN URL 的形式返回,而不是本地文件。先下载,再确认实际获取到的是视频,然后再开始上传:

SRC="https://example.cdn.acedata.cloud/path/video.mp4"
FILE=$(mktemp)   # plain mktemp — `mktemp /tmp/x-XXXXXX.mp4` is not portable

# -f: fail on 4xx/5xx instead of saving the error page. -L: follow CDN redirects.
curl -fsSL -o "$FILE" -w 'http=%{http_code} size=%{size_download}\n' "$SRC"

# Two distinct failures, two distinct messages — a 404/DNS error is a bad URL,
# not a bad video, and saying so saves the next step from misdiagnosing it.
[ -s "$FILE" ] \
  || { echo "download failed (see http= above) — nothing to upload"; rm -f "$FILE"; exit 1; }

# A 200 can still be an HTML interstitial or an expired-link page. Reject that
# rather than allow-listing one container, so WebM/MOV/AVI still pass. `grep -a`
# is required — without it grep treats a binary header as "no match".
head -c 512 "$FILE" | grep -qai '<html\|<!doctype' \
  && { echo "got an HTML page, not a video"; rm -f "$FILE"; exit 1; }

--upload-file 会忽略文件名;YouTube 依据 Content-Type: video/* 标头判断文件类型,因此没有扩展名的临时文件也可以。

上传操作
# Falls back to the placeholder only if the fetch step above didn't set FILE.
FILE="${FILE:-/path/to/video.mp4}"
TITLE="My title"
DESC="My description"
# privacyStatus: public | unlisted | private
META=$(jq -n --arg title "$TITLE" --arg description "$DESC" '{
  snippet: {title: $title, description: $description, categoryId: "22"},
  status: {privacyStatus: "unlisted", selfDeclaredMadeForKids: false}
}')

# 1. Init the resumable session. Keep headers, body and status so a missing
# Location reports the real API error instead of turning into an empty PUT URL.
INIT_HEADERS=$(mktemp)
INIT_BODY=$(mktemp)
INIT_HTTP=$(curl -sS -D "$INIT_HEADERS" -o "$INIT_BODY" -w '%{http_code}' \
  -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -H "X-Upload-Content-Type: video/*" \
  -X POST "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status" \
  -d "$META")
UPLOAD_URL=$(tr -d '\r' < "$INIT_HEADERS" | awk 'tolower($1) == "location:" {print $2; exit}')
if [ -z "$UPLOAD_URL" ]; then
  echo "upload init failed: HTTP $INIT_HTTP: $(jq -r '.error.message // .' "$INIT_BODY" 2>/dev/null || cat "$INIT_BODY")"
  rm -f "$INIT_HEADERS" "$INIT_BODY"
  exit 1
fi
rm -f "$INIT_HEADERS" "$INIT_BODY"

# 2. Upload the bytes -> returns the created video resource (has .id).
RESULT=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -H "Content-Type: video/*" \
  -X PUT --upload-file "$FILE" "$UPLOAD_URL")
echo "$RESULT" | jq -e .id >/dev/null 2>&1 \
  || { echo "upload failed: $(echo "$RESULT" | jq -r '.error.message' 2>/dev/null || echo "$RESULT")"; exit 1; }
VIDEO_ID=$(echo "$RESULT" | jq -r '.id')
VERIFY=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
  -G "https://www.googleapis.com/youtube/v3/videos" \
  --data-urlencode "part=snippet,status,processingDetails" \
  --data-urlencode "id=$VIDEO_ID")
echo "$VERIFY" | jq -e '.items[0].id == "'"$VIDEO_ID"'"' >/dev/null \
  || { echo "upload readback failed for $VIDEO_ID"; exit 1; }
echo "$VERIFY" | jq '.items[0] | {
  video_id: .id,
  canonical_url: ("https://www.youtube.com/watch?v=" + .id),
  privacy: .status.privacyStatus,
  upload_status: .status.uploadStatus,
  processing_status: .processingDetails.processingStatus
}'

只有在确认返回了 id 后,才删除已下载的临时文件 (echo "$RESULT" | jq -e .id >/dev/null && rm -f "$FILE")。遇到 401/403 或连接中断时,下载的文件仍然可用;重新上传比重新获取几百 MB 数据更好。

确认上传成功并返回真实的 .id 后,恰好调用一次 publish_artifact,让结果显示在我的输出中。使用 kind="video"、 channel="youtube"、status="delivered"、真实标题,以及规范的 https://www.youtube.com/watch?v=<id> URL。在摘要中注明实际的隐私设置。如果上传失败或未返回 .id,不要记录为已交付的资源,也绝不可编造 URL。

categoryId 22 = “人物与博客”(安全的默认值)。要设置自定义缩略图(需要先处理好文件),请携带图片调用 POST /upload/youtube/v3/thumbnails/set?videoId=VIDEO_ID。

注意事项

  • **配额:**Data API 按配额计量(默认每天 10,000 个单位)。一次 search 消耗 100 个单位;一次 upload 消耗约 1,600 个单位。短时间内大量搜索 可能耗尽每日配额 → 403 quotaExceeded;请明确告知用户。
  • 没有“我的视频”端点 — 始终通过上传内容播放列表获取。
  • search 结果最终才会一致 — 刚上传的视频可能要过几分钟或几小时才会出现在 search 中;请改为通过上传内容播放列表或视频 id 查询。
  • 上传的视频最初处于 uploaded/processing 状态;处理完成前,统计数据均为 0。

评论

支持 Markdown