SoXで音声感知録音:その3 自動録音環境構築まとめ

前の関連記事:auto_rec_rename.jsをバッチファイルに埋め込む


ほぼツールが揃ったのでまとめておきます。WindowsXPにSox14.4.2のインストールとMP3が扱えるようにした状態で行います。(SoX(Sound eXchange)で音声感知録音:その1SoX(Sound eXchange)でMP3を扱う参照。)
(2015.8.9追記。リアルタイム処理が不要ならSoXで音声感知録音:その5 話し声を準リアルタイム録音のvadエフェクトを使う方法のがよいと思います。)

自動音声感知録音のためのファイル構成


これらについて以下解説します。
.
│  bgd_check.bat    バックグラウンドレベルの測定を行うバッチファイル
│  recy_emp.bat         ゴミ箱を空にするバッチファイル
│  settings.bat     変数設定用のバッチファイル
│  sht_f_del.bat        ファイル名を区間表示に変更してさらに断片的なファイルの削除を行うバッチファイル。
│  sox_killrestart.bat  ロック画面中にsox_rec.batを再起動するバッチファイル。
│  sox_rec.bat          音声感知録音条件でsox.exeを起動するバッチファイル。
│  sox_restart.bat      ロック画面以外のときにsox_rec.batを再起動するバッチファイル。
│  sox_start.bat      コマンドウィンドウのタイトルを設定してsox_rec.batを起動するバッチファイル。
│
└─sox_data  音声感知で録音したデータファイルの保存するフォルダ。
    │  20150417_010003_300_274.mp3
    │  20150418_185254_300_001.mp3
    │
    ├─extracted  renamedフォルダのファイルうち断片的なファイルを取り除いたファイル。
    │      20150417_2255-20150417_2300.mp3
    │      20150417_2315-20150417_2320.mp3
    │      20150417_2325-20150417_2330.mp3
    │      20150417_2335-20150417_2340.mp3
    │
    └─renamed  sht_f_del.batで区間表示にファイル名を変更したファイルを入れるフォルダ。
SoXで音声感知録音:その3 自動録音環境構築まとめ - p--qのsox_bat_files.zipにこれらをまとめたzipファイルがあります。

recy_emp.batのrdコマンドはよくわからずに使ってしまわないようにコメントアウトしてあります。

上記以外にsox.exeを強制終了させるだけのsox_kill.bat、WindowsXPを休止スタンバイさせるstanby.batが含まれています。

変数設定用のバッチファイルsettings.batを作成する


バッチファイルごとに変数を設定を書くのは煩雑なので変数設定用のバッチファイルを作って、それぞれのバッチファイルからそれを読み込むことにします。
rem データディレクトリに残すファイル数。記録中に処理させる場合は1以上にする。
set RF=1

rem 音声ファイル形式の設定(拡張子で判別)。
set EXT=mp3

rem ディレクトリ名の設定。スクリプトファイルのあるディレクトリの下層に作られる。
rem データディレクトリ名。
set DATA_DIR=sox_data
rem 改名後のデータをいれるディレクトリ名。
set RENAMED_DIR=renamed
rem 処理後のファイルの移動先ディレクトリ名。
set EXTRACTED_DIR=extracted

rem 記録しない音声ファイルの長さ(秒未満)。
set SEC=3

rem 自動録音開始終了条件のdBの絶対値の設定(-dB)。
set RMS_Pk_dB=48
rem 自動録音開始条件の持続時間の設定(時:分:秒)。
set STRT=00:00:00.05
rem 自動録音終了条件の持続時間の設定(時:分:秒)。
set END=00:00:01.5
rem 1ファイル当たりの録音区間の秒数の設定(秒)。
set DUR=300

rem sox.exeへのパスの設定。環境変数PATHに設定しているときは不要。
rem Windows7 64bitの場合。
path %PATH%;C:\Program Files (x86)\sox-14-4-2
rem WindowsXPの場合。
path %PATH%;C:\Program Files\sox-14-4-2
rem sox.exeにパスが通っているかの確認。
set T=0
for /F "delims= " %%t in ('sox --version') do set T=%%t
if %T%==0 (
  cls
  echo sox.exeが見つかりません。
  echo.
  pause
  exit
)

rem SoXの既定の音声デバイスの設定。
set AUDIODRIVER=waveaudio

rem このスクリプトがあるディレクトリ。
set SD=%CD%
これでこのsettings.batを各バッチファイルからcall settings.batで読み込みます。

こうすることで設定値を変更したいときはこのsettings.batのみを編集すればよいことになります。

実際に存在しないフォルダを環境変数PATHに追加しても支障がないようですので29行目と31行目のパスの設定はWindowsXPの場合とWindows7 64bitの場合の両方とも行っています。

sox.exeへのパスがない場合sht_f_del.batですべての音声ファイルを削除してしまうので34行目で、sox --version の出力を受け取って、soxコマンドが使えるかどうかの確認をしています。

変数Tにスペースが入ると次のif文の条件式でエラーがでるので半角スペースまでを変数Tに入れています。

これでWindowsXP(とおそらくWindows7 32bit)でもWindows7 64bitでもsox14.4.2がインストールされていればPATHの設定は不要になります。

バックグラウンドレベルの測定を行うバッチファイルbgd_check.bat

@echo off
call settings.bat
echo 10秒間のバックグラウンドレベルの測定
sox -c 1 -d -n trim 0 10 stats
echo RMS Pk dBを自動録音開始条件の参考にする。
pause
静音と思われる状態を10秒間維持してこのbgd.batの結果を得たとします。

10秒間のバックグラウンドレベルの測定

Input File     : 'default' (waveaudio)
Channels       : 1
Sample Rate    : 48000
Precision      : 16-bit
Sample Encoding: 16-bit Signed Integer PCM

In:0.00% 00:00:10.07 [00:00:00.00] Out:480k  [      |      ]        Clip:0
DC offset  -0.000017
Min level  -0.510559
Max level   0.098236
Pk lev dB      -5.84
RMS lev dB    -27.73
RMS Pk dB     -21.92
RMS Tr dB     -43.12
Crest factor   12.43
Flat factor     0.00
Pk count           2
Bit-depth      16/16
Num samples     480k
Length s      10.000
Scale max   1.000000
Window s       0.050
Done.
RMS Pk dBを自動録音開始条件の参考にする。
続行するには何かキーを押してください . . .

小学生でも分かるデシベル(dB)の話によると6dBの差で音の大きさが2倍になるそうです。

RMS Pk dBの値が-27.73なので、自動録音開始条件はこれより少し大きな値、-22dBにしてみてもよいかもしれませんが、これについては試行錯誤で決定するしかありません。

パソコン内蔵のマイクだと録音レベルを低くできない上に雑音が多くて苦戦します。

この値を参考にsettings.batの変数RMS_Pk_dBの値を決定します。(バッチファイルの変数は負でない整数のみ扱えるので単位は-dBになります。)

最初はこのstatsエフェクトの結果のRMS Pk dBの値を取得するバッチファイルを作ろうと思ったのですが、このstatsの結果をどうしても取得できずに断念しました。

linuxではエラー出力から取得できるようなのですが、Windowsでは2>log.txtとしてもだめでした。

音声感知録音(音声感知録音)をするバッチファイルsox_rec.bat

@echo off
rem 自動開始録音。
call settings.bat
if not exist "%SD%\%DATA_DIR%" (mkdir "%SD%\%DATA_DIR%")
cd %SD%\%DATA_DIR%
echo Ctrl+Cで終了します。
for /F "tokens=1,2 delims=." %%x in ('^(for /F "tokens=1,2,3 delims=:" %%p in ^('echo %DUR%.%date:/=%_%time: =0%'^) do @echo %%p%%q%%r^)') do sox -c 1 -d -p | sox -p -C -8.2 "%%y_%%x_.%EXT%" trim 0 %%x silence -l 1 %STRT% -%RMS_Pk_dB%d -1 %END% -%RMS_Pk_dB%d : newfile : restart
このsox_rec.batを実行するとsettings.batで設定した条件で音声感知録音が開始されます。

録音した音声データはsettings.batで設定した「データディレクトリ名」のサブフォルダに保存されます。

linuxBean12.04の場合(もちろんこのバッチファイルは使えません。)は、出力データ量(音声ファイルのデータ量ではなく入力された音声データ量)が507Mぐらいから sox WARN alsa: over-run というエラーがでてきて録音ができなくなりました。

WindowsXPでは6.3GBになってもエラーはでませんでした。

しかしこれはOSの問題ではなくハードウェアの問題かもしれません。

このsox_rec.batではsettings.batで設定した一定時間間隔ごとに新たな連番ファイルを作ることになっていますが、その連番が尽きたときはどうなるかは未確認です。

長時間録音するときは連番が尽きる前に再起動したほうが賢明と思います。

sox_rec.batを再起動するバッチファイルsox_start.batとsox_killrestart.bat


前記の理由でときどきsox_rec.batを再起動してあげないといけませんので、そのバッチファイルを作ります。

起動するのは単にsox_rec.batをcallするだけなので簡単なのですが、コマンドウィンドウで動いているsox_rec.batを終了させるのは簡単にはいきません。

最初にSendKeys メソッドを使う方法でうまくいったと思いました。

sox_rec.batが動くコマンドウィンドウにタイトルをつけておいて、そのタイトルをもとにSendKeys メソッドで手動で終了させるのと全く同じキー入力を送って終了させます。

まずはsox_rec.batを名前をつけたコマンドウィンドウで起動させるバッチファイルsox_start.batを作ります。
@echo off
echo sox_rec.batをタイトルをつけて起動。
start "sox_rec.bat" sox_rec.bat
これでsox_rec.batというタイトルのコマンドウィンドウでsox_rec.batが動きます。
@if(0)==(0) echo off
cscript.exe //nologo /E:JScript "%~f0"
call sox_start.bat
goto :EOF
@end
//SoXを停止させる
var sh = new ActiveXObject("WScript.Shell");
sh.AppActivate("sox_rec.bat");
sh.Sendkeys("^c");
WScript.Sleep(1000);//1ミリ秒では短すぎてだめでした。
sh.Sendkeys("y{ENTER} ");
sh.Sendkeys("exit{ENTER} ");
あとはこのsox_restart.batを実行すればsox_rec.batが動いているコマンドウィンドウが終了され、また新たなコマンドウィンドウがsox_start.batにより起動されます。

これでうまくいったと思ったのですが、このsox_restart.batは画面のロック中には動きませんでした。

SendKeys メソッドは画面のロック中には動かないのでした。
@echo off
rem sox.exeを強制終了して再起動する。
taskkill /f /im sox.exe
call sox_start.bat
SendKeys メソッドの代わりにtaskkillコマンドを使ったsox_killrestart.batを作りました。

このsox_killrestart.batは確実にsox.exeを終了させることはできるのですが、コマンドウィンドウを閉じることができないのが欠点です。

WindowsXPのタスクでsox_killrestart.batなどを定期実行させる


WindowsXPでは、スタート→すべてのプログラム→アクセサリ→システムツール→タスク、でバッチファイルを登録すると定期実行させることができます。


スケジュールされたタスクを追加をダブルクリックするとタスクウィザードが立ち上がります。


定期実行したい実行可能ファイルを選択したら実行間隔を指定します。

分単位の指定もできますが、それはあとで詳細設定から設定しますのでとりあえず「日単位」を選択しておきます。

とりあえず次の実行開始時間なども特にいじらず次に進んで、実行するユーザーのパスワードを入力してタスクウィザードを完了させます。

新しくできたsox_killstart.batのアイコンをダブルクリックしてスケジュールタブを選択します。

「詳細設定」ボタンをクリック。


「タスクを繰り返し実行」をチェックして、このように設定すると10分間間隔で実行されることになります。

通常短時間で定期実行したいのはsox_killstart.batではなくて、auto_rec_rename.jsをバッチファイルに埋め込むで作ったsht_f_del.batになるでしょう。
rem ゴミ箱を空にする
rd /S /Q c:\recycler
ゴミ箱を空にするこのようなバッチファイルrecy_emp.batもたまに動かすとディスク容量が節約できますね。

settings.batを読み込んで利用するバッチファイルsht_f_del.bat


auto_rec_rename.jsをバッチファイルに埋め込むで作ったsht_f_del.batの設定部分をsettings.batを利用して作り直しました。

それぞれのフォルダへのフルパスとその存在確認はDOSコマンド部分でやってしまいました。

そのほうがコマンドが短く済みましたので。でもデバッグはやりにくいです。
@if(0)==(0) echo off
call settings.bat
rem データディレクトリDATA_DIRのフルパス。
set DATA_DIR_P=%SD%\%DATA_DIR%
rem 改名後のデータをいれるディレクトリRENAMED_DIRのフルパス。
set RENAMED_DIR_P=%SD%\%DATA_DIR%\%RENAMED_DIR%
if not exist "%RENAMED_DIR_P%" (mkdir "%RENAMED_DIR_P%")
rem 処理後のファイルの移動先ディレクトリEXTRACTED_DIRのフルパス。
set EXTRACTED_DIR_P=%SD%\%DATA_DIR%\%EXTRACTED_DIR%
if not exist "%EXTRACTED_DIR_P%" (mkdir "%EXTRACTED_DIR_P%")
echo %DATA_DIR%フォルダにある%EXT%ファイルを改名して%RENAMED_DIR%フォルダに移動させています。
for /F %%p in ('cscript.exe //nologo //E:JScript "%~f0"') do set CNT=%%p
set FCNT=1
set DCNT=0
echo %SEC%秒より短い%EXT%ファイルを削除します。
cd "%RENAMED_DIR_P%"
setlocal enabledelayedexpansion
for %%f in ("*.%EXT%") do (
  echo %%fの長さが%SEC%秒未満なら削除します。(!FCNT!/%CNT%^)削除数!DCNT!
  rem sox --iで測定できないときのためにTをリセットする。
  set T=0
  for /F "tokens=1 delims=." %%t in ('sox --i -D %%f') do set T=%%t
  if !T! LEQ %SEC% (
    del /Q %%f
    set /A DCNT=!DCNT!+1
  ) else (move /Y %%f "%EXTRACTED_DIR_P%")
  set /A FCNT=!FCNT!+1
 cls
)
rem なぜか括弧数字付の同名ファイルができるのでそれを削除する。
cd "%EXTRACTED_DIR_P%"
del /Q *(?).%EXT%
endlocal
GOTO :EOF
@end
var sh = new ActiveXObject("WScript.Shell");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var ext = sh.ExpandEnvironmentStrings("%EXT%");//改名対象拡張子。
var data_dir = sh.ExpandEnvironmentStrings("%DATA_DIR_P%");//データディレクトリdata_dirフルパス。
var renamed_dir = sh.ExpandEnvironmentStrings("%RENAMED_DIR_P%");//改名後のデータをいれるディレクトリrenamed_dirのフルパス。
var fldr = fso.GetFolder(data_dir); //データフォルダオブジェクトの取得。
var fc = new Enumerator(fldr.Files); //ファイルコレクションをEnumeratorオブジェクトにして取得。
var f, new_name, j;
var f_arr = [];//処理対象ファイルオブジェクトを格納する配列。
//RegExpオブジェクトを作成。
var re = new RegExp("^(\\d{4})(\\d{2})(\\d{2})_(\\d{2})(\\d{2})(\\d{2})_(\\d+)_(\\d+)\\." + ext + "$");
//ファイルコレクションの各ファイルに対して。
for (; !fc.atEnd() ; fc.moveNext()) {
    f = fc.item(); //ファイルオブジェクトの取得。
    //ファイル名の判別
    if (re.test(f.Name)) {
        f_arr.push(f);//処理対象となるファイルオブジェクトを配列に格納。ファイル名昇順に取得できているはず。
    } else {
        f.Delete();//処理対象とならないファイルは削除。
    }
}
var fn = f_arr.length - sh.ExpandEnvironmentStrings("%RF%")//処理するファイル数。
for (var i = 0; i < fn ; i++) {
    f = f_arr[i];//配列からファイルオブジェクトを取得。
    new_name = new_fname(re.exec(f.Name));//正規表現結果配列からファイル名を取得。
    try {
        j = 0;//「年月日_時分-年月日_時分」を示す要素番号。
        f.Move(renamed_dir + "\\" + new_name[j] + "." + ext);//改名して移動。
    } catch(e) {
        j = 1;//「年月日_時分秒-年月日_時分秒」を示す要素番号。
        f.Move(renamed_dir + "\\" + new_name[j] + "." + ext);//改名して移動。
    } 
}
//renamed_dirフォルダになぜか改名されていないファイルができるときがあるのでそれをdata_dirフォルダへ戻す。
fldr = fso.GetFolder(renamed_dir); //renamed_dirフォルダオブジェクトの取得。
fc = new Enumerator(fldr.Files); //ファイルコレクションをEnumeratorオブジェクトにして取得。
for (; !fc.atEnd() ; fc.moveNext()) {
    f = fc.item(); //ファイルオブジェクトの取得。
    if (re.test(f.Name)) {
        try {
            f.Move(data_dir + "\\" + f.Name);//data_dirフォルダへ戻す。
        } catch(e) {
            f.Delete();//すでにdata_dirフォルダにあるときは削除する。
        } 
        finally{
            fn -= 1;//処理するファイル数を減らす。
        }
    }
} 
WScript.Echo(fn);//バッチファイルに処理対象ファイル数を返す。
//正規表現結果配列から新しいファイル名を返す関数。戻り値は配列[年月日_時分-年月日_時分, 年月日_時分秒-年月日_時分秒]。
function new_fname(r) {
    //ファイル名から記録開始日時のDateオブジェクトを得る。1月を0として開始。
    var start_dt = new Date(r[1], r[2] - 1, r[3], r[4], r[5], r[6]);
    var int_sec = r[7] * 1000; //記録区間長(ミリ秒)
    var int_cnt = r[8]; //記録区間番号
    var start_ms = start_dt.getTime() + int_sec * (int_cnt - 1); //区間開始日時経過ミリ秒
    //経過ミリ秒を数字羅列に変換
    var end_n = date_num(start_ms + int_sec);//区間開始日時。
    var start_n = date_num(start_ms);//区間終了日時。
    var hhmm = start_n[0] + "-" + end_n[0];//新しいファイル名年月日_時分-年月日_時分。
    var hhmmss = start_n[0] + start_n[1] + "-" + end_n[0] + end_n[1];//新しいファイル名年月日_時分秒-年月日_時分秒。
    return [hhmm, hhmmss];
    //経過ミリ秒を年月日の数字羅列に変換する関数。戻り値は配列[年月日_時分, 秒]。
    function date_num(ms) {
        var dt = new Date();//Dateオブジェクトの生成。
        dt.setTime(ms);//経過ミリ秒からDateオブジェクトを取得。
        return [dt.getFullYear() + fm(dt.getMonth() + 1) + fm(dt.getDate()) + "_" + fm(dt.getHours()) + fm(dt.getMinutes()), fm(dt.getSeconds())];
        function fm(m) {
            return ("0" + m).slice(-2);//固定長2桁を返す。
        }
    }
}
(2015.8.7追記。61行目からのエラー処理が間違っていたので訂正しました。)

バッチファイル:JScriptを埋め込んでDOSコマンドと変数をやりとりするでやったExpandEnvironmentStringsメソッドを使ってDOSコマンド部分で作った変数をJScriptに渡しています。

84行目では逆にJScript側から変数をDOSコマンドに向けて渡して12行目で受け取っています。

頭を悩ましたのは18行目のfor文の中の丸括弧の処理です。

auto_rec_rename.jsをバッチファイルに埋め込むで書いたようにfor文のdoの中の入れ子の丸括弧は^でエスケープしなくてもよいのでした。

でもechoの引数の丸閉じ括弧はエスケープが必要です。

多量のファイルを処理するとsox --iが吐き出すエラーで埋まってしまうのと時間が結構かかるので、カウンタ表示もつけました。

このsht_f_del.batをWindowsのタスクで1ファイル当たりの録音区間の秒数より短い間隔で定期実行すれば常に改名されたファイル名が生成されることになります。

運用してみるとなぜかファイル名の最後に「(1)」とついたファイルがいくつかできます。

「(1)」の部分以外は同一のファイル名が必ずあることから、同名ファイルが存在するためにWindowsが勝手に(1)をつけてくれたのだと思います。

同名ファイルを上書きせずに「(1)」と名前を勝手につけてくれるのはありがたいのですが、そもそも同名ファイルができるような処理はしていないはずです。

ハードディスクへの書込のタイミングがずれるときがあってそうなるのかもしれません。

これについては最終結果のディレクトリから問答無用で32行目の del /Q *(?).%EXT% で削除してしまうことにしました。

もう一つよくわからない結果は改名されていないファイルが最終結果のディレクトリに移動してくることです。

関数new_fname()の結果がnullで返ってくるときがあるのかもしれませんがどうも納得いきません。

68行目から83行目はそれらの改名されずにrenamed_dirに移動してきたファイルをまたdata_dirに戻しています。

音声ファイルを結合するバッチファイルsox_concat.bat


このバッチファイルがあるフォルダにあるconcatフォルダにある音声ファイルをすべて結合します。

また各音声ファイルの長さも測定し、さらに結合した音声ファイルの長さもテキストファイルに出力します。
@echo off
call settings.bat
rem 結合する音声ファイルがあるディレクトリ名
set CONCAT_DIR=concat
rem 結合後のファイル名
set CONCAT_F=concatenated
rem 各ファイルの長さを記録するファイル名
set OUT_F=length.txt
rem 結合後のファイルを入れるディレクトリ名
set CONCATENATED_DIR=%CONCAT_F%
rem 結合する音声ファイルがあるディレクトリCONCAT_DIRのフルパス。
set CONCAT_DIR_P=%SD%\%CONCAT_DIR%
if not exist "%CONCAT_DIR_P%" (
  mkdir "%CONCAT_DIR_P%"
  echo %CONCAT_DIR_P%に結合したい音声ファイルを入れてください。
  echo.
  pause
  exit
)
rem ディレクトリCONCATENATED_DIRのフルパス。
set CONCATENATED_DIR_P=%CONCAT_DIR_P%\%CONCATENATED_DIR%
rd /S /Q "%CONCATENATED_DIR_P%"
mkdir "%CONCATENATED_DIR_P%"
rem CONCAT_Fのフルパス。
set CONCAT_F_P=%CONCATENATED_DIR_P%\%CONCAT_F%.%EXT%
rem 結合一時wavファイルのフルパス
set CONCAT_F_wav=%CONCATENATED_DIR_P%\%CONCAT_F%.wav
set OUT_F=%CONCATENATED_DIR_P%\length.txt
cd "%CONCAT_DIR_P%"
rem 音声ファイルを結合する。
echo %CONCAT_DIR%フォルダにある%EXT%ファイルをすべて結合します。
set FLG=1
set FCNT=1
set CNT=0
echo 空欄の場合は0秒以下のファイルです。 > "%OUT_F%"
setlocal enabledelayedexpansion
rem 音声ファイル数合計を調べる。
for %%f in ("*.%EXT%") do (set /A CNT=!CNT!+1)
for %%f in ("*.%EXT%") do (
  rem 各ファイルの長さを出力する
  for /F "tokens=1 delims=" %%t in ('sox --i -d %%f') do echo %%f %%t >> "%OUT_F%"
  rem sox --iで測定できないときのためにTをリセットする。
  set T=0
  for /F "tokens=1 delims=." %%t in ('sox --i -D %%f') do set T=%%t
  rem 0秒より大きいファイルのみ結合する。
  if !T! GTR 0 (
    if !FLG!==1 (
      rem 結合する最初のファイルを取得。
      copy /Y %%f "%CONCAT_F_P%"
      sox "%CONCAT_F_P%" "%CONCAT_F_wav%"
      set FLG=0
    ) else (
      set /A FCNT=!FCNT!+1
      cls
      echo %%fを結合中(!FCNT!/!CNT!^)
      sox %%f "%CONCAT_F_wav%" "%CONCAT_F_wav%"
    )
  )
)
cls
echo 結合したファイルを%EXT%に変更中。
sox "%CONCAT_F_wav%" "%CONCAT_F_P%"
del /Q "%CONCAT_F_wav%"
cls
echo 結合ファイルの長さ >> %OUT_F%
sox --i -d %CONCAT_F_P% >> %OUT_F%
echo !FCNT!個のファイルを%CONCAT_F%.%EXT%に結合しました。
echo 結合ファイル%CONCAT_F%.%EXT%の長さ
sox --i -d %CONCAT_F_P%
echo 各%EXT%ファイルの長さは%OUT_F%に出力しました。
echo.
echo 何かキーを押すと結合ファイルがあるフォルダを開きます。
pause
endlocal
explorer "%CONCATENATED_DIR_P%"
複数ある音声ファイルの合計時間を知りたかったので作ったバッチファイルなのですがものすごく時間がかかります。

時間がかかるのは56行目で音声ファイルを結合する部分です。

mp3から展開するだけで時間がかかるのでファイルサイズには目をつぶって結合ファイルはwavファイルにして最後にmp3ファイルに変換するようにしました。

それでもすごく時間がかかります。

単に合計時間が知りたいだけならsox --i -D の結果を合計したほうがよいですね。

複数の音声ファイルの合計時間を計算するバッチファイルsox_total_length.bat


ということで各音声ファイルの秒数をsox --i -Dで得てその合計を有効桁数小数点以下3桁で出力するバッチファイルを作りました。
@echo off
call settings.bat
rem 測定する音声ファイルがあるディレクトリ名
set CONCAT_DIR=concat
rem 各ファイルの長さを記録するファイル名
set OUT_F=length.txt
rem 測定する音声ファイルがあるディレクトリCONCAT_DIRのフルパス。
set CONCAT_DIR_P=%SD%\%CONCAT_DIR%
if not exist "%CONCAT_DIR_P%" (
  mkdir "%CONCAT_DIR_P%"
  echo %CONCAT_DIR_P%に長さを測定したい音声ファイルを入れてください。
  echo.
  pause
  exit
)
長さを記録するファイルへのフルパス
set OUT_F=%CONCAT_DIR_P%\length.txt
cd "%CONCAT_DIR_P%"
rem 各音声ファイルの長さを測定する
echo %CONCAT_DIR%フォルダにある%EXT%ファイルの長さを測定します。
set FLG=1
set FCNT=1
set CNT=0
rem 合計の秒整数部分。
set T=0
rem 合計の秒少数部分。小数点以下3桁まで取得。
set D=0
echo 空欄の場合は0秒以下のファイルです。 > "%OUT_F%"
setlocal enabledelayedexpansion
rem 音声ファイル数合計を調べる。
for %%f in ("*.%EXT%") do (set /A CNT=!CNT!+1)
for %%f in ("*.%EXT%") do (
  rem 各ファイルの長さを出力する
  cls
  echo %%fを測定中(!FCNT!/!CNT!^)
  for /F "tokens=1 delims=" %%t in ('sox --i -d %%f') do (
    echo %%t
    echo %%f %%t >> "%OUT_F%"
  )
  rem 合計するために秒で長さを取得する。変数に入る最大値が限界なので整数部分と小数部分を分けて計算する。
  set U=000000
  for /F "tokens=1,2 delims=." %%t in ('sox --i -D %%f') do (
    set /A T=!T!+%%t
    rem 小数点以下は6桁と想定している。0から始まると8進数と解釈されるので1を先頭につける。
    set U=1%%u
  set /A D=!D!+!U!/1000
  )
  set /A FCNT=!FCNT!+1
)
cls
rem 小数部分の先頭に加えた1の分を引く
set /A D=!D!-1000*!CNT!
rem 小数点以下の合計から整数部分を取り出して整数部分に加える。
set /A T=!T!+!D!/1000
set /A D=!D!%%1000
rem 時間を取得して固定2桁へ変換
set /A HH=!T!/3600
set HH=0!HH!
set HH=!HH:~-2,2!
rem 分を取得して固定2桁へ変換
set /A T=!T!%%3600
set /A MM=!T!/60
set MM=0!MM!
set MM=!MM:~-2,2!
rem 秒を取得して固定2桁へ変換
set /A SS=!T!%%60
set SS=0!SS!
set SS=!SS:~-2,2!
echo 以上のファイルの合計時間 !HH!:!MM!:!SS!.!D! >> "%OUT_F%"
echo %CONCAT_DIR%フォルダにある!CNT!個の%EXT%ファイルの合計時間です。
echo !HH!:!MM!:!SS!.!D!
echo %OUT_F%に各音声ファイルの測定値があります。
echo.
pause
endlocal
バッチファイルの変数は小数は計算できないので単純に小数部分を整数に1000000を掛けて計算してみたところ出力が負の数になってしまいました。

あまりにも桁が大きくなりすぎたようです。

なので整数部分と小数部分を分けて合計してあとで合算しました。

0から始まる数値は8進数と解釈されてしまうためまず45行目で文字列として先頭に1を加えてから合計して52行目でその加えた分を引いています。

参考にしたサイト


小学生でも分かるデシベル(dB)の話
6dBの差で音の大きさが2倍になるそうです。

WINAMP - k本的に無料ソフト・フリーソフト
昔の定番mp3プレーヤー。複数ファイルをリストに加えると順番に再生できます。

オブジェクトがnullやundefindでないか評価する。 - うなの日記
空文字でないかも評価できるようです。

タスクを使い任意の時間にシャットダウン・休止する : Web Memo.SE
休止かスタンバイはコントロールパネルの電源オプションの設定に依存します。

タスクを使って任意の時間に休止(スタンバイ)から復帰する : Web Memo.SE
私のPCでは復帰は無理でした。

次の関連記事:SoXで音声感知録音:その4 VAD(Voice Activity Detection)の利用

PR

0 件のコメント:

コメントを投稿