Pythonで動画作成自動化してみた
0.自己紹介
初めまして、nabikaと申します。周りの影響でnoteを始めました。理系の大学新三年生で、フットサル部に所属しています。コロナウイルスの影響で、しばらく部活ができなさそうで、勉強しようと思ったのですが、最近勉強したことをすぐに忘れてしまうので、ここに書いていこうと思います。
書く内容としては、フットサル、数学、プログラミングなどを予定しております。日々の学びを共有できたら幸いです。よろしくお願いします。
1.得失点集自動生成!?
私の所属しているフットサル部では、毎回公式戦ごとに得失点集を作成しています。作るのが、少し手間だったので、pythonで自動化してみました。 自動化してできたものがこちらです。
得失点シーンを切り取ったいくつかの動画とテロップを入力すると、上の動画のようなテロップの入った動画を作成してくれます。
2.使い方
※使う気は無くどうやって動かしたかを知りたい人は飛ばしてください
まず、前準備をする必要があります。(全て、Mac用です)。パッケージをインストールするために、まずHomebrewをインストールしましょう。ターミナルで、以下のコードを実行してください。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
次に、ffmpegをインストールします。以下のコードを実行してください。
brew install ffmpeg
次に、パッケージをインストールします。以下の3つのコードをそれぞれ実行していきます。
pip install numpy
pip install opencv-python
pip install pydub
下のファイルをダウンロードして、動画を保存してあるディレクトリに保存してください。
movie.pyを開いて、108行目から114行目を編集します。
path_out = 'movie_out.mp4' # 保存する動画のパス
telop1 = "前半11分 失点"
telop2 = "前半17分 失点"
telop3 = "前半18分 ガデイア → エリサンドロ"
telop4 = "前半19分 リカルジーニョ → エリサンドロ"
movie_info = [['intel_movie_1.mp4',telop1],['intel_movie_2.mp4',telop2],['intel_movie_3.mp4',telop3],['intel_movie_4.mp4',telop4]] # 動画情報を配列にまとめる
上のようなコードが書いてあると思います。これだとmovie.pyを置いたディレクトリのintel_movie_1.mp4、intel_movie_2.mp4、intel_movie_3.mp4、intel_movie_4.mp4を繋げ、またそれぞれに対応するテロップが付きます。
繋げたい動画の数だけ、
telop1 = "入れたいテロップ"
telop2 = "入れたいテロップ"
といれて行って、movie_info に
movie_info = [["一つ目の動画",telop1],["二つ目の動画",telop2],・・・]と入れて行ってください。
最後にターミナルで、
python movie.py
と打つとmovie_out.mp4が作成されます。
3.仕組み
コードの中身はこちらです。
import cv2
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import pydub
from pydub import AudioSegment
import subprocess
# 画像に文字を入れる関数
def telop(frame, text, W, H, thinness):
frame = Image.fromarray(frame) # cv2(NumPy)型の画像をPIL型に変換
frame = frame.convert("RGBA") # RBGA型に変換
txt = Image.new('RGBA', frame.size, (255,255,255,0)) #文字を入れる透明な画像を用意
draw = ImageDraw.Draw(txt) # 描画用のDraw関数を用意
font_size = H//20
font = ImageFont.truetype("/System/Library/Fonts/ヒラギノ明朝 ProN.ttc", font_size) #でフォントを定義
w, h = draw.textsize(text, font) # .textsizeで文字列のピクセルサイズを取得
# テロップの位置positionは画像サイズと文字サイズから決定する
# 横幅中央、縦は下
position = (int((W - w) / 2), int(H - (font_size * 1.5)))
# テキストを描画(位置、文章、フォント、文字色(BGR+α)を指定)
draw.text(position, text, fill=(255, 255, 255, thinness), font=font) #(255,255,255)で白を指定
frame = Image.alpha_composite(frame, txt) #textとframeを合成
frame = frame.convert("RGB") #RGB画像に変換
frame = np.array(frame) #cv2(NumPy)型に変換
return frame
# 複数動画を連結させる関数
def m_combine(movie_info, path_out, scale_factor, fade_in_start, fade_in_end, fade_in_time):
i = 0
# 動画数分ループを回す
for j in movie_info: # 動画情報([path, T/F])をjに格納
path = j[0] # 動画ファイルへのパス
text = j[1] # 入れるテロップ
# 動画読み込みの設定
movie = cv2.VideoCapture(path)
# 動画ファイル保存用の設定
Fs = int(movie.get(cv2.CAP_PROP_FRAME_COUNT)) # 動画の全フレーム数を計算
fps = int(movie.get(cv2.CAP_PROP_FPS)) # 元動画のFPSを取得
print(fps)
fps_new = int(fps * scale_factor) # 動画保存時のFPSはスケールファクターをかける
fade_in_start_f = int(fade_in_start * fps)
fade_in_end_f = int(fade_in_end * fps)
fade_in_time_f = int(fade_in_time * fps) # 単位を秒からframeに変換
W = int(movie.get(cv2.CAP_PROP_FRAME_WIDTH)) # 動画の横幅を取得
H = int(movie.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 動画の縦幅を取得
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 動画保存時のfourcc設定(mp4用)
if i == 0:
video = cv2.VideoWriter("video.mp4", fourcc, fps_new, (W, H), 1) # 動画の仕様
sound = AudioSegment.from_file(path,"mp4") #cv2では音を取れないので、音を読み込む。
else:
sound_new = AudioSegment.from_file(path,"mp4")
sound = sound + sound_new #音をつなげていく
pass
i += 1
k = 0
# ファイルからフレームを1枚ずつ取得して動画処理後に保存する
while k < Fs:
ret, frame = movie.read() # フレームを取得
if ret:
# フェードイン、フェードアウトの処理
if text == None:
pass
if k <= fade_in_start:
pass
if k < fade_in_start_f + fade_in_time_f:
thinness = (k - fade_in_start_f)*255//fade_in_time_f # 徐々に濃くしていく
print(thinness)
frame = telop(frame, text, W, H, thinness)
elif k < Fs - fade_in_end_f - fade_in_time_f:
frame = telop(frame, text, W, H, 255)
elif k < Fs - fade_in_end_f:
thinness = ((Fs - fade_in_end_f) - k)*255//fade_in_time_f # 徐々に薄くしていく
frame = telop(frame, text, W, H, thinness)
k += 1
video.write(frame) # 動画を保存する
# フレームが取得できない場合はループを抜ける
else:
break
# 動画数分のループが終了した最後に、撮影用オブジェクトとウィンドウの解放
movie.release()
sound.export("sound.mp3", format="mp3")
return
# ここからメイン実行文
scale_factor = 1 # FPSにかけるスケールファクター
path_out = 'movie_out.mp4' # 保存する動画のパス
telop1 = "前半11分 失点"
telop2 = "前半17分 失点"
telop3 = "前半18分 ガデイア → エリサンドロ"
telop4 = "前半19分 リカルジーニョ → エリサンドロ"
movie_info = [['intel_movie_1.mp4',telop1],['intel_movie_2.mp4',telop2],['intel_movie_3.mp4',telop3],['intel_movie_4.mp4',telop4]] # 動画情報を配列にまとめる
fade_in_start = 0 # 動画が始まってから何秒後にテロップを入れるか
fade_in_time = 1 # テロップの効果の持続時間
fade_in_end = 0 # 動画が終わるまでに何秒前まで1にテロップを入れるか
# 複数動画を連結させる関数を実行
m_combine(movie_info, path_out, scale_factor, fade_in_start, fade_in_end, fade_in_time)
# 動画と音声を結合
cmd = 'ffmpeg -i video.mp4 -i sound.mp3 ' + path_out
subprocess.run(cmd.split())
結構コメントを入れたので、コメントを読んでいただけるとどういう動きなのかわかると思います。動画というのは、1秒間に何枚かのフレームがあって、パラパラ漫画のように再生しているので、それを一枚ずつ取って、テロップをつけて行ったという感じです。
4.感想
pythonで動画編集するのは初めてでしたが、pythonには便利なパッケージがたくさんあって、割と簡単に実装することができました。今回は、テロップには徐々に濃くする効果と徐々に薄くする効果しかつけませんでしたが、応用すればいろいろな効果を出すことができそうです。今までの知識を行かせた感じがしてとても面白かったので、暇な人は是非今までやっていた面倒な作業を自動化してみてください。今後も自動化を進めていきたいと思います。
ありがとうございました。
この記事が気に入ったらサポートをしてみませんか?