MoviePyは、Pythonのコードで動画のカット・結合・字幕合成・音声差し替えを行うライブラリです。最新版は2.2.1(2025年5月21日)で、2.0でmoviepy.editorが廃止されたため、ネット上に多いfrom moviepy.editor import *で始まるサンプルは今のpip install moviepyでは動きません。移行の説明に載せたv1のコードを除き、この記事のコードはすべてMoviePy 2.2.1で実行して確認したものです(素材は12秒・1280×720・30fps・音声付きのMP4)。v1からの書き換え、インストール時にPillowが古い版へ下がる問題、字幕・画面分割・フレーム加工、書き出し速度の実測までを順に扱います。
まとめ
- 最新版は2.2.1(2025年5月21日)。以後のリリースは無く、GitHubのmasterは2.2.1より97コミット先行している(2026年10月19日時点の比較)。
- v1のコードはそのままでは動かない。
from moviepy.editor import *はfrom moviepy import *へ、subclip()はsubclipped()へ、set_*はwith_*へ、clip.fx(vfx.speedx, 2)はclip.with_effects([vfx.MultiplySpeed(2)])へ変わった。 pip install moviepyはPillowを11.3.0に下げる。2.2.1がPillowを12.0未満に固定しているためで、Pillow 13.0.0が入った環境でもアンインストールされる。11.3.0は、2026年10月17日にPillowが公開したセキュリティ勧告40件のうち39件の対象版に含まれる(修正版は13.0.0)。- FFmpegの手動導入は原則不要。依存の
imageio-ffmpeg0.6.0は、macOS・Windows(64bit)・Linuxの主要アーキテクチャ向けwheelにFFmpegを同梱している(版はmacOSとWindows 64bitが7.1、Linuxが7.0.2)。別途必要なのはpreview()用のffplayだけ。 - 日本語の字幕はフォントのパス指定が必須。省略するとPillowの既定フォントになり、文字が×印の四角になる。
- 今回の計測で時間を食ったのはエンコードより合成。12秒・1280×720の動画にテロップを1枚重ねると、書き出しは単純な再エンコードの約10倍かかった。
- 向くのは同じ手順を何十本にも当てる自動化。1本を凝った演出で仕上げる作業や、無劣化カット・形式変換だけならFFmpegを直接使う方が速い。
MoviePyとは:できることと向かない用途
MoviePyはFFmpegを裏で呼び出すラッパーです。動画をVideoFileClipとして読み込み、切り出し・結合・合成・エフェクト・音声操作をメソッドで重ね、最後にwrite_videofile()で書き出します。ライセンスはMITで、ソースはGitHubのZulko/moviepyにあります。FFmpegのコマンドを1本ずつ組み立てる代わりに、Pythonのループや条件分岐で編集手順を書けるのが利点で、単発の変換ならFFmpegとは?使い方とコマンド一覧|MP4変換・音声抽出・fps変更を9.0で実行確認のコマンドで足ります。
MoviePyを選ぶべきでない場面もはっきりしています。PyPIの説明文自身が、フレームをPython側へ取り出して処理し直すぶんFFmpegを直接使うより遅いと認めています。解像度変更・コーデック変換・無劣化カットだけが目的ならMoviePyを挟む理由はありません。カットの位置を目で詰める編集も、コードで試行錯誤するよりGUIの編集ソフトの方が早く終わります。MoviePyが効くのは、同じ字幕やロゴを大量のファイルに当てる、動画の生成をバッチ処理やCIに組み込む、といった人手の繰り返しをコードに移す場面です。
v1からv2への移行:moviepy.editorが動かない理由
2024年11月20日公開の2.0は互換性を切ったメジャーアップデートでした。検索結果に「moviepy.editorのインストール方法」という質問が並ぶのはこの移行が原因です。公式のUpdating from v1.X to v2.Xに変更点がまとまっており、既存コードで直すのは次の3種類です。
moviepy.editorの廃止とModuleNotFoundErrorの直し方
v2ではmoviepy.editorというモジュールそのものが無くなりました。import moviepy.editorを実行するとModuleNotFoundError: No module named 'moviepy.editor'になります。インストールの失敗ではなく、別パッケージが要るわけでもないので、再インストールしても直りません。インポート行を書き換えます。
# v1(2.x では ModuleNotFoundError)
from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip
# v2
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
# まとめて読み込む場合
from moviepy import *
set_*・subclipなどメソッド名の対応表
v2ではクリップを変更するメソッドが「元のクリップを書き換えず、変更したコピーを返す」方式にそろい、名前もwith_や過去分詞に変わりました。2.2.1のVideoFileClipにはsubclip・set_position・set_duration・fx・resizeのどの属性もありません。
| 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) |
回転 |
clip.fl_image(f) |
clip.image_transform(f) |
フレームごとの加工 |
エフェクトのクラス化とwith_effects()
v1のclip.fx(vfx.speedx, 2)やclip.fadein(1)は、v2ではエフェクトのクラスを作ってwith_effects()にリストで渡す形になりました。moviepy.video.fx.allというインポートパスも無くなり、from moviepy import vfx, afxで読み込みます。
from moviepy import VideoFileClip, vfx, afx
clip = VideoFileClip("input.mp4")
# v1: clip.fx(vfx.speedx, 2).fadein(1).fadeout(1)
clip = clip.with_effects([
vfx.MultiplySpeed(2), # 2倍速(旧 speedx)。12秒の素材が6秒になる
vfx.FadeIn(1), # 旧 fadein
vfx.FadeOut(1), # 旧 fadeout
afx.MultiplyVolume(0.5), # 音量を半分に(旧 volumex)
])
2.2.1のvfxにはMultiplySpeed・FadeIn・FadeOut・CrossFadeIn・CrossFadeOut・Resize・Crop・Rotate・MirrorX・MirrorY・BlackAndWhite・InvertColors・Loop・Freeze・SlideIn・SlideOut・Scrollなど34個、afxにはMultiplyVolume・AudioFadeIn・AudioFadeOut・AudioLoop・AudioNormalize・AudioDelay・MultiplyStereoVolumeの7個があります。名前はどれもキャメルケースで、v1の小文字の関数名(speedx・volumex・fadein)は通りません。v2ではImageMagickへの依存も外れ、文字の描画はPillowで行います。
インストールとFFmpeg・Pillowの扱い
pip install moviepyで入るものとPythonの対応版
導入はpip install moviepyだけです。v1に依存した古いスクリプトと共存させるなら、プロジェクトごとに仮想環境を分けます。
python -m venv .venv
source .venv/bin/activate # Windowsは .venv\Scripts\activate
pip install moviepy
pip show moviepy # Version: 2.2.1
PyPIの説明はPython 3.9以上としていますが、2.2.1のパッケージ情報にはrequires_pythonの宣言がありません。Python 3.9以上を要求しているのは依存のNumPy(numpy>=1.25.0)の側です。そのためPython 3.8でpip install moviepyを実行するとエラーにはならず、依存を満たせる古い版をさかのぼってv1の1.0.3が入ります(uv pipで確認)。v2のコードがImportErrorになったら、まずPythonの版を確かめてください。新しい側は、Python 3.14.6でもpip install moviepyから動画の読み込みまで動きました。依存のPillow 11.3.0がPython 3.14向けのwheelを配布しているためです。
版の確認はpip show moviepyかimportlib.metadata.version("moviepy")で行います。moviepy.__version__は2.2.1を入れても2.1.2を返すので使えません(同梱のversion.pyが更新されていない)。v1のまま使い続けるならpip install "moviepy<2"で1.0.3に固定できますが、1.0.3は2020年から更新が無く、書き換えまでのつなぎにとどめるべきです。
pip install moviepyでPillowが11.3.0に下がる問題と回避策
MoviePy 2.2.1の依存定義はpillow>=9.2.0,<12.0です。そのため、すでにPillow 13.0.0が入った環境でpip install moviepyを実行すると、pipは何も確認せずに13.0.0をアンインストールして11.3.0を入れます。Python 3.13の仮想環境で実際にそうなりました。
pip install "pillow==13.0.0"
pip install moviepy
# Uninstalling pillow-13.0.0:
# Successfully uninstalled pillow-13.0.0
# Successfully installed ... moviepy-2.2.1 ... pillow-11.3.0 ...
問題は11.3.0の脆弱性です。GitHub Advisory Databaseには11.3.0が対象に含まれるPillowの勧告が登録されており、PSD画像読み込み時の境界外書き込み(CVE-2026-25990、12.1.1で修正)やFITSのGZIP解凍爆弾(CVE-2026-40192、12.2.0で修正)などがあります。さらにPillowは2026年10月16日に13.0.0を公開し、翌17日にセキュリティ勧告を40件まとめて公開しました。うち39件は11.3.0も対象版に含み(重大度criticalが2件、highが13件)、いずれも12.3.0までが対象で修正版は13.0.0です。つまりPillow 12系に上げても、これらは直りません。なお13.0.0はPython 3.11以上が必要です。MoviePy側でもPillowの下限を12.2.0へ上げるプルリクエスト#2562が2026年4月から未マージのままです。Pillowそのものの使い方はPillowとは?Pythonの画像処理ライブラリの使い方をコード付きで解説【12.3.0対応】で扱っています。
手元のスクリプトで自分の動画を処理するだけなら、11.3.0のままでも実害は小さいでしょう。外部から受け取った画像やフォントを読み込むサーバー処理にMoviePyを組み込むなら、Pillowの更新を優先すべきです。回避策は2つあります。どちらも2.2.1の依存定義には反しますが、この記事のコード一式(字幕・合成・フレーム加工・GIFを含む)をPillow 13.0.0の環境で実行し直したところ、すべて11.3.0のときと同じ結果になりました。それでも採用前には自分の処理で動作を確かめてください。
| 方法 | 入るPillow | 注意点 |
|---|---|---|
2.2.1を入れた後にpip install -U pillow |
13.0.0 | pip checkが依存違反を報告する。Python 3.11以上 |
| GitHubのmasterから入れる | 13.0.0 | 版表記が2.2.0になり、opencv-python-headlessも入る |
masterのpyproject.tomlはPillowの上限を外しており、pip install "git+https://github.com/Zulko/moviepy.git@master"で入れるとPillow 13.0.0とopencv-python-headless 5.0.0.93がそろいます。ただし版表記は2.2.0で、2.2.1より古い番号です。後からpip install -U moviepyを実行すると、PyPIの2.2.1に入れ替わってPillowも11.3.0に戻ります。condaも事情は同じで、conda-forgeのmoviepy 2.2.1はPillowを11.0未満(10系)に固定しています。pipとcondaの混在についてはpipとcondaの違いと使い分け|混ぜると壊れる理由とAnaconda・Miniforgeの選び方を参照してください。
同梱のFFmpegと、差し替えるときの設定
主要な環境ではFFmpegの手動インストールとPATHの設定は要りません。imageio-ffmpeg 0.6.0のプラットフォーム別wheelにFFmpegのバイナリが入っており、pip install moviepyの時点でそろいます。同梱する版の対応表はmacOS(Intel・Apple Silicon)とWindows 64bitが7.1、Linux(x86_64・aarch64)が7.0.2、Windows 32bitが4.2.2です。公式のインストール手順には「初回利用時にImageIOがダウンロードする」とありますが、現行のimageio-ffmpegでは実行時のダウンロードは起きません。対応wheelの無い環境や、ハードウェアエンコード対応のビルドなど別のFFmpegを使いたいときだけ、MoviePyをインポートする前に環境変数を設定します。
import os
# 差し替えるときだけ有効にする(既定は同梱バイナリ = "ffmpeg-imageio")
# os.environ["FFMPEG_BINARY"] = "/usr/local/bin/ffmpeg" # 特定のバイナリを指定
from moviepy.config import check
check() # 使われるffmpegのパスを表示
"auto-detect"を設定するのは避けてください。PATH上にffmpegが無い環境では値がunsetになり、同梱バイナリも使われず、MoviePy: can't find or access ffmpeg in 'unset'.で書き出しがすべて失敗します。preview()で再生確認をする場合だけ、ffplayを別に用意します。最新のFFmpegの版と入手方法はFFmpegの最新バージョンは9.0|8.1・8.0との違いとインストール・確認方法【2026年10月】にまとめています。
読み込み・カット・結合・書き出しの基本
読み込みと動画情報の取得
from moviepy import VideoFileClip
clip = VideoFileClip("input.mp4")
print(clip.duration) # 12.0(秒)
print(clip.fps) # 30.0
print(clip.size) # [1280, 720]
clip.close() # ループ処理では必ず解放する
大量のファイルをループで処理するときはclose()を呼ぶかwith文で扱います。開いたままだとFFmpegのサブプロセスとファイルハンドルが残り、プロセスが開けるファイル数の上限(ulimit -nで確認できる値)に達するとToo many open filesで止まります。
区間の切り出し(subclipped)と結合
from moviepy import VideoFileClip, concatenate_videoclips
clip = VideoFileClip("input.mp4") # 12秒の素材
part1 = clip.subclipped(2, 5) # 2〜5秒
part2 = clip.subclipped(8, -1) # 8秒〜終端の1秒前(負の値は終端からの位置)
final = concatenate_videoclips([part1, part2], method="compose")
final.write_videofile("output.mp4")
concatenate_videoclips()の既定method="chain"は、全素材の解像度がそろっている前提で連結します。そろっていないと危険です。1280×720の2秒と640×360の2秒をchainで結合したところ、例外は出ず、書き出された映像トラックは2.5秒(75フレーム)で途切れました。後半の640×360のフレームは4枚ずつ1280×720の1フレームに詰め込まれ、画面にタイル状に並んでいます。chainを使えるのは全素材の幅と高さが両方一致するときだけです。resized(width=...)で幅だけそろえても、縦横比が違えば高さがずれます。素材の解像度が混ざる可能性があるならmethod="compose"を指定してください。composeは最大の素材に合わせたキャンバスへ中央寄せで配置します。
CrossFadeInとpaddingによるクロスフェード結合
場面転換をクロスフェードにするには、後ろのクリップにvfx.CrossFadeInをかけ、paddingに負の値を渡して前のクリップと重ねます。4秒と4秒を1秒重ねると全体は7秒になります。
from moviepy import VideoFileClip, concatenate_videoclips, vfx
clip = VideoFileClip("input.mp4")
a = clip.subclipped(0, 4)
b = clip.subclipped(6, 10).with_effects([vfx.CrossFadeIn(1)])
final = concatenate_videoclips([a, b], method="compose", padding=-1)
print(final.duration) # 7
final.write_videofile("crossfade.mp4")
write_videofileの引数と書き出し速度の実測
final.write_videofile(
"output.mp4",
codec="libx264", # MP4の既定
audio_codec="aac", # 省略するとMP4でも音声はMP3(libmp3lame)で格納される
fps=30,
preset="medium", # ultrafast〜veryslow
bitrate="4000k",
logger=None, # 進捗バーを出さない
)
audio_codecを省略した場合、MoviePyは拡張子が.ogvか.webmならlibvorbis、それ以外はlibmp3lameを選びます。MP4にAACの音声を入れたいなら明示が必要です。速度については、12秒・1280×720・30fpsのテスト動画で各設定の書き出し時間を測りました(Intel Core i9-9880H、macOS 26.6.2、Python 3.13.14、同梱FFmpeg 7.1)。表は1回目の値で、Pillowの版を変えた2回目の計測も各項目の差は3秒以内、順位も同じでした。素材とマシンが変われば時間も変わるので、傾向を見るための値として扱ってください。
| 処理 | 時間 | ファイルサイズ |
|---|---|---|
| 再エンコードのみ(libx264・medium) | 7.17秒 | 4.42MB |
| 同(preset=”ultrafast”) | 5.95秒 | 10.26MB |
| 同(threads=4) | 9.33秒 | 4.24MB |
| 同(codec=”h264_videotoolbox”・6000k) | 4.94秒 | 9.40MB |
| テロップ1枚を合成(ultrafast) | 68.93秒 | — |
| 同・640×360に縮小して合成 | 35.36秒 | — |
エンコードの設定を変えても差は数秒ですが、テロップを1枚重ねただけで約10倍に延びました。合成するとフレームごとにPython側で画像を重ね直すためで、今回の計測ではここがボトルネックでした。threadsは既定(None)だと-threadsを渡さずFFmpegに任せるので、4を指定したらかえって遅くなりました。速くしたいなら、エンコードの設定よりも先に、確認用は解像度を落として合成する、合成するクリップと区間を減らす、という順で手を入れます。macOSのh264_videotoolboxは同梱のFFmpeg 7.1でそのまま使えますが、今回の6000k指定ではlibx264(medium)の約2.1倍のファイルサイズになりました。
テキスト・字幕・画像の合成
TextClipと日本語フォントの指定
2.2.1のTextClipの引数はTextClip(font=None, text=None, filename=None, font_size=None, ...)の順です。fontを省略するとPillowの既定フォントになり、日本語は1文字ずつ×印の四角で描かれます。日本語の字幕にはTTF/OTF/TTCのパスを渡します。
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
clip = VideoFileClip("input.mp4").subclipped(0, 6)
txt = TextClip(
font="/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc", # Windowsは C:/Windows/Fonts/meiryo.ttc など
text="サンプル字幕です。長い文はsizeの幅で自動的に折り返されます。",
font_size=48,
color="white",
stroke_color="black",
stroke_width=2,
method="caption", # sizeの幅で折り返す
size=(clip.w - 80, None),
).with_duration(4).with_start(1).with_position(("center", "bottom"))
CompositeVideoClip([clip, txt]).write_videofile("subtitled.mp4")
既定のmethod="label"は文字の幅ぴったりの画像を作り、折り返しません。labelのままsizeもfont_sizeも省くとValueError: Font size is mandatory when method is label and size is Noneになります。
SubtitlesClipによるSRT字幕の焼き込み
字幕が多いなら、SRTファイルをSubtitlesClipで読み込んで重ねます。字幕1件ごとにTextClipを作る関数を渡す形です。
from moviepy import VideoFileClip, TextClip, CompositeVideoClip
from moviepy.video.tools.subtitles import SubtitlesClip
FONT = "/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc"
clip = VideoFileClip("input.mp4")
def make_text(txt):
return TextClip(font=FONT, text=txt, font_size=40, color="white",
stroke_color="black", stroke_width=2)
subs = SubtitlesClip("subs.srt", make_textclip=make_text, encoding="utf-8")
final = CompositeVideoClip([clip, subs.with_position(("center", clip.h - 100))])
final.write_videofile("with_subs.mp4")
ここには落とし穴が2つあります。1つ目は、SRTの最後の字幕が黙って捨てられることです。2.2.1のfile_to_subtitles()は空行を見て字幕を確定させるので、最後の字幕のあとに空行が無いファイルでは3件中2件しか読まれませんでした。末尾が改行1つで終わるファイルでも同じです。SRTの最後に空行を1行足してください。encodingを省くとOSの既定の文字コードで読むので、UTF-8のSRTなら"utf-8"を明示します。
2つ目は、SubtitlesClipのdocstringの例TextClip(text, font='./path/to/font.ttf', ...)がそのままでは動かないことです。v2のTextClipは1番目の引数がfontなので、TypeError: multiple values for argument 'font'になります。上のコードのようにtext=をキーワードで渡します。
ロゴの重ね合わせと画面分割(clips_array)
from moviepy import VideoFileClip, ImageClip, CompositeVideoClip, clips_array, vfx
clip = VideoFileClip("input.mp4").subclipped(0, 3)
# ロゴを左上に重ねる。リストの後ろほど手前に描かれる
logo = (ImageClip("logo.png")
.with_duration(clip.duration)
.resized(height=80)
.with_position((30, 30)))
CompositeVideoClip([clip, logo]).write_videofile("branded.mp4")
# 2×2の画面分割。640×360を4つ並べて1280×720になる
small = clip.resized(width=640)
grid = clips_array([
[small, small.with_effects([vfx.MirrorX()])],
[small.with_effects([vfx.BlackAndWhite()]), small.with_effects([vfx.InvertColors()])],
])
grid.write_videofile("grid.mp4")
with_position()には座標のタプル、("right", "bottom")のような文字列、lambda t: (100 + 50 * t, 200)のような時刻の関数を渡せます。時刻の関数を使うと、テロップを横に流す動きが1行で書けます。clips_array()は行ごとのリストを受け取り、比較動画やビフォーアフターを並べる用途に向きます。
フレーム単位の加工とサムネイル作成
用意されたエフェクトに無い加工は、フレームをNumPy配列として受け取る関数をimage_transform()に渡して書きます(v1のfl_imageに相当)。関数は高さ×幅×3のuint8配列を受け取り、同じ形の配列を返します。セピア調にする例です。
import numpy as np
from moviepy import VideoFileClip
SEPIA = np.array([[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]])
def sepia(frame):
out = frame.astype(np.float32) @ SEPIA.T
return np.clip(out, 0, 255).astype(np.uint8)
clip = VideoFileClip("input.mp4")
clip.image_transform(sepia).write_videofile("sepia.mp4")
1枚の画像を取り出すだけならsave_frame()、配列として扱うならget_frame()、一定間隔で全フレームを回すならiter_frames()を使います。OpenCVなど別の画像処理と組み合わせるときの入り口になります(Python版OpenCVの基本はOpenCVとは?Pythonでの使い方入門|pip installから画像処理・顔検出まで【5.0対応】)。
clip = VideoFileClip("input.mp4")
clip.save_frame("thumb.png", t=2.5) # 2.5秒時点をPNGで保存
frame = clip.get_frame(2.5) # (720, 1280, 3) の配列
for f in clip.iter_frames(fps=2): # 0.5秒ごとに1枚
pass
逆に、画像の並びから動画を作るにはImageSequenceClipを使います。durationsで1枚ごとの表示秒数を指定でき、ImageSequenceClip(["s0.png", "s1.png", "s2.png"], durations=[2, 2, 3])は7秒のスライドショーになります。画像は同じサイズにそろえておきます。
音声の抽出・BGMの差し替え(AudioFileClip)
動画の音声はclip.audioで取り出せます。音声トラックの無い動画ではclip.audioがNoneになり、下のwrite_audiofile()はAttributeErrorで止まるので、一括処理ではif clip.audio is not None:で分岐させます。BGMはAudioFileClipで読み込み、動画の長さに合わせてからwith_audio()で差し替えます。
from moviepy import VideoFileClip, AudioFileClip, CompositeAudioClip, afx
clip = VideoFileClip("input.mp4") # 12秒
clip.audio.write_audiofile("voice.mp3") # 音声だけを書き出す
bgm = AudioFileClip("bgm.mp3") # 5秒しかないBGM
bgm = bgm.with_effects([
afx.AudioLoop(duration=clip.duration), # 12秒になるまで繰り返す
afx.MultiplyVolume(0.3),
afx.AudioFadeOut(2),
])
# 元の音声を残してBGMを重ねる(差し替えるだけならbgmをそのまま渡す)
mixed = CompositeAudioClip([clip.audio, bgm])
clip.with_audio(mixed).write_videofile("with_bgm.mp4", audio_codec="aac")
BGMをbgm.subclipped(0, clip.duration)で動画の長さに切りそろえる書き方は、BGMが動画より短いとValueError: end_time (12.00) should be smaller or equal to the clip's duration (5.04).で止まります。長さが不明な素材には、上のようにafx.AudioLoopを使う方が確実です。
GIF書き出しと一括処理の自動化
GIFはwrite_gif()で書き出します。GIFはフレームごとに画像を持つので、尺を数秒に絞り、幅を480〜640px、fpsを10〜15に落とすのが容量を抑える基本です。
from moviepy import VideoFileClip
clip = VideoFileClip("input.mp4")
clip.subclipped(0, 3).resized(width=480).write_gif("preview.gif", fps=12)
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やタスクスケジューラから定期実行するなら、前述の実測のとおり合成ありの処理は素材の尺の数倍かかる前提で間隔を決め、前回の処理が終わらないうちに次が走らないようロックファイルを併用します。社内の動画生成をこうしたバッチから業務システムに組み込む場合は、フルスクラッチ開発として処理基盤ごと設計する方法もあります。
よくある質問
moviepy.editorがModuleNotFoundErrorになるのはなぜですか?
2.0でmoviepy.editorが削除されたためです。再インストールでは直らないので、from moviepy import VideoFileClipのようにインポート行を書き換えます。すぐに直せない既存コードだけ、pip install "moviepy<2"で1.0.3に固定します。
pip install moviepyだけでFFmpegも使えますか?
macOS・Windows・Linuxの主要な環境なら使えます。imageio-ffmpegのwheelにFFmpeg(macOSとWindows 64bitは7.1、Linuxは7.0.2)が同梱されており、書き出しまで追加のインストールは要りません。別に用意が必要なのはpreview()で使うffplayだけです。
condaでインストールできますか?
conda install -c conda-forge moviepyで2.2.1が入ります。ただしconda-forge版はPillowを11.0未満に固定しているため、Pillow 11以降を使う環境とは同居できません。
MoviePyは商用利用できますか?
MoviePyはMITライセンスなので商用利用できます。注意が要るのは同梱・利用するFFmpeg側で、ビルド構成によってLGPLかGPLになります。製品に同梱するなら、そのFFmpegバイナリのライセンスを確認してください。
Python 3.14で使えますか?
Python 3.14.6でpip install moviepyから動画の読み込みまで動きました。入るのは2.2.1とPillow 11.3.0です。PyPIの2.2.1はPythonの対応範囲を宣言していないので、動作は自分の環境で確かめてください。