お寺のサイトに動画を載せたいとき、最初は動画ファイルをそのままサーバーに置いていました。
でも 200MB の動画が数本あると、あっという間にリポジトリが肥大化してしまいます。
そこで「動画は YouTube に限定公開でアップロードして、記事には iframe を埋め込む」という仕組みを作りました。
Claude Code に動画のパスを渡すだけで、アップロード→動画ID取得→iframe生成まで自動でやってくれます。
完成形のイメージ
# Claude Code に依頼
/post-website
→ 投稿先と内容を入力
→ 動画ファイルのパスを渡す
→ 自動で YouTube にアップロード(限定公開)
→ 動画IDを取得して記事に iframe を埋め込む
→ git push して本番反映
記事に埋め込まれる HTML はこんな感じです。
<div style="position:relative;padding-top:56.25%;">
<iframe
src="https://www.youtube-nocookie.com/embed/XXXXXXXXX"
style="position:absolute;top:0;left:0;width:100%;height:100%;border-radius:8px;"
allowfullscreen>
</iframe>
</div>
youtube-nocookie.com を使うことで、Cookie 同意バナーが不要になります(後述)。
必要なもの
- Google アカウント(YouTube チャンネルがあること)
- Python 3
curlコマンド(macOS は標準で入っています)- Hugo で作った静的サイト
Step 1: Google Cloud Console でプロジェクトを作る
Google Cloud Console を開きます。
1-1. 新しいプロジェクトを作成
画面上部のプロジェクト名をクリック → 「新しいプロジェクト」→ 任意の名前を入力して作成します。
今回は misato-enpukuji-hp というプロジェクト名にしました。
1-2. YouTube Data API v3 を有効化
左のナビゲーションから「API とサービス」→「ライブラリ」→ 検索欄に「YouTube Data API」と入力 → 「YouTube Data API v3」を選択 → 「有効にする」をクリックします。
Step 2: OAuth 同意画面の設定
左メニューの「API とサービス」→「OAuth 同意画面」を開きます。
2-1. ユーザーの種類を選択
「外部」を選択して「作成」をクリックします。(自分だけが使う場合でも「外部」で OK です)
2-2. アプリ情報の入力
- アプリ名: 任意(例:
misato-enpukuji-youtube-uploader) - ユーザーサポートメール: 自分のメールアドレス
- デベロッパーの連絡先情報: 自分のメールアドレス
「保存して次へ」をクリックします。
2-3. スコープを追加
「スコープを追加または削除」→ フィルタに youtube.upload と入力 → https://www.googleapis.com/auth/youtube.upload を選択 → 「更新」→「保存して次へ」をクリックします。
2-4. テストユーザーを追加
「+ ADD USERS」→ 自分の Google アカウントのメールアドレスを入力して保存します。
2-5. ⚠️ 重要: 「本番」に切り替える
デフォルトでは「テスト」ステータスになっています。
テストのままだと、リフレッシュトークンが7日で失効します。
自動化のためには「本番」への切り替えが必要です。ただし本番にするには ドメインの所有権確認 が求められます(Step 3 で実施します)。
Step 3: Google Search Console でドメインの所有権を確認する
Google Search Console を開きます。
「プロパティを追加」→「URL プレフィックス」にサイトのURLを入力(例: https://misato-enpukuji.org/)→「続行」をクリックします。
確認方法として「HTMLタグ」を選ぶと、こんなメタタグが表示されます。
<meta name="google-site-verification" content="あなたのコード" />
Hugo サイトの場合
layouts/_partials/extend_head.html(なければ作成)にこのメタタグを追加します。
<!-- layouts/_partials/extend_head.html -->
<meta name="google-site-verification" content="あなたのコード" />
このファイルは多くの Hugo テーマが <head> に自動的に読み込んでくれます。
git push してサイトをデプロイしたら、Search Console に戻って「確認」ボタンを押してください。数分かかることもあります。
確認完了後、Cloud Console の OAuth 同意画面に戻って「アプリを公開」→「確認」で本番ステータスへ切り替えます。
Step 4: OAuth クライアント ID を作成する
「API とサービス」→「認証情報」→「認証情報を作成」→「OAuth クライアント ID」を選択します。
- アプリケーションの種類: デスクトップアプリ
- 名前: 任意(例:
youtube-uploader)
「作成」すると、クライアント ID と クライアントシークレット が表示されます。後の手順で使いますのでメモしておきましょう。
Step 5: 初回認証でリフレッシュトークンを取得する
以下のセットアップスクリプトを使って、初回認証を行い refresh_token を取得します。
#!/usr/bin/env python3
"""
初回認証スクリプト。
実行すると認証URLが表示されるので、ブラウザで開いて認可します。
リダイレクトされたURLをコピーして貼り付けると refresh_token が取得できます。
"""
import json
import os
import urllib.parse
import urllib.request
CLIENT_ID = "あなたのクライアントID"
CLIENT_SECRET = "あなたのクライアントシークレット"
SCOPE = "https://www.googleapis.com/auth/youtube.upload"
REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob" # コピー&ペースト方式
# 1. 認証URLを生成
params = {
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": SCOPE,
"access_type": "offline",
"prompt": "consent", # 必ず consent を指定(refresh_token を毎回発行するため)
}
auth_url = "https://accounts.google.com/o/oauth2/auth?" + urllib.parse.urlencode(params)
print("以下のURLをブラウザで開いてください:")
print(auth_url)
print()
# 2. 認可コードを入力
code = input("ブラウザに表示されたコードを貼り付けてください: ").strip()
# 3. refresh_token を取得
data = urllib.parse.urlencode({
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code",
}).encode()
req = urllib.request.Request(
"https://oauth2.googleapis.com/token",
data=data,
method="POST",
)
res = json.loads(urllib.request.urlopen(req).read())
# 4. 認証情報を保存
creds = {
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"refresh_token": res["refresh_token"],
}
save_path = os.path.expanduser("~/.config/google/youtube-oauth.json")
os.makedirs(os.path.dirname(save_path), exist_ok=True)
with open(save_path, "w") as f:
json.dump(creds, f, indent=2)
print(f"\n認証情報を {save_path} に保存しました。")
prompt=consent を指定するのがポイントです。これがないと、すでに認証済みのアカウントでは refresh_token が返ってこないことがあります。
Step 6: アップロードスクリプト本体
認証情報ファイルが用意できたら、以下のスクリプトでアップロードできます。
#!/usr/bin/env python3
"""
動画をYouTubeに限定公開(unlisted)でアップロードする。
認証情報: ~/.config/google/youtube-oauth.json
{ "client_id": "...", "client_secret": "...", "refresh_token": "..." }
使い方:
python3 upload_youtube.py <video_path> "<title>" ["<description>"]
出力: アップロードした動画IDと埋め込みURL(最終行に動画IDのみ)
"""
import json
import os
import subprocess
import sys
import tempfile
CREDS_PATH = os.path.expanduser("~/.config/google/youtube-oauth.json")
def get_access_token(creds):
"""リフレッシュトークンを使ってアクセストークンを取得する"""
result = subprocess.run(
[
"curl", "-sS", "-X", "POST",
"https://oauth2.googleapis.com/token",
"-d", f"client_id={creds['client_id']}",
"-d", f"client_secret={creds['client_secret']}",
"-d", f"refresh_token={creds['refresh_token']}",
"-d", "grant_type=refresh_token",
],
capture_output=True, text=True, check=True,
)
return json.loads(result.stdout)["access_token"]
def upload(video_path, title, description):
creds = json.load(open(CREDS_PATH))
access_token = get_access_token(creds)
video_size = os.path.getsize(video_path)
# 動画のメタデータ
meta = {
"snippet": {
"title": title,
"description": description,
"categoryId": "22", # 22 = People & Blogs
},
"status": {
"privacyStatus": "unlisted", # 限定公開
},
}
# メタデータを一時ファイルに書き出す
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
json.dump(meta, f, ensure_ascii=False)
meta_path = f.name
# 再開可能アップロードの開始リクエスト
init = subprocess.run(
[
"curl", "-sS", "-i", "-X", "POST",
"https://www.googleapis.com/upload/youtube/v3/videos"
"?uploadType=resumable&part=snippet,status",
"-H", f"Authorization: Bearer {access_token}",
"-H", "Content-Type: application/json; charset=UTF-8",
"-H", "X-Upload-Content-Type: video/mp4",
"-H", f"X-Upload-Content-Length: {video_size}",
"--data", f"@{meta_path}",
],
capture_output=True, text=True, check=True,
)
os.unlink(meta_path)
# レスポンスヘッダーから upload URL を取り出す
upload_url = None
for line in init.stdout.splitlines():
if line.lower().startswith("location:"):
upload_url = line.split(":", 1)[1].strip()
if not upload_url:
raise RuntimeError("アップロードURLの取得に失敗しました")
# 動画本体をアップロード
put = subprocess.run(
[
"curl", "-sS", "-X", "PUT", upload_url,
"-H", "Content-Type: video/mp4",
"--upload-file", video_path,
],
capture_output=True, text=True, check=True,
)
resp = json.loads(put.stdout)
if "id" not in resp:
raise RuntimeError(f"アップロード失敗: {json.dumps(resp, ensure_ascii=False)}")
return resp["id"]
if __name__ == "__main__":
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
video_path = sys.argv[1]
title = sys.argv[2]
description = sys.argv[3] if len(sys.argv) > 3 else ""
video_id = upload(video_path, title, description)
print(f"https://www.youtube-nocookie.com/embed/{video_id}")
print(video_id)
実行例
python3 upload_youtube.py ./video.mov "台風接近の様子" "2026年9月21日 お寺の境内"
# → https://www.youtube-nocookie.com/embed/xxxxxxxxxxx
# → xxxxxxxxxxx
最終行に動画IDだけ出力されるので、シェルスクリプトや他のツールからも取り出しやすくなっています。
Step 7: Hugo 記事への埋め込み
取得した動画IDを使って記事の Markdown に埋め込みます。
<div style="position:relative;padding-top:56.25%;">
<iframe
src="https://www.youtube-nocookie.com/embed/動画ID"
style="position:absolute;top:0;left:0;width:100%;height:100%;border-radius:8px;"
allowfullscreen>
</iframe>
</div>
padding-top: 56.25% は 16:9 のアスペクト比を CSS だけで維持するおなじみのテクニックです。
なぜ youtube-nocookie.com を使うのか
通常の youtube.com の埋め込みは、ページを開いた時点で Google の Cookie をセットします。EU の GDPR や日本の個人情報保護法の観点から、Cookie バナーで同意を得る必要が出てきます。
youtube-nocookie.com を使うと、視聴者が動画を再生するまで Cookie を一切セットしません。Cookie 同意バナーが不要になります。URL の www.youtube.com を www.youtube-nocookie.com に置き換えるだけで動きます。
Step 8: Claude Code との連携(おまけ)
/post-website というスキルファイルに以下のように組み込んでいます。
動画ファイルが渡された場合、以下を実行する:
python3 scripts/upload_youtube.py <動画ファイルパス> "<タイトル>" "<説明>"
最終行の動画IDを使って iframe を本文に埋め込む
これで Claude Code に「動画のパス」を伝えるだけで、YouTube アップロード → iframe 生成 → git push まで自動でやってくれます。
今日の作業でも実際にこの流れで動きました。
(私)→ /post-website → 動画パスを入力
(Claude Code)→ upload_youtube.py を実行
→ 動画ID: tasBLNtsaWY
→ iframe を記事に埋め込む
→ git push → Cloudflare 自動デプロイ
トラブルシューティング
リフレッシュトークンが7日で切れる
OAuth 同意画面が「テスト」ステータスのままになっています。Step 2-5 をご確認のうえ「本番」に切り替えてください。
refresh_token が返ってこない
セットアップスクリプトで prompt=consent を指定していないと、すでに認証済みのアカウントでは refresh_token が返ってこないことがあります。prompt=consent を追加して再実行してみてください。
アップロードはできるが YouTube に動画が見当たらない
限定公開(unlisted)でアップロードしているので、YouTube Studio の「コンテンツ」→「動画」→「限定公開」タブをご確認ください。
400 Bad Request が返る
クライアント ID / クライアントシークレットが間違っているか、YouTube Data API v3 が有効になっていない可能性があります。Cloud Console でご確認ください。
まとめ
- Google Cloud Console → YouTube Data API v3 有効化 → OAuth クライアント作成
- OAuth 同意画面を「本番」にする(テストのままだとトークンが7日で失効します)
- 本番化にはドメインの所有権確認が必要 → Google Search Console で Hugo のメタタグを使って確認します
refresh_tokenを使うことで、毎回ブラウザ認証なしで API を叩けますyoutube-nocookie.com埋め込みで Cookie バナーが不要になります
静的サイトへの動画掲載は「どこにホストするか問題」がよくあります。YouTube の限定公開を使えばコストゼロで解決できます。公開範囲も制御できるので、お気に入りの方法になりました。