From 68ffa097e187da663fa3f537b430428ea5e8de2e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 25 Aug 2013 09:25:07 +0000 Subject: split out source handling to prepare for avconv/ffmpeg support We should've done this at the start, but we didn't. --- lib/dtas/format.rb | 1 + lib/dtas/player.rb | 11 +-- lib/dtas/player/client_handler.rb | 4 +- lib/dtas/source.rb | 145 +------------------------------------- lib/dtas/source/cmd.rb | 40 +++++++++++ lib/dtas/source/command.rb | 40 ----------- lib/dtas/source/file.rb | 63 +++++++++++++++++ lib/dtas/source/mp3.rb | 37 ---------- lib/dtas/source/mp3gain.rb | 37 ++++++++++ lib/dtas/source/sox.rb | 100 ++++++++++++++++++++++++++ test/test_rg_integration.rb | 2 +- test/test_source.rb | 102 --------------------------- test/test_source_sox.rb | 102 +++++++++++++++++++++++++++ 13 files changed, 353 insertions(+), 331 deletions(-) create mode 100644 lib/dtas/source/cmd.rb delete mode 100644 lib/dtas/source/command.rb create mode 100644 lib/dtas/source/file.rb delete mode 100644 lib/dtas/source/mp3.rb create mode 100644 lib/dtas/source/mp3gain.rb create mode 100644 lib/dtas/source/sox.rb delete mode 100644 test/test_source.rb create mode 100644 test/test_source_sox.rb diff --git a/lib/dtas/format.rb b/lib/dtas/format.rb index 83a541a..a6e424b 100644 --- a/lib/dtas/format.rb +++ b/lib/dtas/format.rb @@ -75,6 +75,7 @@ class DTAS::Format # :nodoc: ivars_to_hash(SIVS) end + # FIXME, move to per-source (sox/avconv/ffmpeg) def from_file(path) @channels = qx(%W(soxi -c #{path})).to_i @type = qx(%W(soxi -t #{path})).strip diff --git a/lib/dtas/player.rb b/lib/dtas/player.rb index c6d6027..57976c2 100644 --- a/lib/dtas/player.rb +++ b/lib/dtas/player.rb @@ -5,7 +5,8 @@ require 'yaml' require 'shellwords' require_relative '../dtas' require_relative 'source' -require_relative 'source/command' +require_relative 'source/sox' +require_relative 'source/cmd' require_relative 'sink' require_relative 'unix_server' require_relative 'buffer' @@ -304,17 +305,17 @@ class DTAS::Player # :nodoc: case source_spec when String - @current = DTAS::Source.new(source_spec) + @current = DTAS::Source::Sox.new(source_spec) echo(%W(file #{@current.infile})) when Array - @current = DTAS::Source.new(*source_spec) + @current = DTAS::Source::Sox.new(*source_spec) echo(%W(file #{@current.infile} #{@current.offset_samples}s)) else - @current = DTAS::Source::Command.new(source_spec["command"]) + @current = DTAS::Source::Cmd.new(source_spec["command"]) echo(%W(command #{@current.command_string})) end - if DTAS::Source === @current + if DTAS::Source::Sox === @current @current.command = @srccmd if @srccmd @current.env = @srcenv.dup unless @srcenv.empty? end diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb index cddbe4c..44f0b57 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -204,7 +204,7 @@ module DTAS::Player::ClientHandler # :nodoc: # this offset in the @current.format (not player @format) @queue.unshift([ @current.infile, "#{__current_decoded_samples}s" ]) else - # DTAS::Source::Command (hash), just rerun it + # DTAS::Source::Cmd (hash), just rerun it @queue.unshift(@current.to_hsh) end # We also want to hard drop the buffer so we do not get repeated audio. @@ -424,7 +424,7 @@ module DTAS::Player::ClientHandler # :nodoc: case msg.shift when "cat" io.emit({ - "command" => @srccmd || DTAS::Source::SOURCE_DEFAULTS["command"], + "command" => @srccmd || DTAS::Source::Sox::SOX_DEFAULTS["command"], "env" => @srcenv, }.to_yaml) when "ed" diff --git a/lib/dtas/source.rb b/lib/dtas/source.rb index 747fa9f..1093b18 100644 --- a/lib/dtas/source.rb +++ b/lib/dtas/source.rb @@ -2,149 +2,6 @@ # Copyright (C) 2013, Eric Wong # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) require_relative '../dtas' -require_relative 'command' -require_relative 'format' -require_relative 'replaygain' -require_relative 'process' -require_relative 'serialize' -# this is usually one input file -class DTAS::Source # :nodoc: - attr_reader :infile - attr_reader :offset - require_relative 'source/common' - require_relative 'source/mp3' - - include DTAS::Command - include DTAS::Process - include DTAS::Source::Common - include DTAS::Source::Mp3 - - SOURCE_DEFAULTS = COMMAND_DEFAULTS.merge( - "command" => 'exec sox "$INFILE" $SOXFMT - $TRIMFX $RGFX', - "comments" => nil, - ) - - SIVS = %w(infile comments command env) - - def initialize(infile, offset = nil) - command_init(SOURCE_DEFAULTS) - @format = nil - @infile = infile - @offset = offset - @comments = nil - @samples = nil - @rg = nil - end - - # this exists mainly to make the mpris interface easier, but it's not - # necessary, the mpris interface also knows the sample rate - def offset_us - (offset_samples / format.rate.to_f) * 1000000 - end - - # returns any offset in samples (relative to the original source file), - # likely zero unless seek was used - def offset_samples - return 0 unless @offset - case @offset - when /\A\d+s\z/ - @offset.to_i - else - format.hhmmss_to_samples(@offset) - end - end - - def precision - qx(%W(soxi -p #@infile), err: "/dev/null").to_i # sox.git f4562efd0aa3 - rescue # fallback to parsing the whole output - s = qx(%W(soxi #@infile), err: "/dev/null") - s =~ /Precision\s+:\s*(\d+)-bit/ - v = $1.to_i - return v if v > 0 - raise TypeError, "could not determine precision for #@infile" - end - - def format - @format ||= begin - fmt = DTAS::Format.new - fmt.from_file(@infile) - fmt.bits ||= precision - fmt - end - end - - # A user may be downloading the file and start playing - # it before the download completes, this refreshes - def samples! - @samples = nil - samples - end - - # This is the number of samples according to the samples in the source - # file itself, not the decoded output - def samples - @samples ||= qx(%W(soxi -s #@infile)).to_i - rescue => e - warn e.message - 0 - end - - # just run soxi -a - def __load_comments - tmp = {} - case @infile - when String - err = "" - cmd = %W(soxi -a #@infile) - begin - qx(cmd, err: err).split(/\n/).each do |line| - key, value = line.split(/=/, 2) - key && value or next - # TODO: multi-line/multi-value/repeated tags - tmp[key.upcase] = value - end - rescue => e - if /FAIL formats: no handler for file extension/ =~ err - warn("#{xs(cmd)}: #{err}") - else - warn("#{e.message} (#{e.class})") - end - # TODO: fallbacks - end - end - tmp - end - - def comments - @comments ||= __load_comments - end - - def replaygain - @rg = DTAS::ReplayGain.new(comments) || - DTAS::ReplayGain.new(mp3gain_comments) - end - - def spawn(format, rg_state, opts) - raise "BUG: #{self.inspect}#spawn called twice" if @to_io - e = format.to_env - e["INFILE"] = @infile - - # make sure these are visible to the "current" command... - @env["TRIMFX"] = @offset ? "trim #@offset" : nil - @env["RGFX"] = rg_state.effect(self) || nil - e.merge!(@rg.to_env) if @rg - - @pid = dtas_spawn(e.merge!(@env), command_string, opts) - end - - def to_hsh - to_hash.delete_if { |k,v| v == SOURCE_DEFAULTS[k] } - end - - def to_hash - rv = ivars_to_hash(SIVS) - rv["samples"] = samples - rv - end +module DTAS::Source # :nodoc: end diff --git a/lib/dtas/source/cmd.rb b/lib/dtas/source/cmd.rb new file mode 100644 index 0000000..2507101 --- /dev/null +++ b/lib/dtas/source/cmd.rb @@ -0,0 +1,40 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative '../../dtas' +require_relative '../source' +require_relative '../command' +require_relative '../serialize' + +class DTAS::Source::Cmd # :nodoc: + require_relative '../source/common' + + include DTAS::Command + include DTAS::Process + include DTAS::Source::Common + include DTAS::Serialize + + SIVS = %w(command env) + + def initialize(command) + command_init(command: command) + end + + def source_dup + rv = self.class.new + SIVS.each { |iv| rv.__send__("#{iv}=", self.__send__(iv)) } + rv + end + + def to_hash + ivars_to_hash(SIVS) + end + + alias to_hsh to_hash + + def spawn(format, rg_state, opts) + raise "BUG: #{self.inspect}#spawn called twice" if @to_io + e = format.to_env + @pid = dtas_spawn(e.merge!(@env), command_string, opts) + end +end diff --git a/lib/dtas/source/command.rb b/lib/dtas/source/command.rb deleted file mode 100644 index 930c5cf..0000000 --- a/lib/dtas/source/command.rb +++ /dev/null @@ -1,40 +0,0 @@ -# -*- encoding: binary -*- -# Copyright (C) 2013, Eric Wong -# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) -require_relative '../../dtas' -require_relative '../source' -require_relative '../command' -require_relative '../serialize' - -class DTAS::Source::Command # :nodoc: - require_relative '../source/common' - - include DTAS::Command - include DTAS::Process - include DTAS::Source::Common - include DTAS::Serialize - - SIVS = %w(command env) - - def initialize(command) - command_init(command: command) - end - - def source_dup - rv = self.class.new - SIVS.each { |iv| rv.__send__("#{iv}=", self.__send__(iv)) } - rv - end - - def to_hash - ivars_to_hash(SIVS) - end - - alias to_hsh to_hash - - def spawn(format, rg_state, opts) - raise "BUG: #{self.inspect}#spawn called twice" if @to_io - e = format.to_env - @pid = dtas_spawn(e.merge!(@env), command_string, opts) - end -end diff --git a/lib/dtas/source/file.rb b/lib/dtas/source/file.rb new file mode 100644 index 0000000..472cb3d --- /dev/null +++ b/lib/dtas/source/file.rb @@ -0,0 +1,63 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative '../../dtas' +require_relative '../source' +require_relative '../command' +require_relative '../format' +require_relative '../process' + +module DTAS::Source::File # :nodoc: + attr_reader :infile + attr_reader :offset + require_relative 'common' # dtas/source/common + include DTAS::Command + include DTAS::Process + include DTAS::Source::Common + + FILE_SIVS = %w(infile comments command env) + + def source_file_init(infile, offset) + @format = nil + @infile = infile + @offset = offset + @comments = nil + @samples = nil + @rg = nil + end + + # this exists mainly to make the mpris interface easier, but it's not + # necessary, the mpris interface also knows the sample rate + def offset_us + (offset_samples / format.rate.to_f) * 1000000 + end + + # returns any offset in samples (relative to the original source file), + # likely zero unless seek was used + def offset_samples + return 0 unless @offset + case @offset + when /\A\d+s\z/ + @offset.to_i + else + format.hhmmss_to_samples(@offset) + end + end + + # A user may be downloading the file and start playing + # it before the download completes, this refreshes + def samples! + @samples = nil + samples + end + + def comments + @comments ||= __load_comments + end + + def to_hash + rv = ivars_to_hash(FILE_SIVS) + rv["samples"] = samples + rv + end +end diff --git a/lib/dtas/source/mp3.rb b/lib/dtas/source/mp3.rb deleted file mode 100644 index 7ceaf8a..0000000 --- a/lib/dtas/source/mp3.rb +++ /dev/null @@ -1,37 +0,0 @@ -# -*- encoding: binary -*- -# Copyright (C) 2013, Eric Wong -# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) -require_relative '../process' - -module DTAS::Source::Mp3 # :nodoc: - include DTAS::Process - # we use dBFS = 1.0 as scale (not 32768) - def __mp3gain_peak(str) - sprintf("%0.8g", str.to_f / 32768.0) - end - - # massage mp3gain(1) output - def mp3gain_comments - tmp = {} - case @infile - when String - @infile =~ /\.mp[g23]\z/i or return - qx(%W(mp3gain -s c #@infile)).split(/\n/).each do |line| - case line - when /^Recommended "(Track|Album)" dB change:\s*(\S+)/ - tmp["REPLAYGAIN_#{$1.upcase}_GAIN"] = $2 - when /^Max PCM sample at current gain: (\S+)/ - tmp["REPLAYGAIN_TRACK_PEAK"] = __mp3gain_peak($1) - when /^Max Album PCM sample at current gain: (\S+)/ - tmp["REPLAYGAIN_ALBUM_PEAK"] = __mp3gain_peak($1) - end - end - tmp - else - raise TypeError, "unsupported type: #{@infile.inspect}" - end - rescue => e - $DEBUG and - warn("mp3gain(#{@infile.inspect}) failed: #{e.message} (#{e.class})") - end -end diff --git a/lib/dtas/source/mp3gain.rb b/lib/dtas/source/mp3gain.rb new file mode 100644 index 0000000..03bc37a --- /dev/null +++ b/lib/dtas/source/mp3gain.rb @@ -0,0 +1,37 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative '../process' + +module DTAS::Source::Mp3gain # :nodoc: + include DTAS::Process + # we use dBFS = 1.0 as scale (not 32768) + def __mp3gain_peak(str) + sprintf("%0.8g", str.to_f / 32768.0) + end + + # massage mp3gain(1) output + def mp3gain_comments + tmp = {} + case @infile + when String + @infile =~ /\.mp[g23]\z/i or return + qx(%W(mp3gain -s c #@infile)).split(/\n/).each do |line| + case line + when /^Recommended "(Track|Album)" dB change:\s*(\S+)/ + tmp["REPLAYGAIN_#{$1.upcase}_GAIN"] = $2 + when /^Max PCM sample at current gain: (\S+)/ + tmp["REPLAYGAIN_TRACK_PEAK"] = __mp3gain_peak($1) + when /^Max Album PCM sample at current gain: (\S+)/ + tmp["REPLAYGAIN_ALBUM_PEAK"] = __mp3gain_peak($1) + end + end + tmp + else + raise TypeError, "unsupported type: #{@infile.inspect}" + end + rescue => e + $DEBUG and + warn("mp3gain(#{@infile.inspect}) failed: #{e.message} (#{e.class})") + end +end diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb new file mode 100644 index 0000000..44b5f17 --- /dev/null +++ b/lib/dtas/source/sox.rb @@ -0,0 +1,100 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative '../../dtas' +require_relative '../source' +require_relative '../replaygain' + +# this is usually one input file +class DTAS::Source::Sox # :nodoc: + require_relative 'file' + require_relative 'mp3gain' + + include DTAS::Source::File + include DTAS::Source::Mp3gain + + SOX_DEFAULTS = COMMAND_DEFAULTS.merge( + "command" => 'exec sox "$INFILE" $SOXFMT - $TRIMFX $RGFX', + "comments" => nil, + ) + + def initialize(infile, offset = nil) + command_init(SOX_DEFAULTS) + source_file_init(infile, offset) + end + + def precision + qx(%W(soxi -p #@infile), err: "/dev/null").to_i # sox.git f4562efd0aa3 + rescue # fallback to parsing the whole output + s = qx(%W(soxi #@infile), err: "/dev/null") + s =~ /Precision\s+:\s*(\d+)-bit/ + v = $1.to_i + return v if v > 0 + raise TypeError, "could not determine precision for #@infile" + end + + def format + @format ||= begin + fmt = DTAS::Format.new + fmt.from_file(@infile) + fmt.bits ||= precision + fmt + end + end + + # This is the number of samples according to the samples in the source + # file itself, not the decoded output + def samples + @samples ||= qx(%W(soxi -s #@infile)).to_i + rescue => e + warn e.message + 0 + end + + # just run soxi -a + def __load_comments + tmp = {} + case @infile + when String + err = "" + cmd = %W(soxi -a #@infile) + begin + qx(cmd, err: err).split(/\n/).each do |line| + key, value = line.split(/=/, 2) + key && value or next + # TODO: multi-line/multi-value/repeated tags + tmp[key.upcase] = value + end + rescue => e + if /FAIL formats: no handler for file extension/ =~ err + warn("#{xs(cmd)}: #{err}") + else + warn("#{e.message} (#{e.class})") + end + end + end + tmp + end + + def replaygain + @rg = DTAS::ReplayGain.new(comments) || + DTAS::ReplayGain.new(mp3gain_comments) + end + + def spawn(format, rg_state, opts) + raise "BUG: #{self.inspect}#spawn called twice" if @to_io + e = format.to_env + e["INFILE"] = @infile + + # make sure these are visible to the "current" command... + @env["TRIMFX"] = @offset ? "trim #@offset" : nil + @env["RGFX"] = rg_state.effect(self) || nil + e.merge!(@rg.to_env) if @rg + + @pid = dtas_spawn(e.merge!(@env), command_string, opts) + end + + def to_hsh + to_hash.delete_if { |k,v| v == SOX_DEFAULTS[k] } + end +end diff --git a/test/test_rg_integration.rb b/test/test_rg_integration.rb index 0d2ac09..79a6563 100644 --- a/test/test_rg_integration.rb +++ b/test/test_rg_integration.rb @@ -103,7 +103,7 @@ class TestRgIntegration < Minitest::Unit::TestCase s = client_socket s.req_ok("rg mode=album_gain") pluck, _ = tmp_pluck - cmd = DTAS::Source::SOURCE_DEFAULTS["command"] + cmd = DTAS::Source::Sox::SOX_DEFAULTS["command"] fifo = tmpfifo s.req_ok("source ed command='env > #{fifo}; #{cmd}'") s.req_ok("sink ed default command='cat >/dev/null' active=true") diff --git a/test/test_source.rb b/test/test_source.rb deleted file mode 100644 index 21a56ac..0000000 --- a/test/test_source.rb +++ /dev/null @@ -1,102 +0,0 @@ -# -*- encoding: binary -*- -# Copyright (C) 2013, Eric Wong -# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) -require './test/helper' -require 'dtas/source' -require 'tempfile' - -class TestSource < Minitest::Unit::TestCase - def teardown - @tempfiles.each { |tmp| tmp.close! } - end - - def setup - @tempfiles = [] - end - - def x(cmd) - system(*cmd) - assert $?.success?, cmd.inspect - end - - def new_file(suffix) - tmp = Tempfile.new(%W(tmp .#{suffix})) - @tempfiles << tmp - cmd = %W(sox -r 44100 -b 16 -c 2 -n #{tmp.path} trim 0 1) - return tmp if system(*cmd) - nil - end - - def test_flac - return if `which metaflac`.strip.size == 0 - tmp = new_file('flac') or return - - source = DTAS::Source.new(tmp.path) - x(%W(metaflac --set-tag=FOO=BAR #{tmp.path})) - x(%W(metaflac --add-replay-gain #{tmp.path})) - assert_equal source.comments["FOO"], "BAR" - rg = source.replaygain - assert_kind_of DTAS::ReplayGain, rg - assert_in_delta 0.0, rg.track_peak.to_f, 0.00000001 - assert_in_delta 0.0, rg.album_peak.to_f, 0.00000001 - assert_operator rg.album_gain.to_f, :>, 1 - assert_operator rg.track_gain.to_f, :>, 1 - end - - def test_mp3gain - return if `which mp3gain`.strip.size == 0 - a = new_file('mp3') or return - b = new_file('mp3') or return - - source = DTAS::Source.new(a.path) - - # redirect stdout to /dev/null temporarily, mp3gain is noisy - File.open("/dev/null", "w") do |null| - old_out = $stdout.dup - $stdout.reopen(null) - begin - x(%W(mp3gain -q #{a.path} #{b.path})) - ensure - $stdout.reopen(old_out) - old_out.close - end - end - - rg = source.replaygain - assert_kind_of DTAS::ReplayGain, rg - assert_in_delta 0.0, rg.track_peak.to_f, 0.00000001 - assert_in_delta 0.0, rg.album_peak.to_f, 0.00000001 - assert_operator rg.album_gain.to_f, :>, 1 - assert_operator rg.track_gain.to_f, :>, 1 - end - - def test_offset - tmp = new_file('sox') or return - source = DTAS::Source.new(*%W(#{tmp.path} 5s)) - assert_equal 5, source.offset_samples - - source = DTAS::Source.new(*%W(#{tmp.path} 1:00:00.5)) - expect = 1 * 60 * 60 * 44100 + (44100/2) - assert_equal expect, source.offset_samples - - source = DTAS::Source.new(*%W(#{tmp.path} 1:10.5)) - expect = 1 * 60 * 44100 + (10 * 44100) + (44100/2) - assert_equal expect, source.offset_samples - - source = DTAS::Source.new(*%W(#{tmp.path} 10.03)) - expect = (10 * 44100) + (44100 * 3/100.0) - assert_equal expect, source.offset_samples - end - - def test_offset_us - tmp = new_file('sox') or return - source = DTAS::Source.new(*%W(#{tmp.path} 441s)) - assert_equal 10000.0, source.offset_us - - source = DTAS::Source.new(*%W(#{tmp.path} 22050s)) - assert_equal 500000.0, source.offset_us - - source = DTAS::Source.new(tmp.path, '1') - assert_equal 1000000.0, source.offset_us - end -end diff --git a/test/test_source_sox.rb b/test/test_source_sox.rb new file mode 100644 index 0000000..6bf5071 --- /dev/null +++ b/test/test_source_sox.rb @@ -0,0 +1,102 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require './test/helper' +require 'dtas/source/sox' +require 'tempfile' + +class TestSource < Minitest::Unit::TestCase + def teardown + @tempfiles.each { |tmp| tmp.close! } + end + + def setup + @tempfiles = [] + end + + def x(cmd) + system(*cmd) + assert $?.success?, cmd.inspect + end + + def new_file(suffix) + tmp = Tempfile.new(%W(tmp .#{suffix})) + @tempfiles << tmp + cmd = %W(sox -r 44100 -b 16 -c 2 -n #{tmp.path} trim 0 1) + return tmp if system(*cmd) + nil + end + + def test_flac + return if `which metaflac`.strip.size == 0 + tmp = new_file('flac') or return + + source = DTAS::Source::Sox.new(tmp.path) + x(%W(metaflac --set-tag=FOO=BAR #{tmp.path})) + x(%W(metaflac --add-replay-gain #{tmp.path})) + assert_equal source.comments["FOO"], "BAR" + rg = source.replaygain + assert_kind_of DTAS::ReplayGain, rg + assert_in_delta 0.0, rg.track_peak.to_f, 0.00000001 + assert_in_delta 0.0, rg.album_peak.to_f, 0.00000001 + assert_operator rg.album_gain.to_f, :>, 1 + assert_operator rg.track_gain.to_f, :>, 1 + end + + def test_mp3gain + return if `which mp3gain`.strip.size == 0 + a = new_file('mp3') or return + b = new_file('mp3') or return + + source = DTAS::Source::Sox.new(a.path) + + # redirect stdout to /dev/null temporarily, mp3gain is noisy + File.open("/dev/null", "w") do |null| + old_out = $stdout.dup + $stdout.reopen(null) + begin + x(%W(mp3gain -q #{a.path} #{b.path})) + ensure + $stdout.reopen(old_out) + old_out.close + end + end + + rg = source.replaygain + assert_kind_of DTAS::ReplayGain, rg + assert_in_delta 0.0, rg.track_peak.to_f, 0.00000001 + assert_in_delta 0.0, rg.album_peak.to_f, 0.00000001 + assert_operator rg.album_gain.to_f, :>, 1 + assert_operator rg.track_gain.to_f, :>, 1 + end + + def test_offset + tmp = new_file('sox') or return + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 5s)) + assert_equal 5, source.offset_samples + + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 1:00:00.5)) + expect = 1 * 60 * 60 * 44100 + (44100/2) + assert_equal expect, source.offset_samples + + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 1:10.5)) + expect = 1 * 60 * 44100 + (10 * 44100) + (44100/2) + assert_equal expect, source.offset_samples + + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 10.03)) + expect = (10 * 44100) + (44100 * 3/100.0) + assert_equal expect, source.offset_samples + end + + def test_offset_us + tmp = new_file('sox') or return + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 441s)) + assert_equal 10000.0, source.offset_us + + source = DTAS::Source::Sox.new(*%W(#{tmp.path} 22050s)) + assert_equal 500000.0, source.offset_us + + source = DTAS::Source::Sox.new(tmp.path, '1') + assert_equal 1000000.0, source.offset_us + end +end -- cgit v1.2.3-24-ge0c7