【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,
)クロスフェード(フェードアウト/フェードイン)
 
動画と動画の間をクロスフェード(フェードアウト/フェードイン)でなめらかにつなげます。clip2
の set_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でのフォントの場所はこちら。 {{python-pil-overlay-title.html#header-3-1}}
ちなみに、動画で使っているフォントはヤマナカデザインワークスさんの 「やわらかドラゴン」 です。有料フォントですが、対応している漢字も4213文字と多く、なにより少し気の抜けたガッツリしすぎない感じが私の好みに合っていてとても気に入ってます。 お気に入りのフォントを一つでも持っていると、幸せな気持ちになれますよ!デザインセンスのない私でも、フォントがデザインしてくれますから(笑)
文字をアニメーションさせて重ねる
 
numpy と scipy
が必要になりますので、インストールしてください。
$ pip install numpy
$ pip install scipyfrom 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,
)
ドキュメントを参考に、できるだけシンプルに分かりやすく書き直しました。ただし、残念ながら日本語はアニメーション表示できませんでした。 https://zulko.github.io/moviepy/examples/moving_letters.html
動画に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で動画の手ぶれ補正をやってみました。
 
