MP3ファイルをアップロードしてWAVに変換する

この記事は[PONOS Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ponos) の19日目の記事です。
昨日は@e73ryoさんでした。



# はじめに

同僚と雑談の中で「**MP3のファイルをアップロードしてWAVファイルに変換。WAVファイルをダウンロードできるようにしたいが、どうすればいい？**」と相談を受けたことがキッカケでスタートし、ざっと動作するものができましたので記事にしました。

開発するにあたり、以下を要件とし、自宅など限定した環境で稼働させることを想定して作成しています。

+ OS: Windows
+ 開発言語: Python
+ そのた: WSLを使わない


# 完成したもの

![完成４.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447787/04850d80-7a3c-34df-7340-2f7cce03d3b6.png)

# 開発環境および使用したツール

+ Windows 10 Home 64bit
+ VSCode
+ コマンドプロンプト

# インストール

#### OSにインストールするもの

+ Python: 3.8.10 (pyenvをインストールするために3.10.0)
+ Pyenv: 2.64.11
+ FFmpeg: 4.4.1

#### 仮想環境にインストールするもの

+ Flask: 2.0.2
+ Pydub: 0.25.1
+ WsgiDAV: 3.1.1

それではインストールを進めていきます。

## ①Pythonを使用できるようにする

将来的に複数のPythonバージョンで開発が行えるようにpyenvをインストールすることにしました。
「Pythonをインストール → pyenvをインストール → 目的のバージョンのPythonをインストール」
のように進めます。

#### 1. Pythonをインストールする
pyenv はpipでインストールします。pipはPython3.4以降に標準で付属していますので、まずはPythonをインストールしていきます。

Pythonのインストーラは公式サイトからダウンロードしました。

https://www.python.org/downloads/

今回は「**Download Python 3.10.0**」のボタンからダウンロードしました。
インストール中に、Path を自動で設定してもらえるように「**Add Python 3.10 to PATH**」にチェックをしました。

以下のコマンドでPythonがインストールされたことが確認できました。

```shell
>python --version
Python 3.10.0
```

#### 2. pyenvをインストールする

pipが使えるようになったので、続いてpyenvのインストールを進めます。

```shell
> pip install pyenv-win --target %USERPROFILE%\.pyenv
Collecting pyenv-win
  Downloading pyenv_win-2.64.11-py3-none-any.whl (44 kB)
     |████████████████████████████████| 44 kB 3.2 MB/s
Installing collected packages: pyenv-win
Successfully installed pyenv-win-2.64.11

> pyenv --version
pyenv 2.64.11
```

インストールできたら、環境変数にパスを追加します。

(1) コントロールパネル > システム > システムの詳細設定 > 詳細設定 > 環境変数 を開く
(2) ユーザーの環境変数の「Path」を選択し「編集」をクリック。以下の２つのパスを新規登録し、既存のPythonのパスより上に設定します。

```shell
%USERPROFILE%\.pyenv\pyenv-win\bin
%USERPROFILE%\.pyenv\pyenv-win\shims
```

#### 3. 目的のバージョンのPythonをインストールする

以下のコマンドでインストールできるバージョンが表示されます。

```shell
> pyenv install --list
```

今回は 3.8.10 をインストールして、通常使用するPythonのバージョンを 3.8.10に設定しました。

```shell
> pyenv install 3.8.10
> pyenv global 3.8.10
```

以前は「Python 3.10.0」と表示されましたが、目的のバージョンが表示されました。

```shell
> python --version
Python 3.8.10
```

これでpyenvで複数のPythonをインストールし、必要に応じてバージョンを切り替えることができるようになりました。
今後他のバージョンのPythonを使う際には 「pyenv install ～」でインストールするだけです。

## ②MP3からWAVに変換するためにFFmpegを使えるようにする

今回、MP3からWAVファイルへの変換処理は **FFmpeg** を利用します。
音声ファイルを変換するために **pydub** というライブラリを使い、このライブラリを通して **FFmpeg** が使われます。

####  FFmpegをインストールする

##### 1. ダウンロードページにアクセス

https://github.com/BtbN/FFmpeg-Builds/releases

（このリンクは公式サイトから辿ったリンクです。- https://ffmpeg.org/download.html ）

##### ２. 「**ffmpeg-n4.4.1-2-gcc33e73618-win64-gpl-4.4.zip**」をダウンロード
※私の環境はWindows10 Home 64bitなので、このファイルをダウンロードしました。

##### ３. FFmpegを解凍して配置

(1) C:\Program Filesに「FFmpeg」フォルダを作成。
(2) Zipファイルを解凍して作成したフォルダに設置。

```shell
c:\Program Files\FFmpeg\bin\
c:\Program Files\FFmpeg\doc\
c:\Program Files\FFmpeg\LICENSE.txt
```
##### ４. 環境変数にパスを追加します。

(1) コントロールパネルから システム > システムの詳細設定 > 詳細設定 > 環境変数 を開く
(2) ユーザーの環境変数の「Path」を選択し「編集」をクリック
(3) 新規ボタンをクリックして以下を入力し保存してください。

```shell
C:\Program Files\FFmpeg\bin
```
これでFFmpegのインストールができました。

## ③仮想環境を作成し必要なライブラリをインストールする

Python3.5以降では「**venv**」という仮想環境が使えます。「python -m venv {フォルダ名}」のように簡単なコマンドで構築できるので非常に便利ですので使っていきます。
今回は、Webアプリケーションフレームワークとして「**Flask**」を使い、
変換後のWAVファイルの一覧やダウンロードするページについては、「**wsgidav**」を利用してWebDAVのページをそのまま表示することにしました。

以下のコマンドで仮想環境を作成していきます。

```shell
# 動作確認用のスクリプトを置くフォルダを作成
>mkdir c:\test
# 作成したフォルダへ移動
>cd C:\test
# 今回はpython 3.8.10を使うようにする(.python-versionが作られます)
c:\test>pyenv local 3.8.10
# 仮想環境を作成
c:\test>python -m venv ./venv
# 仮想環境に入る
c:\test>.\venv\Scripts\activate
# pydubをインストール
(venv) c:\test>pip install pydub
# wsgidavをインストール
(venv) c:\test>pip install cheroot wsgidav
# 仮想環境から出る
(venv) c:\test>deactivate
c:\test>
```

これで一通り環境構築ができましたので、機能を実装していきます。

# 実装スタート

### ①必要なフォルダを作成

アップロードしたMP3ファイルの保管するフォルダを作成

```shell
> mkdir c:\test\uploads
```

変換後のWAVファイルを保管するフォルダを作成

```shell
> mkdir c:\test\downloads
```

### ②コーディング

以下のように**c:\test\upload.py** を作成しました。

```python
import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename
from pydub import AudioSegment
from pathlib import Path

# アップロードしたファイルを保管するフォルダのパス
UPLOAD_FOLDER = './uploads'
# 変換したWAVファイルの保管フォルダのパス（WebDAVで表示するフォルダ）
DOWNLOAD_FOLDER = './downloads'
# アップロードするファイルをMP3に制限する設定
ALLOWED_EXTENSIONS = {'mp3'}

app = Flask(__name__)

# 最大16MBに制限
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            # ファイルがなければ、特に処理せずにリクエストページを表示
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            # ファイル名がなければ、特に処理せずにリクエストページを表示
            return redirect(request.url)
        if file and allowed_file(file.filename):
            # アップロードされたファイルをアップロードフォルダにセーブする
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            # アップロードフォルダにセーブしたMP3ファイルをWAVに変換してダウンロードフォルダに出力する
            song = AudioSegment.from_mp3(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            song.export(os.path.join(app.config['DOWNLOAD_FOLDER'], Path(filename).stem + ".wav"), format="wav")
            return redirect(request.url)
    # アップロードフォーム＆WAVファイル一覧ページを表示する
    return '''
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="utf-8">
        <link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
        <title>MP3からWAVへファイル変換</title>
    </head>
    <body>
    <h1>MP3からWAVへファイル変換</h1>
    <form method="post" enctype="multipart/form-data">
      <input type="file" name="file" accept="audio/mpeg">
      <button type="submit">変換する</button>
    </form>
    <iframe src="http://localhost:8000/" width="900" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
    </body>
    </html>
    '''

app.run(host='0.0.0.0', port=5000, debug=True)

```
※ 「accept="audio/mpeg"」でアップロードできるファイル形式をMP3に限定しています。
※ iframeのsrcはご自身の環境に合わせて設定してください。localhostのままでは他のPCからアクセスした際に表示されません。
※ 上記では5000番ポートで稼働する設定にしています。
※ 少し見た目をキレイにしたいとNo-Class CSS Frameworkの1つ **new.css** を使いました。デザインが不得意な私には便利です。

これで完成です！
サーバを起動して動作確認をしていきます。


### ③サーバを起動する

```shell
>cd C:\test
c:\test>.\venv\Scripts\activate
(venv) c:\test>start python upload.py
(venv) c:\test>start wsgidav --host=0.0.0.0 --port=8000 --root=c:\test\downloads --auth=anonymous
(venv) c:\test>deactivate
c:\test>
```
※ iframeのsrcにポート番号はwsgidavの「--port=8000」と関連しています。

# 動作確認

ブラウザで動作を確認してみましょう。（ここまで実装が完了していたら、このURLで開けます。）

```script
http://localhost:5000
```

![完成１.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447787/9bad93a7-4d44-3091-b312-d0ab7594651c.png)

MP3ファイルを選択して「変換する」ボタンをクリックすると、一覧にWAVファイルが追加されました。

![完成３.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1447787/1a096ef6-b212-57ea-2603-93c6cf3379ef.png)

一覧からファイル名を右クリックして「名前を付けてリンク先を保存...」からダウンロードができますし、ファイル名をクリックすると再生されます。

完成です！


※ 他のPCからアクセスするには、ファイアウォールで以下のポートを開放が必要になります。

+ TCP 5000
+ TCP 8000

# まとめ

今回、音声ファイルの変換については、MP3からWAVへの変換だけに限定しましたが、FFmpegは様々な形式のファイルに対応していますので、少し改良するだけで別の形式に対応したり、もっと汎用的に相互変換するものも作れると思います。

設計当初「変換には結構時間がかかるのでは？」と思っていました。
そのため「ファイルアップロード」「変換ジョブ」「一覧」の処理をそれぞれ分離することを考えていましたが、MP3からWAVへの変換にはあまり時間がかからなかったことから、分離せずにファイルアップロード処理で行うことにしました。

また、WsgiDAVは今回初めて使用しましたが、インストールして起動するだけなので非常に簡単に使えますし、一覧表示やダウンロードなどWevDAVの機能をそのまま使ったので開発期間の短縮にかなり貢献してくれました。

この記事が何か皆さんのお役に立てれば嬉しいです。

明日は @CaL さんです。お楽しみに！

# 参考にしたサイト

https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/patterns/fileuploads.html

https://wsgidav.readthedocs.io/en/latest/

https://chiritsumo-blog.com/pyenv-install-windows/

https://newcss.net/usage/

https://css-tricks.com/no-class-css-frameworks/

https://ffmpeg.org/

http://pydub.com/