diff options
Diffstat (limited to 'lib/dtas')
34 files changed, 171 insertions, 212 deletions
diff --git a/lib/dtas/buffer.rb b/lib/dtas/buffer.rb index 54487c5..0688af9 100644 --- a/lib/dtas/buffer.rb +++ b/lib/dtas/buffer.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 'io/wait' @@ -45,7 +45,7 @@ class DTAS::Buffer # :nodoc: def __dst_error(dst, e) warn "dropping #{dst.inspect} due to error: #{e.message} (#{e.class})" - dst.close unless dst.closed? + dst.close end # This will modify targets diff --git a/lib/dtas/buffer/fiddle_splice.rb b/lib/dtas/buffer/fiddle_splice.rb index ad007eb..d9232cd 100644 --- a/lib/dtas/buffer/fiddle_splice.rb +++ b/lib/dtas/buffer/fiddle_splice.rb @@ -84,7 +84,8 @@ module DTAS::Buffer::FiddleSplice # :nodoc: targets # our one and only target blocked on write else @bytes_xfer += s - :wait_readable # we want to read more from @to_io soon + # s < limit means targets[0] is full + s < limit ? targets : :wait_readable end rescue Errno::EPIPE, IOError => e __dst_error(targets[0], e) diff --git a/lib/dtas/buffer/read_write.rb b/lib/dtas/buffer/read_write.rb index fdf820c..8fdb25d 100644 --- a/lib/dtas/buffer/read_write.rb +++ b/lib/dtas/buffer/read_write.rb @@ -1,10 +1,9 @@ -# 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 'io/nonblock' require_relative '../../dtas' require_relative '../pipe' -require_relative '../nonblock' # compatibility code for non-Linux systems lacking "splice" support. # Used only by -player diff --git a/lib/dtas/buffer/splice.rb b/lib/dtas/buffer/splice.rb index e5d17ab..b9957ce 100644 --- a/lib/dtas/buffer/splice.rb +++ b/lib/dtas/buffer/splice.rb @@ -39,7 +39,8 @@ module DTAS::Buffer::Splice # :nodoc: targets # our one and only target blocked on write else @bytes_xfer += s - :wait_readable # we want to read more from @to_io soon + # s < limit means targets[0] is full + s < limit ? targets : :wait_readable end rescue Errno::EPIPE, IOError => e __dst_error(targets[0], e) diff --git a/lib/dtas/compat_onenine.rb b/lib/dtas/compat_onenine.rb deleted file mode 100644 index b65ea50..0000000 --- a/lib/dtas/compat_onenine.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> -# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> - -# Make Ruby 1.9.3 look like Ruby 2.0.0 to us -# This exists for Debian wheezy users using the stock Ruby 1.9.3 install. -# We'll drop this interface when Debian wheezy (7.0) becomes unsupported. -class String # :nodoc: - def b # :nodoc: - dup.force_encoding(Encoding::BINARY) - end -end unless String.method_defined?(:b) - -def IO # :nodoc: - def self.pipe # :nodoc: - super.each { |io| io.close_on_exec = true } - end -end if RUBY_VERSION.to_f <= 1.9 diff --git a/lib/dtas/edit_client.rb b/lib/dtas/edit_client.rb index a885060..2bdc4d8 100644 --- a/lib/dtas/edit_client.rb +++ b/lib/dtas/edit_client.rb @@ -1,8 +1,7 @@ -# 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 'tempfile' -require 'yaml' require_relative 'unix_client' require_relative 'disclaimer' @@ -14,7 +13,7 @@ module DTAS::EditClient # :nodoc: v.empty? and next return v end - 'vi'.freeze + 'vi' end def client_socket diff --git a/lib/dtas/fadefx.rb b/lib/dtas/fadefx.rb index 7bccff8..0ec108c 100644 --- a/lib/dtas/fadefx.rb +++ b/lib/dtas/fadefx.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' @@ -95,7 +95,7 @@ class DTAS::FadeFX # :nodoc: def parse!(str) return nil if str.empty? type = "t" - str.sub!(/\A([a-z])/, "") and type = DTAS.dedupe_str($1) + str.sub!(/\A([a-z])/, "") and type = -$1 F.new(type, parse_time(str)) end end diff --git a/lib/dtas/mcache.rb b/lib/dtas/mcache.rb index 817bfb8..e0a39af 100644 --- a/lib/dtas/mcache.rb +++ b/lib/dtas/mcache.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2016-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 # encoding: binary @@ -13,16 +13,27 @@ class DTAS::Mcache def lookup(infile) bucket = infile.hash & @mask + st = nil if cur = @tbl[bucket] if cur[:infile] == infile && (DTAS.now - cur[:btime]) < @ttl - return cur + begin + st = File.stat(infile) + return cur if cur[:ctime] == st.ctime + rescue + end end end return unless block_given? @tbl[bucket] = begin cur = cur ? cur.clear : {} + begin + st ||= File.stat(infile) + cur[:ctime] = st.ctime + rescue + return + end if ret = yield(infile, cur) - ret[:infile] = infile.frozen? ? infile : infile.dup.freeze + ret[:infile] = infile.frozen? ? infile : -(infile.dup) ret[:btime] = DTAS.now end ret diff --git a/lib/dtas/mlib.rb b/lib/dtas/mlib.rb index eb7554a..f99ed6a 100644 --- a/lib/dtas/mlib.rb +++ b/lib/dtas/mlib.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -# Copyright (C) 2015-2021 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 # @@ -201,9 +201,7 @@ class DTAS::Mlib # :nodoc: tag_id = tag_map[x] and tag_map["#{x}number"] = tag_id end @tag_rmap = tag_map.invert.freeze - tag_map.merge!(Hash[*(tag_map.map { |k,v| - [DTAS.dedupe_str(k.upcase), v] - }.flatten!)]) + tag_map.merge!(Hash[*(tag_map.map { |k,v| [-(k.upcase), v] }.flatten!)]) @tag_map = tag_map.freeze end @@ -421,7 +419,7 @@ class DTAS::Mlib # :nodoc: return '/' if base == '' # root_node parent_id = node[:parent_id] base += '/' unless node[:tlen] >= 0 - ppath = cache[parent_id] and return DTAS.dedupe_str("#{ppath}/#{base}") + ppath = cache[parent_id] and return -"#{ppath}/#{base}" parts = [] begin node = @db[:nodes][id: node[:parent_id]] @@ -429,9 +427,9 @@ class DTAS::Mlib # :nodoc: parts.unshift node[:name] end while true parts.unshift('') - cache[parent_id] = DTAS.dedupe_str(parts.join('/')) + cache[parent_id] = -(parts.join('/')) parts << base - DTAS.dedupe_str(parts.join('/')) + -(parts.join('/')) end def emit_recurse(node, cache, cb) diff --git a/lib/dtas/nonblock.rb b/lib/dtas/nonblock.rb deleted file mode 100644 index 2cf086c..0000000 --- a/lib/dtas/nonblock.rb +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (C) 2015-2020 all contributors <dtas-all@nongnu.org> -# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> - -class DTAS::Nonblock < IO # :nodoc: - if RUBY_VERSION.to_f <= 2.0 - EX = {}.freeze - def read_nonblock(len, buf = nil, opts = EX) - super(len, buf) - rescue IO::WaitReadable - raise if opts[:exception] - :wait_readable - rescue EOFError - raise if opts[:exception] - nil - end - - def write_nonblock(buf, opts = EX) - super(buf) - rescue IO::WaitWritable - raise if opts[:exception] - :wait_writable - end - end -end diff --git a/lib/dtas/partstats.rb b/lib/dtas/partstats.rb index 45eff34..061ff50 100644 --- a/lib/dtas/partstats.rb +++ b/lib/dtas/partstats.rb @@ -1,5 +1,5 @@ # -*- encoding: binary -*- -# 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' @@ -11,7 +11,6 @@ require_relative 'sigevent' class DTAS::PartStats # :nodoc: CMD = 'sox "$INFILE" -n $TRIMFX $SOXFX stats $STATSOPTS' include DTAS::Process - include DTAS::SpawnFix attr_reader :key_idx attr_reader :key_width @@ -172,7 +171,7 @@ becomes: else next end - key = DTAS.dedupe_str($1) + key = -$1 key_idx = @key_idx[key] parts = line.split(/\s+/) nshift.times { parts.shift } # remove stuff we don't need diff --git a/lib/dtas/pipe.rb b/lib/dtas/pipe.rb index 34d50bd..a7b02b0 100644 --- a/lib/dtas/pipe.rb +++ b/lib/dtas/pipe.rb @@ -1,12 +1,11 @@ -# 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 'writable_iter' -require_relative 'nonblock' # pipe wrapper for -player sinks -class DTAS::Pipe < DTAS::Nonblock # :nodoc: +class DTAS::Pipe < IO # :nodoc: include DTAS::WritableIter attr_accessor :sink diff --git a/lib/dtas/pipeline.rb b/lib/dtas/pipeline.rb index eb2af89..1bebe87 100644 --- a/lib/dtas/pipeline.rb +++ b/lib/dtas/pipeline.rb @@ -1,12 +1,9 @@ -# Copyright (C) 2017-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 'spawn_fix' module DTAS::Pipeline # :nodoc: - include DTAS::SpawnFix - # Process.spawn wrapper which supports running Proc-like objects in # a separate process, not just external commands. # Returns the pid of the spawned process diff --git a/lib/dtas/player.rb b/lib/dtas/player.rb index b39a2e7..6ea3aba 100644 --- a/lib/dtas/player.rb +++ b/lib/dtas/player.rb @@ -1,8 +1,8 @@ -# 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 'yaml' require 'shellwords' +require 'yaml' require_relative '../dtas' require_relative 'xs' require_relative 'source' @@ -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 @@ -123,10 +124,6 @@ class DTAS::Player # :nodoc: rv end - def to_omap(hash) - YAML::Omap === hash ? hash : YAML::Omap.new.merge!(hash) - end - def self.load(hash) rv = new rv.instance_eval do @@ -157,7 +154,6 @@ class DTAS::Player # :nodoc: @source_map.each do |name, src| src_hsh = v[name] or next src.load!(src_hsh) - src.env = to_omap(src.env) end source_map_reload end @@ -168,9 +164,8 @@ class DTAS::Player # :nodoc: if sinks = hash["sinks"] sinks.each do |sink_hsh| - sink_hsh['name'] = DTAS.dedupe_str(sink_hsh['name']) + sink_hsh['name'] = -sink_hsh['name'] sink = DTAS::Sink.load(sink_hsh) - sink.env = to_omap(sink.env) @sinks[sink.name] = sink end end @@ -208,13 +203,13 @@ class DTAS::Player # :nodoc: command = msg.shift case command when "enq" - enq_handler(io, msg[0]) + enq_handler(io, -msg[0]) when "enq-cmd" - enq_handler(io, { "command" => msg[0]}) + enq_handler(io, { "command" => -msg[0]}) when "pause", "play", "play_pause" play_pause_handler(io, command) when "pwd" - io.emit(Dir.pwd) + io.emit(-Dir.pwd) else m = "dpc_#{command.tr('-', '_')}" __send__(m, io, msg) if respond_to?(m) @@ -337,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 @@ -398,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 @@ -440,6 +438,7 @@ class DTAS::Player # :nodoc: end def stop_sinks + @bypass_next = nil @targets.each { |t| drop_target(t) }.clear end @@ -464,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 cf5442d..3c5fe5d 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.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 '../xs' @@ -135,7 +135,7 @@ module DTAS::Player::ClientHandler # :nodoc: # or variable names. sink.valid_name?(name) or return io.emit("ERR sink name invalid") - sink.name = DTAS.dedupe_str(name) + sink.name = -name active_before = sink.active before = __sink_snapshot(sink) @@ -144,7 +144,7 @@ module DTAS::Player::ClientHandler # :nodoc: k, v = kv.split('=', 2) case k when %r{\Aenv\.([^=]+)\z} - sink.env[DTAS.dedupe_str($1)] = v + sink.env[$1] = v when %r{\Aenv#([^=]+)\z} v == nil or return io.emit("ERR unset env has no value") sink.env.delete($1) @@ -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/process.rb b/lib/dtas/process.rb index f93a8c4..02bf77e 100644 --- a/lib/dtas/process.rb +++ b/lib/dtas/process.rb @@ -1,17 +1,15 @@ -# 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 'io/wait' require 'shellwords' require_relative '../dtas' require_relative 'xs' -require_relative 'nonblock' # process management helpers module DTAS::Process # :nodoc: PIDS = {} include DTAS::XS - include DTAS::SpawnFix def self.reaper begin @@ -89,12 +87,12 @@ module DTAS::Process # :nodoc: env = {} end buf = ''.b - r, w = DTAS::Nonblock.pipe + r, w = IO.pipe opts = opts.merge(out: w) r.binmode no_raise = opts.delete(:no_raise) if err_str = opts.delete(:err_str) - re, we = DTAS::Nonblock.pipe + re, we = IO.pipe re.binmode opts[:err] = we end diff --git a/lib/dtas/rg_state.rb b/lib/dtas/rg_state.rb index d463bd8..9a44835 100644 --- a/lib/dtas/rg_state.rb +++ b/lib/dtas/rg_state.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 # @@ -72,7 +72,7 @@ class DTAS::RGState # :nodoc: when 1 then return 'gain 192' else val.abs <= 0.00000001 and return - DTAS.dedupe_str(sprintf('gain %0.8f', val)) + -sprintf('gain %0.8f', val) end end diff --git a/lib/dtas/sigevent/fiddle_efd.rb b/lib/dtas/sigevent/fiddle_efd.rb index 40cec77..8bfa332 100644 --- a/lib/dtas/sigevent/fiddle_efd.rb +++ b/lib/dtas/sigevent/fiddle_efd.rb @@ -1,10 +1,9 @@ -# 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 # used in various places for safe wakeups from IO.select via signals # This requires a modern GNU/Linux system with eventfd(2) support -require_relative '../nonblock' require 'fiddle' class DTAS::Sigevent # :nodoc: @@ -13,12 +12,12 @@ class DTAS::Sigevent # :nodoc: Fiddle::TYPE_INT) # fd attr_reader :to_io - ONE = [ 1 ].pack('Q').freeze + ONE = -([ 1 ].pack('Q')) def initialize fd = EventFD.call(0, 02000000|00004000) # EFD_CLOEXEC|EFD_NONBLOCK raise "eventfd failed: #{Fiddle.last_error}" if fd < 0 - @to_io = DTAS::Nonblock.for_fd(fd) + @to_io = IO.for_fd(fd) @buf = ''.b end diff --git a/lib/dtas/sigevent/pipe.rb b/lib/dtas/sigevent/pipe.rb index e6fbbf2..6c3b83c 100644 --- a/lib/dtas/sigevent/pipe.rb +++ b/lib/dtas/sigevent/pipe.rb @@ -1,15 +1,14 @@ -# 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 # used in various places for safe wakeups from IO.select via signals # A fallback for non-Linux systems lacking the "splice" syscall -require_relative '../nonblock' class DTAS::Sigevent # :nodoc: attr_reader :to_io def initialize - @to_io, @wr = DTAS::Nonblock.pipe + @to_io, @wr = IO.pipe @rbuf = ''.b end diff --git a/lib/dtas/sink.rb b/lib/dtas/sink.rb index 735cdef..966bab4 100644 --- a/lib/dtas/sink.rb +++ b/lib/dtas/sink.rb @@ -1,7 +1,6 @@ -# 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 'yaml' require_relative '../dtas' require_relative 'pipe' require_relative 'process' 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 6f92762..7f197e0 100644 --- a/lib/dtas/source/av_ff_common.rb +++ b/lib/dtas/source/av_ff_common.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' @@ -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[DTAS.dedupe_str($1.upcase)] = DTAS.dedupe_str($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[DTAS.dedupe_str($1.upcase)] = DTAS.dedupe_str($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 3a7fe7d..365c7b6 100644 --- a/lib/dtas/source/sox.rb +++ b/lib/dtas/source/sox.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 # encoding: binary @@ -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 @@ -56,14 +57,14 @@ class DTAS::Source::Sox # :nodoc: key = nil $1.split(/\n/n).each do |line| if line.sub!(/^([^=]+)=/ni, '') - key = DTAS.dedupe_str(DTAS.try_enc($1.upcase, enc)) + key = DTAS.try_enc($1.upcase, enc) end (comments[key] ||= ''.b) << "#{line}\n" unless line.empty? end comments.each do |k,v| v.chomp! DTAS.try_enc(v, enc) - comments[k] = DTAS.dedupe_str(v) + comments[k] = -v end end dst diff --git a/lib/dtas/source/splitfx.rb b/lib/dtas/source/splitfx.rb index 11e4190..2268404 100644 --- a/lib/dtas/source/splitfx.rb +++ b/lib/dtas/source/splitfx.rb @@ -1,7 +1,6 @@ -# Copyright (C) 2014-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 'yaml' require_relative 'sox' require_relative '../splitfx' require_relative '../watchable' @@ -36,7 +35,7 @@ class DTAS::Source::SplitFX < DTAS::Source::Sox # :nodoc: sfx = DTAS::SplitFX.new Dir.chdir(File.dirname(ymlfile)) do # ugh - @ymlhash = YAML.load(buf) + @ymlhash = DTAS.yaml_load(buf) @ymlhash['tracks'] ||= [ "t 0 default" ] sfx.import(@ymlhash) sfx.infile.replace(File.expand_path(sfx.infile)) diff --git a/lib/dtas/spawn_fix.rb b/lib/dtas/spawn_fix.rb deleted file mode 100644 index b586130..0000000 --- a/lib/dtas/spawn_fix.rb +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> -# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> -# workaround for older Rubies: https://bugs.ruby-lang.org/issues/8770 -module DTAS::SpawnFix # :nodoc: - def spawn(*args) - super(*args) - rescue Errno::EINTR - retry - end if RUBY_VERSION.to_f <= 2.1 -end diff --git a/lib/dtas/splitfx.rb b/lib/dtas/splitfx.rb index 9e1cfd0..b94f54b 100644 --- a/lib/dtas/splitfx.rb +++ b/lib/dtas/splitfx.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' @@ -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 @@ -113,7 +115,7 @@ class DTAS::SplitFX # :nodoc: end case v = hash["track_zpad"] - when Integer then @track_zpad = val + when Integer then @track_zpad = v else _bool(hash, "track_zpad") { |val| @track_zpad = val } end @@ -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 @@ -299,6 +303,7 @@ class DTAS::SplitFX # :nodoc: t.fade_in = $1.split(/\s+/) when %r{\Afade_out=(.+)\z} # $1 = "t 4" or just "4" t.fade_out = $1.split(/\s+/) + when %r{\Aenv\.([^=]+)=(.+)\z} then t.env[$1] = -$2 when %r{\A\.(\w+)=(.+)\z} then t.comments[$1] = $2 else raise ArgumentError, "unrecognized arg(s): #{xs(argv)}" @@ -307,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" @@ -320,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)}" @@ -356,9 +374,19 @@ class DTAS::SplitFX # :nodoc: @rate = opts[:rate] @bits = opts[:bits] trim = opts[:trim] and @tracks = [ UTrim.new(trim, @env, @comments) ] - + if trim && opts[:filter] + raise ArgumentError, 'trim and filter are mutually exclusive' + end fails = [] tracks = @tracks.dup + (opts[:filter] || []).each do |re| + field, val = re.split(/=/, 2) + if val + tracks.delete_if { |t| (t.comments[field] || '') !~ /#{val}/ } + else + tracks.delete_if { |t| t.comments.values.grep(/#{re}/).empty? } + end + end pids = {} jobs = opts[:jobs] || tracks.size # jobs == nil => everything at once if opts[:sox_pipe] @@ -384,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/state_file.rb b/lib/dtas/state_file.rb index eac3e2f..f16a866 100644 --- a/lib/dtas/state_file.rb +++ b/lib/dtas/state_file.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 'yaml' @@ -14,7 +14,7 @@ class DTAS::StateFile # :nodoc: end def tryload - YAML.load(IO.binread(@path)) if File.readable?(@path) + DTAS.yaml_load(IO.binread(@path)) if File.readable?(@path) end def dump(obj, force_fsync = false) diff --git a/lib/dtas/track.rb b/lib/dtas/track.rb index 85b667a..3f4b813 100644 --- a/lib/dtas/track.rb +++ b/lib/dtas/track.rb @@ -9,6 +9,6 @@ class DTAS::Track # :nodoc: def initialize(track_id, path) @track_id = track_id - @to_path = path + @to_path = -path end end diff --git a/lib/dtas/unix_accepted.rb b/lib/dtas/unix_accepted.rb index ec7f3ef..63d3ce0 100644 --- a/lib/dtas/unix_accepted.rb +++ b/lib/dtas/unix_accepted.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 'socket' @@ -10,23 +10,22 @@ class DTAS::UNIXAccepted # :nodoc: def initialize(sock) @to_io = sock - @send_buf = [] + @sbuf = [] end # public API (for DTAS::Player) # returns :wait_readable on success def emit(msg) - buffered = @send_buf.size - if buffered == 0 - case rv = sendmsg_nonblock(msg) + if @sbuf.empty? + case rv = @to_io.sendmsg_nonblock(msg, 0, exception: false) when :wait_writable - @send_buf << msg + @sbuf << msg rv else :wait_readable end - else # buffered > 0 - @send_buf << msg + else + @sbuf << msg :wait_writable end rescue => e @@ -35,11 +34,11 @@ class DTAS::UNIXAccepted # :nodoc: # flushes pending data if it got buffered def writable_iter - case sendmsg_nonblock(@send_buf[0]) + case @to_io.sendmsg_nonblock(@sbuf[0], 0, exception: false) when :wait_writable then return :wait_writable else - @send_buf.shift - @send_buf.empty? ? :wait_readable : :wait_writable + @sbuf.shift + @sbuf.empty? ? :wait_readable : :wait_writable end rescue => e e @@ -51,13 +50,13 @@ class DTAS::UNIXAccepted # :nodoc: # EOF, assume no spurious wakeups for SOCK_SEQPACKET return nil if nread == 0 - case msg = recv_nonblock(nread) + case msg = @to_io.recv_nonblock(nread, exception: false) when :wait_readable then return msg when '', nil then return nil # EOF else yield(self, msg) # DTAS::Player deals with this end - @send_buf.empty? ? :wait_readable : :wait_writable + @sbuf.empty? ? :wait_readable : :wait_writable rescue SystemCallError nil end @@ -69,28 +68,4 @@ class DTAS::UNIXAccepted # :nodoc: def closed? @to_io.closed? end - - if RUBY_VERSION.to_f >= 2.3 - def sendmsg_nonblock(msg) - @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR, exception: false) - end - - def recv_nonblock(len) - @to_io.recv_nonblock(len, exception: false) - end - else - def sendmsg_nonblock(msg) - @to_io.sendmsg_nonblock(msg, Socket::MSG_EOR) - rescue IO::WaitWritable - :wait_writable - end - - def recv_nonblock(len) - @to_io.recv_nonblock(len) - rescue IO::WaitReadable - :wait_readable - rescue EOFError - nil - end - end end diff --git a/lib/dtas/unix_client.rb b/lib/dtas/unix_client.rb index 8aa953c..8c73b7d 100644 --- a/lib/dtas/unix_client.rb +++ b/lib/dtas/unix_client.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' @@ -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) @@ -39,7 +39,7 @@ class DTAS::UNIXClient # :nodoc: end def res_wait(timeout = nil) - IO.select([@to_io], nil, nil, timeout) + @to_io.wait_readable(timeout) nr = @to_io.nread nr > 0 or raise EOFError, "unexpected EOF from server" @to_io.recv(nr) diff --git a/lib/dtas/unix_server.rb b/lib/dtas/unix_server.rb index cad3fc4..60ab86c 100644 --- a/lib/dtas/unix_server.rb +++ b/lib/dtas/unix_server.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 'socket' @@ -59,7 +59,7 @@ class DTAS::UNIXServer # :nodoc: def readable_iter # we do not do anything with the block passed to us - case rv = accept_nonblock + case rv = @to_io.accept_nonblock(exception: false) when :wait_readable then return rv else @readers[DTAS::UNIXAccepted.new(rv[0])] = true @@ -114,16 +114,4 @@ class DTAS::UNIXServer # :nodoc: wait_ctl(io, io.readable_iter { |_io, msg| yield(_io, msg) }) end end - - if RUBY_VERSION.to_f >= 2.3 - def accept_nonblock - @to_io.accept_nonblock(exception: false) - end - else - def accept_nonblock - @to_io.accept_nonblock - rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO - :wait_readable - end - end end diff --git a/lib/dtas/watchable.rb b/lib/dtas/watchable.rb index 6168bf3..445bf98 100644 --- a/lib/dtas/watchable.rb +++ b/lib/dtas/watchable.rb @@ -1,8 +1,7 @@ -# 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 'nonblock' begin module DTAS::Watchable # :nodoc: module InotifyCommon # :nodoc: diff --git a/lib/dtas/watchable/fiddle_ino.rb b/lib/dtas/watchable/fiddle_ino.rb index e85fea1..3ec72a1 100644 --- a/lib/dtas/watchable/fiddle_ino.rb +++ b/lib/dtas/watchable/fiddle_ino.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 'fiddle' @@ -22,7 +22,7 @@ class DTAS::Watchable::InotifyReadableIter # :nodoc: def initialize # :nodoc: fd = Inotify_init.call(02000000 | 04000) # CLOEXEC | NONBLOCK raise "inotify_init failed: #{Fiddle.last_error}" if fd < 0 - @to_io = DTAS::Nonblock.for_fd(fd) + @to_io = IO.for_fd(fd) @buf = ''.b @q = [] end @@ -53,7 +53,7 @@ class DTAS::Watchable::InotifyReadableIter # :nodoc: name.size == len or raise "short name #{name.inspect} != #{len}" name.sub!(/\0+\z/, '') or raise "missing: `\\0', inotify_event.name=#{name.inspect}" - name = DTAS.dedupe_str(name) + name = -name end ie = InotifyEvent.new(wd, mask, cookie, len, name) if event |