diff options
-rw-r--r-- | Documentation/dtas-player_protocol.txt | 6 | ||||
-rw-r--r-- | Documentation/dtas-sourceedit.txt | 17 | ||||
-rwxr-xr-x | bin/dtas-sourceedit | 10 | ||||
-rw-r--r-- | lib/dtas/player.rb | 45 | ||||
-rw-r--r-- | lib/dtas/player/client_handler.rb | 33 | ||||
-rw-r--r-- | lib/dtas/source/av.rb | 8 | ||||
-rw-r--r-- | lib/dtas/source/file.rb | 27 | ||||
-rw-r--r-- | lib/dtas/source/sox.rb | 9 | ||||
-rw-r--r-- | test/test_player_integration.rb | 17 | ||||
-rw-r--r-- | test/test_rg_integration.rb | 2 |
10 files changed, 127 insertions, 47 deletions
diff --git a/Documentation/dtas-player_protocol.txt b/Documentation/dtas-player_protocol.txt index 360eb84..07d14ec 100644 --- a/Documentation/dtas-player_protocol.txt +++ b/Documentation/dtas-player_protocol.txt @@ -33,6 +33,7 @@ dtas-sinkedit(1), and dtas-enq(1) also implement this protocol. - ENVVALUE - must be a suitable environment variable (setenv(3)) - COMMAND, this may be quoted string passed to sh -c "", variable/argument expansion will be performed by the shell +- SOURCENAME - "sox" or "av", more backends may be supported in the future - FILENAME - an expanded pathname relative to / is recommended since dtas-player and the client may run in different directories @@ -167,14 +168,15 @@ For little-endian machines, $ECAFMT defaults to: -fs32_le,2,44100 + nonblock=BOOLEAN - drop audio data to avoid holding back other sinks + pipe_size=UNSIGNED - set the size of the pipe for the sink (Linux-only) -* source cat - dump the current source command and env in YAML +* source SOURCENAME cat - dump the current source command and env in YAML -* source ed SOURCEARGS - edit the source (decoder) command and environment. +* source ed SOURCENAME SOURCEARGS - edit the source parameters. This changes here will immediately restart the source process. See the code for dtas-sourceedit(1) for an example of using this. + command=COMMAND - change the command-line used to decode audio + env.ENVNAME=ENVVALUE - set ENVNAME to ENVVALUE for the source process + env#ENVNAME - unset ENVNAME in the source process (only) + + tryorder=INTEGER - lower values are tried first * watch - adds the client to the passive watch list for notifications. It is recommended clients issue no further commands and open diff --git a/Documentation/dtas-sourceedit.txt b/Documentation/dtas-sourceedit.txt index 1f5101b..6ef876c 100644 --- a/Documentation/dtas-sourceedit.txt +++ b/Documentation/dtas-sourceedit.txt @@ -3,23 +3,26 @@ # NAME -dtas-sourceedit - edit the command and environment of the decoder +dtas-sourceedit - edit parameters of a source decoder # SYNOPSYS -dtas-sourceedit +dtas-sourceedit {sox | av} # DESCRIPTION -dtas-sourceedit spawns an editor to allow editing of a sink as a YAML file. -See dtas-player_protocol(7) for details on SINKARGS. +dtas-sourceedit spawns an editor to allow editing of a source as a YAML file. +See dtas-player_protocol(7) for details on SOURCEARGS. # EXAMPLES -Invoking dtas-sourceedit will spawn your favorite text editor on a -given SINKNAME: +Invoking dtas-sourceedit will spawn your favorite text editor on "sox": - $ dtas-sourceedit + $ dtas-sourceedit sox + +To change the way dtas-player calls avconv (part of libav): + + $ dtas-sourceedit av # ENVIRONMENT diff --git a/bin/dtas-sourceedit b/bin/dtas-sourceedit index c55000a..7e30e6b 100755 --- a/bin/dtas-sourceedit +++ b/bin/dtas-sourceedit @@ -8,15 +8,15 @@ require 'dtas/unix_client' require 'dtas/disclaimer' editor = ENV["VISUAL"] || ENV["EDITOR"] || "vi" c = DTAS::UNIXClient.new -usage = $0 -ARGV.size == 0 or abort usage -name = ARGV[0] +usage = "#$0 <sox|av>" +ARGV.size <= 1 or abort usage +name = ARGV[0] || "sox" tmp = Tempfile.new(%w(dtas-sourceedit .yml)) tmp.sync = true tmp.binmode -buf = c.req(%W(source cat)) +buf = c.req(%W(source cat #{name})) abort(buf) if buf =~ /\AERR/ orig = YAML.load(buf) @@ -26,7 +26,7 @@ system(cmd) or abort "#{cmd} failed: #$?" tmp.rewind source = YAML.load(tmp.read) -cmd = %W(source ed) +cmd = %W(source ed #{name}) if env = source["env"] env.each do |k,v| cmd << (v.nil? ? "env##{k}" : "env.#{k}=#{v}") diff --git a/lib/dtas/player.rb b/lib/dtas/player.rb index f1d3507..5cc95c7 100644 --- a/lib/dtas/player.rb +++ b/lib/dtas/player.rb @@ -29,8 +29,6 @@ class DTAS::Player # :nodoc: @queue = [] # files for sources, or commands @paused = false @format = DTAS::Format.new - @srccmd = nil - @srcenv = {} @sinks = {} # { user-defined name => sink } @targets = [] # order matters @@ -40,7 +38,15 @@ class DTAS::Player # :nodoc: @sink_buf = DTAS::Buffer.new @current = nil @watchers = {} - @sources = [ DTAS::Source::Sox.new, DTAS::Source::Av.new ] + @source_map = { + "sox" => DTAS::Source::Sox.new, + "av" => DTAS::Source::Av.new, + } + source_map_reload + end + + def source_map_reload + @sources = @source_map.values.sort_by { |src| src.tryorder } end def echo(msg) @@ -60,13 +66,16 @@ class DTAS::Player # :nodoc: $stdout.write(msg << "\n") end + # used for state file def to_hsh rv = {} rv["socket"] = @socket rv["paused"] = @paused if @paused - src = rv["source"] = {} - src["command"] = @srccmd if @srccmd - src["env"] = @srcenv if @srcenv.size > 0 + src_map = rv["source"] = {} + @source_map.each do |name, src| + src_hsh = src.to_state_hash + src_map[name] = src_hsh unless src_hsh.empty? + end # Arrays rv["queue"] = @queue @@ -109,8 +118,22 @@ class DTAS::Player # :nodoc: instance_variable_set("@#{k}", v) end if v = hash["source"] - @srccmd = v["command"] - e = v["env"] and @srcenv = e + # compatibility with 0.0.0, which was sox-only + # we'll drop this after 1.0.0, or when we support a source decoder + # named "command" or "env" :P + sox_cmd, sox_env = v["command"], v["env"] + if sox_cmd || sox_env + sox = @source_map["sox"] + sox.command = sox_cmd if sox_cmd + sox.env = sox_env if sox_env + end + + # new style: name = "av" or "sox" or whatever else we may support + @source_map.each do |name, src| + src_hsh = v[name] or next + src.load!(src_hsh) + end + source_map_reload end if v = hash["format"] @@ -331,12 +354,6 @@ class DTAS::Player # :nodoc: echo(%W(command #{@current.command_string})) end - # FIXME, support Av overrides - if DTAS::Source::Sox === @current - @current.command = @srccmd if @srccmd - @current.env = @srcenv.dup unless @srcenv.empty? - end - dst = @sink_buf @current.dst_assoc(dst) @current.spawn(@format, @rg, out: dst.wr, in: "/dev/null") diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb index e08d9a3..d580695 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -419,28 +419,39 @@ module DTAS::Player::ClientHandler # :nodoc: end def source_handler(io, msg) - case msg.shift + map = @source_map + op = msg.shift + if op == "ls" + s = map.keys.sort { |a,b| map[a].tryorder <=> map[b].tryorder } + return io.emit(s.join(' ')) + end + + name = msg.shift + src = map[name] or return io.emit("ERR non-existent source name") + case op when "cat" - io.emit({ - "command" => @srccmd || DTAS::Source::Sox::SOX_DEFAULTS["command"], - "env" => @srcenv, - }.to_yaml) + io.emit(src.to_source_cat.to_yaml) when "ed" - before = [ @srccmd, @srcenv ].inspect + before = src.to_state_hash + sd = src.source_defaults msg.each do |kv| k, v = kv.split(/=/, 2) case k when "command" - @srccmd = v.empty? ? nil : v + src.command = v.empty? ? sd[k] : v when %r{\Aenv\.([^=]+)\z} - @srcenv[$1] = v + src.env[$1] = v when %r{\Aenv#([^=]+)\z} v == nil or return io.emit("ERR unset env has no value") - @srcenv.delete($1) + src.env.delete($1) + when "tryorder" + rv = set_int(io, kv, v, true) { |i| src.tryorder = i || sd[k] } + rv == true or return rv + source_map_reload end end - after = [ @srccmd, @srcenv ].inspect - __current_requeue if before != after + after = src.to_state_hash + __current_requeue if before != after && @current.class == before.class io.emit("OK") else io.emit("ERR unknown source op") diff --git a/lib/dtas/source/av.rb b/lib/dtas/source/av.rb index a6a0e3a..61d88b2 100644 --- a/lib/dtas/source/av.rb +++ b/lib/dtas/source/av.rb @@ -17,6 +17,7 @@ class DTAS::Source::Av # :nodoc: "command" => 'avconv -v error $SSPOS -i "$INFILE" $AMAP -f sox - |' \ 'sox -p $SOXFMT - $RGFX', + "tryorder" => 1, ) attr_reader :precision # always 32 @@ -28,8 +29,7 @@ class DTAS::Source::Av # :nodoc: end def try(infile, offset = nil) - rv = dup - rv.source_file_init(infile, offset) + rv = source_file_dup(infile, offset) rv.av_ok? or return rv end @@ -133,4 +133,8 @@ class DTAS::Source::Av # :nodoc: def to_hsh to_hash.delete_if { |k,v| v == AV_DEFAULTS[k] } end + + def source_defaults + AV_DEFAULTS + end end diff --git a/lib/dtas/source/file.rb b/lib/dtas/source/file.rb index a66308b..3663d8d 100644 --- a/lib/dtas/source/file.rb +++ b/lib/dtas/source/file.rb @@ -10,6 +10,7 @@ require_relative '../process' module DTAS::Source::File # :nodoc: attr_reader :infile attr_reader :offset + attr_accessor :tryorder require_relative 'common' # dtas/source/common require_relative 'mp3gain' include DTAS::Command @@ -17,9 +18,17 @@ module DTAS::Source::File # :nodoc: include DTAS::Source::Common include DTAS::Source::Mp3gain - FILE_SIVS = %w(infile comments command env) + FILE_SIVS = %w(infile comments command env) # for the "current" command + SRC_SIVS = %w(command env tryorder) - def source_file_init(infile, offset) + def source_file_dup(infile, offset) + rv = dup + rv.__file_init(infile, offset) + rv + end + + def __file_init(infile, offset) + @env = @env.dup @format = nil @infile = infile @offset = offset @@ -68,4 +77,18 @@ module DTAS::Source::File # :nodoc: DTAS::ReplayGain.new(mp3gain_comments) end + def to_source_cat + ivars_to_hash(SRC_SIVS) + end + + def load!(src_hsh) + SRC_SIVS.each do |field| + val = src_hsh[field] and instance_variable_set("@#{field}", val) + end + end + + def to_state_hash + defaults = source_defaults # see dtas/source/{av,sox}.rb + to_source_cat.delete_if { |k,v| v == defaults[k] } + end end diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb index fa6192d..e1baee9 100644 --- a/lib/dtas/source/sox.rb +++ b/lib/dtas/source/sox.rb @@ -13,6 +13,7 @@ class DTAS::Source::Sox # :nodoc: SOX_DEFAULTS = COMMAND_DEFAULTS.merge( "command" => 'exec sox "$INFILE" $SOXFMT - $TRIMFX $RGFX', + "tryorder" => 0, ) def initialize @@ -23,9 +24,7 @@ class DTAS::Source::Sox # :nodoc: err = "" qx(@env, %W(soxi #{infile}), err_str: err, no_raise: true) return if err =~ /soxi FAIL formats:/ - rv = dup - rv.source_file_init(infile, offset) - rv + source_file_dup(infile, offset) end def precision @@ -100,4 +99,8 @@ class DTAS::Source::Sox # :nodoc: def to_hsh to_hash.delete_if { |k,v| v == SOX_DEFAULTS[k] } end + + def source_defaults + SOX_DEFAULTS + end end diff --git a/test/test_player_integration.rb b/test/test_player_integration.rb index 8f44b49..757ed35 100644 --- a/test/test_player_integration.rb +++ b/test/test_player_integration.rb @@ -188,4 +188,21 @@ class TestPlayerIntegration < Minitest::Unit::TestCase assert_equal "/", s.req("pwd") end + + def test_source_ed + s = client_socket + assert_equal "sox av", s.req("source ls") + s.req_ok("source ed av tryorder=-1") + assert_equal "av sox", s.req("source ls") + s.req_ok("source ed av tryorder=") + assert_equal "sox av", s.req("source ls") + + s.req_ok("source ed sox command=true") + sox = 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")) + assert_equal DTAS::Source::Sox::SOX_DEFAULTS["command"], sox["command"] + end end diff --git a/test/test_rg_integration.rb b/test/test_rg_integration.rb index 3b78e07..b2e39cb 100644 --- a/test/test_rg_integration.rb +++ b/test/test_rg_integration.rb @@ -105,7 +105,7 @@ class TestRgIntegration < Minitest::Unit::TestCase pluck, _ = tmp_pluck cmd = DTAS::Source::Sox::SOX_DEFAULTS["command"] fifo = tmpfifo - s.req_ok("source ed command='env > #{fifo}; #{cmd}'") + s.req_ok("source ed sox command='env > #{fifo}; #{cmd}'") s.req_ok("sink ed default command='cat >/dev/null' active=true") s.req_ok(%W(enq #{pluck.path})) |