Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Flask+Flask-Dropzone+LibROSAで作るオーディオ信号処理基盤

はじめに

NumPyやLibROSA、scikit-learnなどのPythonの素晴らしい信号処理・統計解析モジュールを利用してオーディオ信号処理アプリを作るため、学習コストが低いマイクロフレームワークのFlaskを使いWeb基盤を作ってみました。

実装

ユーザがアップロードした複数のオーディオファイルに対して、LibROSAで処理を行うところまで実装してみました。

今回は、各オーディオファイルを読み込み、時間サンプル数を出力して表示しています。

ファイルのアップロードは、Flask-Dropzoneを利用しています。
examples/in-formを参考にしました。
アップロードされたファイルは、タイムスタンプと乱数文字列で命名したディレクトリに格納するようにしました。

index.html

{% extends "bootstrap/base.html" %}
{% block title %}Wizardcraft.Works{% endblock %}

{% block content %}
    <form action="{{ url_for('handle_form') }}" enctype="multipart/form-data" method="post">
        {{ dropzone.create() }}
        <input type="submit" id="submit" value="Submit and Upload">
    </form>
    {{ dropzone.load_js() }}
    {{ dropzone.config() }}
{% endblock %}

{% block scripts %}
{{ super() }}
{{ dropzone.load_css() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
{% endblock %}

Flaskの主要部(app.py)

# -*- coding: utf-8 -*-
import os
import random
import string
from datetime import datetime

from flask import Flask, render_template
from flask import request, redirect, url_for
from flask import current_app
from flask_dropzone import Dropzone
from flask_bootstrap import Bootstrap
from werkzeug import secure_filename

from signal_proc import signal_proc

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)

app.config.update(
    UPLOADED_PATH=os.path.join(basedir, 'uploads'),
    # Flask-Dropzone config:
    DROPZONE_ALLOWED_FILE_TYPE='audio',
    DROPZONE_MAX_FILE_SIZE=10, # MB
    DROPZONE_IN_FORM=True,
    DROPZONE_UPLOAD_ON_CLICK=True,
    DROPZONE_MAX_FILES=10,
    DROPZONE_PARALLEL_UPLOADS=3,  # set parallel amount
    DROPZONE_UPLOAD_MULTIPLE=True,  # enable upload multiple
    DROPZONE_UPLOAD_BTN_ID='submit',
    DROPZONE_UPLOAD_ACTION='handle_upload',
    #
    UPLOADED_AUDIOFILE_DIRPATH=None,
    UPLOADED_AUDIOFILE_LIST=None,
)
dropzone = Dropzone(app)
bootstrap = Bootstrap(app)


@app.route('/')
def index():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def handle_upload():
    filepath_list = []
    timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S')
    random_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
    dirname = timestamp + "_" + random_str
    dirpath = os.path.join(app.config['UPLOADED_PATH'], dirname)
    current_app.config["UPLOADED_AUDIOFILE_DIRPATH"] = dirpath
    try:
        os.mkdir(dirpath)
    except FileExistsError: 
        pass
    print("upload", dirpath)
    
    for key, f in request.files.items():
        if key.startswith('file'):
            filename = secure_filename(f.filename)
            filepath = os.path.join(dirpath, filename)
            f.save(filepath)
            filepath_list.append(filepath)
    current_app.config["UPLOADED_AUDIOFILE_LIST"] = filepath_list
    print("upload", current_app.config["UPLOADED_AUDIOFILE_LIST"])
    return '', 204

@app.route('/form', methods=['POST'])
def handle_form():
    filepath_list = current_app.config["UPLOADED_AUDIOFILE_LIST"]
    print("form", filepath_list)
    info_list = signal_proc(filepath_list)
    html = '<br>'.join([info for info in info_list])
    return html

if __name__ == '__main__':
    app.run("127.0.0.1", 8080, debug=True)

オーディオ信号処理モジュール

import librosa

def signal_proc(filepath_list, sr=44100, mono=True):
    info_list = []
    for filepath in filepath_list:
        y, sr = librosa.core.load(filepath, sr=sr, mono=mono)
        info_list.append(str(len(y)))
    return info_list

最後に

実際に多くの人に利用される実用的なアプリを管理運用をする場合、Flaskのサーバーでは厳しいので Nginx+uWSGI+Flaskのような環境構築を行うといいようです。

qiita.com