Pythonで時間を合わせて音楽と動画ファイルを結合するGUIアプリを作ってみた！

Pythonを使い、時間を合わせて音楽と動画ファイルを結合するGUIアプリを作ってみました。

## アプリの目的

・気軽に撮影した短い動画に簡単に音楽をつけられるようにすること。

・AIなどで作成した音楽ファイルに合わせて、簡単に動画ファイルを結合させること。

※前回作成したスライドショー（GIFアニメ）に音楽をつけたら、時間が合わず、使い物になりませんでした。そこで今回は動画の再生時間に合わせて音楽ファイルを結合させるアプリを作ってみました。作成した後、AI(「Stable Audio」)でオリジナルの音楽を無料で作成できるという記事を読み、逆のパターンを付け加えました。45秒までなら無料で作成できるようです。

## アプリの使い方

### 1.音楽ファイルを選択

MP3などの音楽ファイルを1つ選択します。

### 2.動画ファイルを選択

MP4などの動画ファイルを1つ選択します。

### 3.ボタンを押す

ボタンは3つ用意されています。

#### MP4（優先）ボタン

① MP4動画の再生時間に合わせてMP3の音楽ファイルをカットし、2つのファイルを結合させます。

例：60秒のMP4（音楽なし） + 120秒のMP3 → 60秒のMP4（音楽付き）


#### MP3（優先）ボタン

② MP3音楽の再生時間に合わせてMP4動画ファイルをカットし、2つのファイルを結合させます。

例: 120秒のMP4（音楽なし） + 45秒のMP3 → 45秒のMP4（音楽付き）


#### NOMALボタン

③ 時間を合わせないで、そのまま音楽MP3と動画MP4ファイルを結合させます。

例: 120秒のMP4（音楽なし） + 10秒のMP3 → 120秒のMP4（10秒だけ音楽が流れる）



## 用意するもの

・音楽ファイル・・・MP3など

・動画ファイル・・・MP4など

### ライブラリをインストール

Pillowをインストール

```
pip install pillow
```

MoviePyをインストール

```
pip install moviepy
```

mutagenをインストール

```
pip install mutagen
```

OpenCVをインストール

```
pip install opencv-python
pip install opencv-contrib-python
```

pydubをインストール

```
pip install pydub
```

## アプリのコード

アプリのコードを実行することで、GUIアプリが自動で起動します。

・MP4ボタンを押した場合→「out_movie.mp4」として保存されます。

・MP3ボタンを押した場合→「out_music.mp4」として保存されます。

・NOMALボタンを押した場合→「out_nomal.mp4」として保存されます。

※Windowsでexe化した場合は、（「dist」フォルダ内にある）アプリのフォルダ内にファイルが作成されています。


```python:MP3+MP4.py
# coding: utf-8
# インポート
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import os
from PIL import Image

#追加ライブラリインポート
import moviepy.editor as mp
from moviepy.editor import *
from mutagen.mp3 import MP3
import cv2
from pydub import AudioSegment
import moviepy.editor as mp


### 関数 ###
# Browse...MP3
def browse_func():
    # グローバル変数の宣言
    global fname
    typ = [('', '*')] 
    dir = '/home/'
    #複数ファイルを選択する
    #fname = filedialog.askopenfilenames(filetypes = typ, initialdir = dir) 
    #print(fname)
    
    #単数のファイルを選択する
    fname = filedialog.askopenfilename(filetypes = typ, initialdir = dir) 

# Browse...MP4
def browse_func2():
    # グローバル変数の宣言
    global fname2
    typ = [('', '*')] 
    dir = '/home/'
    #複数ファイルを選択する
    #fname = filedialog.askopenfilenames(filetypes = typ, initialdir = dir) 
    #print(fname)
    
    #単数のファイルを選択する
    fname2 = filedialog.askopenfilename(filetypes = typ, initialdir = dir) 


# RUN1 NORMAL
def run_func():
    global fname
    global fname2
    try:
         #MP4ファイルを読み込む
         clip = mp.VideoFileClip(fname2).subclip()

         #音楽MP3を付け足して保存
         clip.write_videofile('out_nomal.mp4', audio= fname)
         
    except:
        # ステータスの更新
        statusbar["text"] = " Error!!"


# RUN2 MP3
def run_func2():
    global fname
    global fname2
    
    try:
        audio = MP3(fname) #MP3ファイルのパス
        print (audio.info.length)

        file_path = fname2    # 編集したいMP4動画のパス

        start = 0    # 切り出し開始時刻。秒で表現

        end = audio.info.length    # 切り出し終了時刻。同じく秒で表現

        save_path = "out_music.mp4"    # 編集後のファイル保存先のパス

        video = VideoFileClip(file_path).subclip(start, end)    # ビデオのカット開始

        video.write_videofile(save_path,fps=29)    # fpsは元の動画に合わせて29に設定

    except:
        # ステータスの更新
          statusbar["text"] = " Error!!"



# RUN3 MP4
def run_func3():
    global fname
    global fname2
    file = fname2
    #音楽MP3ファイルのパス
    audio_path = fname
    #MP4ファイルのパス
    image_path = fname2
    try:
        if __name__ == '__main__':
            cap = cv2.VideoCapture(file)                  # MP4動画を読み込む
            video_frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) # フレーム数を取得する
            video_fps = cap.get(cv2.CAP_PROP_FPS)                 # フレームレートを取得する
            video_len_sec = video_frame_count / video_fps         # 長さ（秒）を計算する
            #print(video_len_sec)                                  # 長さ（秒）を出力する
            time = round(video_len_sec) * 1000
            print(time)
        # mp3ファイルの読み込み
        sound = AudioSegment.from_file(audio_path, format="mp3")

        # 5000ms～10000ms(1～10秒)を抽出
        sound1 = sound[1000:time]

        # 抽出した部分を出力
        sound1.export("output1.mp3", format="mp3")

        #MP4ファイルを読み込む
        clip = mp.VideoFileClip(image_path).subclip()

        #音楽ファイルを付け足して保存
        clip.write_videofile('out_movie.mp4', audio= "output1.mp3")

    except:
        # ステータスの更新
          statusbar["text"] = " Error!!"


                
### GUI  ###
# ウインドウの作成
root = tk.Tk()
# ステータスバー設置 
statusbar = tk.Label(root, text =  " Here we go!!",
                     bd = 1, relief = tk.SUNKEN, anchor = tk.W)
statusbar.pack(side = tk.BOTTOM, fill = tk.X)


# 実行ボタン1
run_button = tk.Button(root, text = "NORMAL", command = run_func)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)


# 実行ボタン2
run_button = tk.Button(root, text = "MP3", command = run_func2)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)


# 実行ボタン3
run_button = tk.Button(root, text = "MP4", command = run_func3)
run_button.pack(pady = 10, ipadx = 20, side = tk.BOTTOM)



# ラベルフレーム
frame = ttk.Labelframe(root, text = "Select MP3", padding = 10)
frame.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス
extensions = [".mp3", ".mp4", ".avi", ".wma"]
cb = ttk.Combobox(frame, values = extensions)
cb.pack(side = tk.LEFT)
# 参照ボタン
browse_button = tk.Button(frame, text = "Browse...",
                          command = browse_func)
browse_button.pack(padx = 10, side = tk.LEFT)


# ラベルフレーム2
frame2 = ttk.Labelframe(root, text = "Select MP4", padding = 10)
frame2.pack(padx = 20, pady = 5, side = tk.BOTTOM)
# コンボボックス2
extensions2 = [".mp4", ".avi", ".wma"]
cb2 = ttk.Combobox(frame2, values = extensions2)
cb2.pack(side = tk.LEFT)
# 参照ボタン2
browse_button2 = tk.Button(frame2, text = "Browse...",
                          command = browse_func2)
browse_button2.pack(padx = 10, side = tk.LEFT)

# ウインドウ状態の維持
root.mainloop()
```

## 作成したアプリをexe化する

pyinstallerを使って、アプリをexe化することができます。exe化すると、ボタンをダブルクリックするだけでアプリを起動できるようになります。

```
pip install pyinstaller
```

```
pyinstaller MP3+MP4.py
```

Linux Ubuntuだと非常に簡単にexe化できます。

Windowsはけっこう難しかったです。詳しい手順は以下の記事にまとめてあります。

Python デスクトップアプリ開発に挑戦してみた！ https://zenn.dev/akibaburari/articles/32a89b19fcf889