youtube について
保存済みの OAuth Bearer トークンを使い、curl と jq で YouTube Data API v3 を操作するスキルです。チャンネルや動画の調査、動画のアップロードに対応し、公開には権限と事前確認が必要です。
主な機能
- 01接続中のチャンネル確認
- 02動画・チャンネル・再生リストの検索
- 03アップロード履歴・統計・コメントの取得
- 04再開可能なリクエストによる動画アップロード
- 05OAuth 権限と API エラーの処理
活用シーン
- 01チャンネル情報とアップロード履歴の確認
- 02動画の検索と反応データの確認
- 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 ベアラートークンは $GOOGLE_YOUTUBE_TOKEN に格納されています。すべての呼び出しで、Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN として指定する必要があります。ベース URL:
https://www.googleapis.com/youtube/v3
トークンには常に youtube.readonly と ID 用スコープ(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 が必要)
公開する前にユーザーに確認してください。アップロードしようとしているタイトル、公開範囲、ファイルを提示します。アップロードは 2 段階の再開可能なフローです。メタデータを指定して初期化し、その後バイトデータを 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 が返されたことを確認したら、結果が My Outputs に表示されるように publish_artifact を 1 回だけ呼び出してください。kind="video"、channel="youtube"、status="delivered"、実際のタイトル、および正規の https://www.youtube.com/watch?v=<id> URL を使用します。概要には実際の公開範囲を含めてください。アップロードに失敗した場合や .id が返されなかった場合は、配信済みアーティファクトとして記録せず、URL を捏造しないでください。
categoryId 22 は「People & Blogs」(安全なデフォルト)です。カスタムサムネイルを設定するには(先にファイルを処理する必要があります)、画像を指定して POST /upload/youtube/v3/thumbnails/set?videoId=VIDEO_ID を呼び出してください。
注意点
- 割り当て量: Data API には割り当て量の制限があります(デフォルトで 1 日あたり 10,000 ユニット)。
searchは 100 ユニット、uploadは約 1,600 ユニットを消費します。短時間に検索を繰り返すと、1 日の割り当て量を使い切る可能性があります →403 quotaExceeded。そのままユーザーに伝えてください。 - 「自分の動画」エンドポイントはありません。必ずアップロード済み動画の再生リストを経由してください。
searchの結果は結果整合性です。アップロード直後の動画は、数分から数時間searchに表示されない場合があります。代わりにアップロード済み動画の再生リストか ID から取得してください。- アップロードした動画は
uploaded/processing状態で始まり、処理が完了するまで統計情報は0です。