Class: OllamaChat::ASR

Inherits:
Object
  • Object
show all
Defined in:
lib/ollama_chat/asr.rb

Class Method Summary collapse

Class Method Details

.multipart_form_data(file_path, **rest) ⇒ Hash (private)

Builds a multipart/form-data request body for the ASR endpoint.

Constructs the wire format manually (boundary, text field parts, file part) so the result can be splatted directly into HTTPHandling#request_url_response.

Parameters:

  • file_path (String) —

    path to the WAV file to upload

  • rest (Hash) —

    additional form fields (e.g. model:, language:); nil values are omitted via compact

Returns:

  • (Hash) —

    Excon-compatible options with :headers (Content-Type including the generated boundary), :expects (200), and :body (the assembled multipart string)



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ollama_chat/asr.rb', line 83

def multipart_form_data(file_path, **rest)
  path     = Pathname.new(file_path)
  body     = ''
  boundary = SecureRandom.hex(8)

  rest.compact.each do |field_name, field_value|
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{field_name}\"\r\n\r\n"
    body << "#{field_value}\r\n"
  end

  body << "--#{boundary}" << Excon::CR_NL
  body << %{Content-Disposition: form-data; name="file"; filename="#{path.basename}"} << Excon::CR_NL
  body << 'Content-Type: audio/wav' << Excon::CR_NL
  body << Excon::CR_NL
  body << path.binread
  body << Excon::CR_NL
  body << "--#{boundary}--" << Excon::CR_NL

  {
    headers: { 'Content-Type' => %{multipart/form-data; boundary="#{boundary}"} },
    expects: 200,
    body:    ,
  }
end

.transcribe(source_io, chat:, language: nil) ⇒ String?

Transcribes audio/video content via the audio.cpp ASR endpoint.

Parameters:

  • source_io (IO) —

    the audio/video source stream (must respond to #content_type and be seekable)

  • language (String, nil) (defaults to: nil) —

    optional language hint (e.g. "en", "de") passed as the language form field

  • chat (OllamaChat::Chat) —

    the chat instance (for HTTP middleware)

Returns:

  • (String, nil) —

    the transcribed text, or nil on failure



26
27
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
# File 'lib/ollama_chat/asr.rb', line 26

def transcribe(source_io, chat:, language: nil)
  asr_url   = OC::OLLAMA::CHAT::ASR::URL
  asr_model = OC::OLLAMA::CHAT::ASR::MODEL
  input = Tempfile.new(%w[ asr_in ])
  wav   = Tempfile.new(%w[ asr .wav ])
  begin
    IO.copy_stream(source_io, input)
    input.flush

    cmd = OC::OLLAMA::CHAT::ASR::CONVERT_COMMAND % {
      input: input.path, output: wav.path
    }
    executable = Shellwords.split(cmd).first
    result     = system(cmd, out: File::NULL, err: File::NULL)
    if result.nil?
      chat.feedback("ASR: #{executable} not found in PATH.", type: :warn)
      return
    end
    unless wav.size > 0
      chat.feedback("ASR: #{executable} produced no output.", type: :warn)
      return
    end

    url       = "#{asr_url}/v1/audio/transcriptions"
    form_data = multipart_form_data(wav.path, model: asr_model, language:)
    chat.request_url_response(:post, url, **form_data) do |response|
      return JSON.parse(response.body)['text']
    end
  ensure
    input.close!
    wav&.close!
  end
rescue JSON::ParserError, Excon::Error => e
  if response = e.ask_and_send(:response)
    status = response.status
    result = JSON.parse(response.body) rescue nil
  end
  chat.log(:error, e, data: { status:, result: })
  chat.feedback("ASR transcription failed: #{e.message}", type: :warn)
  nil
end