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



217
218
219
220
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 217

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

def decode(path, sample_rate:, max_decoded_bytes:)
  worker = Thread.new do
    decode_owned(path, sample_rate: sample_rate, max_decoded_bytes: max_decoded_bytes)
  end
  worker.name = "cohere-ffmpeg-decode" if worker.respond_to?(:name=)
  worker.report_on_exception = false
  completed = false
  result = worker.value
  completed = true
  result
ensure
  if defined?(worker) && worker && !completed
    Thread.handle_interrupt(Exception => :never) do
      @functions.fetch(:cancel).call if worker.alive?
    rescue StandardError
      nil
    ensure
      worker.join
    end
  end
end

#duration(path) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/cohere/transcribe/audio/ffmpeg_native.rb', line 192

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