moviepy について
AI生成映像への正確で決定的なテキスト追加や、短いPython動画制作に使うmoviepyのスキルです。Remotionを使わずに確実なラベルや字幕を加えたい編集者向けです。
主な機能
- 01PILによる決定的なテキストオーバーレイ
- 02動画とデータ駆動ビジュアルの合成
- 03音声基準のシーンタイムライン
- 04ナレーションと音量を下げた音楽のミックス
- 05実行可能な短編動画の例
活用シーン
- 01AI生成映像に正確な名前や字幕を追加
- 02Pythonで短い広告動画を制作
- 03チャートにタイトルや出典を重ねる
- 04シーンごとのナレーションに動画時間を合わせる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ digitalsamba/ claude-code-video-toolkit --skill moviepyインストール後、Claude Code で/moviepyを入力して読み込まれたか確認します。
詳細
動画制作向けの moviepy
moviepy は、AI 生成動画に決定論的なテキストを重ねるため、また Remotion のツールチェーンを使わずに、単一ファイルで完結する短い Python 動画プロジェクトを作成するための、ツールキット定番のライブラリです。
さらに根底にある原則は、信頼できるテキストです。テキストが読みやすく、正確で、一貫している必要のあるジャンル(法的、編集上、または商業上の理由から必要な場合)はすべて、AI が動画内に描画するテキストを許容できず、moviepy でオーバーレイするのが自然な解決策です。名前は正しく綴る必要があります。価格は正確でなければなりません。出典の帰属表示はピクセル単位で正確でなければなりません。AI 生成モデルでは、そのどれも保証できません。
moviepy と Remotion の使い分け
| moviepy を使う場合… | Remotion を使う場合… |
|---|---|
| LTX-2 または SadTalker の出力にテキストやラベルを重ねる | 長尺のスプリントレビューや製品デモを作成する |
単一の build.py で 30 秒未満の広告風動画を作成する | 複数のテンプレートやブランドを扱う、デザイン重視の作業 |
データ駆動型のビジュアル(matplotlib FuncAnimation → mp4)を合成する | React コンポーネントやデザインシステムの再利用が必要なもの |
| 既存の動画ファイルに単発の変換を適用する | プロジェクトのライフサイクル(計画 → レンダリング)が重要なもの |
| Node.js を一切使わず、React に頭を悩ませたくない | Remotion Studio でホットリロードプレビューを使いたい |
このスキルで紹介する、実行可能なリファレンスは examples/ に 2 つあります。
examples/quick-spot/build.py— 15 秒の広告風動画です。音声を基準にしたタイムライン、テキストオーバーレイ、オプションのナレーションと音量を下げた音楽が含まれます。外部アセットなしで、すぐに無音の動画をレンダリングできます。examples/data-viz-chart/build.py— 決定論的なタイトルと出典の帰属表示を備えた、時系列データのアニメーションチャートです。matplotlib(データ)と moviepy(信頼できるテキスト)を使い分ける方法を示します。
どちらも uv run build.py で実行でき、すぐに実際の out.mp4 を生成します。このスキルとあわせて読んでください。以下で説明するすべてのパターンが、そこで動作する形で示されています。
依存関係。 moviepy、Pillow、matplotlib はルートの pyproject.toml で宣言されており、ツールキットの 1 行で済む Python セットアップである uv sync でインストールされます。サンプルの実行時に Missing dependency が表示された場合は、リポジトリのルートでそのコマンドを実行してください。サンプルの build.py ファイルにも同じ内容がエラーメッセージで表示され、むき出しのトレースバックを出力することなく正常に終了します。
主なユースケース:AI 生成動画へのテキスト追加
LTX-2 と SadTalker は、どちらもビジュアルのみを出力します。
- LTX-2 は、読みやすいテキストを安定して描画できません(モデルが文字の形を幻覚します。ltx2 スキルの「Bad Prompts」を参照してください)。
- SadTalker は、字幕、ラベル、ローワーサード、コンテキストのないトーキングヘッド動画を出力します。
解決策は、ビジュアルをきれいに生成してから、moviepy でテキストを決定論的に重ねることです。これが、このツールキットにおける標準的なパターンです。
from moviepy import VideoFileClip, ImageClip, CompositeVideoClip
# 1. AI-generated visual (LTX-2 or SadTalker output)
bg = VideoFileClip("lugh_ltx.mp4").without_audio()
# 2. Text rendered via PIL → ImageClip (see "Text rendering" below)
title = (
ImageClip("text_cache/intro_title.png")
.with_duration(2.0)
.with_start(0.5)
.with_position(("center", 880))
)
# 3. Composite
final = CompositeVideoClip([bg, title], size=(1920, 1080))
final.write_videofile("lugh_with_caption.mp4", fps=30, codec="libx264")
よくある用途は次のとおりです。
| 用途 | LTX-2 での使用例 | SadTalker での使用例 |
|---|---|---|
| ヒーロー映像に重ねるタイトルカード | 映画風の LTX-2 B ロール映像に「INTRODUCING LONGARM」を重ねる | 該当なし |
| ローワーサード/ネームプレート | 該当なし | トーキングヘッドの下に「Lugh — Ancient Warrior God」を表示する |
| 引用字幕 | LTX-2 のキャラクターカメオに「I am going home.」を重ねる | 同じテキストを SadTalker のトーキングヘッドに重ねる |
| ブランド表記 | 最後の 1 秒にロゴと URL をフェードインさせる | 同じ |
| コントラスト用の色付きオーバーレイ | テキストの背後に半透明の濃紺レイヤーを配置する | 同じ |
このパターンが特に活きるジャンル
「AI で映像を生成し、テキストを決定論的に重ねる」パターンは、いくつかのスタイルの動画に最適な制作パイプラインです。依頼が次のいずれかに当てはまる場合は、まず moviepy を検討してください。
| ジャンル | 重ねるもの | moviepy が適している理由 |
|---|---|---|
| ニュース/トークヘッド形式の報道 | 話者名のプレート、場所を示すバー、速報バナー、出典表記、引用文 | 名前は正確に表記する必要があります(編集上/法的な理由)。ボリュームが最も大きいカテゴリーです。 |
| ドキュメンタリーのセグメント | インタビュー対象者のローワーサード、章タイトル、アーカイブ映像の出典クレジット、場所のスタンプ | ニュースと同じく、信頼性が求められます。 |
| 予告編/プロモーション映像 | タイトルカード、クレジットのオーバーレイ(「FROM THE DIRECTOR OF…」)、日付の演出、引用カード、CTA | タイミングが非常に重要で、テキストが多く、すべてのフレームが大切です。q2-townhall-longarm-ad の例はまさにこのタイプです。 |
| ソーシャル向けショート動画(Reels、TikTok、Shorts) | 音声なしで視聴する場合に正確な字幕、ハッシュタグのオーバーレイ | ソーシャル動画のほとんどはミュートで視聴されるため、字幕は欠かせません。 |
| 注釈付きの製品デモ | 価格のコールアウト、機能ラベル、画面録画上の「ここをクリック」というポインター、変更前/変更後のラベル | 価格と製品名は正確でなければなりません。 |
| チュートリアル/解説動画 | ステップ番号のオーバーレイ、ターミナルコマンドの字幕、キーボードショートカットのコールアウト | ステップ番号は順番どおりでなければならず、コマンドはコピー&ペーストできる必要があります。 |
適合度はやや低いものの、十分に活用できます。ミュージックビデオ(歌詞のオーバーレイ)、リアクション動画(出典表記)、スポーツの振り返り(スコアのオーバーレイ)、不動産の内覧動画(価格/平方フィート数)、カンファレンストーク(話者名とセッションのプレート)。
SRT を使った字幕付け全般(長尺、タイムコード付き、多言語対応)には moviepy も使えますが、最適ではありません。ffmpeg の subtitles フィルターか、専用の字幕ツールを検討してください。moviepy は手作業で配置するオーバーレイに最適であり、大量の字幕トラックには向いていません。
テキストのレンダリング — TextClip ではなく PIL を使用
重大な注意点: moviepy 2.x の TextClip(method='label') には、タイトなバウンディングボックスに関するバグがあり、文字のアセンダーとディセンダー(大文字の上端や、g/p/y の下端)が切り取られます。Apple Silicon では、文字の端が切れた状態になり、何時間も原因に気づかないことがあります。
回避策: PIL を使ってテキストを透過 PNG にレンダリングし、それを ImageClip として読み込みます。コンテンツハッシュを使って結果をキャッシュすれば、再ビルドのコストはかかりません。
import hashlib
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ARIAL_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
def render_text_png(txt, size, hex_color, cache_dir="./text_cache"):
cache = Path(cache_dir); cache.mkdir(parents=True, exist_ok=True)
key = hashlib.sha1(f"{txt}|{size}|{hex_color}".encode()).hexdigest()[:16]
path = cache / f"{key}.png"
if path.exists():
return str(path)
font = ImageFont.truetype(ARIAL_BOLD, size)
bbox = ImageDraw.Draw(Image.new("RGBA", (1, 1))).textbbox((0, 0), txt, font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
pad = max(20, size // 4)
img = Image.new("RGBA", (tw + pad * 2, th + pad * 2), (0, 0, 0, 0))
rgb = tuple(int(hex_color.lstrip("#")[i:i+2], 16) for i in (0, 2, 4))
ImageDraw.Draw(img).text((pad - bbox[0], pad - bbox[1]), txt, font=font, fill=(*rgb, 255))
img.save(path)
return str(path)
完全なヘルパー関数(太字、位置、フェード用のキーワード引数と、よりすっきりした使い勝手を備えたもの)は examples/quick-spot/build.py にあります。再実装せず、そのままコピーしてください。
オーディオを基準にしたタイムラインのパターン
広告スタイルの編集で、すべてのフレームが重要な場合は、シーンごとの VO を先に生成し、すべての映像を既知の絶対タイムスタンプに合わせます。これにより、タイミングのずれを完全になくせます。パターンの詳細は CLAUDE.md → Video Timing → Audio-Anchored Timelines を参照してください。概要は次のとおりです。
# Audio-anchored timeline (25s):
# Scene 1 tired 0.3 → 3.74 (audio 3.44s)
# Scene 2 worries 4.0 → 8.88 (audio 4.88s)
text_clip("TIRED OF", start=0.5, duration=1.2)
text_clip("THIRD-PARTY", start=1.0, duration=1.8)
vo_clip("01_tired.mp3", start=0.3)
vo_clip("02_worries.mp3", start=4.0)
よく使うレシピ
AI で生成した単一クリップにテキストを重ねる
from moviepy import VideoFileClip, ImageClip, CompositeVideoClip
bg = VideoFileClip("ltx_hero.mp4").without_audio()
caption = (
ImageClip(render_text_png("THE FUTURE OF AGENTS", 140, "#FFFFFF"))
.with_duration(bg.duration)
.with_position(("center", 880))
)
CompositeVideoClip([bg, caption], size=bg.size).write_videofile("captioned.mp4", fps=30)
SadTalker のトークヘッドにローワーサードを重ねる
from moviepy import VideoFileClip, ImageClip, ColorClip, CompositeVideoClip
talking = VideoFileClip("narrator_sadtalker.mp4")
W, H = talking.size
# Semi-transparent bar across the bottom for contrast
bar = (
ColorClip((W, 140), color=(20, 24, 38))
.with_duration(talking.duration)
.with_opacity(0.75)
.with_position(("center", H - 160))
)
name = (
ImageClip(render_text_png("LUGH", 72, "#F06859"))
.with_duration(talking.duration)
.with_position((80, H - 150))
)
title = (
ImageClip(render_text_png("Ancient Warrior God", 36, "#FFFFFF"))
.with_duration(talking.duration)
.with_position((80, H - 80))
)
CompositeVideoClip([talking, bar, name, title]).write_videofile("with_lower_third.mp4", fps=30)
映像が煩雑な場合に、テキストとのコントラストを確保する色付きオーバーレイ
LTX-2 の B ロールは、読みやすいテキストを重ねるには映像が煩雑すぎることがよくあります。動画とテキストの間に、半透明のネイビーのレイヤーを追加してください。
from moviepy import ColorClip
tint = (
ColorClip((W, H), color=(20, 24, 38))
.with_duration(duration)
.with_opacity(0.55)
)
# Composite order: bg → tint → text
CompositeVideoClip([bg, tint, text_clip])
横並びの合成
from moviepy import VideoFileClip, CompositeVideoClip, ColorClip
left = VideoFileClip("demo_a.mp4").resized(width=960).with_position(( 0, "center"))
right = VideoFileClip("demo_b.mp4").resized(width=960).with_position((960, "center"))
bg = ColorClip((1920, 1080), color=(0, 0, 0)).with_duration(max(left.duration, right.duration))
CompositeVideoClip([bg, left, right]).write_videofile("split.mp4", fps=30)
シーンごとの VO と音量を下げた音楽をミックスする
from moviepy import AudioFileClip, CompositeAudioClip
from moviepy.audio.fx.MultiplyVolume import MultiplyVolume
from moviepy.audio.fx.AudioFadeIn import AudioFadeIn
from moviepy.audio.fx.AudioFadeOut import AudioFadeOut
music = AudioFileClip("music.mp3").with_effects([
MultiplyVolume(0.22), # duck under VO
AudioFadeIn(0.5),
AudioFadeOut(1.5),
])
vo = [
AudioFileClip(f"scenes/0{i}.mp3").with_effects([MultiplyVolume(1.15)]).with_start(start)
for i, start in [(1, 0.3), (2, 4.0), (3, 9.1)]
]
final_audio = CompositeAudioClip([music] + vo)
注意点
- moviepy 2.x ではメソッド名が変更されています。
subclipではなくsubclippedを、set_durationなどではなくwith_duration/with_start/with_positionを使ってください。.fadein()/.fadeout()の代わりにwith_effects([...])を使います。オンラインのチュートリアルには 1.x の構文が今も多く掲載されているため、注意してください。 TextClip(method='label')は文字の上部や下部を切り取ります。 必ず上記の PIL を使った回避策を使用してください。OffthreadVideoは Remotion 専用です。 moviepy ではVideoFileClipを使います。両者を混同しないでください。- リサイズで LANCZOS リサンプリングを使うには、Pillow ≥ 10.0 が必要です。
ANTIALIASエラーが発生した場合は、Pillow をアップグレードしてください。 ColorClipには 16 進文字列ではなく RGB タプルを指定します。"#141826"ではなく(20, 24, 38)を使ってください。VideoFileClipでは音声がデフォルトで読み込まれます。 映像のみが必要な場合は.without_audio()を呼び出してください。不要な音声と合成すると、CompositeAudioClipで無音の VO が欠落する原因になります。CompositeVideoClipには必ずsize=(W, H)を指定してください。 指定しない場合、出力サイズは最初のクリップに合わせられ、目標サイズより小さくなることがあります。
用途に応じた選び方
| タスク | ツール |
|---|---|
| 静止画像をアニメーション化する | tools/ltx2.py --input |
| 写実的なポートレートからトーキングヘッドを作成する | tools/sadtalker.py |
| スタイライズされたキャラクターからトーキングヘッドを作成する | tools/ltx2.py --input(ltx2 スキルを参照) |
| 上記のいずれかにラベル/キャプション/ローワーサードを追加する | moviepy + PIL(このスキル) |
| 既存のファイルを変換/圧縮/リサイズする | ffmpeg(ffmpeg スキルを参照) |
| デザインシステムに基づく長尺動画を作成する | Remotion(remotion スキルを参照) |
参考資料
- 実行可能なサンプル — 短い広告風動画:
examples/quick-spot/build.py - 実行可能なサンプル — テキストを重ねたデータ可視化:
examples/data-viz-chart/build.py - 音声に合わせたタイムライン:
CLAUDE.md → Video Timing → Audio-Anchored Timelines - 関連スキル:
ltx2、ffmpeg、remotion