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ステップです。
- 素材の取得: 記事で使った画像3枚を取得(高解像度の原本を使用)
- BGM生成: AIで記事の雰囲気に合うオリジナル楽曲を生成
- 動画生成: 9:16(1080×1920)のスライドショー+テロップ+BGMを書き出し
- 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を有効化
- Google Cloud Console でプロジェクトを作成
- 「YouTube Data API v3」を有効化
- OAuth同意画面(Google Auth Platform)を設定し、スコープに動画アップロードを追加
- クライアント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本あたりの追加コストは数円程度で、手作業で動画編集ソフトを開く必要はありません。
実際にこの仕組みで、以下の流れが定着しました。
- 記事を書く(AIが生成)
- アイキャッチ・本文画像を生成して公開
- 同じ画像からショート動画を自動生成
- YouTubeへ自動投稿(説明欄からブログへ流入)
- Pinterestへ自動ピン投稿
まとめ
画像からショート動画を作ってYouTubeへ自動投稿する仕組みは、「ブログの資産をそのまま動画の資産に転用できる」のが最大のメリットです。新しく動画を撮影する必要がなく、記事を1本書けば動画も1本増えます。
- 動画生成は MoviePy + Pillow(テロップは日本語フォント+画像合成で安定化)
- BGMはAI生成で権利フリー
- YouTube投稿は Data API v3 の resumable upload、public で投稿
- つまずきどころは「スコープの設計」と「後から公開設定を変えられない」の2点
当ブログでは、今後もOpenClaw×AIによるブログ運営の自動化ノウハウを発信していきます!
関連記事
Pinterest APIでブログ記事を90件まとめて自動ピン化してみた!OpenClaw×Pinterest連携のセットアップ手順