ビデオ作成スキル
コードから作成するアニメーションモーショングラフィックス(キネティックタイプ、説明動画、ローンチビデオ、アニメーションチャート、選択可能なビジュアルスタイルのローワーサード)には、
motion-graphicsスキルを使用してください。このスキルは、映像、写真、音声、ffmpeg 編集のためのものです。
ffmpeg(コアエンジン)とPython(フレーム/グラフィックス用Pillow、高レベル編集用MoviePy)を使用して、プログラムで動画を構築します。常に最初に計画し、次に構築し、最後に検証します。
1. 構築前に計画する
コマンドを書く前に、以下を決定しておきます:
| 決定事項 | デフォルト | 備考 |
|---|---|---|
| アスペクト比 | 16:9 (1920x1080) | Reels/Shorts/TikTok は 9:16 (1080x1920)、フィードは 1:1 (1080x1080) |
| フレームレート | 30 fps | 映画調は 24、画面録画/ゲームプレイは 60 |
| 長さ | 指示通り | Reels/Shorts は 60~90 秒以下 |
| フォーマット | MP4 (H.264 + AAC) | ほぼ全ての環境で再生可能。WebM は指定があった場合のみ。GIF は短いループのみ。 |
| オーディオ | オプション | 音楽、ナレーション、または無音 |
ショットリスト(各シーン → 長さ、ビジュアル、テキスト、トランジション、オーディオ)を簡単に書き出します。
2. ツールチェーンを確認する
which ffmpeg ffprobe || (apt-get update && apt-get install -y ffmpeg)
python3 -c "import PIL, moviepy" 2>/dev/null || pip install pillow moviepy --break-system-packages
fc-list | head # テキストに使用できるフォントを確認
3. 標準エンコード設定
特にユーザーから指定がない限り、すべての最終レンダリングでこれらを使用します:
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k
-pix_fmt yuv420p— スマートフォン、QuickTime、ブラウザでの再生に必須。-movflags +faststart— 動画の完全ダウンロード前に再生を開始できるようにします。-crf 18= ほぼロスレス(ファイルサイズ大)、-crf 28= ファイルサイズ小、品質低。-preset veryfastはレンダリングを高速に保ちます。最高品質が要求された場合のみslowを使用します。- H.264 では、幅と高さは偶数である必要があります。
3b. 高速レンダリング — ユーザーが待っています
フルレンダリングは最も時間がかかるステップなので、リクエストごとに1回のフルレンダリングを計画します:
- エンコードする前に確認する。 最終サイズで数フレーム(最も忙しい瞬間、最初と最後のフレーム)を取得・描画し、フレーミング、テキストサイズ、色を確認します。写真動画の場合:
scripts/slideshow.py OUT.mp4 photos… --previewでコンタクトシートを1秒で作成します。 - 1つの巨大なグラフではなく、シーンごとに。 各シーンを同じサイズ、fps、エンコーダー設定で個別のファイルにエンコードし、concat デマルチプレクサと
-c copyで結合し、最後に-c:v copyで音声を追加します。後で1つのシーンを修正する場合、そのシーンだけを再エンコードします。 - トランジションはシーンファイル内に配置(エッジのフェードイン/アウト)。2つの完成ファイルをクロスフェードすると、2回目のフルエンコードが強制されます。
- 静止画を安価に保持する。 画像を1回デコードし、
loopフィルターで繰り返します。-loop 1(フレームごとに画像を再デコード)よりも高速です:ffmpeg -framerate 30 -i still.png -vf "format=yuv420p,loop=-1:1,trim=duration=4,setpts=PTS-STARTPTS" -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p still.mp4 - 中途半端なファイルを実際の名前で保存しない。
name.part.mp4に書き込み、ffmpeg が 0 で終了した場合のみリネームします。中断されたレンダリングでも再生は可能ですが、途中で切れます。 - 特に指定がない限り、出力サイズ、30 fps でフレームを描画します。Webページをフレームごとにキャプチャする場合、PNG ではなく JPEG(品質~90)として保存します。書き込みと読み込みがはるかに高速です。
10枚の写真 → 1080p での測定:従来の単一 -loop 1 + xfade グラフは 13.2 秒。slideshow.py --crossfade は 6.4 秒、シーンファイルパスは音楽込みで 8.9 秒。
完成した動画の変更:
| 変更 | 方法 |
|---|---|
| 1つのシーンの内容 | そのシーンファイルを再レンダリングし、-c copy で再結合 |
| 新しい音楽 / エンディングのトリミング | -c:v copy(新しい音楽:-map 0:v -map 1:a) |
| キャプションの焼き付け、小さなコピーの作成 | 完成ファイルを1回再エンコード |
| すべてのフレームで変化するもの | フルレンダリングは避けられない |
4. レシピ
4.0 写真スライドショー(1コマンド)
python3 scripts/slideshow.py out.mp4 photos/*.jpg --seconds 3 --fade 0.5 --music song.mp3 # 高速、黒フェード
python3 scripts/slideshow.py out.mp4 photos/*.jpg --crossfade # 真のクロスフェード
python3 scripts/slideshow.py out.mp4 photos/*.jpg --zoom --size 1080x1920 # Ken Burns、縦向き
4.1 画像からのスライドショー(フェードトランジション付き)手動の場合
# 各画像を 3 秒表示、0.5 秒クロスフェード、1080p にスケール/パディング
# 各静止画は 1 回デコードされ、ループフィルターで繰り返されます(-loop 1 の 2 倍高速)
F="scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,format=yuv420p,loop=-1:1,trim=duration=3,setpts=PTS-STARTPTS,fps=30"
ffmpeg -framerate 30 -i img1.jpg -framerate 30 -i img2.jpg -framerate 30 -i img3.jpg \
-filter_complex "\
[0]$F[a];[1]$F[b];[2]$F[c];\
[a][b]xfade=transition=fade:duration=0.5:offset=2.5[ab];\
[ab][c]xfade=transition=fade:duration=0.5:offset=5[out]" \
-map "[out]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart slideshow.mp4
クリップ n のオフセット = (前のクリップの合計時間) − (n × フェード時間)。
4.2 静止画像のKen Burns効果(スローズーム)
# d = 出力フレーム数(30 fps で 150 = 5 秒)。-loop は不要、zoompan がフレームを生成します
ffmpeg -i photo.jpg -vf "scale=3840:-2,zoompan=z='min(zoom+0.0008,1.2)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1920x1080:fps=30" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p zoom.mp4
最初に 3840 にアップスケーリングすることで、小さな画像で zoompan に見られるジッターを回避します。
4.3 テキストオーバーレイ / タイトルカード
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
text='My Title':fontsize=72:fontcolor=white:box=1:[email protected]:boxborderw=20:\
x=(w-text_w)/2:y=h-150:enable='between(t,0,4)'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy titled.mp4
アポストロフィ、絵文字、または非ラテン文字(ヒンディー語など)を含むテキストの場合は、そのスクリプトをサポートするフォントを使用して Pillow でテキストを PNG にレンダリングし、それをオーバーレイします:
ffmpeg -i input.mp4 -i title.png -filter_complex "overlay=(W-w)/2:H-h-100:enable='between(t,0,4)'" ...
4.4 何もないところからベタ塗りのタイトルカード
ffmpeg -f lavfi -i color=c=0x111827:s=1920x1080:d=3:r=30 \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='Chapter 1':fontsize=96:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p card.mp4
4.5 クリップのトリミング
# 正確なトリミング(再エンコード):00:00:05 から 10 秒間
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac trimmed.mp4
# 高速トリミング(再エンコードなし、最も近いキーフレームでカット)
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c copy trimmed_fast.mp4
4.6 クリップの結合
同じコーデック/解像度/fps → concat デマルチプレクサ(再エンコードなし):
printf "file 'a.mp4'\nfile 'b.mp4'\nfile 'c.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
異なるソース → 最初に正規化(スケール、fps、オーディオレート)するか、concat フィルターを使用します:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,setsar=1,fps=30[v0];[1:v]scale=1920:1080,setsar=1,fps=30[v1];\
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac joined.mp4
(クリップにオーディオトラックがない場合は、無音を追加します:-f lavfi -i anullsrc=r=48000:cl=stereo を使用し、-shortest を追加します。)
4.7 BGMの追加
# ビデオの下に音楽、最後にフェードアウト、ビデオ終了時に停止
# st = ビデオの長さ - フェードの長さ(ここでは 30 秒のビデオ、3 秒のフェード)— ffprobe で長さを取得します
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.3,afade=t=out:st=27:d=3[m]" \
-map 0:v -map "[m]" -c:v copy -c:a aac -shortest with_music.mp4
# 既存の音声の下に音楽をミックス
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.15[m];[0:a][m]amix=inputs=2:duration=first[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac mixed.mp4
4.8 字幕 / キャプション
.srt ファイルを作成します:
1
00:00:00,000 --> 00:00:03,000
ビデオへようこそ。
2
00:00:03,000 --> 00:00:06,500
これから説明する内容です。
焼き付けます:
ffmpeg -i input.mp4 -vf "subtitles=subs.srt:force_style='FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,BorderStyle=1,Outline=2'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy captioned.mp4
4.9 ソーシャルメディア用にリサイズ(縦 9:16)
# 黒帯の代わりにぼかした背景で埋める
ffmpeg -i landscape.mp4 -filter_complex \
"[0]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=20[bg];\
[0]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy vertical.mp4
4.10 速度変更(早送り / スロー)
# 2倍速(ビデオ + オーディオ)
ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" fast.mp4
4.11 ビデオをGIFに変換(高品質)
ffmpeg -i in.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 out.gif
4.12 コードからフレームをレンダリング(アニメーション、チャート、モーショングラフィックス)
ffmpeg フィルターだけでは不十分な場合、Python で各フレームを描画して結合します:
from PIL import Image, ImageDraw, ImageFont
import os, subprocess
W, H, FPS, SECONDS = 1920, 1080, 30, 4
os.makedirs("frames", exist_ok=True)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 96)
for i in range(FPS * SECONDS):
t = i / (FPS * SECONDS)
img = Image.new("RGB", (W, H), (17, 24, 39))
d = ImageDraw.Draw(img)
x = int(-600 + t * (W + 600)) # テキストが横にスライド
d.text((x, H // 2 - 48), "Hello!", font=font, fill=(255, 255, 255))
img.save(f"frames/{i:05d}.png")
subprocess.run(["ffmpeg", "-y", "-framerate", str(FPS), "-i", "frames/%05d.png",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "animation.mp4"], check=True)
イージング(例:t*t*(3-2*t))を使用すると、より滑らかな動きになります。
4.13 MoviePy(クイックな高レベル編集)
from moviepy import VideoFileClip, TextClip, CompositeVideoClip, concatenate_videoclips, AudioFileClip
clip1 = VideoFileClip("a.mp4").subclipped(0, 5)
clip2 = VideoFileClip("b.mp4").subclipped(2, 8)
video = concatenate_videoclips([clip1, clip2], method="compose")
music = AudioFileClip("music.mp3")
video = video.with_audio(music.subclipped(0, min(video.duration, music.duration))) # 音楽が短い場合がある
video.write_videofile("out.mp4", fps=30, codec="libx264", audio_codec="aac", preset="veryfast")
(MoviePy 2.x API を示しています。1.x では subclip/set_audio を使用します。pip show moviepy で確認してください。)
5. 納品前に検証する
動画を確認せずに引き渡さないでください:
# 長さ、解像度、コーデック、fps
ffprobe -v error -show_entries format=duration:stream=codec_name,width,height,r_frame_rate -of default=nw=1 out.mp4
# キーモーメントで静止フレームを取得して確認
ffmpeg -ss 1 -i out.mp4 -frames:v 1 check_1s.png
ffmpeg -i out.mp4 -vf "fps=1/3,scale=480:-1,tile=4x2" -frames:v 1 contact_sheet.png
チェックリスト:
- 長さが計画と一致している
- 解像度とアスペクト比が正しく、不要な黒帯や引き伸ばしがない
- テキストが読みやすく、安全マージン内にある(端から約5%をキープ。9:16の場合、アプリUIが配置される下部約15%を避ける)
- トランジションが意図した場所にある
- オーディオが存在する(期待される場合)、クリッピングしていない、最後までクリーンに終わる
- スマートフォンで再生できる:H.264、
yuv420p、偶数サイズ
6. よくある問題とその対策
| 問題 | 対策 |
|---|---|
| 動画がiPhone/QuickTimeで再生できない | -pix_fmt yuv420p を追加 |
| "width not divisible by 2" | -1 の代わりに scale=1280:-2 を使用 |
-c copy トリミング後、先頭に黒フレーム | 再エンコードするか、-ss を -i の前に置いて再エンコード |
| concat 後に音声がずれる | 結合前に fps とオーディオサンプルレート(-ar 48000)を正規化 |
| xfade エラー "inputs differ" | すべての入力は同じサイズ、fps、setsar=1、ピクセルフォーマットである必要がある |
drawtext が ' や : で壊れる | エスケープするか、textfile= または Pillow でレンダリングした PNG オーバーレイを使用 |
| 非英語テキストが□で表示される | そのスクリプトをサポートするフォント(例:Noto Sans Devanagari)を使用 |
| ファイルサイズが大きい | CRF を上げる(26~28)、fps または解像度を下げる |
| レンダリングが遅すぎる | -preset veryfast または ultrafast を使用。ドラフト中は解像度を下げる |
7. ワークフローまとめ
- 計画 — アスペクト比、fps、長さ、ショットリスト、オーディオ。
- アセット収集 — 画像、クリップ、音楽、フォント。
ffprobeで確認。 - ドラフトを高速に作成 — 低解像度 /
ultrafastプリセットでタイミング確認。 - 最終版を構築 — 標準エンコード設定(セクション3)。
- 検証 — ffprobe + コンタクトシート + チェックリスト(セクション5)。
- 納品 — 最終ファイルと1行の概要(長さ、解像度、内容)を送付。