diff options
153 files changed, 1584 insertions, 691 deletions
@@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> /GIT-VERSION-FILE /Manifest.txt diff --git a/Documentation/.gitignore b/Documentation/.gitignore index 29953eb..b212ef7 100644 --- a/Documentation/.gitignore +++ b/Documentation/.gitignore @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> *.1 *.5 diff --git a/Documentation/GNUmakefile b/Documentation/GNUmakefile index d3834f9..9bad54f 100644 --- a/Documentation/GNUmakefile +++ b/Documentation/GNUmakefile @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> all:: diff --git a/Documentation/dtas-archive.pod b/Documentation/dtas-archive.pod index 4d7f4e8..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-2016 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> @@ -65,7 +74,7 @@ License: GPL-3.0+ L<https://www.gnu.org/licenses/gpl-3.0.txt> All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. diff --git a/Documentation/dtas-console.pod b/Documentation/dtas-console.pod index 1aa5acc..3bf11af 100644 --- a/Documentation/dtas-console.pod +++ b/Documentation/dtas-console.pod @@ -78,13 +78,13 @@ a problem. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-ctl.pod b/Documentation/dtas-ctl.pod index 699a723..25a8b3f 100644 --- a/Documentation/dtas-ctl.pod +++ b/Documentation/dtas-ctl.pod @@ -62,13 +62,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-cueedit.pod b/Documentation/dtas-cueedit.pod index 4452afe..9ee4797 100644 --- a/Documentation/dtas-cueedit.pod +++ b/Documentation/dtas-cueedit.pod @@ -23,13 +23,13 @@ VISUAL / EDITOR - your favorite *nix text editor, defaults to 'vi' if unset. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-enq.pod b/Documentation/dtas-enq.pod index 9992f73..dceea88 100644 --- a/Documentation/dtas-enq.pod +++ b/Documentation/dtas-enq.pod @@ -28,13 +28,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-env.pod b/Documentation/dtas-env.pod index d0ea2e8..a460700 100644 --- a/Documentation/dtas-env.pod +++ b/Documentation/dtas-env.pod @@ -10,7 +10,7 @@ As dtas uses Bourne shell and exposes it to users, dtas should have a cohesive set of common environment variables across its audio production and playback environments. This attempts to document them. Most of these environments are set and managed by dtas -itself, but users editing commands (e.g. via L<dtas-sourcedit(1)> +itself, but users editing commands (e.g. via L<dtas-sourcedit(1)>) should be aware of them. =head1 ENVIRONMENT @@ -64,12 +64,12 @@ temporary files are placed for most programs. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-msinkctl.pod b/Documentation/dtas-msinkctl.pod index 2cb4187..bee2af0 100644 --- a/Documentation/dtas-msinkctl.pod +++ b/Documentation/dtas-msinkctl.pod @@ -45,13 +45,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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 c4eb696..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 @@ -109,13 +109,13 @@ state across restarts of dtas-player. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 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-player_effects.pod b/Documentation/dtas-player_effects.pod index c0861f5..6c20934 100644 --- a/Documentation/dtas-player_effects.pod +++ b/Documentation/dtas-player_effects.pod @@ -92,12 +92,12 @@ playback hardware are applied at the sink: All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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_protocol.pod b/Documentation/dtas-player_protocol.pod index f709e96..e98197d 100644 --- a/Documentation/dtas-player_protocol.pod +++ b/Documentation/dtas-player_protocol.pod @@ -2,7 +2,7 @@ =head1 NAME -dtas-player_protocol - protocol for controling dtas-player +dtas-player_protocol - protocol for controlling dtas-player =head1 DESCRIPTION @@ -382,7 +382,7 @@ Clear current tracklist Show/or change consume status of the tracklist. Enabling this causes tracks to be deleted from the tracklist after they are played or skipped. -With no args, this will show "true" or "false +With no args, this will show "true" or "false" =item tl current @@ -464,12 +464,12 @@ another client socket to issue non-watch commands. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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_sink_examples.pod b/Documentation/dtas-player_sink_examples.pod index f40707e..a4cc623 100644 --- a/Documentation/dtas-player_sink_examples.pod +++ b/Documentation/dtas-player_sink_examples.pod @@ -71,13 +71,13 @@ See L<dtas-xdelay(1)>. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-sinkedit.pod b/Documentation/dtas-sinkedit.pod index d6af071..f30d29a 100644 --- a/Documentation/dtas-sinkedit.pod +++ b/Documentation/dtas-sinkedit.pod @@ -13,8 +13,8 @@ dtas-sinkedit SINKNAME dtas-sinkedit spawns an editor to allow editing of a sink as a YAML file. See L<dtas-player_protocol(7)> for details on SINKARGS. -On Linux machines with the sleepy_penguin RubyGem installed, L<inotify(7)> -is used to monitor the file for changes while the text exitor is running. +On Linux machines, L<inotify(7)> +is used to monitor the file for changes while the text editor is running. Each time a user finishes saving a file, changes are committed immediately. This behavior may be disabled by using the -N or --no-watch command-line switch. @@ -64,13 +64,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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-sourceedit.pod b/Documentation/dtas-sourceedit.pod index 169f08b..593bcf5 100644 --- a/Documentation/dtas-sourceedit.pod +++ b/Documentation/dtas-sourceedit.pod @@ -16,8 +16,8 @@ a pipe or file, it is parsed as YAML and fed to the L<dtas-player(1)> instance non-interactively. This is useful for loading various profiles from the filesystem. -On Linux machines with the sleepy_penguin RubyGem installed, L<inotify(7)> -is used to monitor the file for changes while the text exitor is running. +On Linux machines, L<inotify(7)> +is used to monitor the file for changes while the text editor is running. Each time a user finishes saving a file, changes are committed immediately. This behavior may be disabled by using the -N or --no-watch command-line switch. @@ -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 @@ -71,13 +67,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 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 b5dadfd..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,11 +37,24 @@ 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 the option to L<sox(1)> via SOX_OPTS. +=item -E, --err-suffix SUFFIX + +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>. Recommended for use with the +L</--stats> switch. + =item -O, --outdir OUTDIR Set output directory instead of current directory. @@ -70,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 @@ -84,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) @@ -115,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 @@ -127,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 @@ -262,7 +302,7 @@ imbalance in a live concert recording from the audience: =head1 COPYRIGHT -Copyright 2013-2016 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 6ca88d0..b5a4b31 100644 --- a/Documentation/dtas-tl.pod +++ b/Documentation/dtas-tl.pod @@ -29,7 +29,7 @@ client). =item consume [BOOLEAN] - show, enable, or disable consume mode Enabling "consume" mode causes tracks to be removed when they are -done playing (or skipped. +done playing (or skipped). =item current - display the current track, "NONE" if not playing @@ -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 @@ -78,7 +80,7 @@ display the current tracklist $ dtas-tl cat -to add an an entire directory of FLAC files +to add an entire directory of FLAC files $ dtas-tl addtail /path/to/directory/*.flac @@ -114,13 +116,13 @@ This defaults to ~/.dtas/player.sock All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 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-xdelay.pod b/Documentation/dtas-xdelay.pod index b05b093..358a31d 100644 --- a/Documentation/dtas-xdelay.pod +++ b/Documentation/dtas-xdelay.pod @@ -80,13 +80,13 @@ are greatly appreciated. All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. =head1 COPYRIGHT -Copyright 2013-2016 all contributors L<mailto:dtas-all@nongnu.org> +Copyright 2013-2020 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/update-footer.rb b/Documentation/update-footer.rb index ea7d7cd..e26e3e3 100755 --- a/Documentation/update-footer.rb +++ b/Documentation/update-footer.rb @@ -1,12 +1,12 @@ #!/usr/bin/env ruby -# Copyright 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright 2015-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true contact = %q{ All feedback welcome via plain-text mail to: L<mailto:dtas-all@nongnu.org> Mailing list archives available at L<https://80x24.org/dtas-all/> -and L<ftp://lists.gnu.org/dtas-all/> +and L<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. } diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 095c769..ecf9879 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,11 +1,11 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2019 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.16.1" +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 8a50278..9019b31 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2021 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> all:: pkg = dtas @@ -74,5 +74,17 @@ $(pkgtgz): .tgz-manifest package: $(pkgtgz) $(pkggem) +# Install symlinks to ~/bin (which is hopefuly in PATH) which point to +# this source tree. +# prefix + bindir matches git.git Makefile: +prefix = $(HOME) +bindir = $(prefix)/bin +symlink-install : + mkdir -p $(bindir) + dtas=$(CURDIR)/dtas.sh && cd $(bindir) && \ + for x in $(CURDIR)/bin/* $(CURDIR)/script/*; do \ + ln -sf "$$dtas" $$(basename "$$x"); \ + done + .PHONY: all .FORCE-GIT-VERSION-FILE test $(test_units) NEWS .PHONY: check-warnings fix-perms @@ -18,10 +18,10 @@ developers do. Please send patches via git-send-email(1) to the public mailing list at <dtas-all@nongnu.org>. Pull requests should be formatted using git-request-pull(1).\ Mailing list archives available at <https://80x24.org/dtas-all/> and -<ftp://lists.gnu.org/dtas-all/>\ +<https://lists.gnu.org/archive/html/dtas-all/>\ No subscription is necessary to post to the mailing list. # COPYRIGHT -Copyright 2013-2016 all contributors <dtas-all@nongnu.org>.\ +Copyright 2013-2020 all contributors <dtas-all@nongnu.org>.\ License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> @@ -1,33 +1,24 @@ -Uncommon for audio software, dtas is 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 reconfigured dtas-player to use something else. mp3gain is required if you want to use ReplayGain with MP3s +(it is no longer in new versions of Debian) If you only intend to use dtas-cueedit, you will need metaflac(1) from the FLAC package. -Debian 7+ users can install dependencies easily: +Debian 10+ users can install dependencies easily: - sudo apt-get install sox libsox-fmt-all mp3gain flac ruby-dev + sudo apt-get install sox libsox-fmt-all flac ruby-dev ruby-charlock-holmes -# installing dtas RubyGem on GNU/Linux (Linux kernel 2.6.32+) - -Be sure to have Ruby development headers and a working C compiler. -This will pull in the sleepy_penguin RubyGems for minor -speedups. If you cannot be bothered to have a development -environment, just use "gem install dtas". - - sudo gem install dtas-linux - -This should pull in the "sleepy_penguin" RubyGems - -For future upgrades of dtas (upgrades to dtas-linux will be infrequent) +For future upgrades of dtas sudo gem update dtas @@ -35,29 +26,30 @@ For future upgrades of dtas (upgrades to dtas-linux will be infrequent) 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/2019/dtas-0.16.1.tar.gz + https://80x24.org/dtas/2022/dtas-0.21.0.tar.gz - $ tar zxvf dtas-0.16.1.tar.gz - $ cd dtas-0.16.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" package: + # To install symlinks into ~/bin (assuming your Ruby executable is "ruby") + $ make symlink-install - * sleepy_penguin - https://bogomips.org/sleepy_penguin/ + # or using setup.rb: + $ sudo ruby setup.rb # CONTACT Please do not hesitate to send plain-text mail to <dtas-all@nongnu.org> regarding installation and to share your notes/experiences. Mailing list archives available at <https://80x24.org/dtas-all/> or -<ftp://lists.gnu.org/dtas-all> +<https://lists.gnu.org/archive/html/dtas-all/> No subscription is necessary to post to the mailing list. # COPYRIGHT -Copyright 2013-2019 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> @@ -25,7 +25,7 @@ for audio. dtas-player supports: * ReplayGain (including fallback gain and peak normalization) dtas-player is a *nix pipeline and process manager. It may be used -spawn and pipe to abitrary Unix commands, not just audio-related +spawn and pipe to arbitrary Unix commands, not just audio-related commands. It can interactively restart/replace the source (audio decoder) component of a pipeline while keeping the sink (playback endpoint) running. @@ -74,19 +74,25 @@ All feedback (comments, results, feature requests, bug reports, patches, pull-requests) via plain-text mail to the mailing list is very much appreciated. -Please send plain-text mail to the list at <dtas-all@nongnu.org>\ -HTML mail will not be read. dtas is for GUI-phobes, by GUI-phobes.\ -Mailing list archives available at <ftp://lists.gnu.org/dtas-all> -or <https://80x24.org/dtas-all/> +Please send plain-text mail to the list at <dtas-all@nongnu.org> +HTML mail will not be read. dtas is for GUI-phobes, by GUI-phobes. +Mailing list archives available at <https://80x24.org/dtas-all/> or +<https://lists.gnu.org/archive/html/dtas-all/>. + No subscription is necessary to post to the mailing list. You may also read via: -NNTP: <nntp://news.public-inbox.org/inbox.comp.audio.dtas> - <nntp://news.gmane.org/gmane.comp.audio.dtas.general> +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://;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-2019 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 @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org>. +# Copyright (C) 2013-2020 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' @@ -3,7 +3,9 @@ * tests for bin/* +* consider rewriting piecemeal in a more stable glue language than Ruby + # COPYRIGHT -Copyright 2013-2016 all contributors <dtas-all@nongnu.org>\ +Copyright 2013-2020 all contributors <dtas-all@nongnu.org> License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> diff --git a/bin/dtas-archive b/bin/dtas-archive index f47f05a..7c0e4f7 100755 --- a/bin/dtas-archive +++ b/bin/dtas-archive @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2015-2016 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 00b5cd8..eedd0f0 100755 --- a/bin/dtas-console +++ b/bin/dtas-console @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2019 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 @@ -53,8 +57,6 @@ tfmt = update_tfmt(prec_step[prec_nr], tsec) events = [] interval = 1.0 / 10 ** prec_nr -pause = nil - def show_events(lineno, screen, events) Curses.setpos(lineno += 1, 0) Curses.clrtoeol @@ -114,14 +116,14 @@ def rg_string(rg, current) rv end -def may_fail(res, events) +def may_fail(c, req, events) + res = c.req(req) events << res if res != "OK" end pre_mute_vol = 1.0 enc_locale = Encoding.find("locale") $stdout.set_encoding(enc_locale) -enc_opts = { undef: :replace, invalid: :replace, replace: '?' } begin Curses.init_screen Curses.nonl @@ -135,7 +137,6 @@ begin pfmt = cur['format'] elapsed = samples = 0 fmt = total = '' - paused = false if current = cur['current'] infile = current['infile'] || current['command'] elapsed = DTAS.now - current['spawn_at'] @@ -150,7 +151,6 @@ begin end elsif cur['paused'] && infile = cur['current_paused'] fmt = "[paused] (#{fmt_to_s(pfmt)})" - paused = true infile = infile['command'] if Hash === infile if Array === infile infile, elapsed = infile @@ -169,7 +169,12 @@ begin # FS encoding != locale encoding, but we need to display an FS path # name to whatever locale the terminal is encoded to, so force it # and risk mojibake... - infile.encode(enc_locale, enc_opts) + 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) @@ -206,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 @@ -220,29 +246,28 @@ begin case event when "pause" if current - pause = current['infile'] || current['command'] + current['infile'] || current['command'] end when %r{\Afile } - pause = nil end 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 - when "j" then c.req_ok("seek -5") - when "k" then c.req_ok("seek +5") + when "j" then may_fail(c, "seek -5", events) + when "k" then may_fail(c, "seek +5", events) when "q" then exit(0) - when Curses::KEY_DOWN then c.req_ok("seek -60") - when Curses::KEY_UP then c.req_ok("seek +60") - when Curses::KEY_LEFT then c.req_ok("seek -10") - when Curses::KEY_RIGHT then c.req_ok("seek +10") - when Curses::KEY_BACKSPACE then c.req_ok("seek 0") + when Curses::KEY_DOWN then may_fail(c, "seek -60", events) + when Curses::KEY_UP then may_fail(c, "seek +60", events) + when Curses::KEY_LEFT then may_fail(c, "seek -10", events) + when Curses::KEY_RIGHT then may_fail(c, "seek +10", events) + when Curses::KEY_BACKSPACE then may_fail(c, "seek 0", events) # yes, some of us have long audio files - when Curses::KEY_PPAGE then c.req_ok("seek +600") - when Curses::KEY_NPAGE then c.req_ok("seek -600") + when Curses::KEY_PPAGE then may_fail(c, "seek +600", events) + when Curses::KEY_NPAGE then may_fail(c, "seek -600", events) when '9' then c.req_ok('rg volume-=0.01') when '0' then c.req_ok('rg volume+=0.01') when '=' then c.req_ok('rg volume=1') @@ -253,8 +278,8 @@ begin when "f" then c.req_ok("rg fallback_gain-=1") when ">" then c.req_ok("tl next") when "<" then c.req_ok("tl prev") - when "!" then may_fail(c.req("cue prev"), events) - when "@" then may_fail(c.req("cue next"), events) + when "!" then may_fail(c, "cue prev", events) + when "@" then may_fail(c, "cue next", events) when "o" then tfmt = update_tfmt(prec_step[prec_nr], tsec = !tsec) when " " c.req("play_pause") @@ -276,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-ctl b/bin/dtas-ctl index 171576f..2d55e16 100755 --- a/bin/dtas-ctl +++ b/bin/dtas-ctl @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require 'dtas/unix_client' diff --git a/bin/dtas-cueedit b/bin/dtas-cueedit index 127b1a0..e176271 100755 --- a/bin/dtas-cueedit +++ b/bin/dtas-cueedit @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/bin/dtas-enq b/bin/dtas-enq index f054687..f49d3f7 100755 --- a/bin/dtas-enq +++ b/bin/dtas-enq @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require 'dtas/unix_client' diff --git a/bin/dtas-mlib b/bin/dtas-mlib index 45c3d01..7a07794 100755 --- a/bin/dtas-mlib +++ b/bin/dtas-mlib @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2015-2020 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 [-d DATABASE-URI] ACTION [ARGS]" diff --git a/bin/dtas-msinkctl b/bin/dtas-msinkctl index 9abf2dc..79c7f26 100755 --- a/bin/dtas-msinkctl +++ b/bin/dtas-msinkctl @@ -1,8 +1,7 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 388f7ba..6a0c9d4 100755 --- a/bin/dtas-partstats +++ b/bin/dtas-partstats @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 ccb3969..c926e5f 100755 --- a/bin/dtas-player +++ b/bin/dtas-player @@ -1,9 +1,8 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 93ab8c9..f2ab514 100755 --- a/bin/dtas-readahead +++ b/bin/dtas-readahead @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2015-2019 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 @@ -57,7 +43,7 @@ def seek_to_cur_pos(cur_pid, fp) end end rescue Errno::ENOENT => e # race, process is dead - return false + return nil rescue => e warn "error reading FDs from for PID:#{cur_pid}: #{e.message}" end @@ -71,7 +57,7 @@ def seek_to_cur_pos(cur_pid, fp) end pos rescue Errno::ENOENT => e # race, process is dead - return false + return nil end def children_of(ppid) @@ -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 61bb959..252270f 100755 --- a/bin/dtas-sinkedit +++ b/bin/dtas-sinkedit @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 713c466..1b3f4ee 100755 --- a/bin/dtas-sourceedit +++ b/bin/dtas-sourceedit @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 839d273..17d915d 100755 --- a/bin/dtas-splitfx +++ b/bin/dtas-splitfx @@ -1,19 +1,20 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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 } @@ -23,6 +24,9 @@ OptionParser.new('', 24, ' ') do |op| end op.on('-b', '--bits RATE', Integer) { |val| opts[:bits] = val } op.on('-t', '--trim POSITION') { |val| opts[:trim] = val.tr(',', ' ') } + op.on('-E', '--err-suffix SUFFIX') do |val| + opts[:err_suffix] = val.start_with?('.') ? val.freeze : ".#{val}" + end op.on('-p', '--sox-pipe') do opts[:sox_pipe] = true default_target = 'sox' @@ -30,13 +34,22 @@ OptionParser.new('', 24, ' ') do |op| op.parse!(ARGV) end +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 @@ -51,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 1ce18de..c7f4c83 100755 --- a/bin/dtas-tl +++ b/bin/dtas-tl @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 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/bin/dtas-xdelay b/bin/dtas-xdelay index 138f521..060752c 100755 --- a/bin/dtas-xdelay +++ b/bin/dtas-xdelay @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true USAGE = "Usage: #$0 [-x FREQ] [-l] /dev/fd/LO /dev/fd/HI DELAY [DELAY ...]" diff --git a/dtas-linux.gemspec b/dtas-linux.gemspec index f792978..a51b4d2 100644 --- a/dtas-linux.gemspec +++ b/dtas-linux.gemspec @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # # this just declares dependencies to make gem installation a little easier @@ -12,7 +12,7 @@ Gem::Specification.new do |s| "via tee(), splice() and eventfd() on Linux" s.email = %q{e@80x24.org} s.files = [] - s.homepage = 'https://80x24.org/dtas/' + s.homepage = 'https://80x24.org/dtas.git/about/' s.add_dependency(%q<dtas>, '~> 0.16') s.add_dependency(%q<sleepy_penguin>, '~> 3.5') s.licenses = 'GPL-3.0+' diff --git a/dtas-mpris.gemspec b/dtas-mpris.gemspec index 3603529..c94271e 100644 --- a/dtas-mpris.gemspec +++ b/dtas-mpris.gemspec @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-2.0+ or later <https://www.gnu.org/licenses/gpl-2.0.txt> # This is GPL-2.0+ instead of GPL-3.0+ because ruby-dbus is LGPL-2.1 (only) Gem::Specification.new do |s| @@ -11,7 +11,7 @@ Gem::Specification.new do |s| "This is currently a dummy package as dtas-mpris is not implemented" s.email = %q{e@80x24.org} s.files = [] - s.homepage = 'https://80x24.org/dtas/' + s.homepage = 'https://80x24.org/dtas.git/about/' s.add_dependency(%q<dtas>) s.add_dependency(%q<ruby-dbus>) s.licenses = 'GPL-2.0+' diff --git a/dtas.gemspec b/dtas.gemspec index ec18254..9bc1cc5 100644 --- a/dtas.gemspec +++ b/dtas.gemspec @@ -1,16 +1,16 @@ -# Copyright (C) 2013-2016 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"].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 s.email = %q{e@80x24.org} s.executables = manifest.grep(%r{\Abin/}).map { |s| s.sub(%r{\Abin/}, "") } s.files = manifest - s.homepage = 'https://80x24.org/dtas/' + 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 @@ -0,0 +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"); 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/examples/zsh-completion/README b/examples/zsh-completion/README new file mode 100644 index 0000000..89b3afd --- /dev/null +++ b/examples/zsh-completion/README @@ -0,0 +1,3 @@ +To use the completion functions defined in this directory either: add +the completions you wish to use to a directory in your $fpath, or add +this directory's path to $fpath *prior* to calling compinit. diff --git a/examples/zsh-completion/_dtas-archive b/examples/zsh-completion/_dtas-archive new file mode 100644 index 0000000..2bfdf7e --- /dev/null +++ b/examples/zsh-completion/_dtas-archive @@ -0,0 +1,16 @@ +#compdef dtas-archive + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments -S \ + "--type=[file type]" \ + "--compression=[compression factor for sox]:select compression:({0..8})" \ + "--jobs=[number of jobs]: :_guard '[0-9]#' value" \ + "--stats[save stats on the file]" \ + "--keep-going[continue after error]" \ + "--dry-run[only print commands, do not run them]" \ + "--repeat=[number of times to check]: :_guard '[0-9]#' value" \ + "--help[display help message]" \ + ":select source:_files" \ + ":select destination:_files" diff --git a/examples/zsh-completion/_dtas-ctl b/examples/zsh-completion/_dtas-ctl new file mode 100644 index 0000000..d82533b --- /dev/null +++ b/examples/zsh-completion/_dtas-ctl @@ -0,0 +1,114 @@ +#compdef dtas-ctl + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + ':dtas-ctl command:(( + cd\:"change the current working directory of the player" + clear\:"clear current queue" + cue\:"display the index/offsets of the embedded CUE sheet" + current\:"output information about the current track/command in YAML" + enq\:"enqueue the given FILENAME for playback" + enq-cmd\:"run the following command for playback" + env\:"set/unset environment variables" + format\:"configure the format between source and sink" + pause\:"pause playback" + play\:"restart playback from pause" + play_pause\:"toggle the play/pause state" + queue\ cat\:"dump the contents of the queue as YAML" + restart\:"restarts all processes in the current pipeline" + rg\:"configure ReplayGain support" + seek\:"seek the current track to a specified time" + skip\:"abort current track/command" + sink\:"control sinks" + source\:"control sources" + state\ dump\:"immediately dump the state of the player" + tl\:"control tracklist" + trim\:"limits playback of all tracks in the tracklist" + watch\:"adds the client to the passive watch list for notifications" + ))' \ + "*::subcmd:->subcmd" && return 0 + +case "$words[1]" in +(cd) + _arguments \ + ":select dir:_path_files -/" + ;; +(cue) + _arguments \ + ':dtas-ctl command:(( + next\:"skip to the next cue sheet offset" + prev\:"skip to the previous cue sheet offset" + goto\:"go to the cue index" + seek\:"seek within the current cue index" + ))' + ;; +(enq) + _arguments \ + "*:select file:_files" + ;; +(format) + _arguments \ + '*:dtas-ctl format command:(( + channels\:"number of channels to use internally" + endian\:"change endianess" + bits\:"sample precision" + rate\:"sample rate of audio" + type\:"change the raw PCM format" + ))' + ;; +(seek) + _arguments \ + ":select track:_guard '[0-9]#' 'track number'" + ;; +(sink) + _arguments \ + ':sink subcommand:(( + ls\:"list names of current sinks" + cat\:"dump SINKNAME config in YAML" + rm\:"remove SINKNAME" + ed\:"create/edit SINKNAME" + ))' + ;; +(source) + _arguments \ + ':source subcommand:(( + cat\:"dump the current source command and env in YAML" + ed\:"edit the source parameters" + ls\:"dump the names of sources sorted by tryorder" + restart\:"restart the current source command" + ))' + ;; +(state dump) + _arguments \ + ":select file:_files" + ;; +(tl) + _arguments \ + ':tl subcommand:(( + add\:"add files to the tracklist" + clear\:"clear current tracklist" + consume\:"show/or change consume status of the tracklist" + current\:"display the pathname to the currently playing track" + current-id\:"display the TRACKID of the currently playing track" + remove\:"remove the track with the given TRACKID from the track list" + get\:"returns a list of TRACKIDS mapped to shell-escaped filenames" + goto\:"plays the given TRACKID" + max\:"sets or gets the maximum number of tracks allowed in the tracklist" + next\:"jump to the next track in the tracklist" + prev\:"jump to the previous track in the tracklist" + repeat\:"show/or change repeat status of the tracklist" + shuffle\:"show/or change the current shuffle status of the tracklist" + swap\:"swaps the positions of two tracks" + tracks\:"returns a list of all TRACKIDS in the tracklist" + ))' + ;; +(trim) + _arguments \ + ":select beginning" \ + ":select end" + ;; +(*) + ;; +esac diff --git a/examples/zsh-completion/_dtas-cueedit b/examples/zsh-completion/_dtas-cueedit new file mode 100644 index 0000000..ac87822 --- /dev/null +++ b/examples/zsh-completion/_dtas-cueedit @@ -0,0 +1,7 @@ +#compdef dtas-cueedit + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + ':select file:_files -g "*.flac"' diff --git a/examples/zsh-completion/_dtas-enq b/examples/zsh-completion/_dtas-enq new file mode 100644 index 0000000..a8f9c3f --- /dev/null +++ b/examples/zsh-completion/_dtas-enq @@ -0,0 +1,7 @@ +#compdef dtas-enq + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + "*:select file:_files" diff --git a/examples/zsh-completion/_dtas-mlib b/examples/zsh-completion/_dtas-mlib new file mode 100644 index 0000000..24418ea --- /dev/null +++ b/examples/zsh-completion/_dtas-mlib @@ -0,0 +1,29 @@ +#compdef dtas-mlib + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. +# +_arguments \ + "--database=[database]:select file:_files" \ + "--force[force updates]" \ + "--help[display help message]" \ + ':dtas-mlib action:(( + dump\:"dump database" + search\:"search database" + stats\:"display statistics" + update\:"migrate database" + ))' \ + "*::subcmd:->subcmd" && return 0 + +case "$words[1]" in +(dump|update) + _arguments -S \ + ":select directory:_path_files -/" + ;; +(search) + _arguments -S \ + "*:search term:" + ;; +(*) + ;; +esac diff --git a/examples/zsh-completion/_dtas-msinkctl b/examples/zsh-completion/_dtas-msinkctl new file mode 100644 index 0000000..941c23b --- /dev/null +++ b/examples/zsh-completion/_dtas-msinkctl @@ -0,0 +1,8 @@ +#compdef dtas-msinkctl + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + "1:select action:(active{,-{set,add,sub}} nonblock)" \ + "*:select sink:($(dtas-ctl sink ls 2> /dev/null))" diff --git a/examples/zsh-completion/_dtas-partstats b/examples/zsh-completion/_dtas-partstats new file mode 100644 index 0000000..9460684 --- /dev/null +++ b/examples/zsh-completion/_dtas-partstats @@ -0,0 +1,7 @@ +#compdef dtas-partstats + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + ":select file:_files" diff --git a/examples/zsh-completion/_dtas-sinkedit b/examples/zsh-completion/_dtas-sinkedit new file mode 100644 index 0000000..be6de53 --- /dev/null +++ b/examples/zsh-completion/_dtas-sinkedit @@ -0,0 +1,11 @@ +#compdef dtas-sinkedit + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + "--no-watch[disable inotify support]" \ + "--dry-run[only print commands, do not run them]" \ + "--verbose[print out commands sent to change the sink]" \ + "--help[display help message]" \ + ":select sink:($(dtas-ctl sink ls 2> /dev/null))" diff --git a/examples/zsh-completion/_dtas-sourceedit b/examples/zsh-completion/_dtas-sourceedit new file mode 100644 index 0000000..56da661 --- /dev/null +++ b/examples/zsh-completion/_dtas-sourceedit @@ -0,0 +1,11 @@ +#compdef dtas-sourceedit + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + "--no-watch[disable inotify support]" \ + "--dry-run[only print commands, do not run them]" \ + "--verbose[print out commands sent to change the source]" \ + "--help[display help message]" \ + ":select source:($(dtas-ctl source ls 2> /dev/null))" diff --git a/examples/zsh-completion/_dtas-splitfx b/examples/zsh-completion/_dtas-splitfx new file mode 100644 index 0000000..dfebb64 --- /dev/null +++ b/examples/zsh-completion/_dtas-splitfx @@ -0,0 +1,17 @@ +#compdef dtas-splitfx + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments -S \ + "--dry-run[only print commands, do not run them]" \ + "--jobs=[number of jobs]: :_guard '[0-9]#' value" \ + "--no-dither[don't apply sox dithering]" \ + "--outdir=[select output directory]:select directory:_path_files -/" \ + "--compression=[compression factor for sox]:select compression:({0..8})" \ + "--rate=[sample rate of audio]:select sample rate:(22050 44100 48000)" \ + "--bits=[sample precision]:select precision:(8 16 24)" \ + "--trim=[sections of audio to cut]:select sections:_guard '[0-9,]#' 'value'" \ + "--sox-pipe[use as pipeline]" \ + '1:select splitfx file:_files -g "*.yml"' \ + "*:select file:_files" diff --git a/examples/zsh-completion/_dtas-tl b/examples/zsh-completion/_dtas-tl new file mode 100644 index 0000000..8e2b098 --- /dev/null +++ b/examples/zsh-completion/_dtas-tl @@ -0,0 +1,52 @@ +#compdef dtas-tl + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments \ + ':dtas-tl command:(( + aac\:"add tracks after current track in the tracklist" + addhead\:"add tracks to the beginning of the tracklist" + addtail\:"add tracks to the end of the tracklist" + consume\:"enabling \"consume\" mode" + current\:"display the current track" + current-id\:"display the track of the current track" + cat\:"display a tracklist" + clear\:"remove all tracks from the tracklist" + edit\:"spawn an editor to allow editing the tracklist" + goto\:"play track immediately" + reto\:"play track matching regular expression" + next\:"play the next track in the tracklist" + prev\:"play the previous track in the tracklist" + repeat\:"control track repeating" + shuffle\:"control playback randomization" + ))' \ + "*::subcmd:->subcmd" && return 0 + +case "$words[1]" in +(aac|addtail) + _arguments \ + ":select file:_files" + ;; +(addhead) + _arguments \ + "*:select file:_files" + ;; +(consume|repeat|shuffle) + _arguments \ + ":select state:(true false)" + ;; +(goto) + _arguments \ + ":select track:($(dtas-ctl tl tracks 2> /dev/null))" + ;; +(reto) + _arguments \ + "-F[use fixed strings]" \ + "-i[ignore case]" \ + ":search term" \ + ":select beginning" + ;; +(*) + ;; +esac diff --git a/examples/zsh-completion/_dtas-xdelay b/examples/zsh-completion/_dtas-xdelay new file mode 100644 index 0000000..356537d --- /dev/null +++ b/examples/zsh-completion/_dtas-xdelay @@ -0,0 +1,17 @@ +#compdef dtas-xdelay + +# To the extent possible under law, James Rowe has waived all copyright and +# related or neighboring rights to this example. + +_arguments -S \ + "--crossover-frequency=[frequency at which to set the crossover]: :_guard '[0-9]#' frequency" \ + "--lowpass-delay[delay the lowpass frequency instead of the highpass one]" \ + "--channels=[number of channels]:select channels:(1 2)" \ + "--rate=[sample rate of audio]:select sample rate:(22050 44100 48000)" \ + "--type=[file type]:select output type:($(sox --help 2> /dev/null | sed -n '/AUDIO FILE FORMATS/s/.*: //p'))" \ + "--dry-run[only print commands, do not run them]" \ + "--lowpass=[Custom format string for lowpass filter]" \ + "--highpass=[Custom format string for highpass filter]" \ + ":select input1:_files" \ + ":select input2:_files" \ + ":select delay:_guard '[0-9]#' delay" diff --git a/lib/dtas.rb b/lib/dtas.rb index 9c1b5a5..cb7c33d 100644 --- a/lib/dtas.rb +++ b/lib/dtas.rb @@ -1,23 +1,16 @@ -# Copyright (C) 2013-2019 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 # DTAS currently exposes no public API for Ruby programmers. -# See https://80x24.org/dtas/ for more info. +# See https://80x24.org/dtas.git/about/ for more info. 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 @@ -25,22 +18,21 @@ module DTAS @null ||= File.open('/dev/null', 'r+') 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" + @libc = nil + def self.libc + @libc ||= begin + require 'fiddle' + Fiddle.dlopen(nil) end 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 39070d7..0688af9 100644 --- a/lib/dtas/buffer.rb +++ b/lib/dtas/buffer.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 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' @@ -8,12 +8,15 @@ require_relative '../dtas' class DTAS::Buffer # :nodoc: begin raise LoadError, "no splice with _DTAS_POSIX" if ENV["_DTAS_POSIX"] - require 'sleepy_penguin' # splice is only in Linux for now... - SleepyPenguin.respond_to?(:splice) or - raise LoadError, 'sleepy_penguin 3.5+ required for splice', [] - require_relative 'buffer/splice' - include DTAS::Buffer::Splice - rescue LoadError + # splice is only in Linux for now + begin + require_relative 'buffer/splice' + include DTAS::Buffer::Splice + rescue LoadError + require_relative 'buffer/fiddle_splice' + include DTAS::Buffer::FiddleSplice + end + rescue LoadError, StandardError require_relative 'buffer/read_write' include DTAS::Buffer::ReadWrite end @@ -42,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 new file mode 100644 index 0000000..d9232cd --- /dev/null +++ b/lib/dtas/buffer/fiddle_splice.rb @@ -0,0 +1,217 @@ +# Copyright (C) 2013-2020 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 'fiddle' # require_relative caller should expect LoadError +require_relative '../../dtas' +require_relative '../pipe' + +# Used by -player on Linux systems with the "splice" syscall +module DTAS::Buffer::FiddleSplice # :nodoc: + MAX_AT_ONCE = 4096 # page size in Linux + MAX_AT_ONCE_1 = 65536 + F_MOVE = 1 + F_NONBLOCK = 2 + + Splice = Fiddle::Function.new(DTAS.libc['splice'], [ + Fiddle::TYPE_INT, # int fd_in, + Fiddle::TYPE_VOIDP, # loff_t *off_in + Fiddle::TYPE_INT, # int fd_out + Fiddle::TYPE_VOIDP, # loff_t *off_out + Fiddle::TYPE_SIZE_T, # size_t len + Fiddle::TYPE_INT, # unsigned int flags + ], + Fiddle::TYPE_SSIZE_T) # ssize_t + + Tee = Fiddle::Function.new(DTAS.libc['tee'], [ + Fiddle::TYPE_INT, # int fd_in, + Fiddle::TYPE_INT, # int fd_out + Fiddle::TYPE_SIZE_T, # size_t len + Fiddle::TYPE_INT, # unsigned int flags + ], + Fiddle::TYPE_SSIZE_T) # ssize_t + + def _syserr(s, func) + raise "BUG: we should not encounter EOF on #{func}" if s == 0 + case errno = Fiddle.last_error + when Errno::EAGAIN::Errno + return :EAGAIN + when Errno::EPIPE::Errno + raise Errno::EPIPE.exception + when Errno::EINTR::Errno + return nil + else + raise SystemCallError, "#{func} error: #{errno}" + end + end + + def splice(src, dst, len, flags) + begin + s = Splice.call(src.fileno, nil, dst.fileno, nil, len, flags) + return s if s > 0 + sym = _syserr(s, 'splice') and return sym + end while true + end + + def tee(src, dst, len, flags = 0) + begin + s = Tee.call(src.fileno, dst.fileno, len, flags) + return s if s > 0 + sym = _syserr(s, 'tee') and return sym + end while true + end + + def buffer_size + @to_io.pipe_size + end + + # nil is OK, won't reset existing pipe, either... + def buffer_size=(bytes) + @to_io.pipe_size = bytes if bytes + @buffer_size = bytes + end + + # be sure to only call this with nil when all writers to @wr are done + def discard(bytes) + splice(@to_io, DTAS.null, bytes, 0) + end + + def broadcast_one(targets, limit = nil) + # single output is always non-blocking + limit ||= MAX_AT_ONCE_1 + s = splice(@to_io, targets[0], limit, F_MOVE|F_NONBLOCK) + if Symbol === s + targets # our one and only target blocked on write + else + @bytes_xfer += s + # s < limit means targets[0] is full + s < limit ? targets : :wait_readable + end + rescue Errno::EPIPE, IOError => e + __dst_error(targets[0], e) + targets.clear + nil # do not return error here, we already spewed an error message + end + + def __tee_in_full(src, dst, bytes) + rv = 0 + while bytes > 0 + s = tee(src, dst, bytes) + bytes -= s + rv += s + end + rv + end + + def __splice_in_full(src, dst, bytes, flags) + rv = 0 + while bytes > 0 + s = splice(src, dst, bytes, flags) + rv += s + bytes -= s + end + rv + end + + # returns the largest value we teed + def __broadcast_tee(blocked, targets, chunk_size) + most_teed = 0 + targets.delete_if do |dst| + begin + t = (dst.nonblock? || most_teed == 0) ? + tee(@to_io, dst, chunk_size, F_NONBLOCK) : + __tee_in_full(@to_io, dst, chunk_size) + if Integer === t + if t > most_teed + chunk_size = t if most_teed == 0 + most_teed = t + end + else + blocked << dst + end + false + rescue IOError, Errno::EPIPE => e + __dst_error(dst, e) + true + end + end + most_teed + end + + def broadcast_inf(targets, limit = nil) + if targets.all?(&:ready_write_optimized?) + blocked = [] + elsif targets.none?(&:nonblock?) + # if all targets are blocking, don't start until they're all writable + r = IO.select(nil, targets, nil, 0) or return targets + blocked = targets - r[1] + + # tell DTAS::UNIXServer#run_once to wait on the blocked targets + return blocked if blocked[0] + + # all writable, yay! + else + blocked = [] + end + + # don't pin too much on one target + bytes = limit || MAX_AT_ONCE + last = targets.pop # we splice to the last one, tee to the rest + + # this may return zero if all targets were non-blocking + most_teed = __broadcast_tee(blocked, targets, bytes) + + # don't splice more than the largest amount we successfully teed + bytes = most_teed if most_teed > 0 + + begin + targets << last + if last.nonblock? || most_teed == 0 + s = splice(@to_io, last, bytes, F_MOVE|F_NONBLOCK) + if Symbol === s + blocked << last + + # we accomplished nothing! + # If _all_ writers are blocked, do not discard data, + # stay blocked on :wait_writable + return blocked if most_teed == 0 + + # the tees targets win, drop data intended for last + if most_teed > 0 + discard(most_teed) + @bytes_xfer += most_teed + # do not watch for writability of last, last is non-blocking + return :wait_readable + end + end + else + # the blocking case is simple + s = __splice_in_full(@to_io, last, bytes, F_MOVE) + end + @bytes_xfer += s + + # if we can't splice everything + # discard it so the early targets do not get repeated data + if s < bytes && most_teed > 0 + discard(bytes - s) + end + :wait_readable + rescue IOError, Errno::EPIPE => e # last failed, drop it + __dst_error(last, e) + targets.pop # we're no longer a valid target + + if most_teed == 0 + # nothing accomplished, watch any targets + return blocked if blocked[0] + else + # some progress, discard the data we could not splice + @bytes_xfer += most_teed + discard(most_teed) + end + + # stop decoding if we're completely errored out + # returning nil will trigger close + return targets[0] ? :wait_readable : nil + end + end +end diff --git a/lib/dtas/buffer/read_write.rb b/lib/dtas/buffer/read_write.rb index 04856c7..8fdb25d 100644 --- a/lib/dtas/buffer/read_write.rb +++ b/lib/dtas/buffer/read_write.rb @@ -1,13 +1,12 @@ -# Copyright (C) 2013-2019 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 systems lacking "splice" support via the -# "sleepy_penguin" 3.5+ RubyGem. Used only by -player +# compatibility code for non-Linux systems lacking "splice" support. +# Used only by -player module DTAS::Buffer::ReadWrite # :nodoc: MAX_AT_ONCE = 512 # min PIPE_BUF value in POSIX attr_accessor :buffer_size diff --git a/lib/dtas/buffer/splice.rb b/lib/dtas/buffer/splice.rb index 281ecfd..b9957ce 100644 --- a/lib/dtas/buffer/splice.rb +++ b/lib/dtas/buffer/splice.rb @@ -1,10 +1,12 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'sleepy_penguin' require_relative '../../dtas' require_relative '../pipe' +SleepyPenguin.respond_to?(:splice) or + raise LoadError, 'sleepy_penguin 3.5+ required for splice', [] # Used by -player on Linux systems with the "sleepy_penguin" RubyGem installed module DTAS::Buffer::Splice # :nodoc: @@ -12,7 +14,6 @@ module DTAS::Buffer::Splice # :nodoc: MAX_AT_ONCE_1 = 65536 F_MOVE = SleepyPenguin::F_MOVE F_NONBLOCK = SleepyPenguin::F_NONBLOCK - TRY = { exception: false }.freeze def buffer_size @to_io.pipe_size @@ -32,12 +33,14 @@ module DTAS::Buffer::Splice # :nodoc: def broadcast_one(targets, limit = nil) # single output is always non-blocking limit ||= MAX_AT_ONCE_1 - s = SleepyPenguin.splice(@to_io, targets[0], limit, F_MOVE|F_NONBLOCK, TRY) + s = SleepyPenguin.splice(@to_io, targets[0], limit, F_MOVE|F_NONBLOCK, + exception: false) if Symbol === s 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) @@ -71,7 +74,8 @@ module DTAS::Buffer::Splice # :nodoc: targets.delete_if do |dst| begin t = (dst.nonblock? || most_teed == 0) ? - SleepyPenguin.tee(@to_io, dst, chunk_size, F_NONBLOCK, TRY) : + SleepyPenguin.tee(@to_io, dst, chunk_size, F_NONBLOCK, + exception: false) : __tee_in_full(@to_io, dst, chunk_size) if Integer === t if t > most_teed @@ -119,7 +123,8 @@ module DTAS::Buffer::Splice # :nodoc: begin targets << last if last.nonblock? || most_teed == 0 - s = SleepyPenguin.splice(@to_io, last, bytes, F_MOVE|F_NONBLOCK, TRY) + s = SleepyPenguin.splice(@to_io, last, bytes, F_MOVE|F_NONBLOCK, + exception: false) if Symbol === s blocked << last diff --git a/lib/dtas/command.rb b/lib/dtas/command.rb index 5ac1eb7..116f880 100644 --- a/lib/dtas/command.rb +++ b/lib/dtas/command.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'serialize' diff --git a/lib/dtas/compat_onenine.rb b/lib/dtas/compat_onenine.rb deleted file mode 100644 index 39cc1ec..0000000 --- a/lib/dtas/compat_onenine.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (C) 2013-2016 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/cue_index.rb b/lib/dtas/cue_index.rb index 74d4098..9ba9334 100644 --- a/lib/dtas/cue_index.rb +++ b/lib/dtas/cue_index.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/disclaimer.rb b/lib/dtas/disclaimer.rb index b5e0a57..91cfb7d 100644 --- a/lib/dtas/disclaimer.rb +++ b/lib/dtas/disclaimer.rb @@ -1,5 +1,5 @@ # :enddoc: -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true DTAS_PROGNAME = File.basename($0) diff --git a/lib/dtas/edit_client.rb b/lib/dtas/edit_client.rb index 82cc857..2bdc4d8 100644 --- a/lib/dtas/edit_client.rb +++ b/lib/dtas/edit_client.rb @@ -1,8 +1,7 @@ -# Copyright (C) 2013-2016 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/encoding.rb b/lib/dtas/encoding.rb index 613e376..bbc6076 100644 --- a/lib/dtas/encoding.rb +++ b/lib/dtas/encoding.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2018-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true @@ -11,15 +11,14 @@ module DTAS::Encoding # :nodoc: private def try_enc_harder(str, enc, old) # :nodoc: + begin + require 'charlock_holmes' + @charlock_holmes = CharlockHolmes::EncodingDetector.new + rescue LoadError + @charlock_holmes = false + end if @charlock_holmes.nil? + case @charlock_holmes - when nil - begin - require 'charlock_holmes' - @charlock_holmes = CharlockHolmes::EncodingDetector.new - rescue LoadError - warn "`charlock_holmes` gem not available for encoding detection" - @charlock_holmes = false - end when false enc_fallback(str, enc, old) else diff --git a/lib/dtas/fadefx.rb b/lib/dtas/fadefx.rb index 1a00653..0ec108c 100644 --- a/lib/dtas/fadefx.rb +++ b/lib/dtas/fadefx.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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/format.rb b/lib/dtas/format.rb index f4ea102..2c26517 100644 --- a/lib/dtas/format.rb +++ b/lib/dtas/format.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/mcache.rb b/lib/dtas/mcache.rb index b638a23..e0a39af 100644 --- a/lib/dtas/mcache.rb +++ b/lib/dtas/mcache.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2016 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 e0f19ab..f99ed6a 100644 --- a/lib/dtas/mlib.rb +++ b/lib/dtas/mlib.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -# Copyright (C) 2015-2019 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 # @@ -129,9 +129,13 @@ class DTAS::Mlib # :nodoc: comments.where(q).delete tmp.each do |tid, val| v = vals[val: val] - q[:val_id] = v ? v[:id] : vals.insert(val: val) - q[:tag_id] = tid - comments.insert(q) + begin + q[:val_id] = v ? v[:id] : vals.insert(val: val) + q[:tag_id] = tid + comments.insert(q) + rescue => e + warn "E: #{e.message} (#{e.class}) q=#{q.inspect} val=#{val.inspect}" + end end end end @@ -197,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 @@ -214,12 +216,16 @@ class DTAS::Mlib # :nodoc: end end + def maybe_blob(path) + path.valid_encoding? ? path : Sequel.blob(path) + end + def scan_file(path, st, parent_id) return if @suffixes !~ path || st.size == 0 # no-op if no change unless @force - if node = @db[:nodes][name: path, parent_id: parent_id] + if node = @db[:nodes][name: maybe_blob(path), parent_id: parent_id] return if st.ctime.to_i == node[:ctime] || node[:tlen] == DM_IGN end end @@ -271,14 +277,16 @@ class DTAS::Mlib # :nodoc: node_id = node.delete(:id) @db[:nodes].where(id: node_id).update(node.merge(q)) node[:id] = node_id + rescue => e + warn "E: #{e.message} (#{e.class}) node=#{node.inspect}" end def node_lookup(parent_id, name) - @db[:nodes][name: name, parent_id: parent_id] + @db[:nodes][name: maybe_blob(name), parent_id: parent_id] end def node_ensure(parent_id, name, tlen, ctime = nil) - q = { name: name, parent_id: parent_id } + q = { name: maybe_blob(name), parent_id: parent_id } if node = @db[:nodes][q] node_update_maybe(node, tlen, ctime) else @@ -289,6 +297,8 @@ class DTAS::Mlib # :nodoc: node[:id] = @db[:nodes].insert(node) end node + rescue => e + warn "E: #{e.message} (#{e.class}) q=#{q.inspect}" end def cd(path) @@ -409,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]] @@ -417,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/mlib/migrations/0001_initial.rb b/lib/dtas/mlib/migrations/0001_initial.rb index 688b6a5..d3da6a3 100644 --- a/lib/dtas/mlib/migrations/0001_initial.rb +++ b/lib/dtas/mlib/migrations/0001_initial.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2015-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> Sequel.migration do diff --git a/lib/dtas/nonblock.rb b/lib/dtas/nonblock.rb deleted file mode 100644 index 504c8d2..0000000 --- a/lib/dtas/nonblock.rb +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (C) 2015-2016 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/parse_freq.rb b/lib/dtas/parse_freq.rb index afc9048..201c284 100644 --- a/lib/dtas/parse_freq.rb +++ b/lib/dtas/parse_freq.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2015-2020 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' diff --git a/lib/dtas/parse_time.rb b/lib/dtas/parse_time.rb index f156c5c..73f5b5c 100644 --- a/lib/dtas/parse_time.rb +++ b/lib/dtas/parse_time.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/partstats.rb b/lib/dtas/partstats.rb index 1037c87..061ff50 100644 --- a/lib/dtas/partstats.rb +++ b/lib/dtas/partstats.rb @@ -1,9 +1,8 @@ # -*- encoding: binary -*- -# Copyright (C) 2013-2016 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 'xs' require_relative 'process' require_relative 'sigevent' @@ -12,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 @@ -56,7 +54,7 @@ class DTAS::PartStats # :nodoc: rd, wr = IO.pipe env = opts[:env] env = env ? env.dup : {} - env["INFILE"] = xs(@infile) + env["INFILE"] = @infile env["TRIMFX"] = "trim #{trim_part.tbeg}s #{trim_part.tlen}s" opts = { pgroup: true, close_others: true, err: wr } pid = spawn(env, CMD, opts) @@ -173,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 58d926c..a7b02b0 100644 --- a/lib/dtas/pipe.rb +++ b/lib/dtas/pipe.rb @@ -1,19 +1,19 @@ -# Copyright (C) 2013-2019 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 -begin - require 'sleepy_penguin' -rescue LoadError -end 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 + if RUBY_PLATFORM =~ /linux/i && File.readable?('/proc/sys/fs/pipe-max-size') + F_SETPIPE_SZ = 1031 + F_GETPIPE_SZ = 1032 + end + def self.new _, w = rv = pipe w.writable_iter_init @@ -21,13 +21,16 @@ class DTAS::Pipe < DTAS::Nonblock # :nodoc: end def pipe_size=(nr) - defined?(SleepyPenguin::F_SETPIPE_SZ) and - fcntl(SleepyPenguin::F_SETPIPE_SZ, nr) + fcntl(F_SETPIPE_SZ, nr) if defined?(F_SETPIPE_SZ) + rescue Errno::EINVAL # old kernel + rescue Errno::EPERM + # resizes fail if Linux is close to the pipe limit for the user + # or if the user does not have permissions to resize end def pipe_size - fcntl(SleepyPenguin::F_GETPIPE_SZ) - end if defined?(SleepyPenguin::F_GETPIPE_SZ) + fcntl(F_GETPIPE_SZ) + end if defined?(F_GETPIPE_SZ) # avoid syscall, we never change IO#nonblock= directly def nonblock? diff --git a/lib/dtas/pipeline.rb b/lib/dtas/pipeline.rb index b900fee..1bebe87 100644 --- a/lib/dtas/pipeline.rb +++ b/lib/dtas/pipeline.rb @@ -1,12 +1,9 @@ -# Copyright (C) 2017-2019 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 37f2c96..6ea3aba 100644 --- a/lib/dtas/player.rb +++ b/lib/dtas/player.rb @@ -1,8 +1,8 @@ -# Copyright (C) 2013-2016 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) @@ -282,7 +277,7 @@ class DTAS::Player # :nodoc: if deleted[0] warn("#{sink.name} died unexpectedly: #{status.inspect}") deleted.each { |t| drop_target(t) } - __current_drop unless @targets[0] + do_pause unless @targets[0] return # sink stays dead if it died unexpectedly end @@ -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 @@ -451,7 +450,7 @@ class DTAS::Player # :nodoc: # pull data from sink_buf into @targets, source feeds into sink_buf def sink_iter wait_iter = broadcast_iter(@sink_buf, @targets) - __current_drop if nil == wait_iter # sink error, stop source + do_pause if nil == wait_iter # sink error, stop source return wait_iter if @current # no source left to feed sink_buf, drain the remaining data @@ -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 1e4ac96..3c5fe5d 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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) @@ -197,19 +197,20 @@ module DTAS::Player::ClientHandler # :nodoc: end end + def __offset_to_i(offset, src) + # either "999s" for 999 samples or HH:MM:SS for time + offset.sub!(/s\z/, '') ? offset.to_i : src.format.hhmmss_to_samples(offset) + end + def __offset_to_samples(offset) - offset.sub!(/s\z/, '') and return offset.to_i - @current.format.hhmmss_to_samples(offset) + __offset_to_i(offset, @current) end # returns seek offset as an Integer in sample count - def __seek_offset_adj(dir, offset) - if offset.sub!(/s\z/, '') - offset = offset.to_i - else # time - offset = @current.format.hhmmss_to_samples(offset) - end - n = __current_decoded_samples + (dir * offset) + def __seek_offset_adj(dir, offset, + src = @current, + current_decoded_samples = __current_decoded_samples) + n = current_decoded_samples + (dir * __offset_to_i(offset, src)) n = 0 if n < 0 "#{n}s" end @@ -391,15 +392,17 @@ module DTAS::Player::ClientHandler # :nodoc: end end + def __offset_direction(offset) + offset.sub!(/\A\+/, '') ? 1 : (offset.sub!(/\A-/, '') ? -1 : nil) + end + def dpc_seek(io, msg) offset = msg[0] or return io.emit('ERR usage: seek OFFSET') if @current if @current.respond_to?(:infile) begin - if offset.sub!(/\A\+/, '') - offset = __seek_offset_adj(1, offset) - elsif offset.sub!(/\A-/, '') - offset = __seek_offset_adj(-1, offset) + if direction = __offset_direction(offset) + offset = __seek_offset_adj(direction, offset) # else: pass to sox directly end rescue ArgumentError @@ -413,7 +416,12 @@ module DTAS::Player::ClientHandler # :nodoc: case file = @queue[0] when String @queue[0] = [ file, offset ] - when Array + when Array # offset already stored, adjust + if direction = __offset_direction(offset) + tmp = try_file(*file) + cur_off = __offset_to_i(file[1].dup, tmp) + offset = __seek_offset_adj(direction, offset, tmp, cur_off) + end file[1] = offset else return io.emit("ERR unseekable") @@ -556,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 4caf96b..02bf77e 100644 --- a/lib/dtas/process.rb +++ b/lib/dtas/process.rb @@ -1,17 +1,15 @@ -# Copyright (C) 2013-2016 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/replaygain.rb b/lib/dtas/replaygain.rb index 116625e..5fa6dcf 100644 --- a/lib/dtas/replaygain.rb +++ b/lib/dtas/replaygain.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true diff --git a/lib/dtas/rg_state.rb b/lib/dtas/rg_state.rb index 6b2c718..9a44835 100644 --- a/lib/dtas/rg_state.rb +++ b/lib/dtas/rg_state.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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/serialize.rb b/lib/dtas/serialize.rb index fbed5af..d8331e6 100644 --- a/lib/dtas/serialize.rb +++ b/lib/dtas/serialize.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true diff --git a/lib/dtas/sigevent.rb b/lib/dtas/sigevent.rb index d4a96d7..16edacb 100644 --- a/lib/dtas/sigevent.rb +++ b/lib/dtas/sigevent.rb @@ -1,10 +1,13 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true begin raise LoadError, "no eventfd with _DTAS_POSIX" if ENV["_DTAS_POSIX"] - require 'sleepy_penguin' - require_relative 'sigevent/efd' + begin + require_relative 'sigevent/efd' + rescue LoadError + require_relative 'sigevent/fiddle_efd' + end rescue LoadError require_relative 'sigevent/pipe' end diff --git a/lib/dtas/sigevent/efd.rb b/lib/dtas/sigevent/efd.rb index 4be2c84..d13c32a 100644 --- a/lib/dtas/sigevent/efd.rb +++ b/lib/dtas/sigevent/efd.rb @@ -1,8 +1,10 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # used in various places for safe wakeups from IO.select via signals # This requires a modern Linux system and the "sleepy_penguin" RubyGem +require 'sleepy_penguin' + class DTAS::Sigevent < SleepyPenguin::EventFD # :nodoc: def self.new super(0, :CLOEXEC) diff --git a/lib/dtas/sigevent/fiddle_efd.rb b/lib/dtas/sigevent/fiddle_efd.rb new file mode 100644 index 0000000..8bfa332 --- /dev/null +++ b/lib/dtas/sigevent/fiddle_efd.rb @@ -0,0 +1,37 @@ +# 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 'fiddle' +class DTAS::Sigevent # :nodoc: + + EventFD = Fiddle::Function.new(DTAS.libc['eventfd'], + [ Fiddle::TYPE_INT, Fiddle::TYPE_INT ], # initval, flags + Fiddle::TYPE_INT) # fd + + attr_reader :to_io + 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 = IO.for_fd(fd) + @buf = ''.b + end + + def signal + @to_io.syswrite(ONE) + end + + def readable_iter + @to_io.read_nonblock(8, @buf, exception: false) + yield self, nil # calls DTAS::Process.reaper + :wait_readable + end + + def close + @to_io.close + end +end diff --git a/lib/dtas/sigevent/pipe.rb b/lib/dtas/sigevent/pipe.rb index 921a5b3..6c3b83c 100644 --- a/lib/dtas/sigevent/pipe.rb +++ b/lib/dtas/sigevent/pipe.rb @@ -1,15 +1,14 @@ -# Copyright (C) 2013-2016 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 "sleepy_penguin" RubyGem -require_relative '../nonblock' +# A fallback for non-Linux systems lacking the "splice" syscall 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 c481032..966bab4 100644 --- a/lib/dtas/sink.rb +++ b/lib/dtas/sink.rb @@ -1,7 +1,6 @@ -# Copyright (C) 2013-2016 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.rb b/lib/dtas/source.rb index 1944894..e3ca17e 100644 --- a/lib/dtas/source.rb +++ b/lib/dtas/source.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/source/av.rb b/lib/dtas/source/av.rb index 0a9d39b..dcebcfd 100644 --- a/lib/dtas/source/av.rb +++ b/lib/dtas/source/av.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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 ae654ba..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-2016 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 @@ -186,7 +209,7 @@ module DTAS::Source::AvFfCommon # :nodoc: e["PROBE"] = @probe_harder ? @probe_harder.join(' ') : nil # make sure these are visible to the source command... - e["INFILE"] = xs(@infile) + e["INFILE"] = @infile e["AMAP"] = amap e["SSPOS"] = sspos e["RGFX"] = rg_state.effect(self) || nil diff --git a/lib/dtas/source/cmd.rb b/lib/dtas/source/cmd.rb index cdcd3b3..435ac07 100644 --- a/lib/dtas/source/cmd.rb +++ b/lib/dtas/source/cmd.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/source/common.rb b/lib/dtas/source/common.rb index a6d1a60..bdcb16d 100644 --- a/lib/dtas/source/common.rb +++ b/lib/dtas/source/common.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> module DTAS::Source::Common # :nodoc: attr_reader :dst_zero_byte # first byte this source object saw diff --git a/lib/dtas/source/ff.rb b/lib/dtas/source/ff.rb index 80436c7..c337b42 100644 --- a/lib/dtas/source/ff.rb +++ b/lib/dtas/source/ff.rb @@ -1,12 +1,10 @@ -# Copyright (C) 2013-2016 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/file.rb b/lib/dtas/source/file.rb index 01ac998..88beb39 100644 --- a/lib/dtas/source/file.rb +++ b/lib/dtas/source/file.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/source/mp3gain.rb b/lib/dtas/source/mp3gain.rb index 3a7569d..7688822 100644 --- a/lib/dtas/source/mp3gain.rb +++ b/lib/dtas/source/mp3gain.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 '../process' diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb index dc23c27..6ca29bc 100644 --- a/lib/dtas/source/sox.rb +++ b/lib/dtas/source/sox.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 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 @@ -113,7 +113,7 @@ class DTAS::Source::Sox # :nodoc: def src_spawn(player_format, rg_state, opts) raise "BUG: #{self.inspect}#src_spawn called twice" if @to_io e = @env.merge!(player_format.to_env) - e["INFILE"] = xs(@infile) + e["INFILE"] = @infile # make sure these are visible to the "current" command... e["TRIMFX"] = trimfx diff --git a/lib/dtas/source/splitfx.rb b/lib/dtas/source/splitfx.rb index f746bee..2268404 100644 --- a/lib/dtas/source/splitfx.rb +++ b/lib/dtas/source/splitfx.rb @@ -1,7 +1,6 @@ -# Copyright (C) 2014-2016 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 a510a9e..0000000 --- a/lib/dtas/spawn_fix.rb +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (C) 2013-2016 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 c0c7ac9..1150ee0 100644 --- a/lib/dtas/splitfx.rb +++ b/lib/dtas/splitfx.rb @@ -1,19 +1,18 @@ -# Copyright (C) 2013-2016 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 'format' require_relative 'process' -require_relative 'xs' require 'tempfile' # The backend for dtas-splitfx(1) command, but also supported by dtas-player # 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 - include DTAS::XS attr_reader :infile, :env, :command # for --trim on the command-line @@ -115,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 @@ -206,12 +205,13 @@ 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)) - comments.sync = true t.comments.each do |k,v| env[k] = v.to_s comments.puts("#{k}=#{v}") end + comments.flush env["COMMENTS"] = "--comment-file=#{comments.path}" infile_env(env, @infile) outarg = outfmt.to_sox_arg @@ -250,7 +250,10 @@ class DTAS::SplitFX # :nodoc: command = 'true' if opts[:dryrun] # still gotta fork # pgroup: false so Ctrl-C on command-line will immediately stop everything - [ dtas_spawn(env, command, pgroup: false), comments ] + o = { pgroup: false } + e = opts[:err_suffix] and + o[:err] = [ "#{env['OUTDIR']}#{env['TRACKNUMBER']}#{e}", 'a' ] + [ dtas_spawn(env, command, o), comments ] end def load_tracks!(hash) @@ -289,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 @@ -298,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)}" @@ -355,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] @@ -383,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 @@ -401,10 +416,10 @@ class DTAS::SplitFX # :nodoc: end def infile_env(env, infile) - env["INFILE"] = xs(infile) + env["INFILE"] = infile dir, base = File.split(File.expand_path(infile)) - env["INDIR"] = xs(dir) - env["INBASE"] = xs(base) + env["INDIR"] = dir + env["INBASE"] = base end def expand_cmd(env, command) # for display purposes only diff --git a/lib/dtas/state_file.rb b/lib/dtas/state_file.rb index b577850..f16a866 100644 --- a/lib/dtas/state_file.rb +++ b/lib/dtas/state_file.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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/tfx.rb b/lib/dtas/tfx.rb index 2ccdcf1..80051e8 100644 --- a/lib/dtas/tfx.rb +++ b/lib/dtas/tfx.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/track.rb b/lib/dtas/track.rb index 35a00c1..3f4b813 100644 --- a/lib/dtas/track.rb +++ b/lib/dtas/track.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2015-2020 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' @@ -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/tracklist.rb b/lib/dtas/tracklist.rb index e6a8fb6..a7f4c15 100644 --- a/lib/dtas/tracklist.rb +++ b/lib/dtas/tracklist.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/unix_accepted.rb b/lib/dtas/unix_accepted.rb index 4a01972..63d3ce0 100644 --- a/lib/dtas/unix_accepted.rb +++ b/lib/dtas/unix_accepted.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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 aae8c9d..8c73b7d 100644 --- a/lib/dtas/unix_client.rb +++ b/lib/dtas/unix_client.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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 ccfa662..60ab86c 100644 --- a/lib/dtas/unix_server.rb +++ b/lib/dtas/unix_server.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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/util.rb b/lib/dtas/util.rb index acfcafe..a74c14e 100644 --- a/lib/dtas/util.rb +++ b/lib/dtas/util.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/watchable.rb b/lib/dtas/watchable.rb index d0f37af..445bf98 100644 --- a/lib/dtas/watchable.rb +++ b/lib/dtas/watchable.rb @@ -1,71 +1,71 @@ -# Copyright (C) 2013-2019 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' begin -require 'sleepy_penguin' + module DTAS::Watchable # :nodoc: + module InotifyCommon # :nodoc: + FLAGS = 8 | 128 # IN_CLOSE_WRITE | IN_MOVED_TO -# used to restart DTAS::Source::SplitFX processing in dtas-player -# if the YAML file is edited -module DTAS::Watchable # :nodoc: - class InotifyReadableIter < SleepyPenguin::Inotify # :nodoc: - def self.new - super(:CLOEXEC) - end - - FLAGS = CLOSE_WRITE | MOVED_TO - - def readable_iter - or_call = false - while event = take(true) # drain the buffer - w = @watches[event.wd] or next - if (event.mask & FLAGS) != 0 && w[event.name] - or_call = true + def readable_iter + or_call = false + while event = take(true) # drain the buffer + w = @watches[event.wd] or next + if (event.mask & FLAGS) != 0 && w[event.name] + or_call = true + end + end + if or_call + @on_readable.call + :delete + else + :wait_readable end end - if or_call - @on_readable.call - :delete - else - :wait_readable - end - end - # we must watch the directory, since - def watch_files(paths, blk) - @watches = {} # wd -> { basename -> true } - @on_readable = blk - @dir2wd = {} - Array(paths).each do |path| - watchdir, watchbase = File.split(File.expand_path(path)) - begin - wd = @dir2wd[watchdir] ||= add_watch(watchdir, FLAGS) - m = @watches[wd] ||= {} - m[watchbase] = true - rescue SystemCallError => e - warn "#{watchdir.dump}: #{e.message} (#{e.class})" + # we must watch the directory, since + def watch_files(paths, blk) + @watches = {} # wd -> { basename -> true } + @on_readable = blk + @dir2wd = {} + Array(paths).each do |path| + watchdir, watchbase = File.split(File.expand_path(path)) + begin + wd = @dir2wd[watchdir] ||= add_watch(watchdir, FLAGS) + m = @watches[wd] ||= {} + m[watchbase] = true + rescue SystemCallError => e + warn "#{watchdir.dump}: #{e.message} (#{e.class})" + end end end - end - end + end # module InotifyCommon - def watch_begin(blk) - @ino = InotifyReadableIter.new - @ino.watch_files(@watch_extra << @infile, blk) - @ino - end + begin + require_relative 'watchable/inotify' + rescue LoadError + # TODO: support kevent + require_relative 'watchable/fiddle_ino' + end - def watch_extra(paths) - @ino.watch_extra(paths) - end + def watch_begin(blk) + @ino = DTAS::Watchable::InotifyReadableIter.new + @ino.watch_files(@watch_extra << @infile, blk) + @ino + end - # Closing the inotify descriptor (instead of using inotify_rm_watch) - # is cleaner because it avoids EINVAL on race conditions in case - # a directory is deleted: https://lkml.org/lkml/2007/7/9/3 - def watch_end(srv) - srv.wait_ctl(@ino, :delete) - @ino = @ino.close - end -end + def watch_extra(paths) + @ino.watch_extra(paths) + end -rescue LoadError -end + # Closing the inotify descriptor (instead of using inotify_rm_watch) + # is cleaner because it avoids EINVAL on race conditions in case + # a directory is deleted: https://lkml.org/lkml/2007/7/9/3 + def watch_end(srv) + srv.wait_ctl(@ino, :delete) + @ino = @ino.close + end + end # module DTAS::Watchable +rescue LoadError, StandardError => e + warn "#{e.message} (#{e.class})" +end # begin diff --git a/lib/dtas/watchable/fiddle_ino.rb b/lib/dtas/watchable/fiddle_ino.rb new file mode 100644 index 0000000..3ec72a1 --- /dev/null +++ b/lib/dtas/watchable/fiddle_ino.rb @@ -0,0 +1,78 @@ +# 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' + +# used to restart DTAS::Source::SplitFX processing in dtas-player +# if the YAML file is edited +class DTAS::Watchable::InotifyReadableIter # :nodoc: + include DTAS::Watchable::InotifyCommon + + Inotify_init = Fiddle::Function.new(DTAS.libc['inotify_init1'], + [ Fiddle::TYPE_INT ], + Fiddle::TYPE_INT) + + Inotify_add_watch = Fiddle::Function.new(DTAS.libc['inotify_add_watch'], + [ Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT ], + Fiddle::TYPE_INT) + + # IO.select compatibility + attr_reader :to_io #:nodoc: + + def initialize # :nodoc: + fd = Inotify_init.call(02000000 | 04000) # CLOEXEC | NONBLOCK + raise "inotify_init failed: #{Fiddle.last_error}" if fd < 0 + @to_io = IO.for_fd(fd) + @buf = ''.b + @q = [] + end + + # struct inotify_event { + # int wd; /* Watch descriptor */ + # uint32_t mask; /* Mask describing event */ + # uint32_t cookie; /* Unique cookie associating related + # events (for rename(2)) */ + # uint32_t len; /* Size of name field */ + # char name[]; /* Optional null-terminated name */ + InotifyEvent = Struct.new(:wd, :mask, :cookie, :len, :name) # :nodoc: + + def take(nonblock) # :nodoc: + event = @q.pop and return event + case rv = @to_io.read_nonblock(16384, @buf, exception: false) + when :wait_readable, nil + return + else + until rv.empty? + hdr = rv.slice!(0,16) + name = nil + wd, mask, cookie, len = res = hdr.unpack('iIII') + wd && mask && cookie && len or + raise "bogus inotify_event #{res.inspect} hdr=#{hdr.inspect}" + if len > 0 + name = rv.slice!(0, len) + name.size == len or raise "short name #{name.inspect} != #{len}" + name.sub!(/\0+\z/, '') or + raise "missing: `\\0', inotify_event.name=#{name.inspect}" + name = -name + end + ie = InotifyEvent.new(wd, mask, cookie, len, name) + if event + @q << ie + else + event = ie + end + end # /until rv.empty? + return event + end while true + end + + def add_watch(watchdir, flags) + wd = Inotify_add_watch.call(@to_io.fileno, watchdir, flags) + raise "inotify_add_watch failed: #{Fiddle.last_error}" if wd < 0 + wd + end + + def close + @to_io = @to_io.close if @to_io + end +end diff --git a/lib/dtas/watchable/inotify.rb b/lib/dtas/watchable/inotify.rb new file mode 100644 index 0000000..36b5746 --- /dev/null +++ b/lib/dtas/watchable/inotify.rb @@ -0,0 +1,13 @@ +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> +# frozen_string_literal: true +require 'sleepy_penguin' + +# used to restart DTAS::Source::SplitFX processing in dtas-player +# if the YAML file is edited +class DTAS::Watchable::InotifyReadableIter < SleepyPenguin::Inotify # :nodoc: + include DTAS::Watchable::InotifyCommon + def self.new + super(:CLOEXEC) + end +end diff --git a/lib/dtas/writable_iter.rb b/lib/dtas/writable_iter.rb index 24d1cee..caf6850 100644 --- a/lib/dtas/writable_iter.rb +++ b/lib/dtas/writable_iter.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/lib/dtas/xs.rb b/lib/dtas/xs.rb index 6836172..d4586a3 100644 --- a/lib/dtas/xs.rb +++ b/lib/dtas/xs.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' 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 9028303..d918351 100755 --- a/perl/dtas-graph +++ b/script/dtas-graph @@ -1,6 +1,10 @@ #!/usr/bin/perl -w -# Copyright (C) 2013-2016 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 +# ASCII art. Useful for displaying complex interations between different +# processes in a non-traditional pipeline. use strict; use Graph::Easy; # for ASCII-art graphs $^O =~ /linux/ or print STDERR "$0 probably only works on Linux...\n"; @@ -123,7 +127,8 @@ foreach my $pid (sort { $a <=> $b } keys %pids) { print "\nPIPEID PIPE_INO\n"; foreach my $pipe_id (sort { $a <=> $b } keys %graphed) { printf "% 6s", "|$pipe_id"; - print " ", $graphed{$pipe_id}, "\n"; + my $ino = $graphed{$pipe_id}; + printf " %u (0x%0x)\n", $ino, $ino; } print $graph->as_ascii; @@ -281,7 +281,6 @@ class ConfigTable 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', - 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', @@ -785,7 +784,7 @@ class ToplevelInstaller else require 'rbconfig' end - ::Config::CONFIG + ::RbConfig::CONFIG end def initialize(ardir_root, config) diff --git a/test/covshow.rb b/test/covshow.rb index df89037..95f8bfa 100644 --- a/test/covshow.rb +++ b/test/covshow.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true # diff --git a/test/helper.rb b/test/helper.rb index 4031394..0a351e5 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true $stdout.sync = $stderr.sync = Thread.abort_on_exception = true diff --git a/test/player_integration.rb b/test/player_integration.rb index 2e81280..66d1c6e 100644 --- a/test/player_integration.rb +++ b/test/player_integration.rb @@ -1,11 +1,10 @@ -# Copyright (C) 2013-2016 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 8f5d8b5..a47e2d4 100644 --- a/test/test_buffer.rb +++ b/test/test_buffer.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' @@ -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 @@ -49,20 +49,20 @@ class TestBuffer < Testcase buf = new_buffer buf.buffer_size = @@max_size assert_equal @@max_size, buf.buffer_size - end if defined?(SleepyPenguin::F_GETPIPE_SZ) + end if defined?(DTAS::Pipe::F_GETPIPE_SZ) def test_buffer_size buf = new_buffer assert_operator buf.buffer_size, :>, 128 buf.buffer_size = @@max_size assert_equal @@max_size, buf.buffer_size - end if defined?(SleepyPenguin::F_GETPIPE_SZ) + end if defined?(DTAS::Pipe::F_GETPIPE_SZ) def test_broadcast_1 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 @@ -108,7 +108,7 @@ class TestBuffer < Testcase assert_equal "HELLO", a[0].read(5) assert_equal "HELLO", b[0].read(5) - return unless defined?(SleepyPenguin::F_GETPIPE_SZ) + return unless defined?(DTAS::Pipe::F_GETPIPE_SZ) b[1].nonblock = true b[1].write('*' * pipe_size(b[1])) @@ -167,7 +167,7 @@ class TestBuffer < Testcase buf.wr.write "HELLO" assert_equal tmp, buf.broadcast(tmp) assert_equal [a[1], b[1]], tmp - end if defined?(SleepyPenguin::F_GETPIPE_SZ) + end if defined?(DTAS::Pipe::F_GETPIPE_SZ) def test_serialize buf = new_buffer @@ -206,6 +206,6 @@ class TestBuffer < Testcase end def pipe_size(io) - io.fcntl(SleepyPenguin::F_GETPIPE_SZ) + io.fcntl(DTAS::Pipe::F_GETPIPE_SZ) end end diff --git a/test/test_encoding.rb b/test/test_encoding.rb index b60ba36..5cd5da7 100644 --- a/test/test_encoding.rb +++ b/test/test_encoding.rb @@ -1,9 +1,8 @@ -# Copyright (C) 2018-2019 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_env.rb b/test/test_env.rb index 4426be3..8520842 100644 --- a/test/test_env.rb +++ b/test/test_env.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'helper' diff --git a/test/test_fadefx.rb b/test/test_fadefx.rb index fb36c21..481a5a5 100644 --- a/test/test_fadefx.rb +++ b/test/test_fadefx.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'helper' diff --git a/test/test_format.rb b/test/test_format.rb index a2118df..81b67bf 100644 --- a/test/test_format.rb +++ b/test/test_format.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_format_change.rb b/test/test_format_change.rb index 73b4403..dc94f02 100644 --- a/test/test_format_change.rb +++ b/test/test_format_change.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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 6957021..983a69e 100644 --- a/test/test_mcache.rb +++ b/test/test_mcache.rb @@ -1,19 +1,29 @@ -# Copyright (C) 2016 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_mlib.rb b/test/test_mlib.rb index b16ea15..75c07f2 100644 --- a/test/test_mlib.rb +++ b/test/test_mlib.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'helper' diff --git a/test/test_parse_freq.rb b/test/test_parse_freq.rb index 7fd5907..b84c131 100644 --- a/test/test_parse_freq.rb +++ b/test/test_parse_freq.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2015-2020 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' diff --git a/test/test_pipeline.rb b/test/test_pipeline.rb index f235410..19cf2b0 100644 --- a/test/test_pipeline.rb +++ b/test/test_pipeline.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2017-2020 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' diff --git a/test/test_player.rb b/test/test_player.rb index 0c79b3b..d27bd89 100644 --- a/test/test_player.rb +++ b/test/test_player.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_player_client_handler.rb b/test/test_player_client_handler.rb index d76eaeb..eee5e49 100644 --- a/test/test_player_client_handler.rb +++ b/test/test_player_client_handler.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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 175ed00..09eceee 100644 --- a/test/test_player_integration.rb +++ b/test/test_player_integration.rb @@ -1,10 +1,9 @@ -# Copyright (C) 2013-2016 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_process.rb b/test/test_process.rb index 368d877..94abf20 100644 --- a/test/test_process.rb +++ b/test/test_process.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_rg_integration.rb b/test/test_rg_integration.rb index 29dd29b..f274272 100644 --- a/test/test_rg_integration.rb +++ b/test/test_rg_integration.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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_rg_state.rb b/test/test_rg_state.rb index 59061a2..729e7f7 100644 --- a/test/test_rg_state.rb +++ b/test/test_rg_state.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_sigevent.rb b/test/test_sigevent.rb new file mode 100644 index 0000000..f7e7385 --- /dev/null +++ b/test/test_sigevent.rb @@ -0,0 +1,20 @@ +# Copyright (C) 2019-2020 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 'helper' +require 'dtas' +require 'dtas/sigevent' + +class TestSigevent < Testcase + def test_sigevent + io = DTAS::Sigevent.new + io.signal + assert IO.select([io]), 'IO.select returns' + res = io.readable_iter do |f,arg| + assert_same io, f + assert_nil arg + end + assert_equal :wait_readable, res + assert_nil io.close + end +end diff --git a/test/test_sink.rb b/test/test_sink.rb index 761ccb1..7214da6 100644 --- a/test/test_sink.rb +++ b/test/test_sink.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 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_sink_pipe_size.rb b/test/test_sink_pipe_size.rb index 1b6db72..86a7f2c 100644 --- a/test/test_sink_pipe_size.rb +++ b/test/test_sink_pipe_size.rb @@ -1,20 +1,17 @@ -# Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org> # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true -begin - require 'sleepy_penguin' - require './test/player_integration' - class TestSinkPipeSizeIntegration < Testcase - include PlayerIntegration +require './test/player_integration' +class TestSinkPipeSizeIntegration < Testcase + include PlayerIntegration - def test_sink_pipe_size_integration - s = client_socket - default_sink_pid(s) - s.req_ok("sink ed default pipe_size=0x1000") - s.req_ok("sink ed default pipe_size=0x10000") - s.req_ok("sink ed default pipe_size=") - s.req_ok("sink ed default pipe_size=4096") - end if SleepyPenguin.const_defined?(:F_SETPIPE_SZ) + def test_sink_pipe_size_integration + s = client_socket + default_sink_pid(s) + s.req_ok("sink ed default pipe_size=0x1000") + s.req_ok("sink ed default pipe_size=0x10000") + s.req_ok("sink ed default pipe_size=") + s.req_ok("sink ed default pipe_size=4096") end -rescue LoadError -end +end if RUBY_PLATFORM =~ /linux/i && + File.readable?('/proc/sys/fs/pipe-max-size') diff --git a/test/test_sink_tee_integration.rb b/test/test_sink_tee_integration.rb index e130f42..3a44e7b 100644 --- a/test/test_sink_tee_integration.rb +++ b/test/test_sink_tee_integration.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_source_av.rb b/test/test_source_av.rb index dddd4a6..3ee115e 100644 --- a/test/test_source_av.rb +++ b/test/test_source_av.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' 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_source_sox.rb b/test/test_source_sox.rb index 8c994f1..6e091ee 100644 --- a/test/test_source_sox.rb +++ b/test/test_source_sox.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' diff --git a/test/test_splitfx.rb b/test/test_splitfx.rb index d952031..f2e0e09 100644 --- a/test/test_splitfx.rb +++ b/test/test_splitfx.rb @@ -1,14 +1,11 @@ -# Copyright (C) 2013-2016 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 5d77b9d..be68079 100644 --- a/test/test_tfx.rb +++ b/test/test_tfx.rb @@ -1,10 +1,9 @@ -# Copyright (C) 2013-2016 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_tracklist.rb b/test/test_tracklist.rb index e98fa8c..b280390 100644 --- a/test/test_tracklist.rb +++ b/test/test_tracklist.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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 'helper' diff --git a/test/test_unixserver.rb b/test/test_unixserver.rb index 64f71be..c91354d 100644 --- a/test/test_unixserver.rb +++ b/test/test_unixserver.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' @@ -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 diff --git a/test/test_util.rb b/test/test_util.rb index f618511..66194cf 100644 --- a/test/test_util.rb +++ b/test/test_util.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org> +# Copyright (C) 2013-2020 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' |