about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-08-26 05:25:22 +0000
committerEric Wong <normalperson@yhbt.net>2013-08-26 05:25:22 +0000
commit2907cc6dd0b0d2d80d44ae292a157651d6e05ee7 (patch)
treeeba4f5253a37039acc8ec16cb0998ac2fe0a64b7
parent3d96e3d4a3ad7fbf45b38c81478f04cffb329e25 (diff)
downloaddtas-2907cc6dd0b0d2d80d44ae292a157651d6e05ee7.tar.gz
We should be fully-capable of managing any number of options
to try sources in.
-rw-r--r--Documentation/dtas-player_protocol.txt6
-rw-r--r--Documentation/dtas-sourceedit.txt17
-rwxr-xr-xbin/dtas-sourceedit10
-rw-r--r--lib/dtas/player.rb45
-rw-r--r--lib/dtas/player/client_handler.rb33
-rw-r--r--lib/dtas/source/av.rb8
-rw-r--r--lib/dtas/source/file.rb27
-rw-r--r--lib/dtas/source/sox.rb9
-rw-r--r--test/test_player_integration.rb17
-rw-r--r--test/test_rg_integration.rb2
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}))