183 lines
5.8 KiB
Ruby
183 lines
5.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
require "srt"
|
|
require "wavefile"
|
|
|
|
RSpec.describe Vosk do
|
|
let(:en_model_path) { File.join(Dir.home, ".cache/vosk/vosk-model-small-en-us-0.4") }
|
|
let(:test_wav_path) { File.expand_path("../example/test.wav", __dir__) }
|
|
let(:wave_reader) { WaveFile::Reader.new(test_wav_path) }
|
|
let(:wave_chunks) do
|
|
chunks = []
|
|
wave_reader.each_buffer(4000) { |buffer| chunks.push(buffer.samples.pack(WaveFile::PACK_CODES.dig(:pcm, 16))) }
|
|
chunks
|
|
end
|
|
|
|
after { wave_reader.close }
|
|
|
|
it "has a version number" do
|
|
expect(Vosk::VERSION).not_to be_nil
|
|
end
|
|
|
|
describe "EndpointerMode" do
|
|
it "defines the correct integer constants" do
|
|
expect([
|
|
Vosk::EndpointerMode::DEFAULT,
|
|
Vosk::EndpointerMode::SHORT,
|
|
Vosk::EndpointerMode::LONG,
|
|
Vosk::EndpointerMode::VERY_LONG,
|
|
]).to eq([0, 1, 2, 3])
|
|
end
|
|
end
|
|
|
|
describe Vosk::SpkModel do
|
|
it "raises Vosk::Error on a bad path" do
|
|
expect do
|
|
described_class.new("/nonexistent/path")
|
|
end.to raise_error(Vosk::Error, "Failed to create a speaker model")
|
|
end
|
|
end
|
|
|
|
describe Vosk::KaldiRecognizer do
|
|
let(:model) { Vosk::Model.new(model_path: en_model_path) }
|
|
let(:wave_sample_rate) { wave_reader.format.sample_rate }
|
|
|
|
describe "initialization" do
|
|
it "accepts (model, sample_rate)" do
|
|
expect(described_class.new(model, wave_sample_rate)).to be_a(described_class)
|
|
end
|
|
|
|
it "accepts (model, sample_rate, grammar_string)" do
|
|
rec = described_class.new(model, wave_sample_rate, '["one two three", "[unk]"]')
|
|
expect(rec).to be_a(described_class)
|
|
end
|
|
|
|
it "raises TypeError for an unknown third argument type" do
|
|
expect { described_class.new(model, wave_sample_rate, 42) }.to raise_error(TypeError)
|
|
end
|
|
end
|
|
|
|
context "when processing audio" do
|
|
subject(:rec) { described_class.new(model, wave_sample_rate) }
|
|
|
|
let(:wave_stream) do
|
|
stream = wave_chunks.each_with_object(StringIO.new) { |chunk, stream| stream.write(chunk) }
|
|
stream.rewind
|
|
stream
|
|
end
|
|
|
|
let(:expected_srt) do
|
|
<<~SRT
|
|
1
|
|
00:00:00,870 --> 00:00:02,610
|
|
what zero zero zero one
|
|
|
|
2
|
|
00:00:03,930 --> 00:00:04,950
|
|
no no to uno
|
|
|
|
3
|
|
00:00:06,240 --> 00:00:08,010
|
|
cyril one eight zero three
|
|
SRT
|
|
end
|
|
|
|
it "accept_waveform returns 0 or 1" do
|
|
results = wave_chunks.map { |chunk| rec.accept_waveform(chunk) }.uniq
|
|
expect(results).to contain_exactly(0, 1)
|
|
end
|
|
|
|
it "result returns valid JSON" do
|
|
rec.accept_waveform(wave_chunks.first)
|
|
expect { JSON.parse(rec.result) }.not_to raise_error
|
|
end
|
|
|
|
it "partial_result returns valid JSON" do
|
|
rec.accept_waveform(wave_chunks.first)
|
|
expect { JSON.parse(rec.partial_result) }.not_to raise_error
|
|
end
|
|
|
|
it "final_result returns valid JSON" do
|
|
expect { JSON.parse(rec.final_result) }.not_to raise_error
|
|
end
|
|
|
|
it "transcribes the test file to non-empty text" do
|
|
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
|
text = JSON.parse(rec.final_result)["text"]
|
|
expect(text).not_to be_empty
|
|
end
|
|
|
|
it "reset clears the partial result" do
|
|
rec.accept_waveform(wave_chunks.first)
|
|
rec.reset
|
|
expect(JSON.parse(rec.partial_result)["partial"]).to be_nil.or(eq(""))
|
|
end
|
|
|
|
it "set_words does not raise" do
|
|
expect { rec.words = true }.not_to raise_error
|
|
end
|
|
|
|
it "set_words includes per-word timing in results" do
|
|
rec.words = true
|
|
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
|
result = JSON.parse(rec.final_result)
|
|
expect(result).to have_key("text")
|
|
expect(result["result"]).to be_an(Array)
|
|
end
|
|
|
|
it "set_partial_words does not raise" do
|
|
expect { rec.partial_words = true }.not_to raise_error
|
|
end
|
|
|
|
it "set_max_alternatives produces an alternatives array" do
|
|
rec.max_alternatives = 5
|
|
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
|
result = JSON.parse(rec.final_result)
|
|
expect(result).to have_key("alternatives")
|
|
expect(result["alternatives"]).to be_an(Array)
|
|
end
|
|
|
|
it "set_endpointer_mode accepts EndpointerMode constants",
|
|
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.46") && "requires libvosk >= 0.3.46" do
|
|
expect { rec.endpointer_mode = :short }.not_to raise_error
|
|
end
|
|
|
|
it "set_endpointer_delays accepts float values",
|
|
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.46") && "requires libvosk >= 0.3.46" do
|
|
expect { rec.set_endpointer_delays(0.5, 1.0, 30.0) }.not_to raise_error
|
|
end
|
|
|
|
it "set_grammar changes the active grammar" do
|
|
expect { rec.grammar = '["one two three", "[unk]"]' }.not_to raise_error
|
|
end
|
|
|
|
it "srt_result produces a valid SRT" do
|
|
rec.words = true
|
|
expect(rec.srt_result(wave_stream)).to eq(expected_srt)
|
|
end
|
|
end
|
|
|
|
context "with a grammar recognizer" do
|
|
subject(:rec) do
|
|
described_class.new(
|
|
model, wave_sample_rate,
|
|
'["one two three four five six seven eight nine zero", "[unk]"]',
|
|
)
|
|
end
|
|
|
|
it "produces a result constrained to the grammar vocabulary" do
|
|
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
|
expect(JSON.parse(rec.final_result)).to have_key("text")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe Vosk::Processor,
|
|
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.48") && "requires libvosk >= 0.3.48" do
|
|
it "raises Vosk::Error on a bad lang/type" do
|
|
expect { described_class.new("xx_invalid", "itn") }.to raise_error(Vosk::Error, "Failed to create processor")
|
|
end
|
|
end
|
|
end
|