7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

librosaを使ってBPM解析をしたので備忘録

Last updated at Posted at 2019-01-20

##はじめに
librosaを使ってbpm解析をしたので備忘録。
BPM解析のアルゴリズム?は以下のサイトに詳しく書かれています。
C/C++言語で音声ファイルのテンポ解析を行うサンプルプログラム

##やったこと
ディレクトリかファイルのパスを指定して.wavと.mp3のデータのbpmを測定する。

##実行環境
Ubuntu16.04
Python3.5

##使用したライブラリ
・pydub
・librosa
の2つを使用しました。(理由は後述します。)

##実装の流れ
[pydubを使用]
・mp3,wavの両方を同時に扱いたいのでmp3はwavに変換後に一時ディレクトリに保存してファイルの形式を整えます。
(wavの場合はそのまま一時ディレクトリにコピーします。)

[librosaを使用]
・一時ディレクトリ内のwavファイルに対してbpmの解析を行う。

一度wav形式に変換している理由は、librosaでbpmを解析するには(たしか)wav形式しか対応していないためです。
pydubかlibrosaのどちらかでもできると思いますが、個人的に2つ使用したほうが楽だったので併用しています。

##実装
import

import
import sys
import os
import pydub as dub
import shutil
import tempfile as temp
import librosa

tmp directoryを作成する

make_TmpDirectory
#明示的に閉じなくてもガベージコレクションが掃除してくれます
tmp = temp.TemporaryDirectory()

#明示的に閉じたい場合は
#tmp.cleanup()
#を最後に書くといいです

mp3をwavに変換してtmpに保存(wavはtmpにコピー)
保存されているdirectoryのpathとファイル名を引数にとっています。

TransToWav
class TransToWav:

    def __init__(self,dir_path,_path):
        self.path = os.path.join(dir_path,_path)
        self.split_path = os.path.splitext(self.path)
        self.ext = self.split_path[-1]
        self.file_name = _path.split(".")[0]
        self.save_file = tmp.name +"/"+ self.file_name + ".wav"


    def trans_wav(self):
        self.music = dub.AudioSegment.from_mp3(self.path)
        self.music.export(self.save_file,format="wav")

    def save_wav(self):
            if self.ext == ".mp3":
                self.trans_wav()
            elif self.ext == ".wav":
                shutil.copyfile(self.path,self.save_file)
            else:
                pass

指定されたpathがディレクトリかファイルかを判別する
pathはプログラム実行時のコマンドライン引数で受けとる仕様にしています。
先ほどのクラスを実行するハンドラのようなクラスを実装します。
(受け取ったpathがディレクトリなのかファイルなのかを判断するため)

WavSaveTmp
class WavSaveTmp:

    def __init__(self,path):
        self.path = path
        if os.path.isdir(self.path) is True:
            self.dir_path = self.path
            self.file_name = [f for f in os.listdir(self.path) if os.path.isfile(os.path.join(self.path,f))]
        elif os.path.isfile(path) is True:
            self.dir_path = os.path.dirname(self.path)
            self.file_name = os.path.basename(self.path)
        else:
            pass
        
    
    def save_tmp(self):
        if type(self.file_name) is list:
            for i in self.file_name:
                handle_wav = TransToWav(self.path,i)
                handle_wav.save_wav()
        else:
            handle_wav = TransToWav(self.dir_path,self.file_name)
            handle_wav.save_wav()

tmpに保存されたwavに対してbpm解析を行う
bpmの解析自体は

y, sr = librosa.load(path)
bpm =  librosa.beat.tempo(y,sr)

で行うことができます。
実行した際に、タプルを使用してから配列にしてね(かなり適当な訳)みたいな警告が出ますが何回か使用した感じ特に問題はなさそうでした。
おそらくlibrosaの問題だと思います。

bpmを解析するコードを書きます。
曲名をキー値とした辞書に解析したbpmを入れていきます。

BpmAnalyse
class BpmAnalyse:

    def __init__(self):
        self.dir_path = tmp.name
        self.file_names = os.listdir(self.dir_path)
        self.file_path = [self.dir_path + "/" + i for i in self.file_names]
        self.bpm = {}

    def analyse_bpm(self):
        for file_name,path in zip(self.file_names,self.file_path):
            if file_name not in self.bpm:
                self.music, self.sr = librosa.load(path)
                self._bpm =  librosa.beat.tempo(self.music,self.sr)
                self.bpm[file_name] = int(self._bpm[0])
            else:
                continue    
        return self.bpm

最後に、実際に使用したコードの全文を載せます。

コード全文

bpm_analyser.py
import sys
import os
import pydub as dub
from pydub.playback import play
import shutil
import tempfile as temp
import librosa

class TransToWav:

    def __init__(self,dir_path,_path):
        self.path = os.path.join(dir_path,_path)
        self.split_path = os.path.splitext(self.path)
        self.ext = self.split_path[-1]
        self.file_name = _path
        self.save_file = tmp.name +"/"+ self.file_name + ".wav"

    def trans_wav(self):
        self.music = dub.AudioSegment.from_mp3(self.path)
        self.music.export(self.save_file,format="wav")

    def save_wav(self):
            if self.ext == ".mp3":
                self.trans_wav()
            elif self.ext == ".wav":
                shutil.copyfile(self.path,self.save_file)
            else:
                pass

class WavSaveTmp:

    def __init__(self,path):
        self.path = path
        if os.path.isdir(self.path) is True:
            self.dir_path = self.path
            self.file_name = [f for f in os.listdir(self.path) if os.path.isfile(os.path.join(self.path,f))]
        elif os.path.isfile(path) is True:
            self.dir_path = os.path.dirname(self.path)
            self.file_name = os.path.basename(self.path)
        else:
            pass
        
    
    def save_tmp(self):
        if type(self.file_name) is list:
            for i in self.file_name:
                handle_wav = TransToWav(self.path,i)
                handle_wav.save_wav()
        else:
            handle_wav = TransToWav(self.dir_path,self.file_name)
            handle_wav.save_wav()

class play_music:

    def __init__(self):
        self.wav_name = os.listdir(tmp.name)
        self.path = [tmp.name + "/" + i for i in self.wav_name]

    def play(self):
        for i in self.path:
            self.sound = dub.AudioSegment.from_wav(i)
            print(type(self.sound))
            play(self.sound)
        self.play()


class BpmAnalyse:
    def __init__(self):
        self.dir_path = tmp.name
        self.file_names = os.listdir(self.dir_path)
        self.file_path = [self.dir_path + "/" + i for i in self.file_names]
        self.bpm = {}

    def analyse_bpm(self):
        for file_name,path in zip(self.file_names,self.file_path):
            if file_name not in self.bpm:
                self.music, self.sr = librosa.load(path)
                self._bpm =  librosa.beat.tempo(self.music,self.sr)
                self.bpm[file_name] = int(self._bpm[0])
            else:
                continue    
        return self.bpm

#path
path = sys.argv[1]
#make temp directory
tmp = temp.TemporaryDirectory()

#mp3,wav save to temp file
save_ = WavSaveTmp(path)
save_.save_tmp()

#bpm analyse
analyser = BpmAnalyse()
bpm_list = analyser.analyse_bpm()

print(bpm_list)

#clean up temp directory
tmp.cleanup()

##実際に動かしてみる

テストするディレクトリ
mp3の曲が1つ,wavの曲が2つあるディレクトリを用意しました

test@ubuntu:~/Musics$ ls
music_1.wav  music_2.wav  music_3.mp3

実行した結果

test@ubuntu:~/bpm_analyse$ python bpm_analyser.py ../Musics/

{'music_2.wav': 129, 'music_1.wav': 95, 'music_3.wav': 112}

一曲だけ選択してみる

test@ubuntu:~/bpm_analyse$ python bpm_analyser.py ../Musics/test/music_2.wav

{'music_2.wav': 129}

見た感じ上手く動いてそうですね。
music_1.wav の実際のbpmは190なのですが半分のbpmが出てくるのはよくあることなので割り切ります。

警告について
librosaを使うと下の警告が出てきますが、librosaがscipyを使ってfftするときにの配列の持ち方で何か言われているっぽい

/usr/local/lib/python3.5/dist-packages/scipy/fftpack/basic.py:160: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.

##最後に
今回はlibrosaを使ってbpm解析を行いました。

はじめて、このようなアウトプット記事を書きましたが案外楽しかったのでこれからも続けていこうと思います。
あと数ヶ月でプログラムを書き始めて1年経ちますがプログラミングの難しさに悪戦苦闘しています。。
なにか間違っているところやアドバイス等あればコメントで指摘してくだされば嬉しいです。

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?