diff options
-rw-r--r-- | Documentation/dtas-archive.pod | 11 | ||||
-rw-r--r-- | Documentation/dtas-player.pod | 10 | ||||
-rw-r--r-- | Documentation/dtas-sourceedit.pod | 8 | ||||
-rw-r--r-- | Documentation/dtas-splitfx.pod | 9 | ||||
-rwxr-xr-x | GIT-VERSION-GEN | 2 | ||||
-rw-r--r-- | GNUmakefile | 2 | ||||
-rw-r--r-- | INSTALL | 6 | ||||
-rw-r--r-- | README | 4 | ||||
-rwxr-xr-x | bin/dtas-archive | 6 | ||||
-rwxr-xr-x | bin/dtas-splitfx | 1 | ||||
-rwxr-xr-x | bin/dtas-tl | 38 | ||||
-rw-r--r-- | dtas.gemspec | 2 | ||||
-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 | 41 | ||||
-rw-r--r-- | lib/dtas/source/ff.rb | 8 | ||||
-rw-r--r-- | lib/dtas/splitfx.rb | 7 | ||||
-rw-r--r-- | lib/dtas/unix_accepted.rb | 4 | ||||
-rw-r--r-- | lib/dtas/unix_client.rb | 2 | ||||
-rwxr-xr-x | script/dtas-2splitfx | 44 | ||||
-rw-r--r-- | test/test_player_integration.rb | 4 | ||||
-rw-r--r-- | test/test_source_ff.rb | 102 | ||||
-rw-r--r-- | test/test_unixserver.rb | 2 |
24 files changed, 266 insertions, 63 deletions
diff --git a/Documentation/dtas-archive.pod b/Documentation/dtas-archive.pod index 157ea70..50237e8 100644 --- a/Documentation/dtas-archive.pod +++ b/Documentation/dtas-archive.pod @@ -52,11 +52,20 @@ Continue after error Number of times to repeat the L<sndfile-cmp(1)> check. Default: 1 +=item -m, --match REGEX + +Only archive files matching a given Ruby (or Perl-compatible) regular +expression. The regular expression is implementation-dependent and +using the Perl-compatible subset of Ruby regexps is recommended as dtas +will be moving away from Ruby at some point. + +Added for dtas v0.22.0 + =back =head1 COPYRIGHT -Copyright 2013-2020 all contributors L<mailto:dtas-all@nongnu.org> +Copyright all contributors L<mailto:dtas-all@nongnu.org> License: GPL-3.0+ L<https://www.gnu.org/licenses/gpl-3.0.txt> diff --git a/Documentation/dtas-player.pod b/Documentation/dtas-player.pod index 932441a..4cfdd12 100644 --- a/Documentation/dtas-player.pod +++ b/Documentation/dtas-player.pod @@ -70,10 +70,10 @@ To play audio on my favorite USB DAC directly to ALSA, I use: =head2 Seeking/playing audio from large video containers (e.g. VOB) fails This is a problem with large VOBs. We recommend breaking up the -VOB into smaller files or using L<avconv(1)> or L<ffmpeg(1)> to extract -the desired audio stream. +VOB into smaller files or using L<ffmpeg(1)> to extract +the desired audio stream at C<$STREAM_NR>. - avconv -analyzeduration 2G -probesize 2G \ + ffmpeg -analyzeduration 2G -probesize 2G \ -i input.vob -vn -sn -c:a copy -map 0:$STREAM_NR output.ext =head1 ADVANCED EXAMPLES @@ -115,7 +115,7 @@ No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2020 all contributors L<mailto:dtas-all@nongnu.org> +Copyright all contributors L<mailto:dtas-all@nongnu.org> License: GPL-3.0+ L<https://www.gnu.org/licenses/gpl-3.0.txt> @@ -123,4 +123,4 @@ License: GPL-3.0+ L<https://www.gnu.org/licenses/gpl-3.0.txt> L<dtas-player_protocol(7)>, L<dtas-ctl(1)>, L<dtas-enq(1)>, L<dtas-sourceedit(1)>, L<dtas-sinkedit(1)>, L<sox(1)>, L<play(1)>, -L<avconv(1)>, L<ffmpeg(1)>, L<screen(1)>, L<tmux(1)> +L<ffmpeg(1)>, L<screen(1)>, L<tmux(1)> diff --git a/Documentation/dtas-sourceedit.pod b/Documentation/dtas-sourceedit.pod index 67ecabf..593bcf5 100644 --- a/Documentation/dtas-sourceedit.pod +++ b/Documentation/dtas-sourceedit.pod @@ -51,11 +51,7 @@ of a previous "dtas-ctl source cat sox" invocation: $ dtas-sourceedit sox < saved.yml -To change the way dtas-player calls avconv (part of libav): - - $ dtas-sourceedit av - -To change the way dtas-player calls ffmpeg (lightly-tested): +To change the way dtas-player calls ffmpeg: $ dtas-sourceedit ff @@ -77,7 +73,7 @@ No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2020 all contributors L<mailto:dtas-all@nongnu.org> +Copyright all contributors L<mailto:dtas-all@nongnu.org> License: GPL-3.0+ L<https://www.gnu.org/licenses/gpl-3.0.txt> diff --git a/Documentation/dtas-splitfx.pod b/Documentation/dtas-splitfx.pod index f8de59b..6f8d9ed 100644 --- a/Documentation/dtas-splitfx.pod +++ b/Documentation/dtas-splitfx.pod @@ -37,6 +37,12 @@ Print, but do not run the commands to be executed Silent operation, commands are not printed as executed +=item -S, --stats + +Add the sox "stats" effect to the end of the effects chain, +use this with L</--err-suffix> to get a C<.stats> file with +every track output + =item -D, --no-dither Disable automatic setting of the DITHERFX env. This also passes @@ -46,7 +52,8 @@ the option to L<sox(1)> via SOX_OPTS. Write the contents of C<stderr>. This is useful for capturing the per-track output of the L<sox(1)> C<stats> effect when -combined with parallel C<--jobs>. +combined with parallel C<--jobs>. Recommended for use with the +L</--stats> switch. =item -O, --outdir OUTDIR diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index ef12348..ecf9879 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -5,7 +5,7 @@ CONSTANT = "DTAS::VERSION" RVF = "lib/dtas/version.rb" GVF = "GIT-VERSION-FILE" -DEF_VER = "v0.20.0" +DEF_VER = "v0.21.0" vn = DEF_VER # First see if there is a version file (included in release tarballs), diff --git a/GNUmakefile b/GNUmakefile index 084a2d8..9019b31 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -82,7 +82,7 @@ bindir = $(prefix)/bin symlink-install : mkdir -p $(bindir) dtas=$(CURDIR)/dtas.sh && cd $(bindir) && \ - for x in $(CURDIR)/bin/*; do \ + for x in $(CURDIR)/bin/* $(CURDIR)/script/*; do \ ln -sf "$$dtas" $$(basename "$$x"); \ done @@ -30,10 +30,10 @@ For future upgrades of dtas Grab the latest tarball from our HTTPS site: - https://80x24.org/dtas/2022/dtas-0.20.0.tar.gz + https://80x24.org/dtas/2022/dtas-0.21.0.tar.gz - $ tar zxvf dtas-0.20.0.tar.gz - $ cd dtas-0.20.0 + $ tar zxvf dtas-0.21.0.tar.gz + $ cd dtas-0.21.0 # To install symlinks into ~/bin (assuming your Ruby executable is "ruby") $ make symlink-install @@ -84,8 +84,8 @@ You may also read via: NNTP: <nntps://news.public-inbox.org/inbox.comp.audio.dtas> <nntp://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.audio.dtas> <nntp://news.gmane.io/gmane.comp.audio.dtas.general> -IMAP: <imaps://anon:mous@public-inbox.org/inbox.comp.audio.dtas.0> - <imap://anon:mous@7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.audio.dtas.0> +IMAP: <imaps://;AUTH=ANONYMOUS@public-inbox.org/inbox.comp.audio.dtas.0> + <imap://;AUTH=ANONYMOUS@7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.audio.dtas.0> Atom: <https://80x24.org/dtas-all/new.atom> <http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/dtas-all/new.atom> (.onion URLs require Tor: <https://www.torproject.org/>) diff --git a/bin/dtas-archive b/bin/dtas-archive index c88873e..7c0e4f7 100755 --- a/bin/dtas-archive +++ b/bin/dtas-archive @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2015-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 usage = "#$0 SOURCE DESTINATION" @@ -30,6 +30,7 @@ stats = false keep_going = false compression = [] comment = [] +match = nil OptionParser.new('', 24, ' ') do |op| op.banner = usage @@ -47,6 +48,7 @@ OptionParser.new('', 24, ' ') do |op| op.on('-r', '--repeat [COUNT]', 'number of times to check', Integer) do |r| repeat = r end + op.on('-m', '--match=REGEX', String) { |s| match = Regexp.new(s) } op.on('-s', '--quiet', '--silent') { silent = true } op.on('-h', '--help') do puts(op.to_s) @@ -55,6 +57,7 @@ OptionParser.new('', 24, ' ') do |op| op.parse!(ARGV) end +match ||= %r/./ comment.push('--comment', '') if comment.empty? dst = ARGV.pop @@ -67,6 +70,7 @@ src.each do |s| src_st = File.stat(s) if src_st.directory? Find.find(s) do |path| + path =~ match or next File.file?(path) or next dir = File.dirname(path) dir_st = File.stat(dir) diff --git a/bin/dtas-splitfx b/bin/dtas-splitfx index d0afc7b..17d915d 100755 --- a/bin/dtas-splitfx +++ b/bin/dtas-splitfx @@ -13,6 +13,7 @@ OptionParser.new('', 24, ' ') do |op| op.on('-n', '--dry-run') { opts[:dryrun] = true } op.on('-j', '--jobs [JOBS]', Integer) { |val| opts[:jobs] = val } # nil==inf op.on('-s', '--quiet', '--silent') { opts[:silent] = true } + op.on('-S', '--stats', 'run stats every track') { opts[:stats] = true } op.on('-f', '--filter FILTER') { |val| (opts[:filter] ||= []) << val } op.on('-D', '--no-dither') { opts[:no_dither] = true } op.on('-O', '--outdir OUTDIR') { |val| opts[:outdir] = val } diff --git a/bin/dtas-tl b/bin/dtas-tl index e58ee31..c7f4c83 100755 --- a/bin/dtas-tl +++ b/bin/dtas-tl @@ -43,7 +43,8 @@ def do_edit(c) track_id = $1.to_i orig_idx[track_id] = orig.size orig << track_id - tmp.write("#{Shellwords.escape(line)} =#{track_id}\n") + line = Shellwords.escape(line) if line.include?("\n") + tmp.write("#{line} =#{track_id}\n") end tmp.flush @@ -100,13 +101,8 @@ def do_edit(c) non_existent = [] add.each do |path, after_id| orig = path - path = Shellwords.split(path)[0] - path = File.expand_path(path) - unless File.exist?(path) - path = orig.dup - path = Shellwords.split(path)[0] - path = File.expand_path(path) - end + path = File.expand_path(orig) + path = File.expand_path(Shellwords.split(path)[0]) unless File.exist?(path) if File.exist?(path) cmd = %W(tl add #{path}) @@ -142,6 +138,8 @@ case cmd = ARGV[0] when 'cat' each_track(c) { |line| print "#{line}\n" } when 'prune' + c2 = nil + pending = 0 each_track(c) do |line| line.sub!(/\A(\d+)=/n, '') or abort "unexpected line=#{line.inspect}\n" track_id = $1.to_i @@ -153,7 +151,19 @@ when 'prune' warn "# #{line}: #{e.class}" # raise other exceptions end - c.req("tl remove #{track_id}") unless ok + unless ok + c2 ||= DTAS::UNIXClient.new + if pending > 5 + c2.res_wait + pending -= 1 + end + pending += 1 + c2.req_start("tl remove #{track_id}") + end + end + while pending > 0 + c2.res_wait + pending -= 1 end when 'aac' # add-after-current ARGV.shift @@ -178,11 +188,11 @@ when "reto" re = ARGV[1] time = ARGV[2] re = Regexp.quote(re) if fixed - re = ignorecase ? %r{#{re}}i : %r{#{re}} - get_track_ids(c).each do |track_id| - res = c.req("tl get #{track_id}") - res.sub!(/\A1 \d+=/, '') - if re =~ res + re = ignorecase ? %r{#{re}}in : %r{#{re}}n + each_track(c) do |line| + line.sub!(/\A(\d+)=/n, '') or abort "unexpected line=#{line.inspect}\n" + track_id = $1 + if re =~ line req = %W(tl goto #{track_id}) req << time if time res = c.req(req) diff --git a/dtas.gemspec b/dtas.gemspec index d5a1916..9bc1cc5 100644 --- a/dtas.gemspec +++ b/dtas.gemspec @@ -3,7 +3,7 @@ Gem::Specification.new do |s| manifest = File.read('.gem-manifest').split(/\n/) s.name = %q{dtas} - s.version = (ENV["VERSION"] || '0.20.0').dup + s.version = (ENV["VERSION"] || '0.21.0').dup s.authors = ["dtas hackers"] s.summary = "duct tape audio suite for *nix" s.description = File.read("README").split(/\n\n/)[1].strip 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..c600c48 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) @@ -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/splitfx.rb b/lib/dtas/splitfx.rb index c7eaf42..1150ee0 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 @@ -204,6 +205,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 @@ -290,6 +292,7 @@ class DTAS::SplitFX # :nodoc: t = T.new t.tbeg = @t2s.call(start_time) t.comments = @comments.dup + title.valid_encoding? or warn "#{title.inspect} encoding invalid" t.comments["TITLE"] = title t.env = @env.dup @@ -395,7 +398,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) diff --git a/script/dtas-2splitfx b/script/dtas-2splitfx new file mode 100755 index 0000000..afa761d --- /dev/null +++ b/script/dtas-2splitfx @@ -0,0 +1,44 @@ +#!/usr/bin/perl -w +# Copyright (C) all contributors <dtas-all@nongnu.org> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> +# parse soxi output and generates a dtas-splitfx-compatible YAML snippet +# usage: dtas-2splitfx 1.flac 2.flac ... >tracks.yml +use v5.12; +use POSIX qw(strftime); +open my $fh, '-|', 'soxi', @ARGV or die $!; +my $title = ''; +my $off = 0; +my $sec = 0; + +my $flush = sub { + my ($start) = @_; + my $frac = $start =~ s/\.([0-9]+)\z// ? $1 : 0; + $start = strftime('%H:%M:%S', gmtime($start)); + $start .= ".$frac" if $frac; + $start; +}; + +while (<$fh>) { + if (/^Duration\s*:\s*([0-9:\.]+)/) { + my $t = $1; + $sec = $t =~ s/\.([0-9]+)\z// ? "0.$1" : 0; + my @t = split(/:/, $t); # HH:MM:SS + my $mult = 1; + while (defined(my $part = pop @t)) { + $sec += $part * $mult; + $mult *= 60; + } + } elsif (s/^title=//i) { + chomp; + $title = $_; + $title =~ tr!"!'!; + } elsif (/^\s*\z/s && $sec) { + my $start = $flush->($off); + say qq(- t $start "), , $title, '"'; + $off += $sec; + $sec = 0; + $title = ''; + } +} +close $fh or die "soxi failed: \$?=$?"; +say qq(- stop ), $flush->($off); diff --git a/test/test_player_integration.rb b/test/test_player_integration.rb index 5059bd2..09eceee 100644 --- a/test/test_player_integration.rb +++ b/test/test_player_integration.rb @@ -209,11 +209,11 @@ class TestPlayerIntegration < Testcase def test_source_ed s = client_socket - assert_equal "sox av ff splitfx", s.req("source ls") + assert_equal "sox ff av splitfx", s.req("source ls") s.req_ok("source ed av tryorder=-1") assert_equal "av sox ff splitfx", s.req("source ls") s.req_ok("source ed av tryorder=") - assert_equal "sox av ff splitfx", s.req("source ls") + assert_equal "sox ff av splitfx", s.req("source ls") s.req_ok("source ed sox command=true") sox = DTAS.yaml_load(s.req("source cat sox")) diff --git a/test/test_source_ff.rb b/test/test_source_ff.rb new file mode 100644 index 0000000..e53e72e --- /dev/null +++ b/test/test_source_ff.rb @@ -0,0 +1,102 @@ +# 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 './test/helper' +require 'dtas/source/ff' +require 'tempfile' + +class TestSourceFf < Testcase + def teardown + @tempfiles.each(&: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 + + x(%W(metaflac --set-tag=FOO=BAR #{tmp.path})) + x(%W(metaflac --add-replay-gain #{tmp.path})) + source = DTAS::Source::Ff.new.try(tmp.path) + assert_equal source.comments["FOO"], "BAR", source.inspect + rg = source.replaygain('track_gain') + 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 + + # 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 + + source = DTAS::Source::Ff.new.try(a.path) + rg = source.replaygain('track_gain') + 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('flac') or return + source = DTAS::Source::Ff.new.try(*%W(#{tmp.path} 5s)) + assert_equal 5, source.offset_samples + + source = DTAS::Source::Ff.new.try(*%W(#{tmp.path} 1:00:00.5)) + expect = 1 * 60 * 60 * 44100 + (44100/2) + assert_equal expect, source.offset_samples + + source = DTAS::Source::Ff.new.try(*%W(#{tmp.path} 1:10.5)) + expect = 1 * 60 * 44100 + (10 * 44100) + (44100/2) + assert_equal expect, source.offset_samples + + source = DTAS::Source::Ff.new.try(*%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('flac') or return + source = DTAS::Source::Ff.new.try(*%W(#{tmp.path} 441s)) + assert_equal 10000.0, source.offset_us + + source = DTAS::Source::Ff.new.try(*%W(#{tmp.path} 22050s)) + assert_equal 500000.0, source.offset_us + + source = DTAS::Source::Ff.new.try(tmp.path, '1') + assert_equal 1000000.0, source.offset_us + end +end if `which ffprobe 2>/dev/null` =~ /ffprobe/ && + `which ffmpeg 2>/dev/null` =~ /ffmpeg/ diff --git a/test/test_unixserver.rb b/test/test_unixserver.rb index 7e99b9e..c91354d 100644 --- a/test/test_unixserver.rb +++ b/test/test_unixserver.rb @@ -41,7 +41,7 @@ class TestUNIXServer < Testcase @srv.run_once # nothing msgs = [] clients = [] - client.send("HELLO", Socket::MSG_EOR) + client.send("HELLO", 0) @srv.run_once do |c, msg| clients << c msgs << msg |