ブログ画像3枚からYouTubeショート動画を全自動で作る方法|BGM生成×テロップ×YouTube API連携の全手順

スマホで縦型ショート動画を確認するクリエイターのイラスト(画像からショート動画を自動生成する仕組み)

PR

AIで記事作成を効率化したいなら → 高品質SEO記事生成AIツール【Value AI Writer byGMO】

※本記事は、2026年9月に実際に構築・運用した自動化パイプラインの記録です。APIの仕様・料金は変更される可能性があります。

こんにちは!Conomacです。

当ブログでは、OpenClaw(AIエージェント基盤)を使ってWordPressブログの記事生成を自動化してきました。今回はその次の一手として、「記事の画像から9:16のショート動画を自動生成し、YouTubeへ自動投稿する」仕組みを作りました。

この記事では、実際に動いているパイプラインの構成とコードのポイント、そして運用して分かった「つまずきポイント」までをまとめて公開します。

この記事でわかること

  • ブログ画像3枚から9:16ショート動画を自動生成する構成(MoviePy + Pillow)
  • BGMをAIで生成して著作権フリーにする方法
  • YouTube Data API v3 のセットアップ(OAuth・スコープ設計)
  • resumable upload による動画の自動投稿の実装
  • 実際にハマったポイントと回避策

作ったパイプラインの全体像

やっていることはシンプルで、次の4ステップです。

  1. 素材の取得: 記事で使った画像3枚を取得(高解像度の原本を使用)
  2. BGM生成: AIで記事の雰囲気に合うオリジナル楽曲を生成
  3. 動画生成: 9:16(1080×1920)のスライドショー+テロップ+BGMを書き出し
  4. YouTube投稿: Data API v3 で public 投稿(説明欄冒頭に記事URL)

実例として、この流れで公開した動画がこちらです(鎌倉の旅行コーデ記事から生成)。

▶️ 鎌倉 旅行コーデ3選(YouTubeショート)
https://www.youtube.com/shorts/HDK4hmmJmlw

この記事で紹介した仕組みは、この動画のようにブログ記事の画像3枚だけから作られています。

使用環境

  • macOS(Apple Silicon)
  • Python 3系(venv)+ MoviePy + Pillow
  • FFmpeg(動画書き出し)
  • Node.js(YouTube API呼び出し)
  • 画像生成・BGM生成: Fal.ai(flux-lora / ace-step)

ステップ1: 素材画像をそろえる

記事で使用した画像3枚を、高解像度の原本から取得します。WordPressのメディアURLから取る場合は、リサイズされたサムネイルではなく、可能な限り大きいサイズを使うのがコツです。9:16へ拡大するため、元が小さいとどうしても粗くなります。

ステップ2: BGMをAIで生成する(権利フリー)

動画に使うBGMは、既存楽曲だと権利処理が必要になるため、AIで生成した完全オリジナル音源を使っています。Fal.ai の音声生成モデル(ace-step)にプロンプトを投げるだけです。

const body = {
  prompt: 'refreshing japanese summer seaside music, acoustic guitar and light percussion, bright uplifting travel mood, no vocals',
  duration: 10,
};
// POST https://queue.fal.run/fal-ai/ace-step
// → response_url を数秒間隔でポーリング → audio.url を保存

記事の雰囲気に合わせてプロンプトを変えるのがポイントです(例: 京都=和風、海辺=軽やかなアコースティック)。

ステップ3: 9:16のテロップ付き動画を生成する

動画の生成は MoviePy で行っています。構成は「画像クリップを縦に連結 → テロップを重ねる → BGMを合成」の3段階です。

from moviepy import ImageClip, CompositeVideoClip, concatenate_videoclips, AudioFileClip
from PIL import Image, ImageDraw, ImageFont
import numpy as np

W, H = 1080, 1920
DUR = 3.4   # 1枚あたりの秒数
FONT = "/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc"

def text_img(text, size, y):
    img = Image.new("RGBA", (W, H), (0, 0, 0, 0))
    d = ImageDraw.Draw(img)
    font = ImageFont.truetype(FONT, size)
    bbox = d.textbbox((0, 0), text, font=font, stroke_width=6)
    x = (W - (bbox[2] - bbox[0])) // 2
    d.text((x - bbox[0], y - bbox[1]), text, font=font,
           fill=(255, 255, 255), stroke_width=6, stroke_fill=(0, 0, 0))
    return np.array(img)

def scene(path, caption):
    c = ImageClip(path).with_duration(DUR).resized(height=H)
    if c.w > W:
        c = c.cropped(x_center=c.w / 2, width=W)
    base = c.with_position(("center", "center"))
    telop = ImageClip(text_img(caption, 64, H - 300)).with_duration(DUR)
    return CompositeVideoClip([base, telop.with_position((0, 0))], size=(W, H))

テロップはPillowで文字画像を作って重ねる方式にしています。MoviePyの標準テキスト機能は日本語フォントの指定が不安定で、環境によって文字化けするためです。

テロップで必ずハマる「絵文字の文字化け」

テロップに絵文字や記号を使うと、フォントにグリフが無く豆腐(□)や化け文字になります。ヒラギノ角ゴシックW6を使う場合、絵文字は避けて日本語・英数字だけにするのが安全です。

ステップ4: YouTube Data API v3 のセットアップ

4-1. Google Cloud Console でAPIを有効化

  1. Google Cloud Console でプロジェクトを作成
  2. 「YouTube Data API v3」を有効化
  3. OAuth同意画面(Google Auth Platform)を設定し、スコープに動画アップロードを追加
  4. クライアントIDを「デスクトップアプリ」で作成

4-2. スコープは upload だけで十分

今回必要なスコープは動画アップロード用の1つだけです。ローカルに一時HTTPサーバーを立てて認可コードを受け取り、リフレッシュトークンと交換して保存します(以降はリフレッシュトークンからアクセストークンを都度取得)。

認証情報はコードに直書きせず、環境変数(.env)で管理します。クライアントID・クライアントシークレット・リフレッシュトークンの3つを保存しておけば、以後は無人で投稿できます。

4-3. アップロードは resumable upload で

動画はサイズが大きいため、セッションを開始して返ってきたURLに本体をPUTするresumable uploadを使います。

// 1) アップロードセッションを開始
const initRes = await fetch(
  'https://www.googleapis.com/upload/youtube/v3/videos' +
  '?uploadType=resumable&part=snippet,status',
  {
    method: 'POST',
    headers: {
      Authorization: authHeader,          // Bearer + アクセストークン
      'Content-Type': 'application/json',
      'X-Upload-Content-Type': 'video/mp4',
      'X-Upload-Content-Length': String(buf.length),
    },
    body: JSON.stringify({
      snippet: { title, description, tags, categoryId: '19' },
      status: { privacyStatus: 'public', selfDeclaredMadeForKids: false },
    }),
  });

// 2) LocationヘッダーのURLへ本体をPUT
const uploadUrl = initRes.headers.get('location');
await fetch(uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'video/mp4' },
  body: buf,
});

ショート動画として扱ってもらうため、タイトルまたは説明欄に #Shorts を入れ、縦型(9:16)・60秒以内に収めています。また説明欄の冒頭にブログ記事URLを置くことで、折りたたまれる前にリンクが見えるようにしています。

実際にハマったポイント

  • 「後から公開設定を変える」はできない: 動画の更新(videos.update)には追加スコープが必要で、アップロード専用スコープでは403になります。最初から public で投稿する運用にしました。
  • 固定コメントのAPIも同様に不可: コメント投稿も別スコープが必要なため、流入導線は「説明欄の冒頭にURL」で代替しています。
  • テロップの絵文字は化ける: 前述のとおり、フォントにグリフが無い記号は使わない。
  • 元画像の解像度が命: 9:16に拡大するため、小さい画像だとぼやけます。原本を使うこと。
  • シークレットの扱い: トークン類はチャットやログに出さず、.env で管理。コードに直書きしない。

運用するとこうなる

記事を公開したあと、画像3枚からショート動画を生成してYouTubeに公開するまでが数分で完了します。BGM生成も含めた1本あたりの追加コストは数円程度で、手作業で動画編集ソフトを開く必要はありません。

実際にこの仕組みで、以下の流れが定着しました。

  1. 記事を書く(AIが生成)
  2. アイキャッチ・本文画像を生成して公開
  3. 同じ画像からショート動画を自動生成
  4. YouTubeへ自動投稿(説明欄からブログへ流入)
  5. Pinterestへ自動ピン投稿

まとめ

画像からショート動画を作ってYouTubeへ自動投稿する仕組みは、「ブログの資産をそのまま動画の資産に転用できる」のが最大のメリットです。新しく動画を撮影する必要がなく、記事を1本書けば動画も1本増えます。

  • 動画生成は MoviePy + Pillow(テロップは日本語フォント+画像合成で安定化)
  • BGMはAI生成で権利フリー
  • YouTube投稿は Data API v3 の resumable upload、public で投稿
  • つまずきどころは「スコープの設計」と「後から公開設定を変えられない」の2点

当ブログでは、今後もOpenClaw×AIによるブログ運営の自動化ノウハウを発信していきます!

関連記事

Pinterest APIでブログ記事を90件まとめて自動ピン化してみた!OpenClaw×Pinterest連携のセットアップ手順

OpenClawに「1行指示」するだけで旅行ブログ記事が完成!DeepSeek×Fal.aiの実コストを公開

【実録】SeaArtの画風をFal.aiで再現!OpenClaw×DeepSeek×Fal.aiでAI画像生成