#やりたいこと
FFmpegを使って、動画から要らないシーンを削除します。
自分はスポーツの動画編集をすることが多いのですが、プレーに関係ないシーンは、作業前に削除しておくとそのあとの編集が楽になります。
#FFmpegとは
動画や音声を変換・編集することができるフリーのオープンソースツールです。
コマンドラインで使用するツールのためとっつきにくいかもしれませんが、多機能かつ実行速度がはやく非常に強力な動画編集ツールとして使用することができます。
今回はFFmpegの下記機能を使用しました。
- シーン検出
- サムネイル画像出力
- 動画の切り出し
- 動画の結合
#使用した動画
NHKクリエイティブ・ライブラリーのビリヤードの動画をダウンロードして加工しました。非営利目的であれば、編集して公開可能の動画となっております。
#シーン変化を検出して、サムネイル画像を出力
コマンドライン上で下記コマンドを実行すると、動画のシーン変化を検出して、各シーンの最初の画像をサムネイル画像として出力します。シーン変化の度合を0~1.0までで設定できるのですが、0.1と変化が小さくても検出できるように設定しました。また、ffoutというファイル名で、処理情報が記載されたファイルを出力してます。
$ ffmpeg -i billiards.mp4 -vf "select=gt(scene\,0.1), scale=640:360,showinfo" -vsync vfr output/%04d.jpg -f null - 2>ffout
#フォルダ内のサムネイル画像を削除することで、要らないシーンを削除
要らないシーンと思うサムネイル画像を削除します。
今回はビリヤードボールの拡大画像だけ残したいので、それ以外の画像を削除しました。
002.jpg 003.jpg 004.jpg
を削除しています。
#ffoutファイルの中身からシーン検出した時間を配列に格納する
Pythonスクリプトで、ffoutのファイルから各シーンの開始時間を抽出します。
開始時間は、pts_time:〇〇.〇〇という形で記載されています。
import re
from datetime import datetime
def loadData(file):
f = open(file)
data = f.read()
f.close()
return data
def getEndTime(data):
date_pattern = re.compile(r'time=(\d{2}:\d{2}:\d{2})')
match = re.findall(date_pattern, data)
dt = datetime.strptime(match[0], '%H:%M:%S')
endTime = dt.hour * 60 * 60 + dt.minute * 60 + dt.second
return endTime
def getTimes(data):
pattern = r'pts_time:([0-9]+\.[0-9]+)' # pts_timeの数値を抽出する
timeList = re.findall(pattern, data)
timeList = [float(n) for n in timeList] # str型をfloat型に変換
return timeList
if __name__ == '__main__':
data = loadData('ffout')
endTime = getEndTime(data)
timeList = getTimes(data)
timeList.append(endTime)
print(timeList)
結果
[1.7017, 5.9059, 13.8472, 17.0504, 19.8198, 21.6883, 23.7237, 25.7257, 34]
#フォルダ内の画像で残ったサムネイル画像の番号をインデックス配列に格納
import os
def getFIleList(path):
fileList = os.listdir(path)
return fileList
def getTimeIndex(fileList):
pattern = r'([1-9]+[0-9]*).jpg'
index = []
for i, fl in enumerate(fileList):
temp = re.findall(pattern, fl)
if(temp != None):
index.append(temp[0])
index = [int(n) - 1 for n in index] # str型をfloat型に変換
return index
if __name__ == '__main__':
path = "./output"
fileList = getFIleList(path)
index = getTimeIndex(fileList)
print(index)
結果
[0, 4, 5, 6, 7]
#フォルダ内の残ったサムネイル画像から動画ファイルを生成(動画の切り出し)
import subprocess
def writeFile(fileName, text):
f = open(fileName, 'w') # 書き込みモードで開く
f.write(text) # 引数の文字列をファイルに書き込む
f.close() # ファイルを閉じる
def generateCommand(index):
text = ''
for i, ii in enumerate(index):
delta = timeList[ii + 1] - timeList[ii]
cmd = 'ffmpeg -ss %s' % timeList[ii] + \
' -i billiards.mp4 -t %g' % delta + ' output/output%s.mp4' % i
print(cmd)
subprocess.call(cmd, shell=True)
text += 'file output/output' + str(i) + '.mp4\n'
return text
if __name__ == '__main__':
text = generateCommand(index)
writeFile('mylist.txt', text)
上のスクリプトを実行することで、ffmpegとして下記コマンドを実行したことになり、5つのmp4ファイルが生成されます。
ffmpeg -ss 1.7017 -i billiards.mp4 -t 4.2042 output/output0.mp4
ffmpeg -ss 19.8198 -i billiards.mp4 -t 1.8685 output/output1.mp4
ffmpeg -ss 21.6883 -i billiards.mp4 -t 2.0354 output/output2.mp4
ffmpeg -ss 23.7237 -i billiards.mp4 -t 2.002 output/output3.mp4
ffmpeg -ss 25.7257 -i billiards.mp4 -t 5.2743 output/output4.mp4
#各動画ファイルを連結
ffmpegでファイルをリストにして、動画を連結させる方法を参考にしました。
↑のPythonスクリプトで、mylist.txtに
file output/output0.mp4
file output/output1.mp4
file output/output2.mp4
file output/output3.mp4
file output/output4.mp4
と記載されています。
下記コマンドを実行することで、mylistファイルに記載された動画ファイルを全て連結してoutput.mp4ファイルとして出力します。
$ ffmpeg -f concat -i mylist.txt -c copy output.mp4
これで要らないシーンが削除された動画が完成です。