Windowsで長時間音声ファイルからの話声抽出

SoXで音声ファイルを結合するバッチファイルまでやっていたことをWindowsXPで一からセットアップします。WindowsXP機の使い道が音声処理ぐらいにしかもう思いつかないので、、、動画に関してはVPCJ2: Windows10でflvファイルのサムネイル表示と再生をするで使ったK-Lite Codec Pack
のStandardに付属のMedia Player Classic Home Cinemaが優秀でした。

SoX、Winamp、FFmpegのインストール


SoXはVPCJ2: SoX(Sound eXchange)とwinampのインストールのWindows10と同じ14.4.2をインストールしました。

Winampはv2.95をインストールしました。

Winampは再生に使うだけなので話声抽出には必要ありません。

m4aファイルも処理させようと思うのでm4aを変換するためにffmpegもダウンロードします。

mp3ファイルを入力に使うにはSoXがmp3ファイルを扱えるような設定が必要ですが、できないときはffmpegでwavに変換する必要があります。(SoX(Sound eXchange)でMP3を扱う参照。)

ffmpegは最新版だと「プロシージャ エントリ ポイント _wfopen_s がダイナミック リンク ライブラリ msvcrt.dll から見つかりませんでした。」というエラーが出てきてインストールできないので古いバージョンをインストールします。

ffmpegだけでGYAO動画をダウンロードする方法 - ギャッターのフリーソフト広場

ここにffmpeg-20140609-git-6d40849-win32-static.7zはうまくいったという報告があったので、Zeranoe FFmpeg - BuildsからたどってZeranoe FFmpeg - Buildsから同じバージョンをダウンロードしました。

7zファイルを解凍してでてきたファイルの中にあるff-prompt.batを起動するとffmpeg用に環境変数を設定してコマンドプロンプトが立ち上がります。

設定しているの環境変数はPATHだけのようです。

ffmpegでm4aファイルをwavに変換する


SoXはm4aファイルを扱えないのでm4aファイルはまずffmpegでwavファイルに変換しておきます。

ffmpeg -i input.m4a output.wav

これでwavファイルに変換できました。

ffmpegのお仕事はここまでで終わりです。


m4aファイルから録音開始日時を取得するのは断念


ファイルの更新日時は録音終了日時に該当するので録音開始日時を取得する方法を考えます。

for %i in ( input.m4a ) do echo %~ti
>for %i in ( 1.m4a ) do echo %~ti

>echo 2016/07/01 10:37
2016/07/01 10:37
1.m4aというファイルの更新日時が得られました。

開始日時は録音時間を更新日時から引けばよいのですが、m4aの録音時間を得る方法がわかりません。

wavファイルならsox -i -dで録音時間がわかるのでこの情報と併用します。

と、思ってバッチファイルを作り始めたらffpmeg.exeと同じフォルダにffprobe.exeを発見しました。

ffmpeg 動画や音声の時間を取得する - Qiita

ffprobe 1.m4a  -hide_banner -show_entries format
>ffprobe 1.m4a  -hide_banner -show_entries format
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '1.m4a':
  Metadata:
    major_brand     : M4A
    minor_version   : 0
    compatible_brands: M4A mp42isom
    creation_time   : 2016-07-01 01:37:29
    date            : 2016-07-01T10:28:18+0900
    encoder         : com.apple.VoiceMemos (iPhone OS 9.3.2)
  Duration: 00:09:06.39, start: 0.000000, bitrate: 64 kb/s
    Stream #0:0(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 62 k
b/s (default)
    Metadata:
      creation_time   : 2016-07-01 01:37:29
      handler_name    : Core Media Audio
[FORMAT]
filename=1.m4a
nb_streams=1
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=0.000000
duration=546.388322
size=4380133
bit_rate=64132
probe_score=100
TAG:major_brand=M4A
TAG:minor_version=0
TAG:compatible_brands=M4A mp42isom
TAG:creation_time=2016-07-01 01:37:29
TAG:date=2016-07-01T10:28:18+0900
TAG:encoder=com.apple.VoiceMemos (iPhone OS 9.3.2)
[/FORMAT]
ffprobeでファイル更新日時と録音時間は表示させることはできましたが、ここからバッチファイルで情報を抽出するのは面倒そうなので先ほどの計画通りにします、、、うーんバッチファイルで日時計算は難しいのでやめました。

ちなみにffprobeで調べるとm4aファイルをwavに変換してもメタデータのdateは元のm4aファイルの情報を引き継いでいました。

なので作るとしたらwavファイルをffprobeで調べてdateを取得してそれの加工ですね。

入力音声ファイルを60秒ずつに分割してそれに対応する話声抽出ファイルを作成するバッチファイル


まず環境設定用のバッチファイルsettings.batを作成します。

結局32bitでも64bitのWindowsでも動くように設定しました。
rem 記録しない音声ファイルの長さ(秒以下)。
set SEC=3
rem vadエフェクト後に分割する間隔(秒)
set F_LEN=10
rem 分割1ファイル当たりの録音区間の秒数の設定(秒)。
set DUR=60
rem 分割処理後に削除する音声ファイルの長さ(秒)
set SEC2=1
rem ディレクトリ名の設定。スクリプトファイルのあるディレクトリの下層に作られる。
rem データディレクトリ名。
set DATA_DIR=data
rem 分割後のデータをいれるディレクトリ名。
set DIVIDED_DIR=divided
rem 無音区間削除後のファイルの移動先ディレクトリ名。
set EXTRACTED_DIR=extracted
rem ffmpeg.exeのパスの設定。
rem Windows10 64bitの場合。
path %PATH%;C:\Program Files2\ffmpeg-20160707-b450b82-win64-static\bin
rem WindowsXPの場合。
path %PATH%;C:\Program Files2\ffmpeg-20140609-git-6d40849-win32-static\bin
rem sox.exeへのパスの設定。
rem Windows10 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%
rem データディレクトリDATA_DIRのフルパス。
set DATA_DIR_P=%SD%\%DATA_DIR%
if not exist "%DATA_DIR_P%" (mkdir "%DATA_DIR_P%")
rem 改名後のデータをいれるディレクトリRENAMED_DIRのフルパス。
set DIVIDED_DIR_P=%SD%\%DIVIDED_DIR%
if not exist "%DIVIDED_DIR_P%" (mkdir "%DIVIDED_DIR_P%")
rem 処理後のファイルの移動先ディレクトリEXTRACTED_DIRのフルパス。
set EXTRACTED_DIR_P=%SD%\%EXTRACTED_DIR%
if not exist "%EXTRACTED_DIR_P%" (mkdir "%EXTRACTED_DIR_P%")
rem 再抽出の作業フォルダのフルパス
set TMP_DIR_P=%SD%\temp
if not exist "%TMP_DIR_P%" (mkdir "%TMP_DIR_P%")
18行目と20行目でffmpeg.exe、23行目と25行目でsox.exeへのパスを設定しているので環境によって変更が必要です。

このバッチファイルがあるフォルダにあるdataフォルダにある音声ファイルに対して処理します。

話声抽出にはSoXの VAD(Voice Activity Detection)エフェクトを使います(SoXで音声感知録音:その4 VAD(Voice Activity Detection)の利用参照)。

60秒(変数DURで設定)ごとに分割した音声ファイルに対して前後からVADエフェクトをかけます。

話声抽出後のファイルの長さが3秒(変数SEC)以下のときはファイルを削除します。

話声抽出後のファイルの長さが10秒(変数F_LEN)より長い時はそのファイルを半割してそれぞれにさらにVADエフェクトをかけて話声抽出をし、その後半割したファイルを結合します。

結合したファイルがの長さが3秒(変数SEC)以下のときはファイルを削除します。

分割されたファイルはdividedフォルダ、話声抽出はextractedフォルダに保存されます。

これらのフォルダはsettings.batを起動すると作成されます。

次は実際に処理を行うextract.batファイルです。
@echo off
call settings.bat
setlocal enabledelayedexpansion
echo ファイル名に空白があると支障があるのでまずファイル名から空白を除去します。
for %%f in ("%DATA_DIR_P%\*.*") do (
  set F=%%~nf
  move "%%f" "%%~dpf!F: =!%%~xf"
)
echo %DATA_DIR_P%にある音声ファイルを%DUR%秒ごとのwavファイルに分割して%DIVIDED_DIR_P%に保存します。
for %%f in ("%DATA_DIR_P%\*.*") do (
  rem 処理ファイルフルパスの取得。
  set F_P=%%f
  if "%%~xf"==".m4a" (
    echo %%~nxfをwavファイルに変換しています。
    set F_P="!TMP_DIR_P!\%%~nf.wav"
    ffmpeg -hide_banner -i "%%f" "!F_P!"
  )
  echo %%~nf.wavを%DUR%秒ごとのwavファイルに分割して%DIVIDED_DIR_P%に保存しています。
  sox "!F_P!" -p | sox -p "%DIVIDED_DIR_P%\%%~nf_%%5n.wav" trim 0 %DUR% : newfile : restart
)
rem 削除した全ファイル数
set DCNT=0
rem 再抽出したファイル数
set REEXT=0
rem 再抽出後に削除したファイル数
set DCNT2=0
for %%f in ("%DIVIDED_DIR_P%\*.wav") do (
  rem 抽出後のファイル名
  set F_NAME="%EXTRACTED_DIR_P%\%%~nxf"
  echo 話声抽出後の%%~nxfの長さが%SEC%秒以下なら削除します。
  echo 既削除数!DCNT!(再抽出後既削除数!DCNT2!/!REEXT!^)
  sox "%%f" !F_NAME! vad reverse vad reverse
  rem sox --iで測定できないときのためにTをリセットする。
  set T=0
  for /F "tokens=1 delims=." %%t in ('sox --i -D !F_NAME!') do set T=%%t
  if !T! LEQ %SEC% (
    del /Q !F_NAME!
    set /A DCNT=!DCNT!+1
    echo !F_NAME!の長さが%SEC%秒以下なので削除しました。
  )
  rem %F_LEN%秒より長いファイルは半割して再抽出
  if !T! GTR %F_LEN% (
  set /A REEXT=!REEXT!+1
    echo !F_NAME!は%F_LEN%秒より長いので再抽出します
    set /A T2=!T!/2
    sox !F_NAME! "%TMP_DIR_P%\001.wav" trim 0 !T2! vad reverse vad reverse
    sox !F_NAME! "%TMP_DIR_P%\002.wav" trim !T2! vad reverse vad reverse
    del /Q !F_NAME!
    sox "%TMP_DIR_P%\001.wav" "%TMP_DIR_P%\002.wav" !F_NAME!
    del /Q "%TMP_DIR_P%\001.wav"
    del /Q "%TMP_DIR_P%\002.wav"
    for /F "tokens=1 delims=." %%t in ('sox --i -D !F_NAME!') do set T=%%t
    if !T! LEQ 1 (
      del /Q !F_NAME!
      set /A DCNT=!DCNT!+1
   set /A DCNT2=!DCNT2!+1
      echo 話声再抽出後の!F_NAME!の長さが1秒以下なので削除しました。
    )
  )
)
rem 作業ディレクトリの削除
rd /s /q "%TMP_DIR_P%"
endlocal
rem winamp.exeへのパスの設定。
rem Windows7 64bitの場合。
path %PATH%;C:\Program Files (x86)\Winamp
rem WindowsXPの場合。
path %PATH%;C:\Program Files\Winamp
echo %EXTRACTED_DIR_P%のファイルリスト作成中。
rem 第一引数をダブルクォートで括るとウィンドウタイトルとして解釈されてしまう。ダミーを入れておく。
start "" winamp /NEW "%EXTRACTED_DIR_P%"
echo %DIVIDED_DIR_P%のファイルリスト作成中。
winamp /NEW /PLAYPAUSE "%DIVIDED_DIR_P%"
settings.batとextract.batを置いたフォルダにdataフォルダを作りそこに音声ファイルを入れてextract.batを実行すると処理が始まります。

ファイル名に空白があるとffmpegでうまく処理できなかったので事前にファイル名を除く処理を追加しました。

SoXをmp3対応にできていないときにmp3ファイルを処理したいときは13行目の.m4aを.mp3に変更すればffmpegがmp3ファイルをwavファイルに変更してくれると思います(未確認)。

64行目以降は処理したファイルを再生するためのものです。

71行目は話声抽出後のファイルの再生、73行目で話声抽出していない分割ファイルの再生をします。

話声抽出ファイルの再生は/PLAYPUASEスイッチで止まるはずはずなのですが、うまく動いていません。

出力ファイルをmp3に変換する


wavファイルのままだとやっぱりファイルサイズが大きくなりすぎるので出力ファイルをmp3に変換することにしました。

SoXファイルがmp3ファイルを扱えるよう設定されていることが必要です。

また前回出力ファイルを消去するかどうか問い合わせするようにしました。

extract.bat
@echo off
call 削除.bat
call settings.bat
setlocal enabledelayedexpansion
echo ファイル名に空白があると支障があるのでまずファイル名から空白を除去します。
for %%f in ("%DATA_DIR_P%\*.*") do (
  set F=%%~nf
  move "%%f" "%%~dpf!F: =!%%~xf"
)
echo %DATA_DIR_P%にある音声ファイルを%DUR%秒ごとのwavファイルに分割して%DIVIDED_DIR_P%に保存します。
for %%f in ("%DATA_DIR_P%\*.*") do (
  rem 処理ファイルフルパスの取得。
  set F_P=%%f
  if "%%~xf"==".m4a" (
    echo %%~nxfをwavファイルに変換しています。
    set F_P="!TMP_DIR_P!\%%~nf.wav"
    ffmpeg -hide_banner -i "%%f" "!F_P!"
  )
  echo %%~nf.wavを%DUR%秒ごとのwavファイルに分割して%DIVIDED_DIR_P%に保存しています。
  sox "!F_P!" -p | sox -p "%DIVIDED_DIR_P%\%%~nf_%%5n.wav" trim 0 %DUR% : newfile : restart
)
rem 削除した全ファイル数
set DCNT=0
rem 再抽出したファイル数
set REEXT=0
rem 再抽出後に削除したファイル数
set DCNT2=0
for %%f in ("%DIVIDED_DIR_P%\*.wav") do (
  rem 抽出後のファイル名
  set F_NAME="%EXTRACTED_DIR_P%\%%~nxf"
  echo 話声抽出後の%%~nxfの長さが%SEC%秒以下なら削除します。
  echo 既削除数!DCNT!(再抽出後既削除数!DCNT2!/!REEXT!^)
  sox "%%f" !F_NAME! vad reverse vad reverse
  echo 抽出元ファイル%%~nxfをmp3に変換します。
  sox "%%f" %OUTOPT% "%%~dpnf.mp3"
  del /Q "%%f"
  rem sox --iで測定できないときのためにTをリセットする。
  set T=0
  for /F "tokens=1 delims=." %%t in ('sox --i -D !F_NAME!') do set T=%%t
  if !T! LEQ %SEC% (
    del /Q !F_NAME!
    set /A DCNT=!DCNT!+1
    echo !F_NAME!の長さが%SEC%秒以下なので削除しました。
  )
  rem %F_LEN%秒より長いファイルは半割して再抽出
  if !T! GTR %F_LEN% (
  set /A REEXT=!REEXT!+1
    echo !F_NAME!は%F_LEN%秒より長いので再抽出します
    set /A T2=!T!/2
    sox !F_NAME! "%TMP_DIR_P%\001.wav" trim 0 !T2! vad reverse vad reverse
    sox !F_NAME! "%TMP_DIR_P%\002.wav" trim !T2! vad reverse vad reverse
    del /Q !F_NAME!
    sox "%TMP_DIR_P%\001.wav" "%TMP_DIR_P%\002.wav" !F_NAME!
    del /Q "%TMP_DIR_P%\001.wav"
    del /Q "%TMP_DIR_P%\002.wav"
    for /F "tokens=1 delims=." %%t in ('sox --i -D !F_NAME!') do set T=%%t
    if !T! LEQ 1 (
      del /Q !F_NAME!
      set /A DCNT=!DCNT!+1
   set /A DCNT2=!DCNT2!+1
      echo 話声再抽出後の!F_NAME!の長さが1秒以下なので削除しました。
    ) 
  )
  if exist !F_NAME! (  
    echo 抽出後のファイル!F_NAME!をmp3に変換します。
    sox "!F_NAME!" %OUTOPT% "%EXTRACTED_DIR_P%\%%~nf.mp3"
    del /Q "!F_NAME!"
  )
)
rem 作業ディレクトリの削除
rd /s /q "%TMP_DIR_P%"
endlocal
rem winamp.exeへのパスの設定。
rem Windows7 64bitの場合。
path %PATH%;C:\Program Files (x86)\Winamp
rem WindowsXPの場合。
path %PATH%;C:\Program Files\Winamp
echo %EXTRACTED_DIR_P%のファイルリスト作成中。
rem 第一引数をダブルクォートで括るとウィンドウタイトルとして解釈されてしまう。ダミーを入れておく。
start "" winamp /NEW "%EXTRACTED_DIR_P%"
echo %DIVIDED_DIR_P%のファイルリスト作成中。
winamp /NEW /PLAYPAUSE "%DIVIDED_DIR_P%" 
削除.bat
echo 前回処理したファイルをすべて削除します。
echo.
echo はい(y^)/いいえ(n^)を選択後Enterキーを押してください。
echo.
set /p KEY="削除しますか?(y/n)"
if "%KEY%" == "y" (
  rd /s /q extracted
  rd /s /q divided
  rd /s /q temp
)
cls
settings.bat
rem 記録しない音声ファイルの長さ(秒以下)。
set SEC=3
rem vadエフェクト後に分割する間隔(秒)
set F_LEN=10
rem 分割1ファイル当たりの録音区間の秒数の設定(秒)。
set DUR=60
rem 分割処理後に削除する音声ファイルの長さ(秒)
set SEC2=1
rem ディレクトリ名の設定。スクリプトファイルのあるディレクトリの下層に作られる。
rem データディレクトリ名。
set DATA_DIR=data
rem 分割後のデータをいれるディレクトリ名。
set DIVIDED_DIR=divided
rem 無音区間削除後のファイルの移動先ディレクトリ名。
set EXTRACTED_DIR=extracted
rem 出力音声ファイルの圧縮オプション
set OUTOPT=-C -9.2
rem ffmpeg.exeのパスの設定。
rem Windows10 64bitの場合。
path %PATH%;C:\Program Files2\ffmpeg-20160707-b450b82-win64-static\bin
rem WindowsXPの場合。
path %PATH%;C:\Program Files2\ffmpeg-20140609-git-6d40849-win32-static\bin
rem sox.exeへのパスの設定。
rem Windows10 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%
rem データディレクトリDATA_DIRのフルパス。
set DATA_DIR_P=%SD%\%DATA_DIR%
if not exist "%DATA_DIR_P%" (mkdir "%DATA_DIR_P%")
rem 改名後のデータをいれるディレクトリRENAMED_DIRのフルパス。
set DIVIDED_DIR_P=%SD%\%DIVIDED_DIR%
if not exist "%DIVIDED_DIR_P%" (mkdir "%DIVIDED_DIR_P%")
rem 処理後のファイルの移動先ディレクトリEXTRACTED_DIRのフルパス。
set EXTRACTED_DIR_P=%SD%\%EXTRACTED_DIR%
if not exist "%EXTRACTED_DIR_P%" (mkdir "%EXTRACTED_DIR_P%")
rem 再抽出の作業フォルダのフルパス
set TMP_DIR_P=%SD%\temp
if not exist "%TMP_DIR_P%" (mkdir "%TMP_DIR_P%")
これら3つのバッチファイルを同じフォルダにいれて、そのフォルダにあるdataフォルダに音声データをいれてextract.batをダブルクリックすると話声抽出を開始します。

参考にしたサイト


無償のFFmpegでWavファイルの変換(Wavに変換)
FFmpegはm4aファイルも扱えます。

ffmpegだけでGYAO動画をダウンロードする方法 - ギャッターのフリーソフト広場
最新版はWindowsXPでは起動できませんでした。

Zeranoe FFmpeg - Builds
WindowsXPにはffmpeg-20140609-git-6d40849-win32-static.7zが使えました。

ffmpeg 動画や音声の時間を取得する - Qiita
ffmpeg付属のffprove.exeで情報の表示はできましたがバッチファイルでの処理はなかなか難しそうです。
PR

0 件のコメント:

コメントを投稿