diff options
73 files changed, 579 insertions, 422 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 f10c6a3..6f8d9ed 100644 --- a/Documentation/dtas-splitfx.pod +++ b/Documentation/dtas-splitfx.pod @@ -26,7 +26,8 @@ to use L<ecasound(1)>, too. =item -j, --jobs [JOBS] Number of jobs to run in parallel. If no number is specified, all -jobs are run in parallel. +jobs are run in parallel. Default: number of CPUS (dtas 0.20.0+), +previous versions of dtas defaulted to 1. =item -n, --dry-run @@ -36,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 @@ -45,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 @@ -76,6 +84,16 @@ outputs the result as a single file with the TRACKNUMBER of "000". For ease-of-typing, commas in this command-line argument are automatically expanded to spaces when passed to sox. +This switch may not be combined with L</--filter> + +=item -f, --filter [FIELD=]VALUE + +Only process tracks matching a given comment FIELD and VALUE. +If no C<=> is exists, then all comment fields are matched +against the specified VALUE. + +This switch may not be combined with L</--trim> + =item -p, --sox-pipe Used in place of an output target to specify outputting audio data in @@ -90,9 +108,9 @@ moves printing of output to stderr and disables parallel job invocation. =item infile - string, the pathname of the original audio file -=item env - ordered hash of environment variables to set for all commands +=item env - hash of environment variables to set for all commands - env: !omap + env: FX: gain 3 stats =item comments - hash of common tags for all audio (e.g. ARTIST, ALBUM, YEAR) @@ -121,7 +139,12 @@ highest-numbered track. Default: true =item targets - hash, see "TARGETS" section -=item command - used only by L<dtas-player(1)> +=item command - override the default sox invocation + +This command may be used to specify an alternate command to process each +track. + +Default: sox "$INFILE" $COMMENTS $OUTFMT $OUTDST $TRIMFX $FX $RATEFX $DITHERFX =back @@ -133,11 +156,22 @@ segment. =over -=item t TIME TITLE [fade_in/fade_out=FADE_ARGS] +=item t TIME TITLE [fade_in/fade_out=FADE_ARGS] [.FIELD=VALUE] [env.X=Y] + +The start of a new track at TIME with TITLE. +An optional L</fade_in> and L</fade_out> may be specified for any tracks. +Per-track comments may be specified in the form of C<.FIELD=VALUE>. +Using per-track C<.ARTIST=FOO> allows proper tagging of multi-artist +compilations. -The start of a new track -at TIME with TITLE. An optional fade_in and fade_out may be specified -for the first/last tracks. +Per-track environment variables may be specified in the form +of C<env.K=V> where C<K> is the environment variable name and +C<V> is its value. Per-track environment variables do not affect +playback of YAML files via L<dtas-player(1)> nor use of the L</--trim> +command-line option. However, per-track environment variables do affect +any tracks written to the filesystem, including those using the L</--filter> +switch. These environment variables are intended to affect the specified +L</command> or default L<sox(1)> invocation. =item skip TIME - skip a segment starting at TIME @@ -268,7 +302,7 @@ imbalance in a live concert recording from the audience: =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-tl.pod b/Documentation/dtas-tl.pod index ac1b47d..b5a4b31 100644 --- a/Documentation/dtas-tl.pod +++ b/Documentation/dtas-tl.pod @@ -60,6 +60,8 @@ optionally seek to POS. POS should be a timestamp in HH:MM:SS.FRAC format. =item prev - play the previous track in the tracklist +=item prune - cull non-existent pathnames from the tracklist + =item repeat 1 - repeat the current track =item repeat false - disable repeat @@ -120,7 +122,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/GIT-VERSION-GEN b/GIT-VERSION-GEN index bc9a128..ecf9879 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,11 +1,11 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-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 CONSTANT = "DTAS::VERSION" RVF = "lib/dtas/version.rb" GVF = "GIT-VERSION-FILE" -DEF_VER = "v0.18.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 @@ -1,7 +1,8 @@ -Uncommon for audio software, dtas is currently implemented in Ruby. +Uncommon for audio software, dtas is currently implemented in Ruby +(and some Perl5). The latest stable release or development snapshot of Ruby is recommended. -However, Ruby 1.9.3 and later works, but older versions of Ruby do not. +However, Ruby 2.3 and later works, but older versions of Ruby do not. SoX is a dependency of dtas-player. While not _strictly_ required, dtas-player uses SoX by default and you will need it unless you've @@ -25,20 +26,20 @@ For future upgrades of dtas sudo gem install dtas -# installing dtas via tarball and setup.rb +# installing dtas via tarball Grab the latest tarball from our HTTPS site: - https://80x24.org/dtas/2021/dtas-0.18.1.tar.gz + https://80x24.org/dtas/2022/dtas-0.21.0.tar.gz - $ tar zxvf dtas-0.18.1.tar.gz - $ cd dtas-0.18.1 - $ sudo ruby setup.rb + $ tar zxvf dtas-0.21.0.tar.gz + $ cd dtas-0.21.0 -GNU/Linux users may optionally install the "sleepy_penguin" RubyGem -for a tiny speed improvement over the default "fiddle" RubyGem. + # To install symlinks into ~/bin (assuming your Ruby executable is "ruby") + $ make symlink-install - * sleepy_penguin - https://yhbt.net/sleepy_penguin/ + # or using setup.rb: + $ sudo ruby setup.rb # CONTACT @@ -50,5 +51,5 @@ No subscription is necessary to post to the mailing list. # COPYRIGHT -Copyright 2013-2021 all contributors <dtas-all@nongnu.org> +Copyright all contributors <dtas-all@nongnu.org> License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> @@ -84,15 +84,15 @@ 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/>) ## Copyright -Copyright 2013-2021 all contributors <dtas-all@nongnu.org> +Copyright all contributors <dtas-all@nongnu.org> License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> dtas is copyrighted Free Software by all contributors, see logs diff --git a/bin/dtas-archive b/bin/dtas-archive index dd3b537..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" @@ -29,6 +29,8 @@ repeat = 1 stats = false keep_going = false compression = [] +comment = [] +match = nil OptionParser.new('', 24, ' ') do |op| op.banner = usage @@ -36,6 +38,7 @@ OptionParser.new('', 24, ' ') do |op| op.on('-C', '--compression [FACTOR]', 'compression factor for sox') { |c| compression = [ '-C', c ] } + op.on('--comment=TEXT', String) { |c| comment.push('--comment', c) } op.on('-j', '--jobs [JOBS]', Integer) { |j| jobs = j } op.on('-S', '--stats', 'save stats on the file') { stats = true } op.on('-k', '--keep-going', 'continue after error') { keep_going = true } @@ -45,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) @@ -53,6 +57,9 @@ OptionParser.new('', 24, ' ') do |op| op.parse!(ARGV) end +match ||= %r/./ +comment.push('--comment', '') if comment.empty? + dst = ARGV.pop src = ARGV.dup @@ -63,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) @@ -137,7 +145,7 @@ thrs = jobs.times.map do |i| if dry_run || !silent names = job.map { |x| Shellwords.escape(x) } - cmd = [ 'sox', *names ] + cmd = [ 'sox', names[0], *compression, *comment, names[1] ] if stats cmd << 'stats' cmd << "2>#{Shellwords.escape(stats_out)}" @@ -151,7 +159,7 @@ thrs = jobs.times.map do |i| end end - cmd = [ 'sox', input, *compression, output ] + cmd = [ 'sox', input, *compression, *comment, output ] if stats cmd << 'stats' cmd = [ *cmd, { err: stats_out } ] diff --git a/bin/dtas-console b/bin/dtas-console index 0c56450..eedd0f0 100755 --- a/bin/dtas-console +++ b/bin/dtas-console @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-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 # @@ -11,21 +11,24 @@ require 'dtas/sigevent' require 'dtas/process' require 'dtas/format' include DTAS::Process -require 'yaml' begin require 'curses' rescue LoadError abort "please install the 'curses' RubyGem to use #$0" end +# workaround https://bugs.debian.org/958973 +$VERBOSE = nil if RUBY_VERSION.to_f < 3.0 + tsec = false se = DTAS::Sigevent.new trap(:WINCH) { se.signal } w = DTAS::UNIXClient.new w.req_ok('watch') c = DTAS::UNIXClient.new -cur = YAML.load(c.req('current')) +cur = DTAS.yaml_load(c.req('current')) readable = [ se, w, $stdin ] +set_title = (ENV['DISPLAY'] || ENV['WAYLAND_DISPLAY']) ? $stdout : nil # current rg mode rg_mode = DTAS::RGState::RG_MODE.keys.unshift("off") @@ -34,6 +37,7 @@ if (rg = cur["rg"]) && (rg = rg["mode"]) else rg_mode_i = 0 end +show_info = false def update_tfmt(prec, tsec) if tsec @@ -167,6 +171,10 @@ begin # and risk mojibake... infile.encode(enc_locale, undef: :replace, invalid: :replace, replace: '?') + if set_title + dir, base = File.split(infile) + set_title.syswrite("\033]0;#{base} dtas-console\07") + end Curses.setpos(lineno += 1, 0) Curses.clrtoeol Curses.addstr(infile) @@ -203,6 +211,27 @@ begin Curses.addstr(extra.join(' ')) pre_mute_vol = cur_vol if cur_vol != 0 + if show_info && current && comments = current['comments'] + Curses.setpos(lineno += 1, 0) + Curses.clrtoeol + Curses.addstr('comments:') + comments.each do |k,v| + v = v.split(/\n+/) + k = k.dump if /[[:cntrl:]]/ =~ k + if first = v.shift + Curses.setpos(lineno += 1, 0) + Curses.clrtoeol + first = first.dump if /[[:cntrl:]]/ =~ first + Curses.addstr(" #{k}: #{first}") + v.each do |val| + val = val.dump if /[[:cntrl:]]/ =~ val + Curses.setpos(lineno += 1, 0) + Curses.clrtoeol + Curses.addstr(" #{val}") + end + end + end + end show_events(lineno, screen, events) Curses.refresh # draw and wait @@ -224,7 +253,7 @@ begin events << "#{Time.now.strftime(tfmt)} #{event}" # something happened, refresh current # we could be more intelligent here, maybe, but too much work. - cur = YAML.load(c.req('current')) + cur = DTAS.yaml_load(c.req('current')) when $stdin # keybindings taken from mplayer / vi case key = Curses.getch @@ -272,6 +301,9 @@ begin interval = 1.0 / 10 ** prec_nr end when 27 # TODO readline/edit mode? + when 'i' + show_info = !show_info + Curses.clear if !show_info else Curses.setpos(screen.maxy - 1, 0) Curses.clrtoeol diff --git a/bin/dtas-msinkctl b/bin/dtas-msinkctl index 189738f..79c7f26 100755 --- a/bin/dtas-msinkctl +++ b/bin/dtas-msinkctl @@ -1,8 +1,7 @@ #!/usr/bin/env ruby -# 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 'dtas/unix_client' usage = "#$0 <active-set|active-add|active-sub|nonblock|active> SINK" c = DTAS::UNIXClient.new @@ -29,7 +28,7 @@ def filter(c, player_sinks, key) rv = [] player_sinks.each do |name| buf = c.req("sink cat #{name}") - sink = YAML.load(buf) + sink = DTAS.yaml_load(buf) rv << sink["name"] if sink[key] end rv diff --git a/bin/dtas-partstats b/bin/dtas-partstats index 884a6d1..6a0c9d4 100755 --- a/bin/dtas-partstats +++ b/bin/dtas-partstats @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# 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 # TODO @@ -8,17 +8,11 @@ # - configurable output formatting # - Sequel/SQLite support require 'dtas/partstats' +require 'etc' infile = ARGV[0] or abort "usage: #$0 INFILE" ps = DTAS::PartStats.new(infile) -def nproc - require 'etc' - Etc.nprocessors -rescue NoMethodError - `nproc 2>/dev/null || echo 2`.to_i -end - -opts = { jobs: nproc } +opts = { jobs: Etc.nprocessors } stats = ps.run(opts) headers = ps.key_idx.to_a diff --git a/bin/dtas-player b/bin/dtas-player index 21bc45f..c926e5f 100755 --- a/bin/dtas-player +++ b/bin/dtas-player @@ -1,9 +1,8 @@ #!/usr/bin/env ruby -# 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 Thread.abort_on_exception = $stderr.sync = $stdout.sync = true -require 'yaml' require 'dtas/player' sock = (ENV["DTAS_PLAYER_SOCK"] || File.expand_path("~/.dtas/player.sock")) state = (ENV["DTAS_PLAYER_STATE"] || diff --git a/bin/dtas-readahead b/bin/dtas-readahead index 3eedd86..f2ab514 100755 --- a/bin/dtas-readahead +++ b/bin/dtas-readahead @@ -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 # @@ -12,13 +12,11 @@ end @ffprobe = 'ffprobe' @avprobe = 'avprobe' -require 'yaml' require 'io/wait' require 'dtas/unix_client' require 'dtas/process' include DTAS::Process -include DTAS::SpawnFix trap(:CHLD) { DTAS::Process.reaper {} } trap(:INT) { exit(0) } trap(:TERM) { exit(0) } @@ -30,18 +28,6 @@ null = DTAS.null @redir = { err: null, out: null, in: null }.freeze require 'pp' -if RUBY_VERSION.to_r >= '2.3'.to_r - # Old Rubies did FIONREAD, which breaks on SOCK_SEQPACKET - def wait_read(w, timeout) - w.to_io.wait_readable(timeout) - end -else - def wait_read(w, timeout) - r = IO.select([w], nil, nil, timeout) - r ? r[0] : nil - end -end - def seek_to_cur_pos(cur_pid, fp) cur_fd = [] fpst = fp.stat @@ -122,7 +108,7 @@ def do_ra(fp, pos, w) len -= n # stop reading immediately if there's an event - if wait_read(w, 0) + if w.to_io.wait_readable(0) adj = @todo_ra pos += size break @@ -141,8 +127,8 @@ def do_open(path) when "---\n" buf << fp.read(fp.size - 4) Dir.chdir(File.dirname(path)) do - yml = YAML.load(buf) - x = yml['infile'] and return File.open(File.expand_path(x).freeze) + yml = DTAS.yaml_load(buf) + x = yml['infile'] and return File.open(-File.expand_path(x)) end end end @@ -156,12 +142,12 @@ begin @todo_ra = @max_ra t0 = DTAS.now fp = nil - cur = YAML.load(c.req('current')) + cur = DTAS.yaml_load(c.req('current')) while @todo_ra > 0 && fp.nil? if current = cur['current'] track = current['infile'] break unless track.kind_of?(String) - track.freeze + track = -track fp = work[track] ||= do_open(track) cur_pid = current['pid'] if fp @@ -178,7 +164,7 @@ begin end # queue has priority, work on it, first - queue = YAML.load(c.req('queue cat')) + queue = DTAS.yaml_load(c.req('queue cat')) while @todo_ra > 0 && track = queue.shift next unless track.kind_of?(String) fp = nil @@ -198,7 +184,7 @@ begin repeat = c.req('tl repeat').split[-1] while @todo_ra > 0 && idx && (cid = ids[idx]) fp = nil - track = c.req("tl get #{cid}").sub!(/\A1 \d+=/, '').freeze + track = -(c.req("tl get #{cid}").sub!(/\A1 \d+=/, '')) begin fp = work[track] ||= do_open(track) rescue SystemCallError @@ -210,7 +196,7 @@ begin end end idx or break - cur = YAML.load(c.req('current')) + cur = DTAS.yaml_load(c.req('current')) current = cur['current'] or break end if current @@ -220,10 +206,10 @@ begin timeout = 0 if timeout < 0 else work.each_value(&:close).clear - fp.close if fp && !fp.closed? + fp.close if fp fp = timeout = nil end - r = wait_read(w, timeout) + r = w.to_io.wait_readable(timeout) p w.res_wait if r rescue EOFError abort "dtas-player exited" diff --git a/bin/dtas-sinkedit b/bin/dtas-sinkedit index 3393aac..252270f 100755 --- a/bin/dtas-sinkedit +++ b/bin/dtas-sinkedit @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# 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 'optparse' @@ -36,10 +36,10 @@ st_in = $stdin.stat buf = c.req(%W(sink cat #{name})) abort(buf) if buf =~ /\AERR/ -orig = YAML.load(buf) +orig = DTAS.yaml_load(buf) commit_update = lambda do |buf| - sink = YAML.load(buf) + sink = DTAS.yaml_load(buf) cmd = %W(sink ed #{name}) update_cmd_env(cmd, orig, sink) @@ -68,7 +68,6 @@ if st_in.file? || st_in.pipe? buf = $stdin.read commit_update.call(buf) else - include DTAS::SpawnFix tmp = tmpyaml tmp_path = tmp.path do_update = lambda { commit_update.call(File.read(tmp_path)) } diff --git a/bin/dtas-sourceedit b/bin/dtas-sourceedit index 23362c2..1b3f4ee 100755 --- a/bin/dtas-sourceedit +++ b/bin/dtas-sourceedit @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# 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 'optparse' @@ -36,10 +36,10 @@ st_in = $stdin.stat buf = c.req(%W(source cat #{name})) abort(buf) if buf =~ /\AERR/ -orig = YAML.load(buf) +orig = DTAS.yaml_load(buf) commit_update = lambda do |buf| - source = YAML.load(buf) + source = DTAS.yaml_load(buf) cmd = %W(source ed #{name}) update_cmd_env(cmd, orig, source) @@ -55,7 +55,6 @@ if st_in.file? || st_in.pipe? buf = $stdin.read commit_update.call(buf) else - include DTAS::SpawnFix tmp = tmpyaml tmp_path = tmp.path do_update = lambda { commit_update.call(File.read(tmp_path)) } diff --git a/bin/dtas-splitfx b/bin/dtas-splitfx index 3ee812e..17d915d 100755 --- a/bin/dtas-splitfx +++ b/bin/dtas-splitfx @@ -1,19 +1,20 @@ #!/usr/bin/env ruby -# 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 'optparse' require 'dtas/splitfx' usage = "#$0 [-n|--dry-run][-j [JOBS]][-s|--silent] SPLITFX_FILE.yml [TARGET]" overrides = {} # FIXME: not tested default_target = "flac" -opts = { jobs: 1 } +opts = { jobs: nil } OptionParser.new('', 24, ' ') do |op| op.banner = usage 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 } op.on('-C', '--compression FACTOR') { |val| opts[:compression] = val } @@ -37,13 +38,18 @@ if opts[:sox_pipe] && opts[:err_suffix] abort '--err-suffix and --sox-pipe are mutually exclusive' end +if opts[:jobs].nil? + require 'etc' + opts[:jobs] = Etc.nprocessors +end + args = [] ARGV.each do |arg| case arg when %r{\A(\w+)=(.*)\z} key, val = $1, $2 # only one that makes sense is infile=another_file - overrides[key] = YAML.load(val) + overrides[key] = DTAS.yaml_load(val) when %r{\A(\w+)\.(\w+)=(.*)\z} # comments.ARTIST='blah' top, key, val = $1, $2, $3 @@ -58,5 +64,5 @@ trap(:INT) { exit 130 } file = args.shift or abort usage target = args.shift || default_target splitfx = DTAS::SplitFX.new -splitfx.import(YAML.load(File.read(file)), overrides) +splitfx.import(DTAS.yaml_load(File.read(file)), overrides) splitfx.run(target, opts) diff --git a/bin/dtas-tl b/bin/dtas-tl index 988e726..c7f4c83 100755 --- a/bin/dtas-tl +++ b/bin/dtas-tl @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# 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 @@ -7,6 +7,8 @@ # itself is also unstable, but better than this one probably). require 'dtas/unix_client' require 'shellwords' +$stdout.binmode +$stderr.binmode def get_track_ids(c) track_ids = c.req("tl tracks") @@ -16,14 +18,18 @@ def get_track_ids(c) track_ids end -def fix_enc!(str, enc) - str.force_encoding(enc) - str.force_encoding(Encoding::ASCII_8BIT) unless str.valid_encoding? +def each_track(c) + get_track_ids(c).each_slice(128) do |track_ids| + res = c.req("tl get #{track_ids.join(' ')}") + res = Shellwords.split(res.sub!(/\A\d+ /, '')) + while line = res.shift + yield line + end + end end def do_edit(c) require 'dtas/edit_client' - require 'yaml' require 'tempfile' extend DTAS::EditClient tmp = Tempfile.new(%w(dtas-tl-edit .txt)) @@ -31,19 +37,14 @@ def do_edit(c) tmp_path = tmp.path orig = [] orig_idx = {} - enc = Encoding.default_external - get_track_ids(c).each_slice(128) do |track_ids| - res = c.req("tl get #{track_ids.join(' ')}") - res = Shellwords.split(res.sub!(/\A\d+ /, '')) - while line = res.shift - line.sub!(/\A(\d+)=/n, '') or abort "unexpected line=#{line.inspect}\n" - fix_enc!(line, enc) - track_id = $1.to_i - orig_idx[track_id] = orig.size - orig << track_id - tmp.write("#{Shellwords.escape(line)} =#{track_id}\n") - end + each_track(c) do |line| + line.sub!(/\A(\d+)=/n, '') or abort "unexpected line=#{line.inspect}\n" + track_id = $1.to_i + orig_idx[track_id] = orig.size + orig << track_id + line = Shellwords.escape(line) if line.include?("\n") + tmp.write("#{line} =#{track_id}\n") end tmp.flush @@ -51,7 +52,7 @@ def do_edit(c) # jump to the line of the currently playing track if using vi or vim # Patches for other editors welcome: dtas-all@nongnu.org if ed =~ /vim?\z/ - cur = YAML.load(c.req('current')) + cur = DTAS.yaml_load(c.req('current')) if tl = cur['tracklist'] if pos = tl['pos'] ed += " +#{pos + 1}" @@ -100,14 +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 - fix_enc!(path, enc) - 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}) @@ -140,15 +135,35 @@ end c = DTAS::UNIXClient.new case cmd = ARGV[0] -when "cat" - enc = Encoding.default_external - get_track_ids(c).each_slice(128) do |track_ids| - res = c.req("tl get #{track_ids.join(' ')}") - res = Shellwords.split(res.sub!(/\A\d+ /, '')) - while line = res.shift - fix_enc!(line, enc) - print "#{line}\n" +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 + ok = false + begin + st = File.stat(line) + ok = st.readable? && st.size? + rescue Errno::ENOENT, Errno::ENOTDIR, Errno::EACCES => e + warn "# #{line}: #{e.class}" + # raise other exceptions end + 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 @@ -173,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 d763a9f..9bc1cc5 100644 --- a/dtas.gemspec +++ b/dtas.gemspec @@ -1,9 +1,9 @@ -# Copyright (C) 2013-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> Gem::Specification.new do |s| manifest = File.read('.gem-manifest').split(/\n/) s.name = %q{dtas} - s.version = (ENV["VERSION"] || '0.18.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 @@ -12,5 +12,5 @@ Gem::Specification.new do |s| s.files = manifest s.homepage = 'https://80x24.org/dtas.git/about/' s.licenses = "GPL-3.0+" - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.3' end @@ -1,7 +1,8 @@ #!/bin/sh -e # symlink this file to a directory in PATH to run dtas (or anything in bin/*) # without needing perms to install globally. Used by "make symlink-install" -p=$(realpath "$0" || readlink "$0") # neither is POSIX, but common -p=$(dirname "$p") c=$(basename "$0") # both are POSIX -exec ${RUBY-ruby} -I"$p"/lib "$p"/bin/"${c%.sh}" "$@" +p=$(realpath "$0" || readlink "$0"); # neither is POSIX, but common +p=$(dirname "$p") c=$(basename "$0"); c="${c%.sh}" +if test -x "$p/bin/$c"; then exec ${RUBY-ruby} -I"$p"/lib "$p/bin/$c" "$@"; +else exec ${PERL-perl} -I"$p"/lib "$p/script/$c" "$@"; fi : this script is too short to copyright diff --git a/examples/splitfx.sample.yml b/examples/splitfx.sample.yml index 9c29df2..979ba0d 100644 --- a/examples/splitfx.sample.yml +++ b/examples/splitfx.sample.yml @@ -11,7 +11,7 @@ comments: # the sox command for dtas-player playback, there is no need to # specify this as it is the default: # command: exec sox "$INFILE" $SOXFMT - $TRIMFX $RGFX $FX -env: !omap +env: PATH: $PATH # these effects may be used in any command in this file, including targets SOX_OPTS: $SOX_OPTS -R diff --git a/examples/tfx.sample.yml b/examples/tfx.sample.yml index b8add0b..144129f 100644 --- a/examples/tfx.sample.yml +++ b/examples/tfx.sample.yml @@ -4,7 +4,7 @@ # test_trimfx.rb relies on this. --- infile: foo.flac -env: !omap +env: PATH: $PATH SOX_OPTS: $SOX_OPTS -R I2: second.flac diff --git a/lib/dtas.rb b/lib/dtas.rb index 1eac704..cb7c33d 100644 --- a/lib/dtas.rb +++ b/lib/dtas.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 @@ -9,15 +9,8 @@ module DTAS # try to use the monotonic clock in Ruby >= 2.1, it is immune to clock # offset adjustments and generates less garbage (Float vs Time object) # :stopdoc: - begin + def self.now # :nodoc: ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - def self.now # :nodoc: - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - end - rescue NameError, NoMethodError - def self.now # :nodoc: - Time.now.to_f # Ruby <= 2.0 - end end @null = nil @@ -33,22 +26,13 @@ module DTAS end end - # String#-@ will deduplicate strings when Ruby 2.5 is released (Dec 2017) - # https://bugs.ruby-lang.org/issues/13077 - if RUBY_VERSION.to_f >= 2.5 - def self.dedupe_str(str) - -str - end - else - # Ruby 2.1 - 2.4, noop for older Rubies - def self.dedupe_str(str) - eval "#{str.inspect}.freeze" - end + # prevent breakage in Psych 4.x; we're a shell and designed to execute code + def self.yaml_load(buf) + require 'yaml' + YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(buf) : YAML.load(buf) end # :startdoc: end -require_relative 'dtas/compat_onenine' -require_relative 'dtas/spawn_fix' require_relative 'dtas/encoding' DTAS.extend(DTAS::Encoding) 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..c600c48 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) @@ -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..6ca29bc 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 @@ -56,14 +56,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..1150ee0 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 @@ -113,7 +114,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 +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 @@ -299,6 +302,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)}" @@ -356,9 +360,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 +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/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 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/perl/dtas-graph b/script/dtas-graph index 776485d..d918351 100755 --- a/perl/dtas-graph +++ b/script/dtas-graph @@ -1,5 +1,5 @@ #!/usr/bin/perl -w -# 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> # # Process visualizer which shows pipe connections between processes with diff --git a/test/player_integration.rb b/test/player_integration.rb index 4ad0e0c..66d1c6e 100644 --- a/test/player_integration.rb +++ b/test/player_integration.rb @@ -1,11 +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 './test/helper' require 'dtas/player' require 'dtas/state_file' require 'dtas/unix_client' -require 'yaml' require 'tempfile' require 'shellwords' require 'timeout' diff --git a/test/test_buffer.rb b/test/test_buffer.rb index 54ee584..a47e2d4 100644 --- a/test/test_buffer.rb +++ b/test/test_buffer.rb @@ -11,7 +11,7 @@ class TestBuffer < Testcase @@max_size = nil if @@max_size == 0 def teardown - @to_close.each { |io| io.close unless io.closed? } + @to_close.each(&:close) end def setup @@ -62,7 +62,7 @@ class TestBuffer < Testcase buf = new_buffer r, w = IO.pipe buf.wr.write "HIHI" - assert_equal :wait_readable, buf.broadcast([w]) + assert_equal [w], buf.broadcast([w]) assert_equal 4, buf.bytes_xfer tmp = [w] r.close diff --git a/test/test_encoding.rb b/test/test_encoding.rb index 9ef6a25..5cd5da7 100644 --- a/test/test_encoding.rb +++ b/test/test_encoding.rb @@ -1,9 +1,8 @@ -# Copyright (C) 2018-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 './test/helper' require 'dtas' -require 'yaml' class TestEncoding < Testcase def test_encoding @@ -13,7 +12,7 @@ comments: ARTIST: !binary |- RW5yaXF1ZSBSb2Ryw61ndWV6 EOD - hash = YAML.load(data) + hash = DTAS.yaml_load(data) artist = DTAS.try_enc(hash['comments']['ARTIST'], Encoding::UTF_8) assert_equal 'Enrique RodrÃguez', artist end diff --git a/test/test_format_change.rb b/test/test_format_change.rb index 95bb860..dc94f02 100644 --- a/test/test_format_change.rb +++ b/test/test_format_change.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 './test/player_integration' @@ -26,7 +26,7 @@ class TestFormatChange < Testcase Timeout.timeout(len) do begin - cur = YAML.load(s.req("current")) + cur = DTAS.yaml_load(s.req("current")) end while cur["sinks"] && sleep(0.01) end diff --git a/test/test_mcache.rb b/test/test_mcache.rb index 2bf0e98..983a69e 100644 --- a/test/test_mcache.rb +++ b/test/test_mcache.rb @@ -1,19 +1,29 @@ -# 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 require './test/helper' require 'dtas/mcache' +require 'tempfile' class TestMcache < Testcase def test_mcache + tmp = Tempfile.new(%W(tmp .sox)) + fn = tmp.path + cmd = %W(sox -r 44100 -b 16 -c 2 -n #{fn} trim 0 1) + system(*cmd) or skip mc = DTAS::Mcache.new exist = nil - mc.lookup('hello') { |infile, hash| exist = hash } + mc.lookup(fn) { |infile, hash| + hash[:ctime] = File.stat(infile).ctime + exist = hash + } assert_kind_of Hash, exist - assert_equal 'hello', exist[:infile] + assert_equal fn, exist[:infile] assert_operator exist[:btime], :<=, DTAS.now - assert_same exist, mc.lookup('hello') + assert_same exist, mc.lookup(fn) assert_nil mc.lookup('HELLO') - assert_same exist, mc.lookup('hello'), 'no change after miss' + assert_same exist, mc.lookup(fn), 'no change after miss' + ensure + tmp.close! end end diff --git a/test/test_player_client_handler.rb b/test/test_player_client_handler.rb index 7ea8cc0..eee5e49 100644 --- a/test/test_player_client_handler.rb +++ b/test/test_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 './test/helper' @@ -71,7 +71,7 @@ class TestPlayerClientHandler < Testcase @sinks["default"] = sink dpc_sink(@io, %W(cat default)) assert_equal 1, @io.size - hsh = YAML.load(@io[0]) + hsh = DTAS.yaml_load(@io[0]) assert_kind_of Hash, hsh assert_equal "default", hsh["name"] assert_match("dither -s", hsh["command"]) diff --git a/test/test_player_integration.rb b/test/test_player_integration.rb index d2e11cf..09eceee 100644 --- a/test/test_player_integration.rb +++ b/test/test_player_integration.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 './test/player_integration' class TestPlayerIntegration < Testcase include PlayerIntegration - include DTAS::SpawnFix def test_cmd_rate env = ENV.to_hash.merge(@fmt.to_env) @@ -140,7 +139,7 @@ class TestPlayerIntegration < Testcase Timeout.timeout(5) do begin yaml = s.req("current") - cur = YAML.load(yaml) + cur = DTAS.yaml_load(yaml) end while cur["sinks"] && sleep(0.01) end @@ -167,7 +166,7 @@ class TestPlayerIntegration < Testcase Timeout.timeout(len) do begin yaml = s.req("current") - cur = YAML.load(yaml) + cur = DTAS.yaml_load(yaml) end while cur["sinks"] && sleep(0.01) end assert(system("cmp", dump.path, expect.path), @@ -196,13 +195,13 @@ class TestPlayerIntegration < Testcase state.close! s = client_socket s.req_ok(%W(state dump #{state_path})) - hash = YAML.load(IO.binread(state_path)) + hash = DTAS.yaml_load(IO.binread(state_path)) assert_equal @sock_path, hash["socket"] assert_equal "default", hash["sinks"][0]["name"] assert_equal "", IO.binread(@state_tmp.path) s.req_ok(%W(state dump)) - orig = YAML.load(IO.binread(@state_tmp.path)) + orig = DTAS.yaml_load(IO.binread(@state_tmp.path)) assert_equal orig, hash ensure File.unlink(state_path) @@ -210,18 +209,18 @@ 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 = YAML.load(s.req("source cat sox")) + sox = DTAS.yaml_load(s.req("source cat sox")) assert_equal "true", sox["command"] s.req_ok("source ed sox command=") - sox = YAML.load(s.req("source cat sox")) + sox = DTAS.yaml_load(s.req("source cat sox")) assert_equal DTAS::Source::Sox::SOX_DEFAULTS["command"], sox["command"] end diff --git a/test/test_rg_integration.rb b/test/test_rg_integration.rb index efa0721..f274272 100644 --- a/test/test_rg_integration.rb +++ b/test/test_rg_integration.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 './test/player_integration' @@ -39,7 +39,7 @@ class TestRgIntegration < Testcase yaml = cur = nil Timeout.timeout(5) do begin - cur = YAML.load(yaml = s.req("current")) + cur = DTAS.yaml_load(yaml = s.req("current")) end while cur["current_offset"] == 0 && sleep(0.01) end @@ -55,7 +55,7 @@ class TestRgIntegration < Testcase Timeout.timeout(5) do begin yaml = s.req("current") - cur = YAML.load(yaml) + cur = DTAS.yaml_load(yaml) end while cur["current"]["env"]["RGFX"] !~ expect && sleep(0.01) end assert_match expect, cur["current"]["env"]["RGFX"] @@ -67,27 +67,27 @@ class TestRgIntegration < Testcase check_gain.call(%r{gain 2\.5}, "track_peak") s.req_ok("rg preamp+=1") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_equal 1, rg["preamp"] s.req_ok("rg preamp-=1") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_nil rg["preamp"] s.req_ok("rg preamp=2") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_equal 2, rg["preamp"] s.req_ok("rg preamp-=0.3") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_equal 1.7, rg["preamp"] s.req_ok("rg preamp-=-0.3") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_equal 2.0, rg["preamp"] s.req_ok("rg preamp-=+0.3") - rg = YAML.load(yaml = s.req("rg")) + rg = DTAS.yaml_load(yaml = s.req("rg")) assert_equal 1.7, rg["preamp"] dethrottle_decoder(s) diff --git a/test/test_sink.rb b/test/test_sink.rb index 873fb3a..7214da6 100644 --- a/test/test_sink.rb +++ b/test/test_sink.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 './test/helper' @@ -27,6 +27,6 @@ class TestSink < Testcase def test_inactive_load orig = { "active" => false }.freeze tmp = orig.to_yaml - assert_equal orig, YAML.load(tmp) + assert_equal orig, DTAS.yaml_load(tmp) end end 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_splitfx.rb b/test/test_splitfx.rb index b6ae01f..f2e0e09 100644 --- a/test/test_splitfx.rb +++ b/test/test_splitfx.rb @@ -1,14 +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 'yaml' require 'dtas/splitfx' require 'thread' require_relative 'helper' class TestSplitfx < Testcase - include DTAS::SpawnFix - def tmp_err(path) err = $stderr.dup $stderr.reopen(path, 'a') @@ -41,7 +38,7 @@ class TestSplitfx < Testcase end def test_example - hash = YAML.load(File.read("examples/splitfx.sample.yml")) + hash = DTAS.yaml_load(File.read("examples/splitfx.sample.yml")) sfx = DTAS::SplitFX.new Dir.mktmpdir do |dir| Dir.chdir(dir) do diff --git a/test/test_tfx.rb b/test/test_tfx.rb index fa77ca5..be68079 100644 --- a/test/test_tfx.rb +++ b/test/test_tfx.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 './test/helper' require 'dtas/tfx' require 'dtas/format' -require 'yaml' class TestTFX < Testcase def rate @@ -12,7 +11,7 @@ class TestTFX < Testcase end def test_example - ex = YAML.load(File.read("examples/tfx.sample.yml")) + ex = DTAS.yaml_load(File.read("examples/tfx.sample.yml")) effects = [] ex["effects"].each do |line| words = Shellwords.split(line) diff --git a/test/test_unixserver.rb b/test/test_unixserver.rb index b061af0..c91354d 100644 --- a/test/test_unixserver.rb +++ b/test/test_unixserver.rb @@ -21,7 +21,7 @@ class TestUNIXServer < Testcase end def teardown - @clients.each { |io| io.close unless io.closed? } + @clients.each(&:close) if File.exist?(@tmp.path) @tmp.close! else @@ -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 |