diff --git a/ruby/.gitignore b/ruby/.gitignore new file mode 100644 index 00000000..d8529416 --- /dev/null +++ b/ruby/.gitignore @@ -0,0 +1,3 @@ +pkg +vosk-model-small-en-us-0.15 +Gemfile.lock diff --git a/ruby/.rspec b/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml new file mode 100644 index 00000000..4f6843d4 --- /dev/null +++ b/ruby/.rubocop.yml @@ -0,0 +1,24 @@ +--- +AllCops: + TargetRubyVersion: 2.6 + NewCops: enable + +require: + - rubocop-rspec + - rubocop-performance + - rubocop-rake + - rubocop-packaging + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/LineLength: + Max: 80 + +Style/Documentation: + Enabled: false diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 00000000..803724df --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +gemspec + +gem "rake", "~> 13.0" +gem "rspec", "~> 3.12" +gem "rubocop", "~> 1.48" +gem "rubocop-packaging", "~> 0.5.2" +gem "rubocop-performance", "~> 1.16" +gem "rubocop-rake", "~> 0.6.0" +gem "rubocop-rspec", "~> 2.19" diff --git a/ruby/README.md b/ruby/README.md new file mode 100644 index 00000000..87309114 --- /dev/null +++ b/ruby/README.md @@ -0,0 +1,55 @@ +# Vosk + +Ruby bindings to [vosk-api](https://github.com/alphacep/vosk-api) using [fiddle](https://github.com/ruby/fiddle). + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'vosk' +``` + +Or, to install the gem using `git`: + +```ruby +gem "vosk", + git: "https://github.com/alphacep/vosk-api", + glob: "ruby/vosk.gemspec" +``` + +And then execute: + +``` +bundle install +``` + +Or install it yourself as: +``` +gem install vosk +``` + +## Usage + +``` +git clone https://github.com/alphacep/vosk-api +cd vosk-api/ruby +bundle +bundle exec ruby examples/transcribe.rb +``` + +See [examples/transcribe.rb](examples/transcribe.rb) for more info. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/alphacep/vosk-api. + +## License + +The gem is available as open source under the terms of the [Apache-2.0](https://opensource.org/license/apache-2-0/) license. diff --git a/ruby/Rakefile b/ruby/Rakefile new file mode 100644 index 00000000..0803e9d0 --- /dev/null +++ b/ruby/Rakefile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rspec/core/rake_task" +require "rake/clean" +require "rubocop/rake_task" + +CLEAN.include "pkg", "vosk-model-small-en-us-0.15" + +RSpec::Core::RakeTask.new(:spec) + +RuboCop::RakeTask.new(:lint) + +task default: %i[build lint spec] diff --git a/ruby/bin/console b/ruby/bin/console new file mode 100755 index 00000000..84fbf0ef --- /dev/null +++ b/ruby/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "vosk" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/ruby/bin/setup b/ruby/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/ruby/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/ruby/examples/transcribe.rb b/ruby/examples/transcribe.rb new file mode 100644 index 00000000..8852dc87 --- /dev/null +++ b/ruby/examples/transcribe.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "vosk" +require "json" +require "net/http" + +MODEL = "vosk-model-small-en-us-0.15" + +unless File.directory?(MODEL) + puts "Downloading #{MODEL}..." + model_zip = Net::HTTP.get(URI("https://alphacephei.com/vosk/models/#{MODEL}.zip")) + File.binwrite("model.zip", model_zip) + system "unzip model.zip", exception: true + File.delete("model.zip") + puts "Done" +end + +# Vosk::FFI.vosk_set_log_level(-1) + +model = Vosk::FFI.vosk_model_new(MODEL) +recognizer = Vosk::FFI.vosk_recognizer_new(model, 16_000.0) + +file = File.open("../python/example/test.wav", "r") +file.seek 44, IO::SEEK_SET + +audio = file.read + +Vosk::FFI.vosk_recognizer_accept_waveform(recognizer, audio, audio.size) + +result = Vosk::FFI.vosk_recognizer_final_result(recognizer).to_s + +puts JSON.parse(result) diff --git a/ruby/lib/vosk.rb b/ruby/lib/vosk.rb index 680e039a..f14711c8 100644 --- a/ruby/lib/vosk.rb +++ b/ruby/lib/vosk.rb @@ -1,5 +1,7 @@ -class Vosk - def self.hi - puts "Hello world!" - end +# frozen_string_literal: true + +module Vosk end + +require_relative "vosk/version" +require_relative "vosk/ffi" diff --git a/ruby/lib/vosk/ffi.rb b/ruby/lib/vosk/ffi.rb new file mode 100644 index 00000000..6f746d9b --- /dev/null +++ b/ruby/lib/vosk/ffi.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "fiddle" +require "fiddle/import" +require "rbconfig" + +module Vosk + module FFI + extend Fiddle::Importer + + begin + lib = "libvosk.#{RbConfig::CONFIG["SOEXT"]}" + dlload(lib) + rescue Fiddle::DLError => e + abort <<~MSG + Could not find `#{lib}`. + + Install Vosk-api, then try again. + + Error: #{e&.cause&.message || e&.message || e.inspect} + MSG + end + + # rubocop:disable Layout/LineLength + + extern "VoskModel *vosk_model_new(const char *model_path);" + extern "VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate);" + extern "int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length);" + extern "const char *vosk_recognizer_result(VoskRecognizer *recognizer);" + extern "const char *vosk_recognizer_final_result(VoskRecognizer *recognizer);" + extern "void vosk_recognizer_reset(VoskRecognizer *recognizer);" + extern "void vosk_recognizer_free(VoskRecognizer *recognizer);" + extern "void vosk_model_free(VoskModel *model);" + extern "void vosk_set_log_level(int log_level);" + + # rubocop:enable Layout/LineLength + end +end diff --git a/ruby/lib/vosk/version.rb b/ruby/lib/vosk/version.rb new file mode 100644 index 00000000..546ca797 --- /dev/null +++ b/ruby/lib/vosk/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Vosk + VERSION = "0.3.45" +end diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..b0f86904 --- /dev/null +++ b/ruby/spec/spec_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + config.disable_monkey_patching! + config.warnings = true + config.order = :random + Kernel.srand config.seed +end diff --git a/ruby/spec/vosk/ffi_spec.rb b/ruby/spec/vosk/ffi_spec.rb new file mode 100644 index 00000000..659b1bfd --- /dev/null +++ b/ruby/spec/vosk/ffi_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "vosk/ffi" + +RSpec.describe Vosk::FFI do + %w[ + vosk_model_new + vosk_recognizer_new + vosk_recognizer_accept_waveform + vosk_recognizer_result + vosk_recognizer_final_result + vosk_recognizer_reset + vosk_recognizer_free + vosk_model_free + vosk_set_log_level + ].each do |method| + it ".#{method}" do + expect(described_class).to respond_to(method) + end + end +end diff --git a/ruby/spec/vosk_spec.rb b/ruby/spec/vosk_spec.rb new file mode 100644 index 00000000..b26d11c1 --- /dev/null +++ b/ruby/spec/vosk_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "vosk" + +RSpec.describe Vosk do + it "has a version number" do + expect(Vosk::VERSION).to eq("0.3.45") + end +end diff --git a/ruby/vosk.gemspec b/ruby/vosk.gemspec index 9e41882b..e0ef80ef 100644 --- a/ruby/vosk.gemspec +++ b/ruby/vosk.gemspec @@ -1,12 +1,19 @@ +# frozen_string_literal: true + +require_relative "lib/vosk/version" + Gem::Specification.new do |s| s.name = "vosk" - s.version = "0.3.45" + s.version = Vosk::VERSION s.summary = "Offline speech recognition API" - s.description = "Vosk is an offline open source speech recognition toolkit. It enables speech recognition for 20+ languages and dialects - English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish. More to come." + s.description = "Vosk is an offline open source speech recognition toolkit. It enables speech recognition for 20+ languages and dialects - English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish. More to come." # rubocop:disable Layout/LineLength s.authors = ["Alpha Cephei Inc"] s.email = "contact@alphacphei.com" - s.files = ["lib/vosk.rb"] - s.homepage = - "https://rubygems.org/gems/vosk" - s.license = "Apache 2.0" + s.files = Dir.glob("lib/**/*.rb") + s.homepage = "https://rubygems.org/gems/vosk" + s.license = "Apache 2.0" + s.required_ruby_version = ">= 2.6.0" + s.metadata["homepage_uri"] = "https://github.com/alphacep/vosk-api" + s.metadata["source_code_uri"] = "https://github.com/alphacep/vosk-api" + s.metadata["rubygems_mfa_required"] = "true" end