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
OPTIONAL_FUNCTIONS =
{
  versions: [%i[voidp size_t], :int]
}.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.



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

def initialize(path)
  @path = path
  @handle = Fiddle::Handle.new(path, Fiddle::RTLD_NOW)
  functions = FUNCTIONS.to_h do |name, signature|
    [name, bind_function(name, signature)]
  end
  OPTIONAL_FUNCTIONS.each do |name, signature|
    functions[name] = bind_function(name, signature)
  rescue Fiddle::DLError
    nil
  end
  @functions = functions.freeze
end

Instance Attribute Details

#avutil_majorObject (readonly)

Returns the value of attribute avutil_major.



99
100
101
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 99

def avutil_major
  @avutil_major
end

#diagnosticObject (readonly)

Returns the value of attribute diagnostic.



99
100
101
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 99

def diagnostic
  @diagnostic
end

#ffmpeg_versionsObject (readonly)

Returns the value of attribute ffmpeg_versions.



99
100
101
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 99

def ffmpeg_versions
  @ffmpeg_versions
end

#pathObject (readonly)

Returns the value of attribute path.



99
100
101
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 99

def path
  @path
end

Class Method Details

.candidate_pathsObject



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 58

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



45
46
47
48
49
50
51
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 45

def load
  @mutex ||= Mutex.new
  # A failed load is deliberately retryable. Embedders may set an
  # adapter override or make a packaged/system library available
  # after an earlier best-effort audio probe.
  @mutex.synchronize { @instance ||= load_uncached }
end

.loadedObject



53
54
55
56
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 53

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

Instance Method Details

#cancel_all!Object



242
243
244
245
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 242

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

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



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 144

def decode(path, sample_rate:, max_decoded_bytes:)
  Internal::InterruptibleNativeCall.run(
    cancel: -> { @functions.fetch(:cancel).call },
    join_interval: CANCELLATION_JOIN_INTERVAL,
    missing_outcome: TranscriptionRuntimeError.new(
      "Native FFmpeg decode worker exited without reporting an outcome"
    ),
    thread_name: "cohere-ffmpeg-decode"
  ) do
    decode_owned(path, sample_rate: sample_rate, max_decoded_bytes: max_decoded_bytes)
  end
end

#duration(path) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 217

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



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
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 115

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
  raise TranscriptionRuntimeError, "Native FFmpeg libraries are unavailable: #{@diagnostic}" unless status.zero?

  versions = @functions[:versions]
  return clear_ffmpeg_versions unless versions

  tuple_bytes = 4 * Fiddle::SIZEOF_INT
  tuple = Fiddle::Pointer.malloc(tuple_bytes, Fiddle::RUBY_FREE)
  tuple[0, tuple_bytes] = [0, 0, 0, 0].pack("i!*")
  version_status = versions.call(tuple, 4)
  unless version_status.zero?
    raise TranscriptionRuntimeError,
          "Native FFmpeg libraries did not report their version tuple: #{@diagnostic}"
  end

  @ffmpeg_versions = tuple[0, tuple_bytes].unpack("i!4").freeze
  unless @ffmpeg_versions.all?(&:positive?)
    raise TranscriptionRuntimeError,
          "Native FFmpeg libraries reported an invalid version tuple: #{@ffmpeg_versions.inspect}"
  end

  @avutil_major = @ffmpeg_versions.fetch(2)
  self
end