Class: Cohere::Transcribe::Audio::FFmpegNative::Library

Inherits:
Object
  • Object
show all
Defined in:
lib/cohere/transcribe/audio/ffmpeg_native.rb

Constant Summary collapse

FUNCTIONS =
{
  probe: [%i[voidp size_t], :int],
  decode: [%i[voidp int uint64 voidp voidp voidp size_t], :int],
  duration: [%i[voidp voidp voidp size_t], :int],
  cancel: [[], :void],
  free: [[:voidp], :void]
}.freeze
TYPE_MAP =
{
  void: Fiddle::TYPE_VOID,
  voidp: Fiddle::TYPE_VOIDP,
  int: Fiddle::TYPE_INT,
  size_t: Fiddle::TYPE_SIZE_T,
  uint64: Fiddle::TYPE_UINT64_T
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Library

Returns a new instance of Library.



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 89

def initialize(path)
  @path = path
  @handle = Fiddle::Handle.new(path, Fiddle::RTLD_NOW)
  @functions = FUNCTIONS.to_h do |name, (arguments, result)|
    address = @handle["cohere_audio_ffmpeg_#{name}"]
    function = Fiddle::Function.new(
      address,
      arguments.map { |type| TYPE_MAP.fetch(type) },
      TYPE_MAP.fetch(result)
    )
    [name, function]
  end.freeze
end

Instance Attribute Details

#diagnosticObject (readonly)

Returns the value of attribute diagnostic.



87
88
89
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 87

def diagnostic
  @diagnostic
end

#pathObject (readonly)

Returns the value of attribute path.



87
88
89
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 87

def path
  @path
end

Class Method Details

.candidate_pathsObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 46

def candidate_paths
  explicit = ENV.fetch("COHERE_TRANSCRIBE_AUDIO_LIBRARY", nil)
  return [explicit] if explicit && !explicit.empty?

  packaged = File.expand_path("../native", __dir__)
  patterns = case RbConfig::CONFIG.fetch("host_os")
             when /darwin/ then ["libcohere_audio*.dylib"]
             when /mswin|mingw|cygwin/ then ["cohere_audio*.dll", "libcohere_audio*.dll"]
             else ["libcohere_audio.so", "libcohere_audio.so.*"]
             end
  paths = patterns.flat_map { |pattern| Dir.glob(File.join(packaged, pattern)) }.sort
  paths.concat(system_library_names)
  paths.uniq
end

.loadObject



36
37
38
39
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 36

def load
  @mutex ||= Mutex.new
  @mutex.synchronize { @instance ||= load_uncached }
end

.loadedObject



41
42
43
44
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 41

def loaded
  mutex = @mutex
  mutex&.synchronize { @instance }
end

Instance Method Details

#cancel_all!Object



190
191
192
193
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 190

def cancel_all!
  @functions.fetch(:cancel).call
  nil
end

#decode(path, sample_rate:, max_decoded_bytes:) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 113

def decode(path, sample_rate:, max_decoded_bytes:)
  output_slot = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP, Fiddle::RUBY_FREE)
  output_slot[0, Fiddle::SIZEOF_VOIDP] = [0].pack("J")
  count_slot = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT64_T, Fiddle::RUBY_FREE)
  count_slot[0, Fiddle::SIZEOF_INT64_T] = [0].pack("q")
  message = Fiddle::Pointer.malloc(ERROR_CAPACITY, Fiddle::RUBY_FREE)
  message[0, ERROR_CAPACITY] = "\0" * ERROR_CAPACITY
  maximum = max_decoded_bytes || 0

  status = @functions.fetch(:decode).call(
    c_string(path.to_s),
    sample_rate,
    maximum,
    output_slot,
    count_slot,
    message,
    ERROR_CAPACITY
  )
  address = output_slot[0, Fiddle::SIZEOF_VOIDP].unpack1("J")
  count = count_slot[0, Fiddle::SIZEOF_INT64_T].unpack1("q")
  detail = message.to_s.force_encoding(Encoding::UTF_8).scrub
  unless status.zero?
    @functions.fetch(:free).call(address) unless address.zero?
    raise Interrupt, "Native FFmpeg decode was cancelled for #{path}: #{detail}" if status == CANCELLED_STATUS

    raise TranscriptionRuntimeError, "Cannot decode #{path} through native FFmpeg: #{detail}"
  end
  if count.negative? || (count.positive? && address.zero?)
    @functions.fetch(:free).call(address) unless address.zero?
    raise TranscriptionRuntimeError, "Native FFmpeg returned invalid output metadata for #{path}"
  end
  bytes = count * Fiddle::SIZEOF_FLOAT
  if max_decoded_bytes && bytes > max_decoded_bytes
    @functions.fetch(:free).call(address) unless address.zero?
    raise TranscriptionRuntimeError,
          "Native FFmpeg exceeded the configured decoded-audio memory limit for #{path}"
  end

  begin
    require "numo/narray"
    if count.zero?
      Numo::SFloat.zeros(0)
    else
      Numo::SFloat.from_binary(Fiddle::Pointer.new(address)[0, bytes])
    end
  rescue LoadError => e
    raise TranscriptionRuntimeError, "numo-narray is required for decoded audio: #{e.message}"
  ensure
    @functions.fetch(:free).call(address) unless address.zero?
  end
end

#duration(path) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 165

def duration(path)
  duration_slot = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE, Fiddle::RUBY_FREE)
  duration_slot[0, Fiddle::SIZEOF_DOUBLE] = [-1.0].pack("d")
  message = Fiddle::Pointer.malloc(ERROR_CAPACITY, Fiddle::RUBY_FREE)
  message[0, ERROR_CAPACITY] = "\0" * ERROR_CAPACITY
  status = @functions.fetch(:duration).call(
    c_string(path.to_s),
    duration_slot,
    message,
    ERROR_CAPACITY
  )
  detail = message.to_s.force_encoding(Encoding::UTF_8).scrub
  unless status.zero?
    raise Interrupt, "Native FFmpeg duration probe was cancelled for #{path}: #{detail}" if status == CANCELLED_STATUS

    raise TranscriptionRuntimeError,
          "Cannot inspect #{path} through native FFmpeg: #{detail}"
  end

  seconds = duration_slot[0, Fiddle::SIZEOF_DOUBLE].unpack1("d")
  return nil unless seconds.finite? && seconds >= 0.0

  seconds
end

#probe!Object



103
104
105
106
107
108
109
110
111
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 103

def probe!
  message = Fiddle::Pointer.malloc(ERROR_CAPACITY, Fiddle::RUBY_FREE)
  message[0, ERROR_CAPACITY] = "\0" * ERROR_CAPACITY
  status = @functions.fetch(:probe).call(message, ERROR_CAPACITY)
  @diagnostic = message.to_s.force_encoding(Encoding::UTF_8).scrub.freeze
  return self if status.zero?

  raise TranscriptionRuntimeError, "Native FFmpeg libraries are unavailable: #{@diagnostic}"
end