Module: Ffmprb::Util

Defined in:
lib/ffmprb/util.rb,
lib/ffmprb/util/thread.rb,
lib/ffmprb/util/proc_vis.rb,
lib/ffmprb/util/threaded_io_buffer.rb

Defined Under Namespace

Modules: ProcVis Classes: BrokenPipeError, Reader, Thread, ThreadedIoBuffer, TimeLimitError

Constant Summary collapse

FFMPEG_BROKEN_PIPE_ERROR_RE =
/^.*\berror\b.*:.*\bbroken pipe\b.*$/i

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.cmd_timeoutObject

Returns the value of attribute cmd_timeout.



17
18
19
# File 'lib/ffmprb/util.rb', line 17

def cmd_timeout
  @cmd_timeout
end

.ffmpeg_cmdObject

Returns the value of attribute ffmpeg_cmd.



16
17
18
# File 'lib/ffmprb/util.rb', line 16

def ffmpeg_cmd
  @ffmpeg_cmd
end

.ffmpeg_inputs_maxObject

Returns the value of attribute ffmpeg_inputs_max.



16
17
18
# File 'lib/ffmprb/util.rb', line 16

def ffmpeg_inputs_max
  @ffmpeg_inputs_max
end

.ffprobe_cmdObject

Returns the value of attribute ffprobe_cmd.



16
17
18
# File 'lib/ffmprb/util.rb', line 16

def ffprobe_cmd
  @ffprobe_cmd
end

Class Method Details

.assert_options_empty!(opts) ⇒ Object



87
88
89
# File 'lib/ffmprb/util.rb', line 87

def assert_options_empty!(opts)
  fail ArgumentError, "Unknown options: #{opts}"  unless opts.empty?
end

.ffmpeg(*args, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: true) ⇒ Object

TODO un-colorise ffmpeg output for logging, also, convert ^M into something



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ffmprb/util.rb', line 24

def ffmpeg(*args, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: true)
  args = %w[-loglevel debug] + args  if
    Ffmprb.ffmpeg_debug
  sh(
    *ffmpeg_cmd, *args,
    output: :stderr,
    limit: limit,
    timeout: timeout,
    ignore_broken_pipes: ignore_broken_pipes,
    broken_pipe_error_re: FFMPEG_BROKEN_PIPE_ERROR_RE
  )
end

.ffprobe(*args, limit: nil, timeout: cmd_timeout) ⇒ Object



19
20
21
# File 'lib/ffmprb/util.rb', line 19

def ffprobe(*args, limit: nil, timeout: cmd_timeout)
  sh *ffprobe_cmd, *args, limit: limit, timeout: timeout
end

.sh(*cmd, input: nil, output: :stdout, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: false, broken_pipe_error_re: nil) ⇒ Object



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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ffmprb/util.rb', line 37

def sh(*cmd, input: nil, output: :stdout, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: false, broken_pipe_error_re: nil)
  cmd = cmd.map &:to_s  unless cmd.size == 1
  cmd_str = cmd.size != 1 ? cmd.map{|c| sh_escape c}.join(' ') : cmd.first
  cmd_log_line = "#{log_hash cmd_str}: `#{cmd_str}`"
  timeout = [timeout, limit].compact.min
  thr = Thread.new cmd_log_line do
    Ffmprb.logger.info "Popening #{cmd_log_line}..."
    Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
      begin
        stdin.write input  if input
        stdin.close

        log_cmd = cmd.first.upcase
        stdout_r = Reader.new(stdout, store: output == :stdout, log_with: log_cmd)
        stderr_r = Reader.new(stderr, store: true, log_with: log_cmd, log_as: output == :stderr && Logger::DEBUG || Logger::INFO)
        stderr_s = nil

        Thread.timeout_or_live(limit, log: "while waiting for #{cmd_log_line}", timeout: timeout) do |time|
          value = wait_thr.value
          status = value.exitstatus  # NOTE blocking
          if status != 0
            stderr_s = stderr_r.read
            if (value.signaled? && value.termsig == Signal.list['PIPE']) ||
               # NOTE this doesn't seem to work for ffmpeg 4.x (it ignores SIGPIPEs)
               (broken_pipe_error_re && status == 1 && stderr_s =~ broken_pipe_error_re)
              if ignore_broken_pipes
                Ffmprb.logger.info "Ignoring broken pipe: #{cmd_log_line}"
              else
                fail BrokenPipeError, cmd_log_line
              end
            else
              status ||= "sig##{value.termsig}"
              fail Error, "#{cmd_log_line} (#{status}):\n#{stderr_s}"
            end
          end
        end
        Ffmprb.logger.debug{"FINISHED: #{cmd_log_line}"}

        Thread.join_children! limit, timeout: timeout

        # NOTE only one of them will return non-nil, see above
        stdout_r.read || stderr_s || stderr_r.read
      ensure
        process_dead! wait_thr, cmd_str, limit
      end
    end
  end
  thr.value
end