Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

自動的にYouTuber風ジャンプカット動画をつくる|動画の無音区間を自動的に消す

More than 1 year has passed since last update.

はじめに

2019-07-07追記
Javaを動かす環境を作るのが大変な人もいるので、
無音区間のデータファイルからシェルスクリプト(.sh)に変換してダウンロードできる簡易Webアプリを作りました!
急ぎで作ったので、わかりにくいところもあるとは思いますが.....
こちらから使用できます!

Pythonで動くプログラムを作成してくれた方がいました
2019-08-21追記
https://qiita.com/dokugaku-hitori/items/5df7114aaab291bf7c62

Windowsで動くWebアプリを作りました 2019-12-03追記
非エンジニアでもわかりやすいように、Qiita記事ではなくYouTubeに使い方の動画をUPしてあります!
https://www.youtube.com/watch?v=3yj6acuY_0U

YouTube
の動画編集ってある程度自動化ではないか?
と思いました。

  • 個人的にYouTubeでレビュワーをしていて、日銭を稼いでいる
  • ジャンプカット多めでテンポが早い動画のほうがレビュー動画として向いている
  • ジャンプカット多めが日本のYouTubeのトレンドになりつつある
  • おそらくこれを考案したのが、ジェットダイスケである。通称ジェットカット

ジャンプカット(ジェットカット)とは

動画を編集するときに波形だけを見る。
無音区間は再生しなくても目だけで見えるので、無音が続いているところをカットし続ける

2019-01-06 午前6.07のイメージ.jpg
10年前のジェットカット説明
ジェットカットとMosoで作るビデオブログ@明星和楽
たしかにテンポの良い動画にはなるが、長い動画だと大変

いい感じのフリーソフトは存在しない(たぶん)

音声のみであれば、無音区間を消すものは多くあった。
筆者のメイン環境はMacなので、開発しやすそうな便利な物を探した結果

ffmpeg

$ brew install ffmpeg

でインストール可能

FFmpegについての詳細はこちら

無音区間のデータをファイルに出力

  • -i "/Users/merarli/Downloads/test.MOV"に適当に喋っている動画をいれる
  • BGMなど入っているとうまくいかないかもしれない
  • 声が小さいとうまくいない場合があるが、パラメータ調整すればいけそう

以下のコマンドで無音区間情報を入ったtxtファイルを生成する。
silencedetect=noise=-30dB:d=0.4はおそらく、-30bB以下の音が0.4秒以上続いたときという意味だろう
(よくわかっていません😅(コメント欄で教えてください😂))

ffmpeg -i "/Users/merarli/Downloads/test.MOV" -af silencedetect=noise=-30dB:d=0.4 -f null - 2> test.txt
生成されたファイル
[silencedetect @ 0x7fae69744d80] silence_start: 0
[silencedetect @ 0x7fae69744d80] silence_end: 0.669773 | silence_duration: 0.669773
Output #0, null, to 'pipe:':
  Metadata:
    major_brand     : qt  
    minor_version   : 0
    compatible_brands: qt  
    com.apple.quicktime.creationdate: 2018-12-20T22:25:31+0900
    com.apple.quicktime.location.ISO6709: +35.6846+139.9267+146.167/
    com.apple.quicktime.make: Apple
    com.apple.quicktime.model: iPhone XS
    com.apple.quicktime.software: 12.0.1
    encoder         : Lavf58.20.100
    Stream #0:0(und): Video: wrapped_avframe, yuv420p, 1920x1080, q=2-31, 200 kb/s, 59.94 fps, 59.94 tbn, 59.94 tbc (default)
    Metadata:
      encoder         : Lavc58.35.100 wrapped_avframe
      creation_time   : 2018-12-20T13:25:31.000000Z
      handler_name    : Core Media Video
    Side data:
      displaymatrix: rotation of -0.00 degrees
    Stream #0:1(und): Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s (default)
    Metadata:
      creation_time   : 2018-12-20T13:25:31.000000Z
      handler_name    : Core Media Audio
      encoder         : Lavc58.35.100 pcm_s16le
[silencedetect @ 0x7fae69744d80] silence_start: 1.24209
[silencedetect @ 0x7fae69744d80] silence_end: 2.01805 | silence_duration: 0.775964
frame=  114 fps=0.0 q=-0.0 size=N/A time=00:00:03.04 bitrate=N/A speed=6.06x    
[silencedetect @ 0x7fae69744d80] silence_start: 4.60442
frame=  246 fps=245 q=-0.0 size=N/A time=00:00:05.24 bitrate=N/A speed=5.23x    
[silencedetect @ 0x7fae69744d80] silence_end: 5.98735 | silence_duration: 1.38293
[silencedetect @ 0x7fae69744d80] silence_start: 6.73524
frame=  377 fps=250 q=-0.0 size=N/A time=00:00:07.40 bitrate=N/A speed= 4.9x    
[silencedetect @ 0x7fae69744d80] silence_end: 7.74567 | silence_duration: 1.01043
frame=  512 fps=254 q=-0.0 size=N/A time=00:00:09.65 bitrate=N/A speed= 4.8x    
[silencedetect @ 0x7fae69744d80] silence_start: 9.47834
[silencedetect @ 0x7fae69744d80] silence_end: 10.256 | silence_duration: 0.777687
[silencedetect @ 0x7fae69744d80] silence_start: 10.3514
[silencedetect @ 0x7fae69744d80] silence_end: 11.2398 | silence_duration: 0.888458
[silencedetect @ 0x7fae69744d80] silence_start: 11.3495
frame=  646 fps=257 q=-0.0 size=N/A time=00:00:11.88 bitrate=N/A speed=4.73x    
[silencedetect @ 0x7fae69744d80] silence_end: 12.1559 | silence_duration: 0.806395
[silencedetect @ 0x7fae69744d80] silence_start: 12.2653
[silencedetect @ 0x7fae69744d80] silence_end: 13.1997 | silence_duration: 0.934376
[silencedetect @ 0x7fae69744d80] silence_start: 13.3081
[silencedetect @ 0x7fae69744d80] silence_end: 14.0962 | silence_duration: 0.788141
frame=  782 fps=259 q=-0.0 size=N/A time=00:00:14.18 bitrate=N/A speed= 4.7x    
[silencedetect @ 0x7fae69744d80] silence_start: 15.1737
[silencedetect @ 0x7fae69744d80] silence_end: 16.1274 | silence_duration: 0.953696
frame=  914 fps=260 q=-0.0 size=N/A time=00:00:16.39 bitrate=N/A speed=4.66x    
[silencedetect @ 0x7fae69744d80] silence_start: 16.2342
[silencedetect @ 0x7fae69744d80] silence_end: 17.2161 | silence_duration: 0.981837
[silencedetect @ 0x7fae69744d80] silence_start: 17.3058
[silencedetect @ 0x7fae69744d80] silence_end: 18.3106 | silence_duration: 1.00476
frame= 1051 fps=261 q=-0.0 size=N/A time=00:00:18.64 bitrate=N/A speed=4.63x    
[silencedetect @ 0x7fae69744d80] silence_start: 18.4241
[silencedetect @ 0x7fae69744d80] silence_end: 19.1211 | silence_duration: 0.697029
[silencedetect @ 0x7fae69744d80] silence_start: 19.1807
[silencedetect @ 0x7fae69744d80] silence_end: 19.8421 | silence_duration: 0.661338
frame= 1187 fps=262 q=-0.0 size=N/A time=00:00:20.38 bitrate=N/A speed= 4.5x    
frame= 1222 fps=262 q=-0.0 Lsize=N/A time=00:00:20.38 bitrate=N/A speed=4.37x    
video:640kB audio:3512kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

※情報が多いので、必要な部分だけを記載しています。

動画をカットしてみる

無音区間の情報が入っているのでそれを利用
silence_startsilence_endの時間情報を使えば、動画をバシバシカットしてくれる!
わざわざ、動画編集ソフトつかうのもイケてないので

ffmpeg -i in.MOV -ss 0.669773 -t 1.24209 output.mp4

こんな感じで動画を切り取って生成しましょう!
in.MOVの0.669773秒から1.24209秒間の動画を切り取って動画ファイルを生成する。

って意味です。

silence_startsilence_endの時間で動画を生成すると無音音声になります。
silence_endsilence_startの時間で動画を生成しましょう!

スクリプト化

これを手作業でやっていると、元も子もないので自動化したい。

silence_endsilence_startの情報をうまく使ってシェルスクリプト(.sh)にしたい。
個人的にJavaの扱いになれているのでJavaでテキストファイルの整形してシェルスクリプトにして書き出しをする。

こんな感じに出力されます。

出力される例.sh
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 0.771687 -t 2.991543 00000_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 4.21821 -t 1.8220999999999998 00001_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 13.0331 -t 6.5038 00002_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 24.5468 -t 5.500499999999999 00003_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 30.4868 -t 11.125800000000002 00004_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 44.7275 -t 20.547599999999996 00005_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 67.1255 -t 2.0420000000000016 00006_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 71.1251 -t 4.2821 00007_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 77.6302 -t 2.797799999999995 00008_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 80.9795 -t 2.8579000000000008 00009_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 84.3757 -t 0.9155000000000086 00010_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 86.3504 -t 12.63130000000001 00011_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 99.8297 -t 5.311300000000003 00012_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 105.853 -t 13.952000000000012 00013_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 120.29 -t 0.5889999999999986 00014_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 125.912 -t 3.5379999999999825 00015_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 130.895 -t 13.064999999999998 00016_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 144.363 -t 9.789999999999992 00017_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 159.259 -t 9.106000000000023 00018_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 169.138 -t 6.002999999999986 00019_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 175.755 -t 9.941000000000003 00020_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 190.52 -t 20.769999999999982 00021_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 219.606 -t 4.746000000000009 00022_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 227.524 -t 0.3580000000000041 00023_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 228.75 -t 10.967000000000013 00024_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 240.886 -t 0.0010000000000047748 00025_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 241.383 -t 8.379999999999995 00026_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 250.192 -t 9.569000000000017 00027_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 261.246 -t 6.0190000000000055 00028_hoge.MOV
ffmpeg -i /Users/merarli/Movies/input.mp4 -ss 267.755 -t 0.33600000000001273 00029_hoge.MOV

ソースコード

Main.java
package com.company;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class Main {

    public static void main(String[] args) {
        //使い方★1~★4を適当に入力してください。

        //カットしたい動画ファイルのパス ★1に入力
        String in_movie_path = "/Users/merarli/Downloads/test.MOV"; //★1

        //出力してほしいファイル名を指定 ★2
        //特に変更しなくても動きます。
        //1_out_.MOV,2_out_.MOV ....のように出力されます。
        String out_movie_path = "out_.MOV"; //★2

        // ffmpeg -i "/Users/merarli/Downloads/test.MOV" -af silencedetect=noise=-30dB:d=0.4 -f null - 2> test.txt
        // 無音区間のデータが入っているファイルパスを★3に入力
        String ffmpeg_file = "/Users/merarli/Desktop/test.txt";//★3

        //シェルスクリプトの保存場所を指定
        //★4に指定
        //この場合だとDesktopに保存される。
        String shell_file = "/Users/merarli/Desktop/auto_duration_test.sh"; //★4



        //ここからコピペしました。https://www.sejuku.net/blog/20924
        try {

            File file = new File(ffmpeg_file);

            // 2.ファイルが存在しない場合に例外が発生するので確認する
            if (!file.exists()) {
                System.out.print("ファイルが存在しません");
                return;
            }

            // 3.FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
            FileReader fileReader = new FileReader(file);

            String text_file = "";
            int data;
            char c;
            while ((data = fileReader.read()) != -1) {
                c = (char) data;
                text_file += String.valueOf(c);
            }

//            System.out.println("text_file: \n" + text_file);
            String tmp_file = "";
            String regex = "silence_start.*\\n|silence_end.*\\|";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(text_file);

            while (m.find()) {
//                System.out.println(m.group());
                tmp_file += m.group();
            }

            System.out.println(tmp_file);

            String duration = "";
            //動画の秒数を抽出
            String regex_dura = "Duration:\\s...........,";
            Pattern p_dura = Pattern.compile(regex_dura);
            Matcher m_dura = p_dura.matcher(text_file);

            while (m_dura.find()) {
//                System.out.println(m.group());
                duration += m_dura.group();
            }

            duration = duration.replaceAll("Duration:\\s","");
            duration = duration.replaceAll(",","");
            System.out.println("duration: " + duration);

            // |とか消した
            tmp_file = tmp_file.replaceAll("\\s\\|","\n");
            //数字データだけにしたいので、silence_start:とか消す
            tmp_file = tmp_file.replaceAll("silence_start:\\s|silence_end:\\s","");

            System.out.println(tmp_file);
            String silences[] = tmp_file.split("\n");

            String out_put_file = "";
            int count = 0;
            double end_time = 0.0;
            for (int i = 1; i < silences.length; i=i+2) {


                //最終行ではArrayIndexOutOfBoundsExceptionがでるので、duration(動画終了時間)を代わりに使う
                //動画の最後だけうまく切り取れないかも;;;
                if(i+1 >= silences.length){
//                    end_time = Double.parseDouble(silences[i]) - Double.parseDouble(duration);
//                    System.out.println("ffmpeg -i " + in_movie_path + " -ss " + silences[i] + " -t " + end_time + " " + count + "_" + out_movie_path);
//                    out_put_file += "ffmpeg -i " + in_movie_path + " -ss " + silences[i] + " -t " + end_time + " " + count + "_" + out_movie_path + "\n";
                }else{
                    end_time = Double.parseDouble(silences[i+1]) - Double.parseDouble(silences[i]);
                    System.out.println("ffmpeg -i " + in_movie_path + " -ss " + silences[i] + " -t " + end_time + " " + count + "_" + out_movie_path);
                    out_put_file += "ffmpeg -i " + in_movie_path + " -ss " + silences[i] + " -t " + end_time + " " + count + "_" + out_movie_path + "\n";
                }
                count++;
            }

            try {
                FileWriter fw = new FileWriter(shell_file);
                fw.write(out_put_file);
                fw.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            fileReader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}


この場合だとシェルスクリプトがDesktopに生成されます

以下の通りに権限を与えて、実行
【注意】
※結構時間がかかります。
※出力動画ファイルはシェルスクリプトを実行したディレクトリに生成されます。
※二回目実行するときはファイル名が重複するので、上書きするかどうかを問われます。[Y/N]で答えてあげてください。
※[Y/N]を問われたくなければ、重複しているファイルを削除してください。

$ cd ~/Desktop
$ chmod 777 auto_duration_test.sh
$ ./auto_duration_test.sh

お疲れ様です

この場合だとデスクトップこのように動画ファイルが生成されていると思います。
これをAdobe Premiere Proや、Final Cut Proなどで調理してあげてください。
※1秒以下の短い動画の場合動画編集ソフトがフリーズする可能性があるので、気をつけてください。
※以下のスクショは実際に編集に使ったデータ
スクリーンショット 2019-01-06 午前6.22.43.png

最後に

アプリ化して誰でも簡単に使えるようにすれば、YouTubeの動画編集している人たちの助けになるかも?

merarli
JavaScriptが好きです。 大学生: クルーズ代理店と某デジタルメディアの2つで長期就業型エンジニアインターン 新卒: 名古屋の自動車パーツメーカーで社内エンジニア 現在は渋谷で働いています
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away