動画内の沈黙を自動でCUTするプログラムを書いてみた【動画編集らくらくシリーズ①】

毎度毎度、手作業でおこなっている動画内の沈黙CUTを自動化してみました。
今回の使用言語はPythonです。

プログラムの流れは以下のように想定しています。
1.動画の中の沈黙をカット
2.動画を分割
3.分割した動画を結合

<参考記事>
Pythonで動画の沈黙をカットして、動画を分割する↓
https://qiita.com/dokugaku-hitori/items/5df7114aaab291bf7c62

動画の結合↓
https://jp.videoproc.com/edit-convert/combine-merge-video-by-ffmpeg.htm

今回作成するプログラムは上記参考記事のソースをコピペして作成しております。
ですので、作成の中で躓いたところを記事にしてゆきます!

スポンサーリンク

開発環境

OS:Windows10
Python 3.7.4
環境:Anaconda
エディタ:VScode

事前準備

  1. Pythonを利用するためにAnacondaをインストール
    Anacondaは、Pythonの様々なモジュールやツールを簡単にインストール&管理できるプラットフォームです。
  2. FFmpegのインストール
    FFmpegがインストールされていないと動きません。
    ~windowsへインストールする場合~
    (1)Get packages & executable filesのwindowsマークをクリック
    (2)Windows builds from gyan.devをクリック
    (3)release下のLinksの中から
      ~/ffmpeg-release-full.7z
     をダウンロード
    (4)7zの解凍ソフトがない場合はこちらからダウンロード
      7ZIPダウンロード
  3. FFmpegを展開して、binフォルダ直下の3つの実行ファイルを適当なフォルダに格納
    私は、local\programs\ffmpeg\binに格納しました。
  4. コマンドで使用できるように、FFmpegのpathを通す。
    (1)windowsボタン+PauseBreakでシステムwindowが立ち上がる
    (2)システムの詳細設定
    (3)詳細設定 → 「環境変数」
    (4)変数Pathに「編集」で先ほどbinファイルを設置したディレクトリ(値)を追加
    ※ユーザー環境変数とシステム環境変数の両者にpathを通さなければコマンド認識しませんでした。コマンドで”ffmpeg”が認識されない場合は、再起動も試してみてください。

コード(全体)

import subprocess
import os
import glob
#動画元素材の取得
def mk_movieList(movie_folder):
    files = os.listdir(movie_folder)
    files = [x for x in files if x[-4:] == '.mp4']
    files = [x for x in files if x[0] != '.']
    return files

def mk_starts_ends(wk_dir,movie):
    os.chdir(wk_dir)
    output = subprocess.run(["ffmpeg","-i",movie,"-af", "silencedetect=noise=-33dB:d=0.6","-f","null","-"], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    print(output)
    s = str(output)
    lines = s.split('\\n')
    time_list = []
    for line in lines:
        if "silencedetect" in line:
            words = line.split(" ")
            for i in range(len(words)):
                if "silence_start" in words[i]:
                    time_list.append(float((words[i+1]).replace('\\r','')))
                if "silence_end" in words[i]:
                    time_list.append(float((words[i+1]).replace('\\r','')))

    print(time_list)
    starts_ends = list(zip(*[iter(time_list)]*2))
    return starts_ends

def mk_jumpcut(wk_dir,movie,starts_ends):
    os.chdir(wk_dir)
    for i in range(len(starts_ends)-1):
        movie_name = movie.split(".")
        splitfile = "./JumpCut/" + movie_name[0] + "_" + str(i) + ".mp4"
        print(splitfile)
        output = subprocess.run(["ffmpeg", "-i",movie,"-ss",str(starts_ends[i][1]),"-t",str(starts_ends[i+1][0]-starts_ends[i][1]),splitfile],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        print(output)

#動画の結合
def join_movie(movie_files,out_path):
    videos = glob.glob(movie_files)
    print(videos)

    # join対象のlist
    with open("JumpCut/tmp.txt","w") as fp:
        lines = [f"file '{os.path.split(line)[1]}'" for line in videos]
        #1,10,11,~のようになってしまうのを防止。並び替え
        lineList = sorted(lines,key=len)
        fp.write("\n".join(lineList))

    output = subprocess.run(["ffmpeg","-f","concat","-i","JumpCut/tmp.txt","-c","copy",out_path],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    print(output)

#ディレクトリの指定
movie_folder = "元動画を格納するディレクトリ(絶対パス)"
movie_files = "分割した動画が格納されるディレクトリ(絶対パス)*.mp4"
out_path = "join_out.mp4"

os.chdir(movie_folder)
wk_dir = os.path.abspath(".")
try:
    os.mkdir("JumpCut")
except:
    pass

movie_list = mk_movieList(movie_folder)

for movie in movie_list:
    print(movie)
    starts_ends = mk_starts_ends(wk_dir,movie)
    print(starts_ends)
    mk_jumpcut(wk_dir,movie,starts_ends)
    join_movie(movie_files,out_path)
    print(movie_files,out_path)

コードの説明

動画のジャンプカット

参考記事に詳しく書かれているので、こちらの記事をご参照ください!
https://qiita.com/dokugaku-hitori/items/5df7114aaab291bf7c62

躓いたところは、コード23行、25行目の箇所で\\rがエラーで返ってきてしまったのでreplaceで文字列の置き換えを行っております。

time_list.append(float((words[i+1]).replace('\\r','')))

動画の結合

動画の結合では、結合対象のファイル名をtxt等で抽出してから結合するといった方法が推奨されているみたいです。
この部分で結合対象のファイル名をtextに外だししました。

    with open("JumpCut/tmp.txt","w") as fp:
        lines = [f"file '{os.path.split(line)[1]}'" for line in videos]
        #1,10,11,~のようになってしまうのを防止。並び替え
        lineList = sorted(lines,key=len)
        fp.write("\n".join(lineList))

2行目の os.pathでは、globによって絶対パスの表記になってしまっているため、ファイル名のみ抽出するためにsplitをかけています。絶対パスでは、ffmpegで連結する際に読み込みerrorとなってしまいます。
4行目のソートでは、分割された動画が10本以上ある場合、1,10,11,12….と並んでしまうため、sortedのlengthをkeyにつけてファイル名の並び替えを施しています。
ソートしない場合、結合の順番があべこべになってしまいますので、注意が必要です。

最後に・・・注意すべきこと

元素材となる動画ファイルは、英語で表記してください。
エンジニアの中では暗黙の了解ですが、動画編集者は日本語のタイトルをつける場合も多いと思います。
日本語のファイルで実行した場合、結合のffmpegが対象ファイルを読み込む際に文字化けを起こして正常機能しません。
元素材ついては、すべて英語でセットする事をお勧めします。

このプログラムはmp4のみで起動します。

失敗談

動画の結合にcv2を用いてやろうと当初は考えておりました。
が、結合できたはいいが、音声がない!!!!!!!
調べると、動画と音声を分けて抽出し、合成するコードを書けば良いらしいのですが、ffmpegの結合を用いた方がコードが短く楽だったので、cv2を用いるのはやめましたww
参考?までに失敗コードの途中作業までを載せます。うまく作動しませんので、紆余曲折あったんだなぁというあたたかい目で見ていただけますとうれしいですwww

    # fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
    # movie = cv2.VideoCapture(videos[0])
    # fps = movie.get(cv2.CAP_PROP_FPS)
    # height = movie.get(cv2.CAP_PROP_FRAME_HEIGHT)
    # width = movie.get(cv2.CAP_PROP_FRAME_WIDTH)

 
    # output = cv2.VideoWriter(out_path,int(fourcc),fps,(int(width), int(height)))

次回の記事では、動画の音声からテキストを自動的に抽出するプログラミングを紹介しております。ぜひ、併せて読んでくださいm(_)m

タイトルとURLをコピーしました