diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/dtas/player.rb | 7 | ||||
-rw-r--r-- | lib/dtas/player/client_handler.rb | 2 | ||||
-rw-r--r-- | lib/dtas/source/av.rb | 7 | ||||
-rw-r--r-- | lib/dtas/source/av_ff_common.rb | 43 | ||||
-rw-r--r-- | lib/dtas/source/ff.rb | 8 | ||||
-rw-r--r-- | lib/dtas/source/sox.rb | 5 | ||||
-rw-r--r-- | lib/dtas/splitfx.rb | 27 | ||||
-rw-r--r-- | lib/dtas/unix_accepted.rb | 4 | ||||
-rw-r--r-- | lib/dtas/unix_client.rb | 2 |
9 files changed, 75 insertions, 30 deletions
diff --git a/lib/dtas/player.rb b/lib/dtas/player.rb index 06ba788..6ea3aba 100644 --- a/lib/dtas/player.rb +++ b/lib/dtas/player.rb @@ -37,6 +37,7 @@ class DTAS::Player # :nodoc: @paused = false @format = DTAS::Format.new @bypass = [] # %w(rate bits channels) (not worth Hash overhead) + @bypass_next = nil # source_spec @sinks = {} # { user-defined name => sink } @targets = [] # order matters @@ -331,6 +332,7 @@ class DTAS::Player # :nodoc: # called when the player is leaving idle state def spawn_sinks(source_spec) + @bypass_next = nil return true if @targets[0] @sinks.each_value do |sink| sink.active or next @@ -392,6 +394,8 @@ class DTAS::Player # :nodoc: if ! @bypass.empty? && pending.respond_to?(:format) new_fmt = bypass_match!(@format.dup, pending.format) if new_fmt != @format + @bypass_next = source_spec + return if @sink_buf.inflight > 0 stop_sinks # we may fail to start below format_update!(new_fmt) end @@ -434,6 +438,7 @@ class DTAS::Player # :nodoc: end def stop_sinks + @bypass_next = nil @targets.each { |t| drop_target(t) }.clear end @@ -458,7 +463,9 @@ class DTAS::Player # :nodoc: end # nothing left inflight, stop the sinks until we have a source + bn = @bypass_next stop_sinks + next_source(bn) if bn # are we restarting for bypass? :ignore end diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb index 2914fe7..3c5fe5d 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -564,7 +564,7 @@ module DTAS::Player::ClientHandler # :nodoc: rescue => e res = "ERR dumping to #{xs(sf.path)} #{e.message}" end - io.to_io.send(res, Socket::MSG_EOR) + io.to_io.send(res, 0) ensure exit!(0) end diff --git a/lib/dtas/source/av.rb b/lib/dtas/source/av.rb index 39cad6c..dcebcfd 100644 --- a/lib/dtas/source/av.rb +++ b/lib/dtas/source/av.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> +# Copyright (C) all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require_relative '../../dtas' @@ -13,13 +13,12 @@ class DTAS::Source::Av # :nodoc: 'avconv -v error $SSPOS $PROBE -i "$INFILE" $AMAP -f sox - |' \ 'sox -p $SOXFMT - $TRIMFX $RGFX', - # this is above ffmpeg because this av is the Debian default and - # it's easier for me to test av than ff - "tryorder" => 1, + "tryorder" => 2, ) def initialize command_init(AV_DEFAULTS) + @mcache = nil @av_ff_probe = "avprobe" end diff --git a/lib/dtas/source/av_ff_common.rb b/lib/dtas/source/av_ff_common.rb index 5299fdb..7f197e0 100644 --- a/lib/dtas/source/av_ff_common.rb +++ b/lib/dtas/source/av_ff_common.rb @@ -7,10 +7,10 @@ require_relative '../replaygain' require_relative '../xs' require_relative 'file' -# Common code for libav (avconv/avprobe) and ffmpeg (and ffprobe) -# TODO: newer versions of both *probes support JSON, which will be easier to -# parse. However, the packaged libav version in Debian 7.0 does not -# support JSON, so we have an ugly parser... +# Common code for ffmpeg/ffprobe and the abandoned libav (avconv/avprobe). +# TODO: newer versions of both *probes support JSON, which will be easier +# to parse. libav is abandoned, nowadays, and Debian only packages +# ffmpeg+ffprobe nowadays. module DTAS::Source::AvFfCommon # :nodoc: include DTAS::Source::File include DTAS::XS @@ -21,10 +21,23 @@ module DTAS::Source::AvFfCommon # :nodoc: attr_reader :format attr_reader :duration + CACHE_KEYS = [ :@duration, :@probe_harder, :@comments, :@astreams, + :@format ].freeze + + def mcache_lookup(infile) + (@mcache ||= DTAS::Mcache.new).lookup(infile) do |input, dst| + tmp = source_file_dup(infile, nil, nil) + tmp.av_ff_ok? or return nil + CACHE_KEYS.each { |k| dst[k] = tmp.instance_variable_get(k) } + dst + end + end + def try(infile, offset = nil, trim = nil) - rv = source_file_dup(infile, offset, trim) - rv.av_ff_ok? or return - rv + ent = mcache_lookup(infile) or return + ret = source_file_dup(infile, offset, trim) + CACHE_KEYS.each { |k| ret.instance_variable_set(k, ent[k]) } + ret end def __parse_astream(cmd, stream) @@ -79,7 +92,7 @@ module DTAS::Source::AvFfCommon # :nodoc: err = "".b begin - s = qx(@env, cmd, err_str: err, no_raise: true) + s = qx(@env, cmd, err_str: err, no_raise: true, rlimit_cpu: [ 1, 2 ]) rescue Errno::ENOENT # avprobe/ffprobe not installed return false end @@ -104,13 +117,14 @@ module DTAS::Source::AvFfCommon # :nodoc: prev_cmd = cmd end while incomplete.compact[0] + enc = Encoding.default_external # typically Encoding::UTF_8 # old avprobe s.scan(%r{^\[FORMAT\]\n(.*?)\n\[/FORMAT\]\n}m) do |_| f = $1.dup f =~ /^duration=([\d\.]+)\s*$/nm and @duration = $1.to_f # TODO: multi-line/multi-value/repeated tags f.gsub!(/^TAG:([^=]+)=(.*)$/ni) { |_| - @comments[$1.upcase] = -($2) + @comments[-DTAS.try_enc($1.upcase, enc)] = $2 } end @@ -118,13 +132,22 @@ module DTAS::Source::AvFfCommon # :nodoc: s.scan(%r{^\[format\.tags\]\n(.*?)\n\n}m) do |_| f = $1.dup f.gsub!(/^([^=]+)=(.*)$/ni) { |_| - @comments[$1.upcase] = -$2 + @comments[-DTAS.try_enc($1.upcase, enc)] = $2 } end s.scan(%r{^\[format\]\n(.*?)\n\n}m) do |_| f = $1.dup f =~ /^duration=([\d\.]+)\s*$/nm and @duration = $1.to_f end + comments.each do |k,v| + v.chomp! + comments[k] = -DTAS.try_enc(v, enc) + end + + # ffprobe always uses "track", favor FLAC convention "TRACKNUMBER": + if @comments['TRACK'] && !@comments['TRACKNUMBER'] + @comments['TRACKNUMBER'] = @comments.delete('TRACK') + end ! @astreams.compact.empty? end diff --git a/lib/dtas/source/ff.rb b/lib/dtas/source/ff.rb index 687cd18..c337b42 100644 --- a/lib/dtas/source/ff.rb +++ b/lib/dtas/source/ff.rb @@ -1,12 +1,10 @@ -# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> +# Copyright (C) all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require_relative '../../dtas' require_relative 'av_ff_common' # ffmpeg support -# note: only tested with the compatibility wrapper in the Debian 7.0 package -# (so still using avconv/avprobe) class DTAS::Source::Ff # :nodoc: include DTAS::Source::AvFfCommon @@ -15,12 +13,12 @@ class DTAS::Source::Ff # :nodoc: 'ffmpeg -v error $SSPOS $PROBE -i "$INFILE" $AMAP -f sox - |' \ 'sox -p $SOXFMT - $TRIMFX $RGFX', - # I haven't tested this much since av is in Debian stable and ff is not - "tryorder" => 2, + "tryorder" => 1, ) def initialize command_init(FF_DEFAULTS) + @mcache = nil @av_ff_probe = "ffprobe" end diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb index 6ca29bc..365c7b6 100644 --- a/lib/dtas/source/sox.rb +++ b/lib/dtas/source/sox.rb @@ -24,7 +24,7 @@ class DTAS::Source::Sox # :nodoc: return if @last_failed == infile @last_failed = infile case msg - when Process::Status then msg = "failed with #{msg.exitstatus}" + when Process::Status then msg = "failed with #{msg.inspect}" when 0 then msg = 'detected zero samples' end warn("soxi #{infile}: #{msg}\n") @@ -39,7 +39,8 @@ class DTAS::Source::Sox # :nodoc: def mcache_lookup(infile) (@mcache ||= DTAS::Mcache.new).lookup(infile) do |input, dst| err = ''.b - out = qx(@env.dup, %W(soxi #{input}), err_str: err, no_raise: true) + out = qx(@env.dup, %W(soxi #{input}), err_str: err, no_raise: true, + rlimit_cpu: [ 1, 2 ]) return soxi_failed(infile, out) if Process::Status === out return soxi_failed(infile, err) if err =~ /soxi FAIL formats:/ out =~ /^Duration\s*:[^=]*= (\d+) samples /n diff --git a/lib/dtas/splitfx.rb b/lib/dtas/splitfx.rb index c7eaf42..b94f54b 100644 --- a/lib/dtas/splitfx.rb +++ b/lib/dtas/splitfx.rb @@ -10,7 +10,8 @@ require 'tempfile' # Unlike the stuff for dtas-player, dtas-splitfx is fairly tied to sox # (but we may still pipe to ecasound or anything else) class DTAS::SplitFX # :nodoc: - CMD = 'sox "$INFILE" $COMMENTS $OUTFMT $OUTDST $TRIMFX $FX $RATEFX $DITHERFX' + CMD = 'sox "$INFILE" $COMMENTS $OUTFMT $OUTDST $TRIMFX $FX' \ + ' $RATEFX $DITHERFX $STATS' include DTAS::Process attr_reader :infile, :env, :command @@ -65,6 +66,7 @@ class DTAS::SplitFX # :nodoc: # $CHANNELS (input) # $BITS_PER_SAMPLE (input) def initialize + @tshift = 0 @env = {} @comments = {} @track_start = 1 @@ -204,6 +206,7 @@ class DTAS::SplitFX # :nodoc: elsif outfmt.bits && outfmt.bits <= 16 env["DITHERFX"] = "dither -s" end + env['STATS'] = 'stats' if opts[:stats] comments = Tempfile.new(%W(dtas-splitfx-#{t.comments["TRACKNUMBER"]} .txt)) t.comments.each do |k,v| env[k] = v.to_s @@ -288,8 +291,9 @@ class DTAS::SplitFX # :nodoc: start_time = argv.shift title = argv.shift t = T.new - t.tbeg = @t2s.call(start_time) + t.tbeg = @t2s.call(start_time) + @tshift t.comments = @comments.dup + title.valid_encoding? or warn "#{title.inspect} encoding invalid" t.comments["TITLE"] = title t.env = @env.dup @@ -308,11 +312,24 @@ class DTAS::SplitFX # :nodoc: prev = @tracks.last and prev.commit(t.tbeg) @tracks << t + when 'tshift' + tshift = argv.shift + argv.empty? or raise ArgumentError, 'tshift does not take extra args' + if tshift.sub!(/\A-=/, '') + @tshift = @tshift - @t2s.call(tshift) + elsif tshift.sub!(/\A\+=/, '') + @tshift = @tshift + @t2s.call(tshift) + elsif tshift.sub!(/\A-/, '') + @tshift = -@t2s.call(tshift) + else + tshift.sub!(/\A\+/, '') + @tshift = @t2s.call(tshift) + end when "skip" stop_time = argv.shift argv.empty? or raise ArgumentError, "skip does not take extra args" s = Skip.new - s.tbeg = @t2s.call(stop_time) + s.tbeg = @t2s.call(stop_time) + @tshift # s.comments = {} # s.env = {} prev = @tracks.last or raise ArgumentError, "no tracks to skip" @@ -321,7 +338,7 @@ class DTAS::SplitFX # :nodoc: when "stop" stop_time = argv.shift argv.empty? or raise ArgumentError, "stop does not take extra args" - samples = @t2s.call(stop_time) + samples = @t2s.call(stop_time) + @tshift prev = @tracks.last and prev.commit(samples) else raise ArgumentError, "unknown command: #{xs(cmd)}" @@ -395,7 +412,7 @@ class DTAS::SplitFX # :nodoc: @out.puts "DONE #{done[0].inspect}" if $DEBUG done[1].close! else - fails << [ t, status ] + fails << [ done[0], status ] end end diff --git a/lib/dtas/unix_accepted.rb b/lib/dtas/unix_accepted.rb index a84eade..63d3ce0 100644 --- a/lib/dtas/unix_accepted.rb +++ b/lib/dtas/unix_accepted.rb @@ -17,7 +17,7 @@ class DTAS::UNIXAccepted # :nodoc: # returns :wait_readable on success def emit(msg) if @sbuf.empty? - case rv = @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR, exception: false) + case rv = @to_io.sendmsg_nonblock(msg, 0, exception: false) when :wait_writable @sbuf << msg rv @@ -34,7 +34,7 @@ class DTAS::UNIXAccepted # :nodoc: # flushes pending data if it got buffered def writable_iter - case @to_io.sendmsg_nonblock(@sbuf[0], Socket::MSG_EOR, exception: false) + case @to_io.sendmsg_nonblock(@sbuf[0], 0, exception: false) when :wait_writable then return :wait_writable else @sbuf.shift diff --git a/lib/dtas/unix_client.rb b/lib/dtas/unix_client.rb index 71f833c..8c73b7d 100644 --- a/lib/dtas/unix_client.rb +++ b/lib/dtas/unix_client.rb @@ -24,7 +24,7 @@ class DTAS::UNIXClient # :nodoc: def req_start(args) args = xs(args) if Array === args - @to_io.send(args, Socket::MSG_EOR) + @to_io.send(args, 0) end def req_ok(args, timeout = nil) |