【Python】MoviePyで動画編集の自動化【動画編集への道#1】

【Python】MoviePyで動画編集の自動化【動画編集への道#1】

FFmpegは使いずらい!オプションの書き方がわけわかめ><

そんなあなたへ朗報です。

Pythonで動画編集ができるライブラリ「MoviePy」を使えば、カンタンに動画編集ができちゃいます!「MoviePy」では、動画のカットやフェードアウト、フェードインなどのようなトランジション(エフェクト効果)はもちろん、画像を動画にしたり、テロップを入れたりできます。

シェルスクリプトでゴリゴリ加工するよりも、Pythonでプログラミングしたほうがストレスなく楽しいですし、「MoviePy」かなりオススメですよ!カンタンな動画なら、動画編集ソフトを使わずにPythonで自動化できちゃいます!

はじめに

Pythonやシェルを使った動画編集の第一弾としまして、MoviePyでの動画編集をご紹介していきます。

実は「MoviePy」のコアの部分はFFmpegを使っているのですが、FFmpegのような不可解な書き方は一切ありません。Pythonらしいオブジェクト指向で動画編集をプログラミングできます。「MoviePy」で作られた作品例が ギャラリー で公開されてますので、ご覧になってみてください。

次のような動画フォーマットを元に、MoviePyで動画を編集していきますね。

項目
サイズ 1280 × 720
エンコード H.264、 AAC

MoviePyのインストール

pipでMoviePyをインストールしましょう。Pythonは3.x系を使っていきます。

$ pip install moviepy

それではMoviePyの使い方、プログラミング例をご紹介していきます。

指定時間でカット

from moviepy.editor import *

start = "00:00:03" # 開始時刻
end = "00:00:06" # 終了時刻

final_clip = VideoFileClip("in.mp4").subclip(start, end)

final_clip.write_videofile(
    "split.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True
)

音が出ない時の対処

これだと音が出ない

video.write_videofile("out.mp4")

エンコードを指定して解決

video.write_videofile(
    "out.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True
)

動画をクロップしてリサイズする

次のプログラムは、元動画のアスペクト比を維持してクロップします。この処理は、VidStabで手ぶれ補正した動画で黒いボーダー部分を切り落とすのにそのまま使えると思います。

アスペクト比を破壊してクロップしたい場合は、リサイズ処理を取り除いてください。

from moviepy.editor import *
import moviepy.video.fx.all as vfx


def crop(clip, margin): # Crop outline keeping original size.
    w,h = clip.size
    ratio = w/h
    start_x = int(margin * ratio)
    start_y = margin
    crop_width = int(w - start_x * 2)
    crop_height = int(h - start_y * 2)

    clip = vfx.crop(clip,
        x1=start_x, width=crop_width,
        y1=start_y, height=crop_height
    )
    return vfx.resize(clip, (w, h))


clip = VideoFileClip("split.mp4")

final_clip = crop(clip, 100)

final_clip.write_videofile(
    "crop.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True,
)

クロスフェード(フェードアウト/フェードイン)

クロスフェード処理
クロスフェード処理

動画と動画の間をクロスフェード(フェードアウト/フェードイン)でなめらかにつなげます。clip2set_start(開始時間) を正しく指定しないと、動画同士が重なってしまうのでご注意ください。

from moviepy.editor import *


in_path = "in.mp4"

fade_duration = 2

clip1 = VideoFileClip(in_path).subclip("00:01:39", "00:01:41")
clip1 = clip1.crossfadeout(fade_duration).audio_fadeout(fade_duration)

duration = clip1.duration
print(duration)

clip2 = VideoFileClip(in_path).subclip("00:07:20", "00:07:23").set_start(duration)
clip2 = clip2.crossfadein(fade_duration).audio_fadein(fade_duration)


final_clip = CompositeVideoClip([clip1, clip2])

final_clip.write_videofile(
    "crossfade.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True
)

クロスディゾルブ

クロスディゾルブ処理
クロスディゾルブ処理

真っ暗になる部分を作らずに、動画同士が重なり合って移り変わるようにするには「クロスディゾルブ」を使うと思います。moviepyにそれらしき機能が見当たらないので、クロスフェードをつかってクロスディゾルブを表現してみました。

from moviepy.editor import *


def crossdissolve(c1, c2):
    video_fade_duration = 0.3
    audio_fade_duration = 1

    c1 = c1.crossfadeout(video_fade_duration).audio_fadeout(audio_fade_duration)
    duration = c1.duration
    print(duration)
    c2 = c2.set_start(duration - video_fade_duration * 2)
    c2 = c2.crossfadein(video_fade_duration).audio_fadein(audio_fade_duration)


    return CompositeVideoClip([c1, c2])


if __name__ == "__main__":
    in_path = "in.mp4"
    clip1 = VideoFileClip(in_path).subclip("00:01:39", "00:01:41")
    clip2 = VideoFileClip(in_path).subclip("00:07:20", "00:07:23")

    final_clip = crossdissolve(clip1, clip2)

    final_clip.write_videofile(
        "crossdissolve.mp4",
        codec='libx264', 
        audio_codec='aac', 
        temp_audiofile='temp-audio.m4a', 
        remove_temp=True,
        #threads=2 # =CPU Core
    )

画像を動画化

画像を動画化するプログラムです。

PILを使って、指定したサイズにフィットしてリサイズする関数も混じっています。ImageClip の引数には、画像のパスを直接渡すか numpy.array の形にして渡す必要があります。

from moviepy.editor import *
from PIL import Image
import numpy as np


"""
指定したサイズにフィットしてリサイズする
"""
def resize_fit(im, resize_w, resize_h):

    # アスペ比
    w, h = im.size
    resize_aspect = resize_w / resize_h
    origin_aspect = w / h
    

    # リサイズ & クロップ
    if resize_aspect > origin_aspect: # リサイズ画像の方が横長
        r_h = int(resize_w / w * h)
        im = im.resize((resize_w, r_h))
        return im.crop((0, 0, resize_w, resize_h))
    else:
        r_w = int(resize_h / h * w)
        im = im.resize((r_w, resize_h))
        return im.crop((0, 0, resize_w, resize_h))

if __name__ == "__main__":
    path = "a.jpg"
    img = Image.open(path)
    img = resize_fit(img, 1280, 720)

    clip = ImageClip(np.array(img)).set_duration('00:00:05')
    #clip = clip.resize(newsize=(1280,720))

    clip.write_videofile(
        "img2mp4.mp4",
        fps=30,
    )

動画ファイルと動画ファイルを結合

from moviepy.editor import *


clip1 = VideoFileClip("in1.mp4")
clip2 = VideoFileClip("in2.mp4")
clip3 = VideoFileClip("in3.mp4")

final_clip = concatenate_videoclips([clip1,clip2,clip3])


final_clip.write_videofile(
    "combine.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True,
)

テロップを入れる(文字を重ねる)

テロップを入れる(文字を重ねる)
テロップを入れる(文字を重ねる)

from moviepy.editor import *


clip = VideoFileClip("in.mp4").subclip("00:07:20", "00:07:24")

txtclip = TextClip('荒川サイクリングロード\nCycling in Tokyo!', fontsize=80, font='/Users/mopipico/Library/Fonts/yawarakadragon.otf', color='white')
cvc = CompositeVideoClip([clip, txtclip.set_pos(('center', 'center'))])
final_clip = cvc.set_duration(clip.duration)

final_clip.write_videofile(
    "telop.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True,
)


macOSでのフォントの場所は、こちらの記事をご参考ください。

ちなみに、動画で使っているフォントはヤマナカデザインワークスさんの 「やわらかドラゴン」 です。有料フォントですが、対応している漢字も4213文字と多く、なにより少し気の抜けたガッツリしすぎない感じが私の好みに合っていてとても気に入ってます。

お気に入りのフォントを一つでも持っていると、幸せな気持ちになれますよ!デザインセンスのない私でも、フォントがデザインしてくれますから(笑)

文字をアニメーションさせて重ねる

文字をアニメーションさせて重ねる
文字をアニメーションさせて重ねる

numpyscipy が必要になりますので、インストールしておいてください。

$ pip install numpy
$ pip install scipy
from moviepy.editor import *
from moviepy.video.tools.segmenting import findObjects
import numpy as np


video_clip = VideoFileClip("in.mp4").subclip("00:07:20", "00:07:25")
screensize = video_clip.size # (1280, 720)

text_clip = TextClip('ARAKAWA\nCycling in Tokyo!', color = 'white', font = "/Users/mopipico/Library/Fonts/yawarakadragon.otf", kerning = 5, fontsize = 80)
text_clip = text_clip.set_pos('center')
cvc = CompositeVideoClip([text_clip], size = screensize)

# helper function
rotMatrix = lambda a: np.array( [[np.cos(a), np.sin(a)],
                                 [-np.sin(a), np.cos(a)]] )


def effect(screenpos, i, nletters):
     
    # damping
    d = lambda t : 1.0/(0.3 + t**8)
    # angle of the movement
    a = i * np.pi / nletters
     
    # using helper function
    v = rotMatrix(a).dot([-1, 0])
     
    if i % 2 : v[1] = -v[1]
         
    # returning the function
    return lambda t: screenpos + 400 * d(t)*rotMatrix(0.5 * d(t)*a).dot(v)
 

letters = findObjects(cvc) # Returns a list of ImageClips representing each a separate object on the screen.


text_clip = CompositeVideoClip([letter.set_pos(effect(letter.screenpos, i, len(letters))) for i, letter in enumerate(letters)], size = screensize)
text_clip = text_clip.subclip(0, 4)
text_clip.fps = 24


final_clip = CompositeVideoClip([video_clip, text_clip])  

final_clip.write_videofile(
    "animation_text.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True,
)

ドキュメントを参考に、できるだけシンプルに分かりやすく書き直しました。ただし、残念ながら日本語はアニメーション表示できませんでした。

動画にBGMを付け加える

こんな感じで動画の音声とBGMをミックスしたり、動画の音声を丸ごと別の音声に入れ替えたりできます。

from moviepy.editor import *


in_path = "in.mp4"
video_clip = VideoFileClip(in_path).subclip("00:01:44", "00:01:50")
bgm_clip = AudioFileClip("mikenekonowaltz.mp3").subclip(0, 5)
final_audio = CompositeAudioClip([video_clip.audio, bgm_clip])
    
final_clip = video_clip.set_audio(final_audio)

final_clip.write_videofile(
    "audio.mp4",
    codec='libx264', 
    audio_codec='aac', 
    temp_audiofile='temp-audio.m4a', 
    remove_temp=True,
    #threads=2 # =CPU Core
)

おわり

以上でMoviePyのざっくりとした使い方の解説をおわります。他にも紹介しきれないほど細かな機能がありますので、各自でソースコードを追いかけるなり、ドキュメントを参照するなりしてみてください。

MoviePyではじめて動画編集をしてみて、単純な加工レベルならスクリプトのみだけでできる実感が湧きました。入力ファイルやビルドパス、切り取るタイムコードなどは、YAMLファイルで管理できるようにしておくととても便利ですよ。おすすめのアイデアです。

さて、次回の「スクリプトのみで動画編集への道」ですが、Pythonで動画の手ぶれ補正をやってみました。ぜひこちらも合わせてご参考いただければと思います。

記事に関するご質問などがあれば、
Twitter または お問い合わせ までご連絡ください。
Python学習にオススメの本をご紹介!
Pandasでデータサイエンスはじめよう!
関連記事