動画を画像・画像を動画に変換 | Python

# はじめに
研究で動画をイベントデータに変更したいという場面がありました。
その際、直接動画をイベントデータに変更できるものもあれば、画像からイベントデータに変更するものもありました。
そこで、動画と画像を自由に変換できるプログラムを作成しました。
また、pythonに慣れたいというモチベーションもあったので、今後も使う機会のありそうなツールをpythonで作成しようと思いました。

# Video2Frame
作成したプログラムが以下になります。
最初は動画をフレーム画像に変換する目的だったため、Video2Frameと題しましたが、後からFrame2Video機能も実装しました。。

https://github.com/ryhara/Video2Frame

### 機能
主に以下の機能があります
- **video2frame**
動画をフレームごとに切り出して、画像として保存します。
- **frame2video**
１つのディレクトリにある複数の画像から動画を作成します。画像は名前順で使用されます。
- **cut**
開始時間と終了時間を指定して、その間のみを動画として切り出します。
- **split**
分割したい数を指定して、動画を分割します。
- **info**
動画のパス, width, height, フレーム数, フレームレート, 秒数などの情報を出力します

使用方法はREADME.mdをご確認ください。

# 実装
環境の設定が面倒くさいという方は以下のコードなどを参考に関数のみご活用ください
基本はPython3, opencv, moviepy, ffmpegが入っていれば動くと思います

<details><summary>import</summary>

```py
import cv2
import os
import datetime
import time
from moviepy.editor import VideoFileClip, ImageSequenceClip
```
</details>


<details><summary>video2frame</summary>

```py
def video_to_image_all(input_path, output_path='../data/output', basename='frame', extension='jpg'):
    """Save all frames of a video to images.

    Args:
        input_path (str): input_path of the video.
        output_path (str, optional): output_path of the images. Defaults to '../data/output'.
        basename (str, optional): basename for the saved images. Defaults to 'frame'.
        extension (str, optional): extension for the saved images. Defaults to 'jpg'.
    """
    cap = cv2.VideoCapture(input_path)

    if not cap.isOpened():
        return

    timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    output_path = os.path.join(output_path, timestamp)
    os.makedirs(output_path, exist_ok=True)
    base_path = os.path.join(output_path, basename)

    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    n = 0

    while True:
        ret, frame = cap.read()
        if ret:
            cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), extension), frame)
            n += 1
        else:
            break
```
</details>

<details><summary>frame2video</summary>

```py
def frames_to_video(input_path, output_path='../data/output', basename='video', extension='mp4', fps=30):
    """Convert frames to a video.

    Args:
        input_path (str): input_path of the frames.
        output_path (str, optional): output_path of the video. Defaults to '../data/output'.
        fps (int, optional): frame rate of the video. Defaults to 30.
    """
    os.makedirs(output_path, exist_ok=True)
    output_path = os.path.join(output_path, '{}.{}'.format(basename, extension))
    if os.path.isdir(input_path):
        frames_dir = input_path
        frame_paths = sorted([os.path.join(frames_dir, f) for f in os.listdir(frames_dir) if f.endswith(('.png', '.jpg'))])
        clip = ImageSequenceClip(frame_paths, fps=fps)
        clip.write_videofile(output_path, codec='libx264', audio_codec='aac')
    else:
        raise ValueError("input_path must be a directory. images in the directory will be used to create the video.")
```
</details>

<details><summary>cut</summary>

```py
def video_cut(input_path, output_path='../data/output', basename='video', extension='mp4', start=0, end=10, swap_aspect=False):
    """Cut a video.

    Args:
        input_path (str): input_path of the video.
        output_path (str, optional): output_path of the video. Defaults to '../data/output'.
        basename (str, optional): basename for the saved video. Defaults to 'video_'.
        extension (str, optional): extension for the saved video. Defaults to 'mp4'.
        start (int, optional): start time to cut the video. Defaults to 0.
        end (int, optional): end time to cut the video. Defaults to 10.
        swap_aspect (bool, optional): swap aspect ratio of the video. Defaults to False.
    """
    output_filename = f"{basename}_{start}s_{end}s.{extension}"
    output_file_path = os.path.join(output_path, output_filename)

    os.makedirs(output_path, exist_ok=True)

    clip = VideoFileClip(input_path)

    width, height = clip.size

    if swap_aspect:
        cut_clip = clip.subclip(start, end).resize((height, width))
    else:
        cut_clip = clip.subclip(start, end)

    cut_clip.write_videofile(output_file_path, codec='libx264', audio_codec='aac')

    clip.close()
    cut_clip.close()
```
</details>

<details><summary>split</summary>

```py
def video_split(input_path, output_path='../data/output', basename='video', extension='mp4', split_num=2, swap_aspect=False):
    """Split a video into multiple videos.

    Args:
        input_path (str): input_path of the video.
        output_path (str, optional): output_path of the video. Defaults to '../data/output'.
        basename (str, optional): basename for the saved video. Defaults to 'video_'.
        extension (str, optional): extension for the saved video. Defaults to 'mp4'.
        split_num (int, optional): number of split videos. Defaults to 2.
        swap_aspect (bool, optional): swap aspect ratio of the video. Defaults to False.
    """
    os.makedirs(output_path, exist_ok=True)

    clip = VideoFileClip(input_path)
    total_duration = clip.duration
    width, height = clip.size

    clip_duration = total_duration / split_num
    clip.close()

    for i in range(split_num):
        clip = VideoFileClip(input_path)
        start_time = i * clip_duration
        end_time = min((i + 1) * clip_duration, total_duration)

        output_filename = f"{basename}_{i+1}.{extension}"
        output_file_path = os.path.join(output_path, output_filename)

        if swap_aspect:
            cut_clip = clip.subclip(start_time, end_time).resize((height, width))
        else:
            cut_clip = clip.subclip(start_time, end_time)

        cut_clip.write_videofile(output_file_path, codec='libx264', audio_codec='aac')

        clip.close()
        cut_clip.close()
        time.sleep(2)

```
</details>

<details><summary>info</summary>

```py
def video_info(input_path):
    """Print information of a video.

    Args:
        input_path (str): input_path of the video.
    """
    cap = cv2.VideoCapture(input_path)

    if not cap.isOpened():
        return

    print ('File:', input_path)
    print('Frame width:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
    print('Frame height:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    print('Frame count:', int(cap.get(cv2.CAP_PROP_FRAME_COUNT)))
    print('Frame rate:', cap.get(cv2.CAP_PROP_FPS))
    print('Duration (s):', int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) / cap.get(cv2.CAP_PROP_FPS))
```
</details>




# 最後に
簡単な動作確認しか行っていないので、不完全部分もあるかもしれませんがご容赦ください。
何か問題があればgithubのissueまたはコメントをお願いします。
せっかく作成したので、誰かの役に立てば嬉しいです。最後まで読んでいただきありがとうございました。

# 参考

https://zulko.github.io/moviepy/index.html

https://note.nkmk.me/python-opencv-video-to-still-image/