From 37eae22446feb5a805d9cd59f6ad54362829189f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 28 Jan 2015 07:44:05 +0000 Subject: player: support the "trim" parameter 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"). --- 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 ++++---- 8 files changed, 87 insertions(+), 24 deletions(-) (limited to 'lib') 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 # 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 #@offset" : 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 #@offset" : nil + e["TRIMFX"] = trimfx e["RGFX"] = rg_state.effect(self) || nil e.merge!(@rg.to_env) if @rg -- cgit v1.2.3-24-ge0c7