PythonとPydubとSqlite3を用いてデータベースに音データを格納しデータベースから音データを復元してみた

こんにちは。

wavファイルやmp3ファイルといったオーディオファイルをデータベースに格納したいと考えました。
また、データベースで格納されたオーディオ情報を取り出して、オーディオファイルに復元したいと思いました。

今回のブログでは、『**`Pythonによってオーディオファイルからオーディオ情報を作成し、SQLを用いてデータベースに格納する`**』、『**`データベースからSQLを用いてオーディオ情報を取り出し、それをオーディオファイルに復元する`**』、この二点について述べていきます。

この記事で得られるサブ情報は以下の通りです。
>**・Pythonスクリプト上でSqlite3を用いたデータベースの接続方法
・Pythonスクリプト上でSqlite3を用いたテーブルの操作
・バイナリデータをBLOB型として直接データベースに格納する方法
・Pydubを用いたオーディオ情報の取得方法、オーディオファイルの作成方法**

それでは早速手順を追っていきましょう。

#0. 作成したスクリプトの概要
今回作成したスクリプトの概要は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1728508/fbff82cc-eacf-504e-b9ef-60a74de0e645.png)

`audio_to_db.py`では、オーディオファイルをオーディオ情報に変換したのちに、オーディオ情報をデータベースに格納しています。
`audio_to_db.py`の解説を[セクション1](#1-オーディオファイルからオーディオ情報を作成しデータベースに格納する)で行っています。

また、`db_to_audio.py`では、データベースからオーディオ情報を取り出し、オーディオファイルに復元しています。
`db_to_audio.py`の解説を[セクション2](#2-データベースからオーディオ情報を取り出しそれをオーディオファイルに復元する)で行っています。

オーディオファイルとオーディオ情報のやり取りを`Pydub`モジュールを用いて行いました。
また、データベースとのやりとりを`Sqlite3`モジュールを用いて行いました。


#1. オーディオファイルからオーディオ情報を作成し、データベースに格納する
オーディオファイルをデータベースに格納していきます。
データベース格納までの手順としては、次のような概要になります。


>[1.1. オーディオファイルをPydubモジュールを用いてオーディオ情報に変換](#11-オーディオファイルをpydubモジュールを用いてオーディオ情報に変換)
[1.2. Sqlite3モジュールを用いてテーブルを作成し、オーディオ情報を格納](#12-sqlite3モジュールを用いてテーブルを作成しオーディオ情報を格納)


オーディオファイルを変換するのにPythonのPydubモジュールを用います。
それに合わせて、今回はPythonスクリプトでSQLを記述することで、ファイルの変換からデータベース格納の流れをPythonスクリプト一個でまとめたいと思います。
PythonでSQLを記述するにはSqlite3モジュールを用います。

##1.0. 全体のソースコード
オーディオファイルをデータベースに格納するソースコードは以下の通りです。

```py:audio_to_db.py
import sqlite3
from contextlib import closing
from pydub import AudioSegment
import re
import sys

filepath = sys.argv[1] #データベースに格納するオーディオファイルのパスをコマンドライン引数で指定
extension = re.sub(r'.*[.]', '', filepath) #オーディオファイルの拡張子

#AudioSegmentオブジェクトとしてオーディオファイルを読み込む
#拡張子によって読み込み方法を指定(WAV, MP3)
audio = AudioSegment.from_wav(filepath) if extension == 'wav'\
    else AudioSegment.from_mp3(filepath)

#オーディオ情報を取得
#バイナリデータ, ビット数(16bit->2, 24bit->3, ...)
#サンプリング周波数(44100Hzになりがち), チャネル(1ch, 2ch)
audio_data = AudioSegment.get_array_of_samples(audio)
audio_sample_width = audio.sample_width
audio_frame_rate = audio.frame_rate
audio_channels = audio.channels

# print(type(audio_data), audio_sample_width, audio_frame_rate, audio_channels)

#データベースと接続
dbname = 'sample_audio.db' #DB名
with closing(sqlite3.connect(dbname)) as connection: #closing書いておくといいとのこと。
    cursor = connection.cursor() #データベースとの接続で、作業スペースcursorを確保
    
    #audioテーブルが無い場合に新たに作成する。
    #オーディオ情報のバイナリデータdataはblob型で格納する。
    sql = '''
        CREATE TABLE IF NOT EXISTS audio (id integer primary key autoincrement, filepath text, data blob, sample_width integer, frame_rate integer, channels integer)
    '''
    cursor.execute(sql) #sql構文をcursorに反映

    #audioテーブルにオーディオ情報のレコードを追加する。
    #sql内で(?,?,?,?,?)とすることで、cursor.execute()の第二引数が?の変数になる。
    sql = '''
        INSERT INTO audio (filepath, data, sample_width, frame_rate, channels) values (?,?,?,?,?)
    '''
    data = [filepath, audio_data, audio_sample_width, audio_frame_rate, audio_channels]
    cursor.execute(sql, data)

    #データベースに変更を保存->接続を閉じる。
    connection.commit()
    connection.close()
```

以降では、ソースコードの詳細について解説していきます。

##1.1. オーディオファイルをPydubモジュールを用いてオーディオ情報に変換
ソースコードを元に、ここではオーディオファイルをオーディオ情報に変換する方法について、詳細を記述していきます。

###1.1.1. Pydubモジュールのインストール
まずは、必要となるモジュールをインストールしましょう。
オーディオファイルをオーディオ情報に変換するのに、今回はPydubモジュールをインストールします。
同時に、Pydubモジュールを用いるために必要な、ffmpegをインストールします。

```shell
#ffmpegをインストール
apt install ffmpeg

#Pydubをインストール
pip3 install pydub
```

###1.1.2. PydubでオーディオファイルからAudioSegmentオブジェクトを作成
Pydubモジュールを用いて、オーディオファイルをオーディオ情報に変換してみます。

```py
filepath = sys.argv[1]
extension = re.sub(r'.*[.]', '', filepath)
audio = AudioSegment.from_wav(filepath) if extension == 'wav'\
    else AudioSegment.from_mp3(filepath)
```

AudioSegmentオブジェクトとしてオーディオ情報を格納します。
`audio = AudioSegment.from_wav(filepath)`では、WAVファイル`filepath`からオーディオ形成に必要となる情報を格納します。
MP3ファイルの場合は`from_mp3(filepath)`となります。


###1.1.3. AudioSegmentオブジェクトからオーディオ情報を取得
次に、AudioSegmentからオーディオ情報を取り出しましょう。

```py
audio_data = AudioSegment.get_array_of_samples(audio)
audio_sample_width = audio.sample_width
audio_frame_rate = audio.frame_rate
audio_channels = audio.channels
```

AudioSegmentから取り出すことができるオーディオ情報は以下の通りです。

>>
**data: オーディオのバイナリデータ
sample_width: ビット数, 2->16bit, 3->24bit,...
frame_rate: サンプリング周波数
channels: チャネル, ステレオ or モノラル**

オーディオのバイナリデータは、`AudioSegment.get_array_of_samples(audio)`で入手できます。
これらの4つの情報は、データベースからオーディオファイルを復元する際にも、必要となる情報になります。

##1.2. Sqlite3モジュールを用いてテーブルを作成し、オーディオ情報を格納
オーディオ情報を格納するためのデータベースおよびテーブルを作成し、オーディオファイルを格納していきます。

###1.2.1. Sqlite3モジュールのインストール
まずは、データベースに接続するためにSQL構文を使いたいので、今回はSqlite3モジュールをインストールします。

```shelll
#Sqlite3をインストール
pip3 install sqlite3
```

###1.2.2. Sqlite3を用いてデータベースと接続する方法
次に、Sqlite3モジュールを用いて、オーディオ情報を格納するデータベースと接続してみます。

```py
dbname = 'sample_audio.db' #DB名
with closing(sqlite3.connect(dbname)) as connection:
    cursor = connection.cursor()
    sql = '''
        SQLを記述
    '''
    cursor.execute(sql)
    sql = ''' #複数SQL構文がある場合は、cursor.execute()を繰り返す。
        SQLを記述
    '''
    cursor.execute(sql)

    connection.commit()
    connection.close()
```

`with closing(sqlite3.connect(dbname)) as connection`とすることで、データベースと接続します。
dbnameというデータベースのパスがない場合は、自動生成してくれるようです。

次に、データベースとの接続connectionを作成した後に、`connection.cursor()`でcursorオブジェクトを作成します。
このcursorオブジェクトでSQLの操作を行います。
`cursor.execute(sql)`で、コメントアウトした構文sqlを、cursor上で反映します。


ここで注意ですが、cursorオブジェクト上でsqlを反映させただけでは、データベース上のテーブル本体には反映されません。
`connection.commit()`とすることで、初めてデータベース上のテーブルに反映されます。

最後に`connection.close()`でデータベースとの接続を切ります。

###1.2.2. SQL構文を記述してaudioテーブルを作成
次に、オーディオ情報を格納するためのテーブルを作成するSQL構文の内容を説明します。

```py
sql = '''
        CREATE TABLE IF NOT EXISTS audio (id integer primary key autoincrement, filepath text, data blob, sample_width integer, frame_rate integer, channels integer)
'''
```

`CREATE TABLE IF NOT EXISTS audio`を記述して、audioテーブルがデータベース上に無い場合に作成するようにしています。
そのため、データベースやaudioテーブルを自ら削除しない限り、原則的にはこのSQLはスルーされます。

オーディオ情報を格納するカラムの定義は次になります。
>>
**id: 主キー, INT 
filepath: オーディオファイルの名前, TEXT
data: オーディオのバイナリデータ, BLOB
sample_width: ビット数, INT
frame_rate: サンプリング周波数, INT
channels: チャネル, INT**


また、メディアデータをデータベースに格納するとき、格納方法は二通り存在すると考えます。

>
**・メディアデータのバイナリデータをBLOB型としてデータベースに直接格納する。
・メディアデータのパスをデータベースに格納し、ローカルの指定されたパスにメディアデータを配置する。**


今回のテーブルでは、オーディオのバイナリデータはBLOB型を用いてデータベースの中に直接格納しています。

###1.2.3. SQL構文を記述してaudioテーブルにオーディオ情報を格納
次に、作成したaudioテーブルにオーディオ情報を格納していきます。

```py
sql = '''
        INSERT INTO audio (filepath, data, sample_width, frame_rate, channels) values (?,?,?,?,?)
    '''
data = [filepath, audio_data, audio_sample_width, audio_frame_rate, audio_channels]
cursor.execute(sql, data)
```

`INSERT INTO audio (columns) values (?)`によって、audioテーブルにレコードを挿入することができます。
また、SQLコメントアウトの中に`(?)`と記述することで、`cursor.execute(sql, data)`の第二引数リスト型dataが`(?)`の変数になります。

これによって、audioテーブルにオーディオ情報を格納することができます。

以上によって、**`オーディオファイルからオーディオ情報を作成し、データベースに格納する`**ことが出来ました。

#2. データベースからオーディオ情報を取り出し、それをオーディオファイルに復元する
次にデータベースからオーディオファイルを復元してみましょう。手順としては、次のような概要になります。

>
[2.1. Sqlite3モジュールを用いてデータベースからオーディオ情報を取得](#21-sqlite3モジュールを用いてデータベースからオーディオ情報を取得)
[2.2. Pydubモジュールを用いてオーディオファイルに復元](#22-pydubモジュールを用いてオーディオファイルに復元)

こちらも、Pythonスクリプト一個でまとめていきます。

##2.0. 全体のソースコード
データベースからオーディオファイルに復元するソースコードは以下の通りです。

```py:db_to_audio.py
import sqlite3
from contextlib import closing
from pydub import AudioSegment
import re
import sys

dbname = 'sample_audio.db'

#データベースと接続
with closing(sqlite3.connect(dbname)) as connection:
    cursor = connection.cursor()

    #audioテーブルから、指定したidのオーディオデータのレコードを取り出したい。
    sql = '''
            SELECT id, filepath, data, sample_width, frame_rate, channels FROM audio Where id=(?)
        '''
    cursor.execute(sql, sys.argv[1]) #コマンドライン引数でidを指定し、sql構文をcursorに反映
    output = cursor.fetchone() #取り出した1個のレコードをOUTPUTに格納
    connection.close()

#オーディオ情報を取り出す。
#id, パス, バイナリデータ, ビット数, サンプリングレート, チャネルを各変数に格納
[audio_id, audio_filepath, audio_data, audio_sample_width, audio_frame_rate, audio_channels] = output
print(audio_id, audio_filepath, type(audio_data), audio_sample_width, audio_frame_rate, audio_channels)

#バイナリデータ, ビット数, サンプリングレート, チャネルからAudioSegmentを作成
audio = AudioSegment(audio_data, sample_width=audio_sample_width, frame_rate=audio_frame_rate, channels=audio_channels)

extension = re.sub(r'.*[.]', '', audio_filepath) #ファイルの拡張子
audio.export('result_'+audio_filepath, format=extension) #拡張子を指定してオーディオファイルを出力
```

次の見出しから、ソースコードの主要部分について解説していきます。

##2.1. Sqlite3モジュールを用いてデータベースからオーディオ情報を取得
データベースからオーディオファイルの復元に必要となるオーディオ情報を取得していきます。

```py
sql = '''
            SELECT id, filepath, data, sample_width, frame_rate, channels FROM audio Where id=(?)
        '''
cursor.execute(sql, sys.argv[1])
output = cursor.fetchone()
[audio_id, audio_filepath, audio_data, audio_sample_width, audio_frame_rate, audio_channels] = output
```

`SELECT columns FROM audio where id=(?)`と記述することによって、audioテーブルからidが変数`(?)`と一致するレコードを取得し、cursorオブジェクトに格納します。
`cursor.execute(sql, sys.argv[1])`によって、変数`(?)`をコマンドライン引数で指定できるようにしました。

`cursor.fetchone()`によって、SELECT構文によって取得したオーディオ情報を、cursorオブジェクトから取得することができます。
ちなみに、レコードを複数まとめて取得したい場合、`cursor.fetchall()`によって複数取得することができます。

##2.2. Pydubモジュールを用いてオーディオファイルに復元
データベースから取得したオーディオ情報をオーディオファイルに復元していきます。

```py
audio = AudioSegment(audio_data, sample_width=audio_sample_width, frame_rate=audio_frame_rate, channels=audio_channels)

extension = re.sub(r'.*[.]', '', audio_filepath)
audio.export('result_'+audio_filepath, format=extension)
```

`AudioSegment(data, sample_width, frame_rate, channels)`に、データベースから取得したオーディオ情報を格納して、AudioSegmentオブジェクトを作成します。

最後に、AudioSegmentオブジェクトの`export(path, format)`メソッドを用いて、オーディオファイルを出力します。
formatにはオーディオファイルの拡張子を渡します。

以上によって、**`データベースからSQLを用いてオーディオ情報を取り出し、それをオーディオファイルに復元する`**ことが出来ました。

#3.終わりに・感想
今回のブログでは、『**`Pythonによってオーディオファイルからオーディオ情報を作成し、SQLを用いてデータベースに格納する`**』、『**`データベースからSQLを用いてオーディオ情報を取り出し、それをオーディオファイルに復元する`**』、この二点について手順を述べてきました。

今回は、データベースをローカル上に保存する形となりましたが、AWS等クラウド上にデータベースを格納して、アクセスするのがトレンドなのでしょうね。
また、オーディオのバイナリデータをBLOB型としてデータベースに直接保存しましたが、この方法は多数派なのでしょうか。。。
実務だと、クラウド上だと、メディアデータをどうやって保存するのが現実的でしょうか。。。
さらなる勉強が必要そうです。

こちらのブログが少しでもお役に立つことが出来れば幸いです。
それでは、最後までお読みいただきありがとうございました。