MoviePyはPythonから動画のカット・結合・字幕合成・音声差し替えを行うライブラリで、最新版は2.2.1(2025年5月21日リリース)です。バージョン2.0でmoviepy.editorが廃止され、set_*系メソッドとfx()によるエフェクト適用も置き換わったため、ネット上に残る大半のサンプルコードはpip install moviepyで入る現行版では動きません。ここでは現行版で実際に動く書き方を軸に、v1コードの移行手順、インストールとFFmpegの扱い、テキスト合成・音声処理・GIF書き出し・一括処理までをまとめます。
まとめ
- 最新版は2.2.1(2025年5月21日)。
pip install moviepyで入るのはv2系で、Python 3.9以上が前提(pip側で下限が宣言されていないため、3.8以下でもインストールは通り実行時に壊れる)。 - v1のコードはそのままでは動かない。
from moviepy.editor import *はfrom moviepy import *へ、subclip()はsubclipped()へ、set_position()などset_*はwith_*へ変わった。 - エフェクトはクラスになった。
clip.fx(vfx.speedx, 2)はclip.with_effects([vfx.MultiplySpeed(2)])と書く。fadein/fadeoutもvfx.FadeIn/vfx.FadeOutに置き換わった。 - FFmpegは手動導入しなくてよい。依存の
imageio-ffmpegにバイナリが同梱されており、pip install moviepyだけで書き出しまで動く。FFMPEG_BINARYの設定はシステム側のFFmpegに差し替えるときだけ。 - 日本語字幕はフォント指定が必須。
TextClipのfontを省略するとPillowの既定フォントになり、日本語は正しく描画されない。 - 向くのは定型処理の自動化(大量ファイルの一括変換・字幕焼き込み)。1本の動画を凝った演出で仕上げる用途にはGUI編集ソフトの方が速い。
MoviePyでできることと、向かない用途
MoviePyはFFmpegをPythonオブジェクトの背後に隠すラッパーです。動画をVideoFileClipとして読み込み、切り出し・結合・合成・エフェクト・音声操作をメソッドチェーンで記述し、最後にwrite_videofile()で書き出します。FFmpegのコマンドライン引数を組み立てる代わりに、Pythonの制御構文(ループ・条件分岐・ファイル走査)で編集手順そのものを書ける。FFmpegとは?使い方とコマンド一覧を動画変換・音声抽出の実例で解説で扱うコマンド単発の処理を、繰り返しと分岐つきで再現できます。
一方で、MoviePyを選ぶべきでない場面もはっきりしています。MoviePyはフレームをPython側に取り出してから合成し直すため、その分のオーバーヘッドがFFmpegを直接叩く場合より必ず乗ります。解像度変更・コーデック変換・無劣化カットだけが目的なら、MoviePyを挟む理由はありません。また、カットのタイミングを目で追いながら詰めるような編集はコードでの試行錯誤に向かず、GUIの編集ソフトの方が短時間で終わります。MoviePyが効くのは「同じ手順を何十本にも適用する」「動画生成をスクリプトやCIに組み込む」といった、人手の繰り返しをコードに移す場面です。
MoviePy v2で変わった書き方(v1のコードが動かない理由)
2024年11月のv2.0は破壊的変更を伴うメジャーアップデートで、記事やQiitaに残るv1系のサンプルは現行版では例外を投げます。検索結果の上位に「moviepy.editorのインストール方法」といった質問が並ぶのは、この移行が原因です。まず押さえるべきは次の3点です。
moviepy.editorの廃止とModuleNotFoundErrorの直し方
v2ではmoviepy.editorという名前空間そのものが削除されました。from moviepy.editor import VideoFileClipを実行するとModuleNotFoundError: No module named 'moviepy.editor'になりますが、これはインストールの失敗ではなく、v2にモジュールが存在しないためです。moviepy.editorという別パッケージがあるわけでもないので、再インストールでは直りません。インポート行そのものを書き換えます。
# v1(動かない)
from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip
# v2(現行版)
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
# まとめて読み込む場合
from moviepy import *
set_*からwith_*へ、subclipからsubclippedへ
v2ではクリップを変更するメソッドが「元のオブジェクトを書き換えず、変更したコピーを返す」方式に統一され、名前もwith_始まりに変わりました。既存コードの置換対象は次のとおりです。
| v1の書き方 | v2の書き方 | 用途 |
|---|---|---|
clip.subclip(10, 20) |
clip.subclipped(10, 20) |
区間の切り出し |
clip.set_duration(5) |
clip.with_duration(5) |
表示時間の指定 |
clip.set_start(3) |
clip.with_start(3) |
合成時の開始位置 |
clip.set_position("center") |
clip.with_position("center") |
合成時の座標 |
clip.set_audio(bgm) |
clip.with_audio(bgm) |
音声の差し替え |
clip.set_fps(30) |
clip.with_fps(30) |
フレームレート |
clip.resize(width=640) |
clip.resized(width=640) |
リサイズ |
clip.crop(x1=0, y1=0) |
clip.cropped(x1=0, y1=0) |
切り抜き |
clip.rotate(90) |
clip.rotated(90) |
回転 |
エフェクトのクラス化とwith_effects()での適用
v1ではclip.fx(vfx.speedx, 2)やclip.fadein(1)のように関数・メソッドとして適用していましたが、v2ではエフェクトがEffectクラスの実装になり、with_effects()にインスタンスのリストを渡す形に変わりました。moviepy.video.fx.allというインポートパスも存在しません。
from moviepy import VideoFileClip, vfx, afx
clip = VideoFileClip("input.mp4")
# v1: clip.fx(vfx.speedx, 2).fadein(1).fadeout(1)
# v2: エフェクトのインスタンスをリストで渡す
clip = clip.with_effects([
vfx.MultiplySpeed(2), # 2倍速(旧 speedx)
vfx.FadeIn(1), # 1秒かけてフェードイン(旧 fadein)
vfx.FadeOut(1), # 1秒かけてフェードアウト(旧 fadeout)
afx.MultiplyVolume(0.5), # 音量を半分に(旧 volumex)
])
v2で使える主な映像エフェクトはResize、Crop、Rotate、MultiplySpeed、AccelDecel、FadeIn、FadeOut、CrossFadeIn、CrossFadeOut、Loop、MirrorX、MirrorY、BlackAndWhite、InvertColors、MaskColor、Marginなど。音声側(afx)はMultiplyVolume、AudioFadeIn、AudioFadeOut、AudioNormalize、AudioLoop、AudioDelay、MultiplyStereoVolumeが用意されています。クラス名はいずれもキャメルケースで、v1の小文字関数名(speedx、volumex、fadein)はもう通りません。
なお、v2ではImageMagick・PyGame・OpenCV・SciPyへの依存が外れ、画像処理はPillowに一本化されました。v1でImageMagickの導入に手間取った環境でも、v2なら追加インストールなしでテキスト描画まで動きます。Pillow側の画像加工と組み合わせるならPillowを用いた主要な画像加工テクニックが参考になります。
インストール・FFmpeg・バージョン確認
pipでの導入と仮想環境
導入はpip install moviepyだけです。プロジェクトごとに仮想環境を切っておくと、v1に依存した既存スクリプトと共存させられます。
python -m venv .venv
source .venv/bin/activate # Windowsは .venv\Scripts\activate
pip install moviepy
condaを使う環境ではconda install -c conda-forge moviepyで導入できます。conda-forgeも2.2.1を提供しているためv2が入りますが、既存環境の依存解決の結果しだいで古い版に落ちることはあるので、導入後に次項の手順でバージョンを確認してください。pipとの使い分けはpipとcondaの違いと使い分け|Anaconda・Miniconda・商用ライセンスまで解説で整理しています。
FFmpegの自動同梱と、差し替え時の設定
FFmpegの手動インストールとPATH設定は不要です。依存パッケージのimageio-ffmpegにFFmpegバイナリが同梱されており、pip install moviepyの時点で一緒に入ります(公式ドキュメントには「初回利用時にImageIOがダウンロードする」と書かれていますが、現行のimageio-ffmpegはwheelに同梱済みで、実行時のダウンロードは発生しません)。社内のビルド済みFFmpegやハードウェアエンコード対応版に差し替えたい場合だけ、MoviePyのオブジェクトをインポートする前に環境変数を設定します。
import os
# 差し替えるときだけ有効化する(既定は同梱バイナリ = "ffmpeg-imageio")
# os.environ["FFMPEG_BINARY"] = "auto-detect" # PATH上のffmpegを使う
# os.environ["FFMPEG_BINARY"] = "/usr/local/bin/ffmpeg" # 特定のバイナリを指定
from moviepy.config import check
check() # 検出されたバイナリを表示
auto-detectを安易に設定してはいけません。PATH上にffmpegが無い環境ではこの値がunsetに落ち、同梱バイナリも使われなくなってMoviePy: can't find or access ffmpeg in 'unset'.で書き出しが全て失敗します。FFmpegを手動導入していない読者は、この2行をコメントアウトしたままにしてください。なおpreview()で再生確認をする場合だけはffplayが別途必要です。バージョンごとの対応コーデックはFFmpeg 8.1の新機能と8.0からの変更点|インストール方法・最新版8.1.2まで解説を確認してください。
バージョン確認と、v1に固定する場合の注意
バージョンはpip showで確認します。moviepy.__version__は使わないでください。2.2.1をインストールしても、同梱のversion.pyが更新されておらず2.1.2と表示されるため、「古い版が入った」と誤認します。
pip show moviepy # Version: 2.2.1 と表示される
# あるいは
python -c "import importlib.metadata as m; print(m.version('moviepy'))"
既存のv1コードを今すぐ書き換えられない場合は、pip install "moviepy<2"でv1.0.3に固定できます。ただしv1.0.3は2020年のリリースで開発が止まっており、新しいPythonでは依存パッケージの解決に失敗することがあります。固定するならPythonの版も併せて固定してください。あくまで移行までの時間稼ぎと割り切り、上の対応表に沿った置換を進める方が結局は早く済みます。
動画の読み込み・カット・結合・書き出し
読み込みと動画情報の取得
from moviepy import VideoFileClip
clip = VideoFileClip("input.mp4")
print(clip.duration) # 尺(秒・float)
print(clip.fps) # フレームレート
print(clip.size) # [幅, 高さ]
clip.close() # 明示的に解放(ループ処理では必須)
大量のファイルをループで処理するときはclose()を呼ぶかwith文で扱ってください。開いたままだとFFmpegのサブプロセスとファイルハンドルが残り続け、OSのファイルディスクリプタ上限(macOSの既定はulimit -nで256)に達するとToo many open filesで落ちます。
区間の切り出し(subclipped)と結合
from moviepy import VideoFileClip, concatenate_videoclips
clip = VideoFileClip("input.mp4")
# 10秒〜20秒を切り出す(v1のsubclipに相当)
part1 = clip.subclipped(10, 20)
# 末尾から5秒前まで(負の値は終端からの相対指定)
part2 = clip.subclipped(30, -5)
# 複数シーンを連結。解像度やfpsが違う素材はmethod="compose"を指定する
final = concatenate_videoclips([part1, part2], method="compose")
final.write_videofile("output.mp4")
method="chain"(既定)は素材の解像度が揃っている前提で高速に連結します。サイズの異なる動画を混ぜると出力が破綻するため、素材が不揃いならmethod="compose"を使い、事前にresized()で幅を揃えておきます。
write_videofileの主要引数と処理時間
final.write_videofile(
"output.mp4",
codec="libx264", # MP4の標準的な選択
audio_codec="aac", # 音声が無音になる場合はここを明示
fps=30,
threads=4, # エンコードのスレッド数
preset="medium", # ultrafast〜veryslow。速度と圧縮率のトレードオフ
bitrate="4000k",
)
presetはx264に渡される設定で、ultrafastにすればエンコードは速くなりますが同じ画質でファイルサイズが膨らみます。確認用の書き出しはultrafast、納品用はmedium以上、という切り替えが現実的です。threadsを増やしても、フレーム生成がPython側で行われる合成処理(テキスト重ね・エフェクト)はスレッド数に比例して速くはなりません。ボトルネックはエンコードではなくフレーム合成側にあることが多く、まず解像度を落として試すのが確実です。
テキストと画像の合成(TextClip / ImageClip)
TextClipと日本語フォントの指定
v2のTextClipはTextClip(font=None, text=None, font_size=None, color='black', ...)という署名で、fontを省略するとPillowの既定フォントが使われます。この既定フォントは日本語のグリフを持たないため、日本語字幕では必ずTTF/OTFのパスを渡してください(省略すると豆腐や空白になります)。
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
clip = VideoFileClip("input.mp4")
txt = TextClip(
font="/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
text="サンプル字幕",
font_size=48,
color="white",
stroke_color="black",
stroke_width=2,
method="caption", # sizeに合わせて自動改行する
size=(clip.size[0] - 80, None),
).with_duration(5).with_start(2).with_position(("center", "bottom"))
CompositeVideoClip([clip, txt]).write_videofile("subtitled.mp4")
method="label"(既定)はテキスト幅に合わせた最小サイズのクリップを作り、改行はしません。長い字幕を折り返したいときはmethod="caption"とsizeをセットで指定します。なおmethod="label"でsizeを指定しない場合、font_sizeは省略できません(Font size is mandatory when method is label and size is Noneで例外になります)。
ロゴ画像の重ね合わせと位置指定
from moviepy import VideoFileClip, ImageClip, CompositeVideoClip
clip = VideoFileClip("input.mp4")
logo = (ImageClip("logo.png")
.with_duration(clip.duration)
.resized(height=80)
.with_position((30, 30))) # 左上から30pxずつ
CompositeVideoClip([clip, logo]).write_videofile("branded.mp4")
with_position()には座標のタプルのほか、("right", "bottom")のような文字列、lambda t: (100 + 50 * t, 200)のような時間関数を渡せます。時間関数を使うと、テロップを横に流すアニメーションが数行で書けます。重ねる順序はCompositeVideoClipのリスト順(後ろほど手前)です。
音声の抽出・BGM差し替え
動画の音声はclip.audioとしてAudioClipの形で取り出せます。抽出・音量調整・差し替えはいずれもこの属性を経由します。
from moviepy import VideoFileClip, AudioFileClip, afx
clip = VideoFileClip("input.mp4")
# 音声だけを書き出す
clip.audio.write_audiofile("voice.mp3")
# BGMを動画の尺に合わせ、音量を30%・末尾2秒でフェードアウト
bgm = (AudioFileClip("bgm.mp3")
.subclipped(0, clip.duration)
.with_effects([afx.MultiplyVolume(0.3), afx.AudioFadeOut(2)]))
clip.with_audio(bgm).write_videofile("with_bgm.mp4", audio_codec="aac")
元の音声を残したままBGMを重ねる場合はCompositeAudioClip([clip.audio, bgm])で合成してからwith_audio()に渡します。BGMが動画より短いと途中で無音になるため、afx.AudioLoop(duration=clip.duration)でループさせるか、上のようにsubclipped()で尺を揃えます。書き出し時にaudio_codec="aac"を省略すると環境によって音声が入らないことがあるので、MP4では明示しておくのが安全です。
GIF書き出しと一括処理の自動化
GIFはwrite_gif()で書き出します。切り出し・リサイズと組み合わせて、動画の一部をサムネイル代わりのアニメーションにするのが典型的な使い方です。
from moviepy import VideoFileClip
clip = VideoFileClip("input.mp4")
# 冒頭3秒を幅480px・12fpsのGIFに(尺とfpsを削るのが容量対策の要)
(clip.subclipped(0, 3)
.resized(width=480)
.write_gif("preview.gif", fps=12))
GIFはフレームごとに画像を持つため、10秒を超える素材やフルHDのままの書き出しは容量が数十MBに膨らみます。尺を数秒に絞り、幅を480〜640px、fpsを10〜15に落とすのが実用的な範囲です。
MoviePyの本領は、こうした処理をファイル単位のループに落とし込むところにあります。次のスクリプトは、フォルダ内の全MP4に同じロゴとフェードを適用して書き出します。
from pathlib import Path
from moviepy import VideoFileClip, ImageClip, CompositeVideoClip, vfx
src_dir = Path("raw")
out_dir = Path("edited")
out_dir.mkdir(exist_ok=True)
for path in sorted(src_dir.glob("*.mp4")):
with VideoFileClip(str(path)) as clip:
logo = (ImageClip("logo.png")
.with_duration(clip.duration)
.resized(height=60)
.with_position(("right", "top")))
final = (CompositeVideoClip([clip, logo])
.with_effects([vfx.FadeIn(0.5), vfx.FadeOut(0.5)]))
final.write_videofile(
str(out_dir / f"{path.stem}_edited.mp4"),
codec="libx264",
audio_codec="aac",
logger=None, # 進捗バーを抑止
)
定期実行に載せるなら、このスクリプトをcronやタスクスケジューラから呼びます。処理時間は素材の尺・解像度・エフェクト数に比例して伸びるので、実行間隔は実際の1本あたりの所要時間と本数から逆算し、前回の処理が終わらないうちに次が走らないようロックファイルを併用してください。
よくある質問
moviepy.editorがModuleNotFoundErrorになるのはなぜですか
v2.0でmoviepy.editorという名前空間が削除されたためです。インストールの失敗ではないので再インストールしても直りません。from moviepy import VideoFileClipのように直接インポートするか、どうしても既存コードを変えられない場合のみpip install "moviepy<2"でv1.0.3に固定します。
pip install moviepyだけでFFmpegも使えるようになりますか
なります。依存のimageio-ffmpegにFFmpegバイナリが同梱されているため、pip install moviepyだけで書き出しまで動きます。別途インストールが必要なのは、preview()で再生確認する場合のffplayと、独自ビルドのFFmpegに差し替えたい場合だけです。
condaでインストールできますか
conda install -c conda-forge moviepyで導入できます。conda-forgeも2.2.1を提供しているのでv2が入りますが、既存環境の依存解決しだいで古い版に落ちることはあります。導入後にpip show moviepyでバージョンを確認してください(moviepy.__version__は2.2.1でも2.1.2を返すため判定に使えません)。
MoviePyのライセンスは商用利用できますか
MoviePyはMITライセンスで、商用利用も可能です。ただし内部で利用するFFmpegはビルド構成によってライセンス(LGPL/GPL)が変わります。製品に同梱する場合は、MoviePyではなく同梱するFFmpegバイナリのライセンス条件を確認してください。
write_videofileが遅いのですが、threadsやpresetで速くなりますか
効果は限定的です。threadsとpresetはエンコード段(x264)に効きますが、テキスト合成やエフェクトを含む処理ではフレーム生成がPython側で走るため、そちらがボトルネックになります。まずresized()で解像度を落とす、エフェクトの数を減らす、確認用はpreset="ultrafast"にする、という順で試すのが効率的です。