about summary refs log tree commit homepage
path: root/lib/dtas/sink_reader_play.rb
blob: 17a01904a24782d119b2ed49a7bcff1a577a47d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# -*- encoding: binary -*-
# :stopdoc:
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
require_relative '../dtas'

# parses lines from play(1) -S/--show-progress like this:
#  In:0.00% 00:00:37.34 [00:00:00.00] Out:1.65M [ -====|====  ]        Clip:0
#
# The authors of sox probably did not intend for the output of play(1) to
# be parsed, but we do it anyways.  We need to be ready to update this
# code in case play(1) output changes.
# play -S/--show-progress
class DTAS::SinkReaderPlay
  attr_reader :time, :out, :meter, :clips, :headroom
  attr_reader :to_io
  attr_reader :wr # this is stderr of play(1)

  def initialize
    @to_io, @wr = IO.pipe
    reset
  end

  def readable_iter
    buf = Thread.current[:dtas_lbuf] ||= ""
    begin
      @rbuf << @to_io.read_nonblock(1024, buf)

      # do not OOM in case SoX changes output format on us
      @rbuf.clear if @rbuf.size > 0x10000

      # don't handle partial read
      next unless / Clip:\S+ *\z/ =~ @rbuf

      if @rbuf.gsub!(/(.*)\rIn:\S+ (\S+) \S+ Out:(\S+)\s+(\[[^\]]+\]) /m, "")
        err = $1
        @time = $2
        @out = $3
        @meter = $4
        if @rbuf.gsub!(/Hd:(\d+\.\d+) Clip:(\S+) */, "")
          @headroom = $1
          @clips = $2
        elsif @rbuf.gsub!(/\s+Clip:(\S+) */, "")
          @headroom = nil
          @clips = $1
        end

        $stderr.write(err)
      end
    rescue EOFError
      return nil
    rescue Errno::EAGAIN
      return :wait_readable
    end while true
  end

  def close
    @wr.close unless @wr.closed?
    @to_io.close
  end

  def reset
    @rbuf = ""
    @time = @out = @meter = @headroom = @clips = nil
  end

  def closed?
    @to_io.closed?
  end
end