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

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.cmd_timeoutObject

Returns the value of attribute cmd_timeout.



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

def cmd_timeout
  @cmd_timeout
end

.ffmpeg_cmdObject

Returns the value of attribute ffmpeg_cmd.



14
15
16
# File 'lib/ffmprb/util.rb', line 14

def ffmpeg_cmd
  @ffmpeg_cmd
end

.ffmpeg_inputs_maxObject

Returns the value of attribute ffmpeg_inputs_max.



14
15
16
# File 'lib/ffmprb/util.rb', line 14

def ffmpeg_inputs_max
  @ffmpeg_inputs_max
end

.ffprobe_cmdObject

Returns the value of attribute ffprobe_cmd.



14
15
16
# File 'lib/ffmprb/util.rb', line 14

def ffprobe_cmd
  @ffprobe_cmd
end

Class Method Details

.assert_options_empty!(opts) ⇒ Object



73
74
75
# File 'lib/ffmprb/util.rb', line 73

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 warn on broken pipes incompatibility with 4.x or something



22
23
24
25
26
# File 'lib/ffmprb/util.rb', line 22

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

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



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

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) ⇒ Object



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
71
# File 'lib/ffmprb/util.rb', line 28

def sh(*cmd, input: nil, output: :stdout, limit: nil, timeout: cmd_timeout, ignore_broken_pipes: false)
  cmd = cmd.map &:to_s  unless cmd.size == 1
  cmd_str = cmd.size != 1 ? cmd.map{|c| sh_escape c}.join(' ') : cmd.first
  timeout = [timeout, limit].compact.min
  thr = Thread.new "`#{cmd_str}`" do
    Ffmprb.logger.info "Popening `#{cmd_str}`..."
    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)

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

        Thread.join_children! limit, timeout: timeout

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