はじめに
録画映像の倍速処理に伴い,音声も再生速度を速くすると,音が高くなってしまう.これを防ぐには,FFT を介してピッチを維持したままテンポを変える必要がある.音声処理ソフトではこのような機能が用意されており,そのようなソフトの一つが Audacity である.
Audacity では,定型処理を自動化するためのスクリプティング機能が用意されているので,これを使って AviUtl ExEdit2 のプラグインで橋渡ししたいところであるが,AviUtl の API には再生時間を変化させる機能は用意されていない.仕方ないので,ファイル出力を介してではあるが,可能な範囲で自動化してみよう.
前提条件
AviUtl ExEdit2 では,WAV ファイル出力が用意されていないので,出力プラグイン を使う.
Audacity はデフォルトでオフの mod-script-pipe を Enable にして再起動しておく.
Python および watchdog をインストールしておく.
スクリプト
import math
class AudacityPipe:
def __init__(self):
try:
self.tofile = open(r'\\.\pipe\ToSrvPipe', 'w')
self.fromfile = open(r'\\.\pipe\FromSrvPipe', 'rt')
self.eol = '\r\n\0'
except FileNotFoundError:
self.tofile = None
self.fromfile = None
raise RuntimeError("Audacity の名前付きパイプが見つかりませんでした.mod-script-pipe を Enable にした Audacity が起動していることを確認してください.")
def __del__(self):
if self.tofile is not None:
self.tofile.close()
self.fromfile.close()
def send_command(self, command):
self.tofile.write(command+self.eol)
self.tofile.flush()
def get_response(self):
result = ''
line = ''
while True:
result += line
line = self.fromfile.readline()
if line == '\n' and len(result) > 0:
break
return result
def do_command(self, command):
self.send_command(command)
return self.get_response()
# ここより上は Audacity のスクリプト例から,下は自分で必要なものを書いた
def open(self, file):
self.do_command('Import2: Filename="' + file + '"')
def write(self, file):
self.do_command('Export2: Filename="' + file + '" NumChannels=2')
def close(self):
self.do_command('TrackClose:')
# ピッチを維持して rate 倍速.16 < rate なら再帰処理
def change_tempo(self, rate):
if 16 < rate:
r = 1<<math.floor(math.log2(rate)*0.5)
self.change_tempo(rate/r)
self.change_tempo(r)
else:
self.do_command(f'ChangeTempo: Percentage={(rate-1.0)*100.0} SBSMS=True')
def treble(self, db):
self.do_command(f'BassAndTreble: Treble={db}')
import os
import time
import re
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import audacitypipe
class WavHandler(FileSystemEventHandler):
def __init__(self):
super().__init__()
self.last_processed = ''
self.ap = audacitypipe.AudacityPipe()
# 書き込みが完了するまで待機
def wait_for_written(self, path):
s = -1
try:
s2 = os.path.getsize(path)
except FileNotFoundError:
time.sleep(1)
s2 = os.path.getsize(path)
while s != s2:
s = s2
time.sleep(1)
s2 = os.path.getsize(path)
def process(self, name, rate):
path = os.path.abspath(f'{name}_{rate}.wav')
self.wait_for_written(path)
self.ap.open(path)
self.ap.change_tempo(rate)
self.ap.treble(-5)
path = os.path.abspath(f'{name}.wav')
self.ap.write(path)
self.ap.close()
def on_created(self, event):
if self.last_processed == event.src_path:
# 書き込み完了後にも on_created が発火するようなので,
# 直前のトリガーパスと同じならスキップ
return
self.last_processed = event.src_path
m = re.compile(r'^(.*?)_(\d*)\.wav$').match(event.src_path)
if m:
# name_n.wav を n 倍速して name.wav に書き出し
print(f'New file detected: {event.src_path}')
self.process(m.group(1), int(m.group(2)))
else:
# 倍速処理後のファイル名には _ が含まれないので,こちらを通る
print(f'New file detected, but ignored: {event.src_path}')
if __name__ == '__main__':
path = './wav' # 監視フォルダ兼書き出し先
event_handler = WavHandler()
observer = Observer()
observer.schedule(event_handler, path, recursive=False)
observer.start()
print(f'Watching folder: {path}...')
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
以上の2ファイルを用意し,python script.py を実行した状態で,AviUtl ExEdit2 から WAVファイル出力プラグインで ./wav に name_n.wav という名前(nは整数)で出力すると,同フォルダに n 倍速された音声が name.wav という名前で出力される.テンポチェンジでは音は高くならないのだが,少しキンキンする感じがするので,トレブルで高音域を -5 dB している.具体的な処理手順等は好みで変えてもいいだろう.
出力先を調整して,かんしくん を併用すれば,出力した音声ファイルをタイムラインに投げ返すこともできるだろう.
おわりに
今回は,Audacity と watchdog を使って,WAV ファイル出力に反応して,自動的にテンポチェンジをするスクリプト例を示した.条件に応じてより堅牢な書き方をしたほうがいい場合もあるかもしれない.処理内容もあくまで例に過ぎないが,自分好みの処理を書く際の参考にはなるだろう.