お寺のサイトに動画を載せたいとき、最初は動画ファイルをそのままサーバーに置いていました。

でも 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.comwww.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 の限定公開を使えばコストゼロで解決できます。公開範囲も制御できるので、お気に入りの方法になりました。