Class: OpenAI::LocalAudio::MediaProcess Private

Inherits:
Object
  • Object
show all
Defined in:
lib/openai/helpers/local_audio/process.rb,
sig/openai/helpers/local_audio/interface.rbs

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv, input:, output:, timeout:, capture_errors: false) ⇒ MediaProcess

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of MediaProcess.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/openai/helpers/local_audio/process.rb', line 41

def initialize(argv, input:, output:, timeout:, capture_errors: false)
  @deadline = self.class.clock + self.class.duration(timeout, name: :timeout) unless timeout.nil?
  @error_reader, error_writer = IO.pipe(binmode: true) if capture_errors
  @pid = Process.spawn(
    self.class.environment,
    *argv,
    in: input,
    out: output,
    err: error_writer || File::NULL,
    close_others: true,
    unsetenv_others: true
  )
rescue Errno::ENOENT
  @error_reader&.close
  raise DependencyError.new("Install the required FFmpeg/FFplay executable on PATH."), cause: nil
rescue SystemCallError
  @error_reader&.close
  raise DeviceError.new("Cannot start the local audio executable."), cause: nil
ensure
  error_writer&.close
end

Class Method Details

.clockFloat

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Float)


29
# File 'lib/openai/helpers/local_audio/process.rb', line 29

def self.clock = Process.clock_gettime(Process::CLOCK_MONOTONIC)

.duration(value, name:) ⇒ Float

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • value (Numeric)
  • name: (String, Symbol)

Returns:

  • (Float)

Raises:

  • (ArgumentError)


31
32
33
34
35
36
37
38
39
# File 'lib/openai/helpers/local_audio/process.rb', line 31

def self.duration(value, name:)
  unless value.is_a?(Numeric) && value.real? && value.finite? && value.positive?
    raise ArgumentError, "#{name} must be a positive finite number of seconds"
  end

  result = value.to_f
  raise ArgumentError, "#{name} is not representable" unless result.finite?
  result
end

.environmentHash[String, String]

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Hash[String, String])


28
# File 'lib/openai/helpers/local_audio/process.rb', line 28

def self.environment = ENV.to_h.slice(*ENVIRONMENT_KEYS)

Instance Method Details

#check_deadlinevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Raises:



63
64
65
66
# File 'lib/openai/helpers/local_audio/process.rb', line 63

def check_deadline
  raise TimeoutError, "Local audio operation timed out." if @deadline && self.class.clock >= @deadline
  errors?
end

#closevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/openai/helpers/local_audio/process.rb', line 119

def close
  return unless @pid
  reap
  unless @status
    signal("TERM")
    deadline = self.class.clock + 0.25
    until @status || self.class.clock >= deadline
      reap
      sleep(0.01) unless @status
    end

    unless @status
      signal("KILL")
      _, @status = Process.waitpid2(@pid)
    end
  end

ensure
  @pid = nil
  @error_reader&.close unless @error_reader&.closed?
end

#errors?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Drain at most one chunk per polling step, retaining no diagnostic content.

Returns:



69
70
71
72
73
74
75
76
# File 'lib/openai/helpers/local_audio/process.rb', line 69

def errors?
  if @error_reader && !@error_reader.closed?
    chunk = @error_reader.read_nonblock(65_536, exception: false)
    @errors = true if chunk.is_a?(String) && !chunk.empty?
  end

  !!@errors
end

#read(io, stop_on_exit: false) ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters:

  • io (IO, StringIO)
  • stop_on_exit: (Boolean) (defaults to: false)

Returns:

  • (String, nil)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/openai/helpers/local_audio/process.rb', line 78

def read(io, stop_on_exit: false)
  loop do
    check_deadline
    return io.read(65_536) if io.is_a?(StringIO)
    result = io.read_nonblock(65_536, exception: false)
    return result unless result == :wait_readable
    if stop_on_exit
      reap
      raise PlaybackError, "Audio player exited before the input ended." if @status
    end

    io.wait_readable(0.05)
  end
end

#waitProcess::Status

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Process::Status)


109
110
111
112
113
114
115
116
117
# File 'lib/openai/helpers/local_audio/process.rb', line 109

def wait
  until @status
    check_deadline
    reap
    sleep(0.01) unless @status
  end

  @status
end

#write(io, bytes) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Parameters:

  • io (IO)
  • bytes (String)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/openai/helpers/local_audio/process.rb', line 93

def write(io, bytes)
  offset = 0
  while offset < bytes.bytesize
    check_deadline
    result = io.write_nonblock(bytes.byteslice(offset, 65_536), exception: false)
    if result == :wait_writable
      io.wait_writable(0.05)
    else
      offset += result
    end
  end

rescue Errno::EPIPE
  raise PlaybackError.new("Audio player closed before receiving the input."), cause: nil
end