🎤
ZOOM H4e の 32-bit float WAV ファイルを ffmpeg で mp3 に
ZOOM のハンディレコーダー H4essential は 32-bit float で録音できて最高なんだけど、そのままではファイルサイズが巨大かつ再生できる環境を選ぶので、ffmpeg を使ってノーマライズと mp3 への変換を行う。
基本的には 任意のラウドネス値に音量を調整する loudnorm に書かれた手順を Ruby スクリプトで実行しているだけ。
デフォルトのパラメータは オーディオコンテンツのラウドネス値の正規化 - Alexa Skills Kit を参考にした。
スクリプト
#!/usr/bin/env ruby
require "open3"
require "shellwords"
require "json"
require "optparse"
def sh(*args)
puts args.shelljoin
out = ""
err = ""
status = nil
Open3.popen3(*args) do |stdin, stdout, stderr, wait_thread|
# スレッドを使って標準エラー出力をリアルタイムで処理
err_thread = Thread.new do
stderr.each_char do |char|
$stderr.print char
err << char
end
end
# 標準出力をキャプチャ
stdout.each_char do |char|
out << char
end
# 標準エラー出力のスレッドが完了するのを待機
err_thread.join
# プロセスの終了を待つ
status = wait_thread.value
end
# ステータスコードを確認
unless status.success?
puts err
# exit 1
end
[out, err]
end
def loudnorm(array)
"loudnorm=" + array.map {|e|
case e
when Hash
e.map {|k, v| "#{k}=#{v}"}.join(":")
else
e
end
}.join(",")
end
options = {
i: -14, # integrated loudness target
tp: -3, # maximum true peak
lra: 11, # loudness range target
}
OptionParser.new do |o|
o.banner = "Usage: #{File.basename($0).gsub(".rb", "")} [options] INPUT_FILE OUTPUT_FILE"
o.on("-i", "--integrated-loudness NUMBER", Float, "Integrated loudness target") {|v| options[:i] = v}
o.on("-tp", "--true-peak NUMBER", Float, "Maximum true peak") {|v| options[:tp] = v}
o.on("-lra", "--loudness-range NUMBER", Float, "Loudness range target") {|v| options[:lra] = v}
o.parse!(ARGV)
end
input, output = ARGV
unless input
warn "Input file required."
exit 1
end
unless output
warn "Output file required."
exit 1
end
i = options.fetch(:i)
tp = options.fetch(:tp)
lra = options.fetch(:lra)
out, err = sh(
"ffmpeg",
"-i", input,
"-af", loudnorm([{I: i, TP: tp, LRA: lra, print_format: "json"}]),
"-f", "null",
"-",
)
result = JSON.parse(err.match(/Parsed_loudnorm.+?\n(.+)/m)[1])
sh(
"ffmpeg",
"-y", # Overwrite output files without asking
"-i", input,
"-af", loudnorm(
[
{
I: i, TP: tp, LRA: lra, print_format: "json",
measured_I: result.fetch("input_i"),
measured_TP: result.fetch("input_tp"),
measured_LRA: result.fetch("input_lra"),
measured_thresh: result.fetch("input_thresh"),
offset: result.fetch("target_offset"),
},
"channelmap=channel_layout=stereo",
"aresample=44100:resampler=soxr",
]
),
"-c:v", "copy",
"-c:a", "mp3",
output,
)
使い方
ruby wav2mp3.rb 入力ファイル 出力ファイル
Discussion