Cythonで音声ファイルを読み込み、無音部分ごとに分ける

# はじめに
Cython初心者です。備忘録として。

# やりたいこと
特定のディレクトリ内にある複数の音声データ(.wavや.mp3など)を読み込み、それぞれのファイルについて無音部分ごとに分割する。読み込まれた音声データや出力される音声データは別のディレクトリに移動させる。

# 何故Cythonなのか
大きな音声データを扱うための前処理として行う際に、Pythonだと速度が足りないため。

# 実行環境
OS : Windows10
ディレクトリ構成は以下のような感じ。

```
sample
  |- datas_input     # 読み込む音声ファイル
  |- datas_output    # 出力される音声ファイル
  |     |- outputs   # 分割された後の音声ファイルが入る
  |- main.py         # 実行ファイル
  |- sounds.pyx      # Cythonによるモジュール
  |- setup.py        # Cythonコードをコンパイルするためのファイル
```

# 必要なライブラリ・モジュール
事前にpydubをインストールしておく。
参考は[こちら](https://algorithm.joho.info/programming/python/pydub-install/)
また、忘れずに`pip install cython`もしておく。

```python:
from glob import glob
from pydub import AudioSegment
from pydub.silence import split_on_silence
from os.path import basename
import shutil
```

# 処理内容

```python:sounds.pyx
cdef split(data):
    cdef int n = 0
    cdef str file_path = ''
    cdef list data_list = []
    
    sound = AudioSegment.from_file(data)
    chunks = split_on_silence(sound, min_silence_len = 1000, silence_thresh = -50, keep_silence = 100)

    for n, chunk in enumerate(chunks):
        file_path = 'datas_output/outputs/output' + str(n) + '_' + basename(data)
        data_list.append(file_path)
        chunk.export(file_path)
    
    return data_list


cpdef load():
    cdef list sounds = []
    cdef list sound_list = []
    cdef list new_sound_list = []
    cdef str data = ''
    
    sounds = glob('datas_input/*')
    if len(sounds) == 0:
        print('There is no data')
        return None
    
    for data in sounds:
        sound_list.append(shutil.move(data, 'datas_output/'))
    
    for data in sound_list:
        new_sound_list.extend(split(data))
    
    return new_sound_list
```

# 簡単な説明
1. `glob('datas_input/*')`で`datas_input`にある全てのファイル名のリストを一括で取得。
2. `shutil.move(data, 'datas_output/')`で`data`を`datas_output/`に移動。
3. `AudioSegment.from_file(data)`で`data`を読み込み。
4. `split_on_silence(sound, min_silence_len = 1000, silence_thresh = -50, keep_silence = 100)`で`sound`を無音部分ごとにカット。参考は[こちら](https://algorithm.joho.info/programming/python/pydub-split-on-silence/)
5. `chunk.export(file_path)`で`file_path`に出力。

# Cythonを使うための処理
setupのためのファイル。

```python:setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
    cmdclass = {'build_ext' : build_ext},
    ext_modules = [Extension('sounds', ['sounds.pyx'])]
)
```

# 呼び出し側のPythonコード
こちらはCythonで作られたモジュールをimportする側。

```python:main.py
import sounds

def main():
    sound_list = sounds.load()
    if sound_list == None:
        exit()
    for data in sound_list:
        print('[data] : ' + data)

    # ...処理...


if __name__ == '__main__':
    main()
```

# 実行
`main.py`を実行すると、`datas_input`に入っていた音声データが全て`datas_output`に移動する。さらに、そこにsplitされた後の音声データも保存されているはずである。テスト用データは、[こちら](https://pj.ninjal.ac.jp/corpus_center/csj/sample.html)からダウンロードしておくとよい。上手くいけば、以下のような結果が得られる。

```
$ python setup.py build_ext --inplace
$ python main.py
[data] : datas_output/outputs/output0_〇〇.mp3
[data] : datas_output/outputs/output1_〇〇.mp3
[data] : datas_output/outputs/output2_〇〇.mp3
[data] : datas_output/outputs/output0_△△.mp3
                   ・
                   ・
                   ・
```

# エラーが出たら
同じディレクトリ内に`ffmpeg.exe`と`ffmprobe.exe`があるかどうかチェックする。それ以外の場合はエラーメッセージをググる。