dtas-all
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] player: support the "trim" parameter


From: Eric Wong
Subject: [PATCH] player: support the "trim" parameter
Date: Wed, 28 Jan 2015 07:48:33 +0000

This feature is intended to allow users to "zoom-in" on a
particular portion of a track to tweak parameters (either
with dtas-sourceedit(1) or via playback of splitfx YAML files).
This may be combined with looping the tracklist
(via "tl repeat").
---
 Documentation/dtas-player_protocol.txt | 12 ++++++++++++
 lib/dtas/player.rb                     | 15 ++++++++++-----
 lib/dtas/player/client_handler.rb      | 29 +++++++++++++++++++++++++++++
 lib/dtas/source/av.rb                  |  2 +-
 lib/dtas/source/av_ff_common.rb        | 25 ++++++++++++++++++-------
 lib/dtas/source/ff.rb                  |  2 +-
 lib/dtas/source/file.rb                | 24 +++++++++++++++++++++---
 lib/dtas/source/sox.rb                 |  6 +++---
 lib/dtas/source/splitfx.rb             |  8 ++++----
 9 files changed, 99 insertions(+), 24 deletions(-)

diff --git a/Documentation/dtas-player_protocol.txt 
b/Documentation/dtas-player_protocol.txt
index 82986cf..e1b2487 100644
--- a/Documentation/dtas-player_protocol.txt
+++ b/Documentation/dtas-player_protocol.txt
@@ -283,6 +283,18 @@ Commands here should be alphabetized according to 
`LC_ALL=C sort'
 * tl tracks
   returns a list of all TRACKIDS in the tracklist
 
+* trim [off|TBEG [TLEN]]
+  Limits playback of all tracks in the tracklist to the time starting
+  at TBEG and ending after TLEN has elapsed.  Not specifying TLEN will
+  cause sox.  Like the sox "trim" effect, prefixing TLEN with a '='
+  will be interpreted as an absolute stop time.
+  Both TBEG and TLEN should be specified in seconds, not sample counts.
+  Specifying "off" (without quotes) disables trim.
+  This feature is intended to allow users to "zoom-in" on a particular
+  portion of a track to tweak parameters (either with
+  dtas-sourceedit(1) or via playback of splitfx YAML files) and often
+  combined with looping the tracklist (via "tl repeat").
+
 * watch - adds the client to the passive watch list for notifications.
   It is recommended clients issue no further commands and open
   another client socket to issue non-watch commands.
diff --git a/lib/dtas/player.rb b/lib/dtas/player.rb
index d6707c3..2920dcd 100644
--- a/lib/dtas/player.rb
+++ b/lib/dtas/player.rb
@@ -45,6 +45,7 @@ class DTAS::Player # :nodoc:
     @sink_buf = DTAS::Buffer.new
     @current = nil
     @watchers = {}
+    @trim = nil
     @source_map = {
       "sox" => (sox = DTAS::Source::Sox.new),
       "av" => DTAS::Source::Av.new,
@@ -83,6 +84,7 @@ class DTAS::Player # :nodoc:
     rv = {}
     rv["socket"] = @socket
     rv["paused"] = @paused if @paused
+    rv["trim"] = @trim if @trim
     src_map = rv["source"] = {}
     @source_map.each do |name, src|
       src_hsh = src.to_state_hash
@@ -135,7 +137,7 @@ class DTAS::Player # :nodoc:
         v = v["buffer_size"]
         @sink_buf.buffer_size = v
       end
-      %w(socket queue paused bypass).each do |k|
+      %w(socket queue paused bypass trim).each do |k|
         v = hash[k] or next
         instance_variable_set("@#{k}", v)
       end
@@ -247,6 +249,8 @@ class DTAS::Player # :nodoc:
       io.emit(Dir.pwd)
     when "tl"
       tl_handler(io, msg)
+    when "trim"
+      trim_handler(io, msg)
     end
   end
 
@@ -367,23 +371,24 @@ class DTAS::Player # :nodoc:
     end
   end
 
-  def try_file(*args)
+  def try_file(file, offset = nil)
     @sources.each do |src|
-      rv = src.try(*args) and return rv
+      rv = src.try(file, offset, @trim) and return rv
     end
 
     # keep going down the list until we find something
     while source_spec = @queue.shift
       @sources.each do |src|
-        rv = src.try(*source_spec) and return rv
+        rv = src.try(file, offset, @trim) and return rv
       end
     end
 
     # don't get stuck in an infinite loop if @tl.repeat==true and we can't
     # decode anything (FS errors, sox uninstalled, etc...)
     while path_off = @tl.advance_track(false)
+      path, off = path_off
       @sources.each do |src|
-        rv = src.try(*path_off) and return rv
+        rv = src.try(path, off, @trim) and return rv
       end
     end
 
diff --git a/lib/dtas/player/client_handler.rb 
b/lib/dtas/player/client_handler.rb
index 68f1d80..a867265 100644
--- a/lib/dtas/player/client_handler.rb
+++ b/lib/dtas/player/client_handler.rb
@@ -1,10 +1,12 @@
 # Copyright (C) 2013-2015 all contributors <address@hidden>
 # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
 require_relative '../xs'
+require_relative '../parse_time'
 
 # client protocol handling for -player
 module DTAS::Player::ClientHandler # :nodoc:
   include DTAS::XS
+  include DTAS::ParseTime
 
   # returns true on success, wait_ctl arg on error
   def set_bool(io, kv, v)
@@ -183,6 +185,7 @@ module DTAS::Player::ClientHandler # :nodoc:
     bytes = bytes < 0 ? 0 : bytes # maybe negative in case of sink errors
   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
@@ -680,5 +683,31 @@ module DTAS::Player::ClientHandler # :nodoc:
       io.emit("NOCUE")
     end
   end
+
+  def trim_handler(io, msg)
+    case msg.size
+    when 0
+      io.emit({ 'trim' => @trim }.to_yaml)
+    when 1, 2
+      case msg[0]
+      when 'off'
+        @trim = nil
+      else
+        begin
+          tbeg = parse_time(msg[0])
+          if tlen = msg[1]
+            absolute = tlen.sub!(/\A=/, '') # 44:00 =44:55
+            tlen = parse_time(tlen)
+            tlen -= tbeg if absolute
+          end
+          @trim = [ tbeg, tlen ] # seconds as float, since we don't know rate
+        rescue => e
+          return io.emit("ERR #{e.message}")
+        end
+      end
+      __current_requeue
+      io.emit('OK')
+    end
+  end
 end
 # :startdoc:
diff --git a/lib/dtas/source/av.rb b/lib/dtas/source/av.rb
index 722c798..c86b5d2 100644
--- a/lib/dtas/source/av.rb
+++ b/lib/dtas/source/av.rb
@@ -10,7 +10,7 @@ class DTAS::Source::Av # :nodoc:
   AV_DEFAULTS = COMMAND_DEFAULTS.merge(
     "command" =>
       'avconv -v error $SSPOS $PROBE -i "$INFILE" $AMAP -f sox - |' \
-      'sox -p $SOXFMT - $RGFX',
+      '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
diff --git a/lib/dtas/source/av_ff_common.rb b/lib/dtas/source/av_ff_common.rb
index 03526d2..189e135 100644
--- a/lib/dtas/source/av_ff_common.rb
+++ b/lib/dtas/source/av_ff_common.rb
@@ -19,8 +19,8 @@ module DTAS::Source::AvFfCommon # :nodoc:
   attr_reader :precision # always 32
   attr_reader :format
 
-  def try(infile, offset = nil)
-    rv = source_file_dup(infile, offset)
+  def try(infile, offset = nil, trim = nil)
+    rv = source_file_dup(infile, offset, trim)
     rv.av_ff_ok? or return
     rv
   end
@@ -101,10 +101,20 @@ module DTAS::Source::AvFfCommon # :nodoc:
     ! @astreams.compact.empty?
   end
 
-  def sspos(offset)
-    offset =~ /\A(\d+)s\z/ or return "-ss #{offset}"
-    samples = $1.to_f
-    sprintf("-ss %0.9g", samples / @format.rate)
+  def sspos
+    return unless @offset || @trim
+    off = offset_samples / @format.rate.to_f
+    sprintf('-ss %0.9g', off)
+  end
+
+  def av_ff_trimfx # for sox
+    return unless @trim
+    tbeg, tlen = @trim # Floats
+    tend = tbeg + tlen
+    off = offset_samples / @format.rate.to_f
+    tlen = tend - off
+    tlen = 0 if tlen < 0
+    sprintf('trim 0 %0.9g', tlen)
   end
 
   def select_astream(as)
@@ -150,8 +160,9 @@ module DTAS::Source::AvFfCommon # :nodoc:
     # make sure these are visible to the source command...
     e["INFILE"] = @infile
     e["AMAP"] = amap
-    e["SSPOS"] = @offset ? sspos(@offset) : nil
+    e["SSPOS"] = sspos
     e["RGFX"] = rg_state.effect(self) || nil
+    e["TRIMFX"] = av_ff_trimfx
     e.merge!(@rg.to_env) if @rg
 
     @pid = dtas_spawn(e, command_string, opts)
diff --git a/lib/dtas/source/ff.rb b/lib/dtas/source/ff.rb
index c59de40..d5e744c 100644
--- a/lib/dtas/source/ff.rb
+++ b/lib/dtas/source/ff.rb
@@ -12,7 +12,7 @@ class DTAS::Source::Ff  # :nodoc:
   FF_DEFAULTS = COMMAND_DEFAULTS.merge(
     "command" =>
       'ffmpeg -v error $SSPOS $PROBE -i "$INFILE" $AMAP -f sox - |' \
-      'sox -p $SOXFMT - $RGFX',
+      'sox -p $SOXFMT - $TRIMFX $RGFX',
 
     # I haven't tested this much since av is in Debian stable and ff is not
     "tryorder" => 2,
diff --git a/lib/dtas/source/file.rb b/lib/dtas/source/file.rb
index 8a9fa34..75b4a43 100644
--- a/lib/dtas/source/file.rb
+++ b/lib/dtas/source/file.rb
@@ -21,17 +21,18 @@ module DTAS::Source::File # :nodoc:
   FILE_SIVS = %w(infile comments command env) # for the "current" command
   SRC_SIVS = %w(command env tryorder)
 
-  def source_file_dup(infile, offset)
+  def source_file_dup(infile, offset, trim)
     rv = dup
-    rv.__file_init(infile, offset)
+    rv.__file_init(infile, offset, trim)
     rv
   end
 
-  def __file_init(infile, offset)
+  def __file_init(infile, offset, trim)
     @env = @env.dup
     @format = nil
     @infile = infile
     @offset = offset
+    @trim = trim
     @comments = nil
     @samples = nil
     @cuebp = nil
@@ -47,6 +48,13 @@ module DTAS::Source::File # :nodoc:
   # returns any offset in samples (relative to the original source file),
   # likely zero unless seek was used
   def offset_samples
+    off = __offset_samples
+    return off unless @trim
+    tbeg = @trim[0] * format.rate
+    tbeg < off ? off : tbeg
+  end
+
+  def __offset_samples
     return 0 unless @offset
     case @offset
     when /\A\d+s\z/
@@ -56,6 +64,16 @@ module DTAS::Source::File # :nodoc:
     end
   end
 
+  # creates the effect to fill the TRIMFX env
+  def trimfx
+    return unless @offset || @trim
+    fx = "trim #{offset_samples}s"
+    if @trim && @trim[1]
+      fx << sprintf(' =%0.9gs', (@trim[0] + @trim[1]) * format.rate)
+    end
+    fx
+  end
+
   # A user may be downloading the file and start playing
   # it before the download completes, this refreshes
   def samples!
diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb
index 99dfe35..91a3c40 100644
--- a/lib/dtas/source/sox.rb
+++ b/lib/dtas/source/sox.rb
@@ -38,13 +38,13 @@ class DTAS::Source::Sox # :nodoc:
     command_init(SOX_DEFAULTS)
   end
 
-  def try(infile, offset = nil)
+  def try(infile, offset = nil, trim = nil)
     err = ""
     cmd = %W(soxi -s #{infile})
     s = qx(@env.dup, cmd, err_str: err, no_raise: true)
     return if err =~ /soxi FAIL formats:/
     self.class.try_to_fail_harder(infile, s, cmd) or return
-    source_file_dup(infile, offset)
+    source_file_dup(infile, offset, trim)
   end
 
   def format
@@ -81,7 +81,7 @@ class DTAS::Source::Sox # :nodoc:
     e["INFILE"] = @infile
 
     # make sure these are visible to the "current" command...
-    e["TRIMFX"] = @offset ? "trim address@hidden" : nil
+    e["TRIMFX"] = trimfx
     e["RGFX"] = rg_state.effect(self) || nil
     e.merge!(@rg.to_env) if @rg
 
diff --git a/lib/dtas/source/splitfx.rb b/lib/dtas/source/splitfx.rb
index 680ad8b..b7b9b86 100644
--- a/lib/dtas/source/splitfx.rb
+++ b/lib/dtas/source/splitfx.rb
@@ -20,7 +20,7 @@ class DTAS::Source::SplitFX < DTAS::Source::Sox # :nodoc:
     @sox = sox
   end
 
-  def try(ymlfile, offset = nil)
+  def try(ymlfile, offset = nil, trim = nil)
     @splitfx = @ymlhash = nil
     st = File.stat(ymlfile)
     return false if !st.file? || st.size > MAX_YAML_SIZE
@@ -41,8 +41,8 @@ class DTAS::Source::SplitFX < DTAS::Source::Sox # :nodoc:
     end
     @splitfx = sfx
     @infile = ymlfile
-    sox = @sox.try(sfx.infile, offset) or return false
-    rv = source_file_dup(ymlfile, offset)
+    sox = @sox.try(sfx.infile, offset, trim) or return false
+    rv = source_file_dup(ymlfile, offset, trim)
     rv.sox = sox
     rv.env = sfx.env
     rv.sfx = sfx
@@ -66,7 +66,7 @@ class DTAS::Source::SplitFX < DTAS::Source::Sox # :nodoc:
     @sfx.infile_env(e, @sox.infile)
 
     # make sure these are visible to the "current" command...
-    e["TRIMFX"] = @offset ? "trim address@hidden" : nil
+    e["TRIMFX"] = trimfx
     e["RGFX"] = rg_state.effect(self) || nil
     e.merge!(@rg.to_env) if @rg
 
-- 
EW



reply via email to

[Prev in Thread] Current Thread [Next in Thread]