Skip to content

Commit

Permalink
Merge pull request #6035 from avalonmediasystem/ride_the_waves
Browse files Browse the repository at this point in the history
Optimize waveform generation
  • Loading branch information
cjcolvar authored Sep 16, 2024
2 parents fe09a36 + 6534429 commit bdf83fb
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 23 deletions.
40 changes: 22 additions & 18 deletions app/services/waveform_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,33 +75,37 @@ def empty_waveform(master_file)

def get_normalized_peaks(uri)
wave_io = get_wave_io(uri.to_s)
peaks = gather_peaks(wave_io)
peaks = []
reader = nil
abs_max = 0
begin
reader = WaveFile::Reader.new(wave_io)
reader.each_buffer(@samples_per_pixel) do |buffer|
sample_min, sample_max = buffer.samples.minmax
peaks << [sample_min, sample_max]
abs_max = [abs_max, sample_min.abs, sample_max.abs].max
end
rescue WaveFile::InvalidFormatError
# ffmpeg generated no wavefile data
end
return [] if peaks.blank?
max_peak = peaks.flatten.map(&:abs).max
res = 2**(@bit_res - 1)
factor = max_peak.zero? ? 1 : res / max_peak.to_f
peaks.map { |peak| peak.collect { |num| (num * factor).to_i } }
factor = abs_max.zero? ? 1 : res / abs_max.to_f
peaks.each do |peak|
peak[0] = (peak[0] * factor).to_i
peak[1] = (peak[1] * factor).to_i
end
peaks
ensure
Process.wait(wave_io.pid) if wave_io&.pid
reader&.close
wave_io&.close
end

def get_wave_io(uri)
headers = "-headers $'Referer: #{Rails.application.routes.url_helpers.root_url}\r\n'" if uri.starts_with? "http"
normalized_uri = uri.starts_with?("file") ? Addressable::URI.unencode(uri) : uri
timeout = 60000000 # Must be in microseconds. Current value = 1 minute.
cmd = "#{Settings.ffmpeg.path} #{headers} -rw_timeout #{timeout} -i '#{normalized_uri}' -f wav -ar 44100 - 2> /dev/null"
cmd = "#{Settings.ffmpeg.path} #{headers} -rw_timeout #{timeout} -i '#{normalized_uri}' -f wav -ar 44100 -ac 1 - 2> /dev/null"
IO.popen(cmd)
end

def gather_peaks(wav_file)
peaks = []
begin
WaveFile::Reader.new(wav_file).each_buffer(@samples_per_pixel) do |buffer|
peaks << [buffer.samples.flatten.min, buffer.samples.flatten.max]
end
rescue WaveFile::InvalidFormatError
# ffmpeg generated no wavefile data
end
peaks
end
end
2 changes: 1 addition & 1 deletion spec/fixtures/meow.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
* --- END LICENSE_HEADER BLOCK ---
*/

{"sample_rate":44100,"bits":8,"samples_per_pixel":1024,"length":54,"data":[-75,72,-113,106,-109,104,-117,112,-128,127,-122,114,-105,86,-81,76,-81,76,-93,88,-108,105,-112,108,-99,96,-91,91,-77,77,-63,70,-49,55,-39,45,-36,40,-36,40,-30,31,-30,29,-28,25,-24,16,-17,16,-10,8,-7,7,-4,4,-3,3,-2,2,-2,2,-2,2,-1,1,-2,2,-2,2,-2,2,-1,1,-1,1,-1,1,-1,1,-1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
{"sample_rate":44100,"bits":8,"samples_per_pixel":1024,"length":54,"data":[-75, 72, -113, 106, -109, 104, -117, 112, -127, 128, -122, 114, -105, 87, -81, 75, -80, 76, -94, 88, -108, 105, -111, 108, -98, 96, -90, 87, -76, 76, -63, 70, -47, 53, -37, 44, -34, 38, -34, 39, -29, 31, -29, 27, -27, 25, -24, 16, -15, 13, -9, 8, -6, 5, -3, 4, -2, 3, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
Binary file added spec/fixtures/meow.mono.wav
Binary file not shown.
8 changes: 4 additions & 4 deletions spec/services/waveform_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
let(:service) { WaveformService.new }

describe "get_waveform_json" do
let(:wav_path) { File.join(Rails.root, "spec/fixtures/meow.wav") }
let(:wav_path) { File.join(Rails.root, "spec/fixtures/meow.mono.wav") }

it "should return waveform json from file" do
allow(service).to receive(:get_wave_io).and_return(open(wav_path))
Expand Down Expand Up @@ -67,7 +67,7 @@

context "http file" do
let(:uri) { "http://domain/to/video.mp4" }
let(:cmd) {"#{Settings.ffmpeg.path} -headers $'Referer: http://test.host/\r\n' -rw_timeout 60000000 -i '#{uri}' -f wav -ar 44100 - 2> /dev/null"}
let(:cmd) {"#{Settings.ffmpeg.path} -headers $'Referer: http://test.host/\r\n' -rw_timeout 60000000 -i '#{uri}' -f wav -ar 44100 -ac 1 - 2> /dev/null"}

it "should call ffmpeg with headers" do
service.send(:get_wave_io, uri)
Expand All @@ -77,7 +77,7 @@

context "local file" do
let(:uri) { "file:///path/to/video.mp4" }
let(:cmd) {"#{Settings.ffmpeg.path} -rw_timeout 60000000 -i '#{uri}' -f wav -ar 44100 - 2> /dev/null"}
let(:cmd) {"#{Settings.ffmpeg.path} -rw_timeout 60000000 -i '#{uri}' -f wav -ar 44100 -ac 1 - 2> /dev/null"}

it "should call ffmpeg without headers" do
service.send(:get_wave_io, uri)
Expand All @@ -87,7 +87,7 @@
context 'with spaces in filename' do
let(:uri) { 'file:///path/to/special%20video%20file.mp4' }
let(:unencoded_uri) { 'file:///path/to/special video file.mp4' }
let(:cmd) {"#{Settings.ffmpeg.path} -rw_timeout 60000000 -i '#{unencoded_uri}' -f wav -ar 44100 - 2> /dev/null"}
let(:cmd) {"#{Settings.ffmpeg.path} -rw_timeout 60000000 -i '#{unencoded_uri}' -f wav -ar 44100 -ac 1 - 2> /dev/null"}

it "should call ffmpeg without url encoding" do
service.send(:get_wave_io, uri)
Expand Down

0 comments on commit bdf83fb

Please sign in to comment.