Python
ffmpeg
動画編集

FFmpegで動画の要らないシーンを削除する~シーン検出とサムネイル画像出力~

やりたいこと

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

出力したファイル一覧は↓です。
image.png

フォルダ内のサムネイル画像を削除することで、要らないシーンを削除

要らないシーンと思うサムネイル画像を削除します。
今回はビリヤードボールの拡大画像だけ残したいので、それ以外の画像を削除しました。
002.jpg 003.jpg 004.jpg
を削除しています。

image.png

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

これで要らないシーンが削除された動画が完成です。