diff options
Diffstat (limited to 'lib/dtas/source')
-rw-r--r-- | lib/dtas/source/cmd.rb (renamed from lib/dtas/source/command.rb) | 2 | ||||
-rw-r--r-- | lib/dtas/source/file.rb | 63 | ||||
-rw-r--r-- | lib/dtas/source/mp3gain.rb (renamed from lib/dtas/source/mp3.rb) | 2 | ||||
-rw-r--r-- | lib/dtas/source/sox.rb | 100 |
4 files changed, 165 insertions, 2 deletions
diff --git a/lib/dtas/source/command.rb b/lib/dtas/source/cmd.rb index 930c5cf..2507101 100644 --- a/lib/dtas/source/command.rb +++ b/lib/dtas/source/cmd.rb @@ -6,7 +6,7 @@ require_relative '../source' require_relative '../command' require_relative '../serialize' -class DTAS::Source::Command # :nodoc: +class DTAS::Source::Cmd # :nodoc: require_relative '../source/common' include DTAS::Command 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 <normalperson@yhbt.net> +# 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/mp3gain.rb index 7ceaf8a..03bc37a 100644 --- a/lib/dtas/source/mp3.rb +++ b/lib/dtas/source/mp3gain.rb @@ -3,7 +3,7 @@ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) require_relative '../process' -module DTAS::Source::Mp3 # :nodoc: +module DTAS::Source::Mp3gain # :nodoc: include DTAS::Process # we use dBFS = 1.0 as scale (not 32768) def __mp3gain_peak(str) 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 <normalperson@yhbt.net> +# 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 |