Pythonで英語CDを分割してみた
蒸し暑くなってきて、もともと少ないやる気がさらに無くなってきたのだけど、
多少は何かしないといけないなぁと思って、なんとなくDUO3.0の例文でも覚えようかと思った。
で、英語CDにありがちな1トラックにいくつかのセンテンスが入っているのが鬱陶しいので、
とりあえず分割ファイルをつくろうと思ってスクリプトを書いてみた。
やり方はいたって単純でレベルの低い部分がしばらく続いたら分割する。
こんな感じでやった
- iTunesでwavエンコーダを使ってCDからwavファイルを作る。
- 設定ファイルを作る
- 実行
設定ファイルはYAMLで書く。
src_path と dst_path は必須で、それ以外はオプション。
src_path: wavファイルのあるフォルダ dst_path: 分割したファイルを出力するフォルダ params: 分割するためのパラメータ profiles: 分割するための情報
paramsの説明
分割するためのパラメータをPythonのタプルの書式で書く。
- 無音の音量レベル(0-32768)
- 無音の継続時間(秒) … これより短い場合は無音部分とみなされない
- 音声の継続時間(秒) … これより短い場合は捨てる
params: (75, 0.50, 1.0)
profilesの説明
各トラック毎の音声開始時間(秒)と終了時間(秒)をタプルの書式で書く。
profilesが無ければ、paramsの設定でprofilesを作成する。(あらかじめどの区間で区切るかが分かっているときに使う)
profiles: - - ( 3.244, 7.142) # 1 - ( 7.142, 12.462) # 2 - ( 12.462, 20.028) # 3 - ( 20.028, 26.871) # 4 - ( 26.871, 31.658) # 5 - ( 31.658, 37.434) # 6 - ( 37.434, 44.912) # 7 - ( 45.150, 49.823) # 8 - ( 49.823, 57.293) # 9
paramsだけだと細かくなりすぎてしまう。
妥協すまいとトラックごとにパラメータ調整できるようにとか色々やってみたけど、
半日くらいかけてバシっとキマる設定を探すのは諦めて、profilesを指定できる方法にしてみた。
今はDUO3.0用の profiles を作っています。まだ英文は覚えていない。本末転倒。
ソースコード
#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import math import wave import yaml def bytes_to_shorts(lst): it = iter(lst) while True: a = 0x00ff & ord(it.next()) b = 0x00ff & ord(it.next()) x = ((b << 8 ) + a) if x & 0x8000: x = -(0x10000 - x) yield x def split_2ch(it): while True: l = it.next() r = it.next() yield l, r def pp(id, begin, end): print ' - (%7.3f, %7.3f) # %2d' % (begin, end, id) def make_profile(filepath, silent_lv=150, min_silent_interval=0.5, min_voice_interval=2.0): src = wave.open(filepath, 'rb') params = src.getparams() nchannles, sampwidth, framerate = params[:3] def iterator(src, buf_size = 1024): read_bytes = buf_size * (nchannles * sampwidth) while True: buf = src.readframes(buf_size) for l, r in split_2ch(bytes_to_shorts(buf)): yield (l + r) / 2 if len(buf) != read_bytes: break print 'make_profile' id = 0 result = [] beginOfVoice = beginOfSilent = endOfSilent = None for i, x in enumerate(iterator(src)): if abs(x) <= silent_lv: if beginOfSilent is None: beginOfSilent = i else: endOfSilent = i else: if beginOfVoice is None: beginOfVoice = i else: if beginOfSilent is not None and endOfSilent is not None: silent_interval = (endOfSilent - beginOfSilent) / float(framerate) if silent_interval >= min_silent_interval: voice_interval = (beginOfSilent - beginOfVoice) / float(framerate) if voice_interval >= min_voice_interval: begin = beginOfVoice / float(framerate) end = endOfSilent / float(framerate) result.append((begin, end)) pp(id, begin, end) id += 1 beginOfVoice = None else: beginOfVoice = None beginOfSilent = endOfSilent = None else: if beginOfSilent is not None and endOfSilent is not None: silent_interval = (endOfSilent - beginOfSilent) / float(framerate) if silent_interval >= min_silent_interval: voice_interval = (beginOfSilent - beginOfVoice) / float(framerate) if voice_interval >= min_voice_interval: begin = beginOfVoice / float(framerate) end = endOfSilent / float(framerate) result.append((begin, end)) pp(id, begin, end) src.close() return result def write_waves(filepath, profile, dst_path): src = wave.open(filepath, 'rb') params = src.getparams() nchannles, sampwidth, framerate = params[:3] result = [] for i, (begin, end) in enumerate(profile): beginOfVoice = int(math.ceil(begin * float(framerate))) endOfVoice = int(math.ceil(end * float(framerate))) src.setpos(beginOfVoice) dat = src.readframes(endOfVoice - beginOfVoice) filename = os.path.basename(filepath) root, ext = os.path.splitext(filename) filename = '%s_%02d%s' % (root, i, ext) filepath2 = os.path.join(dst_path, filename) dst = wave.open(filepath2, 'wb') dst.setparams(src.getparams()) dst.writeframes(dat) dst.close() result.append((filepath2, begin, end)) src.close() return result def main(argc, argv): for f in argv[1:]: settings = yaml.load(open(f)) src_path = settings['src_path'] dst_path = settings['dst_path'] targets = settings.get('targets') if not isinstance(targets, list): targets = None params = settings.get('params') if params: params = eval(params) else: params = (100, 0.50, 2.0) profiles = settings.get('profiles') file_no = 0 for filename in os.listdir(src_path): if not filename.endswith('wav'): continue file_no += 1 if targets and filename not in targets: continue filepath = os.path.join(src_path, filename) print '%s' % filepath if profiles and file_no <= len(profiles): profile = [ eval(x) for x in profiles[file_no - 1] ] else: profile = make_profile(filepath, *params) file_list = write_waves(filepath, profile, dst_path) for output, begin, end in file_list: print ' %10s: %7.3f, %7.3f' % (output, begin, end) if __name__ == '__main__': main(len(sys.argv), sys.argv)