From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS22989 208.118.235.0/24 X-Spam-Status: No, score=-1.1 required=3.0 tests=AWL,BAYES_00,SPF_PASS, URIBL_BLOCKED shortcircuit=no autolearn=unavailable version=3.3.2 X-Original-To: dtas-all@80x24.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by dcvr.yhbt.net (Postfix) with ESMTPS id 02AE520A0F for ; Sun, 24 Apr 2016 02:45:39 +0000 (UTC) Received: from localhost ([::1]:53984 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1auA3G-0002L4-4W for dtas-all@80x24.org; Sat, 23 Apr 2016 22:45:38 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50474) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1auA3C-0002HB-Jf for dtas-all@nongnu.org; Sat, 23 Apr 2016 22:45:36 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1auA39-0003jt-C5 for dtas-all@nongnu.org; Sat, 23 Apr 2016 22:45:34 -0400 Received: from dcvr.yhbt.net ([64.71.152.64]:33671) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1auA39-0003jn-1c for dtas-all@nongnu.org; Sat, 23 Apr 2016 22:45:31 -0400 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 5CC2820A0F; Sun, 24 Apr 2016 02:45:29 +0000 (UTC) Date: Sun, 24 Apr 2016 02:45:29 +0000 From: Eric Wong To: dtas-all@nongnu.org Subject: [PATCH] player: extra "cue" seeking functionality Message-ID: <20160424024528.GA10993@dcvr.yhbt.net> References: <20160411022743.GA22090@dcvr.yhbt.net> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20160411022743.GA22090@dcvr.yhbt.net> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 64.71.152.64 X-BeenThere: dtas-all@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dtas-all-bounces+dtas-all=80x24.org@nongnu.org Sender: "dtas-all" These commands allow easier jumping within a track marked by embedded CUE sheets. I've found them helpful for tracking out large recordings of multiple tracks (e.g. vinyl transfers or live concert recordings). --- This is a followup-to commit 285155328f72 https://80x24.org/dtas-all/20160411022743.GA22090@dcvr.yhbt.net/ Documentation/dtas-player_protocol.pod | 19 ++++++++-- lib/dtas/player/client_handler.rb | 66 ++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/Documentation/dtas-player_protocol.pod b/Documentation/dtas-player_protocol.pod index d0dd25e..be9f4ef 100644 --- a/Documentation/dtas-player_protocol.pod +++ b/Documentation/dtas-player_protocol.pod @@ -96,7 +96,7 @@ Commands here should be alphabetized according to `LC_ALL=C sort' PENDING: this may be renamed to "queue clear" or "queue-clear" -=item cue - display the index/offsets of the embedded cue sheet +=item cue - display the index/offsets of the embedded CUE sheet =item cue next - skip to the next cue sheet offset @@ -109,11 +109,26 @@ This may just seek to the beginning if there is no embedded cue sheet or if we are playing the first (embedded) track. -=item cue goto INTEGER - go to the cue index denoted by INTEGER +=item cue goto INTEGER [TIMESTAMP] - go to the cue index denoted by INTEGER 0 is first track as returned by "cue". Negative values of INTEGER allows selecting track relative to the last track (-1 is the last track, -2 is the penultimate, and so on). +The optional TIMESTAMP allows starting the index track at a given +point, negative values allow going to a set point before the given +index. In other words: "cue goto 1 -5" seeks to the last five seconds +of the first track while "cue goto 1 5" seeks to the fifth second of +the second track + +=item cue seek TIMESTAMP - seek within the current cue index + +Like the normal "seek" command, but this confines the seeking +within the currently playing index. As with "seek", +"+" and "-" prefixes allow seeking relative to the currently +playing position + +Using a "=-" prefix allows seeking to the previous track within +the CUE sheet, relative to the currently playing track. =item current - output information about the current track/command in YAML diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb index 8a15974..838abf7 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -186,6 +186,21 @@ def bytes_decoded(src = @current) bytes = bytes < 0 ? 0 : bytes # maybe negative in case of sink errors end + def __goto_offset_samples(offset) + if offset.sub!(/\A\+/, '') + __offset_to_samples(offset) + elsif offset.sub!(/\A-/, '') + __offset_to_samples(offset) * -1 + else # ignore leading '=' for sox compat with 2nd "trim" arg + __offset_to_samples(offset) + end + end + + def __offset_to_samples(offset) + offset.sub!(/s\z/, '') and return offset.to_i + @current.format.hhmmss_to_samples(offset) + end + # returns seek offset as an Integer in sample count def __seek_offset_adj(dir, offset) if offset.sub!(/s\z/, '') @@ -377,7 +392,7 @@ def seek_internal(cur, offset) end def dpc_seek(io, msg) - offset = msg[0] + offset = msg[0] or return io.emit('ERR usage: seek OFFSET') if @current if @current.respond_to?(:infile) begin @@ -730,6 +745,7 @@ def _dpc_tl_swap(io, msg) end def __bp_prev_next(io, msg, cur, bp) + # msg = [ (prev|next) [, (track|pregap|subindex||any) ] case type = msg[1] when nil, "track" bp.keep_if(&:track?) @@ -772,6 +788,47 @@ def __bp_prev_next(io, msg, cur, bp) io.emit("OK") end + def __bp_seek(io, msg, cur, bp) + offset = msg[1] or return io.emit('ERR usage: cue seek OFFSET') + + # relative + offset work just like normal, non-CUE "seek" + offset =~ /\A[\+-]/ and return dpc_seek(io, [ offset ]) + + # "=-" is special, it means go before the current index start: + dir = offset.sub!(/\A=-/, '') ? -1 : 1 + + begin + offset = __offset_to_samples(offset) + rescue ArgumentError + return io.emit('ERR bad time format') + end + + ds = __current_decoded_samples + fmt = cur.format + prev = 0 + bp.each do |ci| + ci_offset = ci.offset_samples(fmt) + break if ci_offset >= ds + prev = ci_offset + end + offset = offset * dir + prev + seek_internal(cur, "#{offset < 0 ? 0 : offset}s") + io.emit('OK') + end + + def __bp_goto(io, msg, cur, bp) + index = msg[1] or return io.emit('NOINDEX') + ci = bp[index.to_i] or return io.emit('BADINDEX') + if offset = msg[2] + fmt = cur.format + offset = "#{ci.offset_samples(fmt) + __goto_offset_samples(offset)}s" + else + offset = ci.offset + end + seek_internal(cur, offset) + io.emit('OK') + end + def dpc_cue(io, msg) cur = @current if cur.respond_to?(:cuebreakpoints) @@ -782,11 +839,8 @@ def dpc_cue(io, msg) io.emit(tmp.to_yaml) when "next", "prev" return __bp_prev_next(io, msg, cur, bp) - when "goto" - index = msg[1] or return io.emit("NOINDEX") - ci = bp[index.to_i] or return io.emit("BADINDEX") - seek_internal(cur, ci.offset) - return io.emit("OK") + when 'seek' then return __bp_seek(io, msg, cur, bp) + when 'goto' then return __bp_goto(io, msg, cur, bp) end else io.emit("NOCUE") -- EW