VideoLoopProcessor: 動画とオーディオを組み合わせる魔法のツール

2024/07/25に公開

はじめに

皆さん、こんにちは!今日は、「VideoLoopProcessor」という面白いプログラムについてお話しします。このプログラムは、動画をオーディオファイルの長さに合わせてループ処理する素晴らしいツールです。簡単に言えば、短い動画を音楽の長さに合わせて繰り返し再生させる魔法のようなプログラムなんです!

デモ動画

VideoLoopProcessorの全体像

まずは、このプログラムが何をするのか、全体像を見てみましょう。

この図を見ると、VideoLoopProcessorが行う主な処理が分かりますね。短い動画と音楽ファイルを入力として受け取り、最終的に音楽の長さに合わせた動画を作成します。その過程で、動画の逆再生、速度変更、結合、音楽との合成といった様々な処理を行っているんです。

プログラムの詳細な流れ

では、このプログラムがどのように動作するのか、もう少し詳しく見ていきましょう。

1. 初期設定

まず、プログラムを使うために必要な情報を設定します。

input_file = "cat_is_playing_dj.mp4"
output_file = "output.mp4"
audio_file = "input_tracks_enka/o_青い夜の響き.mp3"

processor = VideoLoopProcessor(input_file, output_file, audio_file)

ここでは、入力する動画ファイル(猫がDJをしている動画らしいですね!)、出力するファイル名、そして使用する音楽ファイル(演歌のようです)を指定しています。

2. 動画処理の開始

次に、processメソッドを呼び出して実際の処理を開始します。

processor.process()

この一行で魔法のような処理が始まります!では、その中身を見ていきましょう。

3. 逆再生動画の作成

まず、入力された動画を逆再生した新しい動画を作成します。

このステップで、元の動画を逆再生した新しい動画ファイルができあがります。猫が後ろ向きにDJをしているような面白い動画になっているでしょうね!

4. 動画のループ処理

ここからが本当の魔法です。音楽の長さに合わせて、元の動画と逆再生動画を交互に使いながら、速度を変えてループさせていきます。

この処理を繰り返すことで、音楽の長さに合わせた面白い動画ができあがっていくんです。速度がランダムに変わるので、見ていて飽きない動画になりますよ!

5. 動画の結合

ループ処理で作成した複数の動画を一つにまとめます。

これで、音楽の長さに合わせた一本の動画ができあがりました!

6. 音楽との合成

最後に、結合した動画と元の音楽ファイルを合成します。

これで完成です!猫のDJ動画が、演歌に合わせてユニークなリズムで動く面白い動画ができあがりました。

全体コード

import subprocess
import sys
import random
import os
import shutil
from loguru import logger
from typing import List

from art import *
class VideoLoopProcessor:
    """
    動画をオーディオファイルの長さに合わせてループ処理するクラス。
    """

    def __init__(
        self,
        input_file: str,
        output_file: str,
        audio_file: str,
        output_dir: str = "output",
        temp_file_prefix: str = "temp_",
        speed_min: float = 0.7,
        speed_max: float = 1.3
    ):
        self.input_file = input_file
        self.output_file = output_file
        self.audio_file = audio_file
        self.output_dir = os.path.join(output_dir, os.path.splitext(os.path.basename(self.input_file))[0])
        self.temp_file_prefix = temp_file_prefix
        self.speed_min = speed_min
        self.speed_max = speed_max

        self.reversed_file = f"{self.temp_file_prefix}reversed.mp4"
        self.temp_combined_file = f"{self.temp_file_prefix}combined.mp4"
        self.filelist_name = "filelist.txt"

        os.makedirs(self.output_dir, exist_ok=True)
        self._copy_files_to_output_dir()

    def _copy_files_to_output_dir(self):
        """入力ファイルとオーディオファイルを処理ディレクトリにコピーする"""
        logger.info(f"入力ファイル '{self.input_file}' を '{self.output_dir}' にコピーしています...")
        shutil.copy2(self.input_file, self.output_dir)
        logger.info(f"入力ファイル '{self.audio_file}' を '{self.output_dir}' にコピーしています...")
        shutil.copy2(self.audio_file, self.output_dir)

    def _get_audio_duration(self, audio_file: str) -> float:
        """指定されたオーディオファイルの長さを秒単位で取得する"""
        logger.info(f"オーディオファイル '{audio_file}' の長さを取得しています...")
        result = subprocess.run(
            [
                "ffprobe",
                "-v", "error",
                "-show_entries", "format=duration",
                "-of", "default=noprint_wrappers=1:nokey=1",
                os.path.basename(audio_file)
            ],
            capture_output=True,
            text=True,
            cwd=self.output_dir  
        )
        duration = float(result.stdout)
        logger.info(f"オーディオの長さ: {duration:.2f}秒")
        return duration

    def _concat_videos(self, file_list: List[str], output_file_path: str):
        """ファイルリスト内のビデオファイルを連結する"""
        filelist_path = os.path.join(self.output_dir, self.filelist_name)
        with open(filelist_path, "w") as f:
            for file_path in file_list:
                f.write(f"file '{file_path}'\n")
        subprocess.run(
            ["ffmpeg", "-f", "concat", "-safe", "0", "-i", self.filelist_name, "-c", "copy", output_file_path],
            check=True,
            cwd=self.output_dir,
        )
        logger.info(f"出力ファイル '{output_file_path}' を作成しました")

    def process(self):
        """動画を処理するメインのメソッド"""
        tprint(">>  VideoLoopProcessor", font="rnd-large")
        logger.info("動画を逆再生しています...")
        subprocess.run(["ffmpeg", "-i", self.input_file, "-vf", "reverse", self.reversed_file], cwd=self.output_dir)

        videos = [os.path.basename(self.input_file), self.reversed_file]
        total_duration = 0
        i = 0

        while total_duration < self._get_audio_duration(self.audio_file):
            video = videos[i % 2]
            i += 1

            speed = random.uniform(self.speed_min, self.speed_max)
            temp_file = f"{self.temp_file_prefix}{int(total_duration * 1000):06d}.mp4"

            logger.info(f"動画 '{video}' を速度 {speed:.2f}x で処理しています...")
            subprocess.run(
                [
                    "ffmpeg",
                    "-i", video,
                    "-filter:v", f"setpts={1 / speed}*PTS",
                    temp_file
                ],
                cwd=self.output_dir
            )

            videos.append(temp_file)
            total_duration += self._get_audio_duration(os.path.join(self.output_dir, temp_file))

        logger.info("動画を結合しています...")
        self._concat_videos(videos, self.temp_combined_file)

        logger.info("MP3と結合しています...")
        subprocess.run(
            [
                "ffmpeg",
                "-i", self.temp_combined_file,
                "-i", os.path.basename(self.audio_file),
                "-map", "0:v",
                "-map", "1:a",
                "-c:v", "copy",
                "-shortest",
                self.output_file
            ],
            cwd=self.output_dir
        )

        logger.info(f"最終出力ファイル '{os.path.join(self.output_dir, self.output_file)}' を作成しました")


if __name__ == "__main__":
    input_file = "cat_is_playing_dj.mp4"
    output_file = "output.mp4"
    audio_file = "input_tracks_enka/o_青い夜の響き.mp3" 

    processor = VideoLoopProcessor(input_file, output_file, audio_file)
    processor.process()

まとめ

VideoLoopProcessorは、短い動画を音楽に合わせて繰り返し再生する素晴らしいツールです。動画を逆再生したり、速度を変えたりすることで、単純な繰り返しではない面白い動画を作り出します。

このプログラムを使えば、短い面白い動画を長い音楽に合わせて延長したり、音楽に合わせてリズミカルに動く動画を作ったりすることができます。YouTubeやSNSで使える面白いコンテンツを作るのに最適なツールと言えるでしょう。

ぜひ、あなたも試してみてください。きっと素晴らしい作品ができあがるはずです!

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Discussion