snapshot of https://github.com/ggml-org/whisper.cpp.git @ da54572229bcf64ba367d96c7ef15770376c4280 (2026-09-15)
Some checks failed
Bindings Tests (Go) / ubuntu-22 (push) Failing after 31s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/ppc64le, icx, icpx, ON) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/ppc64le, icx, icpx, ON) (push) Failing after 3s
CI (vad) / vad (push) Failing after 3s
CI (wasm) / emscripten (Release) (push) Failing after 4s
Bindings Tests (Ruby) / ubuntu-22 (push) Failing after 37s
CI (android) / android (push) Failing after 16s
CI (android) / android_java (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Release) (push) Failing after 2s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Release) (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-low-perf (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-high-perf (push) Failing after 2s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Release) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Release) (push) Failing after 4s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Release) (push) Failing after 3s
CI (quantize) / quantize (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (ADDRESS) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (THREAD) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (UNDEFINED) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm64, icx, icpx, ON) (push) Failing after 3s
Examples WASM / deploy-wasm-github-pages (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (20.x) (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (22.x) (push) Failing after 2s
CI (coreml) / coreml-base-en (push) Has been cancelled
CI (self-hosted) / gpu-vulkan (push) Has been cancelled
CI (self-hosted) / npu-amd-windows (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm2 (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Debug) (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Release) (push) Has been cancelled
CI (self-hosted) / gpu-cuda (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-low-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf-sve (push) Has been cancelled
CI (freebsd) / freeBSD-latest (push) Has been cancelled
CI (self-hosted) / gpu-metal (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Debug) (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Release) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=iOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=macOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=tvOS) (push) Has been cancelled
CI (self-hosted) / npu-amd-linux (push) Has been cancelled
CI (windows) / windows-msys2 (Release, clang, clang-x86_64, CLANG64) (push) Has been cancelled
CI (windows) / windows-msys2 (Release, gcc, ucrt-x86_64, UCRT64) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-cuda.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-cuda]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-intel.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-intel]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-musa.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-musa]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-vulkan]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-vulkan-arm64]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-arm64]) (push) Has been cancelled
Mark inactive issues stale / mark-issues (push) Failing after 1s
Some checks failed
Bindings Tests (Go) / ubuntu-22 (push) Failing after 31s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/ppc64le, icx, icpx, ON) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/ppc64le, icx, icpx, ON) (push) Failing after 3s
CI (vad) / vad (push) Failing after 3s
CI (wasm) / emscripten (Release) (push) Failing after 4s
Bindings Tests (Ruby) / ubuntu-22 (push) Failing after 37s
CI (android) / android (push) Failing after 16s
CI (android) / android_java (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Release) (push) Failing after 2s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Release) (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-low-perf (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-high-perf (push) Failing after 2s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Release) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Release) (push) Failing after 4s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Release) (push) Failing after 3s
CI (quantize) / quantize (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (ADDRESS) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (THREAD) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (UNDEFINED) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm64, icx, icpx, ON) (push) Failing after 3s
Examples WASM / deploy-wasm-github-pages (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (20.x) (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (22.x) (push) Failing after 2s
CI (coreml) / coreml-base-en (push) Has been cancelled
CI (self-hosted) / gpu-vulkan (push) Has been cancelled
CI (self-hosted) / npu-amd-windows (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm2 (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Debug) (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Release) (push) Has been cancelled
CI (self-hosted) / gpu-cuda (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-low-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf-sve (push) Has been cancelled
CI (freebsd) / freeBSD-latest (push) Has been cancelled
CI (self-hosted) / gpu-metal (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Debug) (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Release) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=iOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=macOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=tvOS) (push) Has been cancelled
CI (self-hosted) / npu-amd-linux (push) Has been cancelled
CI (windows) / windows-msys2 (Release, clang, clang-x86_64, CLANG64) (push) Has been cancelled
CI (windows) / windows-msys2 (Release, gcc, ucrt-x86_64, UCRT64) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-cuda.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-cuda]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-intel.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-intel]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-musa.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-musa]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-vulkan]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-vulkan-arm64]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-arm64]) (push) Has been cancelled
Mark inactive issues stale / mark-issues (push) Failing after 1s
This commit is contained in:
3
bindings/ruby/.document
Normal file
3
bindings/ruby/.document
Normal file
@@ -0,0 +1,3 @@
|
||||
README.md
|
||||
LICENSE
|
||||
sig
|
||||
9
bindings/ruby/.gitignore
vendored
Normal file
9
bindings/ruby/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
LICENSE
|
||||
pkg/
|
||||
lib/whisper.*
|
||||
ext/examples/
|
||||
ext/ggml/
|
||||
ext/include/
|
||||
ext/scripts/
|
||||
ext/src/
|
||||
test/fixtures/
|
||||
2
bindings/ruby/.rdoc_options
Normal file
2
bindings/ruby/.rdoc_options
Normal file
@@ -0,0 +1,2 @@
|
||||
title: whispercpp
|
||||
main_page: README.md
|
||||
169
bindings/ruby/CHANGELOG.md
Normal file
169
bindings/ruby/CHANGELOG.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Changelog
|
||||
|
||||
## [1.3.9] - Unreleased
|
||||
|
||||
Bundled whisper.cpp: `1.9.3`
|
||||
|
||||
- Fixed test fixture JFKReader's format string.
|
||||
- Add #free method to `Whisper::Context`, `Whisper::VAD::Context` and `Whisper::Parakeet::Context`.
|
||||
- Checked MemoryView format more strictly.
|
||||
|
||||
## [1.3.8] - 2026-07-28
|
||||
|
||||
Bundled whisper.cpp: `1.9.1`
|
||||
|
||||
- Added `Whisper::Context#full_n_vad_segments`,
|
||||
`#full_get_vad_segment_t0`, and `#full_get_vad_segment_t1` for reading the
|
||||
speech segments produced by VAD-enabled transcription.
|
||||
- Changed standalone VAD processing to release Ruby's Global VM Lock.
|
||||
- Fixed VAD parameter ownership and type handling, compatibility with older
|
||||
CMake option syntax, and model cache lookup behavior.
|
||||
([#3931](https://github.com/ggml-org/whisper.cpp/pull/3931))
|
||||
|
||||
## [1.3.7] - 2026-06-17
|
||||
|
||||
Bundled whisper.cpp: `1.9.0`
|
||||
|
||||
- Added Ruby bindings for NVIDIA Parakeet, including models, contexts,
|
||||
parameters, segments, tokens, callbacks, and shared output helpers.
|
||||
([#3885](https://github.com/ggml-org/whisper.cpp/pull/3885))
|
||||
- Added Windows build support and support for additional Ruby MemoryView
|
||||
inputs, and changed transcription to release Ruby's Global VM Lock.
|
||||
([#3775](https://github.com/ggml-org/whisper.cpp/pull/3775))
|
||||
- Fixed dangling pointers, a memory leak, and a segmentation fault during
|
||||
parallel transcription.
|
||||
([#3715](https://github.com/ggml-org/whisper.cpp/pull/3715))
|
||||
- Raised the minimum supported Ruby version from 3.1 to 3.3.
|
||||
|
||||
## [1.3.6] - 2026-03-19
|
||||
|
||||
Bundled whisper.cpp: `1.8.4`
|
||||
|
||||
- Added the low-level `Whisper::VAD::Context#segments_from_samples` API and
|
||||
accepted `Pathname`-compatible model and audio paths.
|
||||
([#3633](https://github.com/ggml-org/whisper.cpp/pull/3633))
|
||||
- Added `Whisper::Context::Params` for configuring context initialization and
|
||||
fixed token memory ownership.
|
||||
([#3647](https://github.com/ggml-org/whisper.cpp/pull/3647))
|
||||
- Added a missing context null check.
|
||||
([#3689](https://github.com/ggml-org/whisper.cpp/pull/3689))
|
||||
|
||||
## [1.3.5] - 2026-01-16
|
||||
|
||||
Bundled whisper.cpp: `1.8.3`
|
||||
|
||||
- Added standalone voice activity detection through
|
||||
`Whisper::VAD::Context`, `Whisper::VAD::Segments`, and
|
||||
`Whisper::VAD::Segment`.
|
||||
([#3518](https://github.com/ggml-org/whisper.cpp/pull/3518))
|
||||
- Updated the packaged Silero VAD model support to v6.2.0.
|
||||
([#3524](https://github.com/ggml-org/whisper.cpp/pull/3524))
|
||||
- Added `Whisper::Token` and corrected model URI handling.
|
||||
([#3575](https://github.com/ggml-org/whisper.cpp/pull/3575))
|
||||
- Fixed segmentation faults in token and segment access.
|
||||
([#3591](https://github.com/ggml-org/whisper.cpp/pull/3591))
|
||||
|
||||
## [1.3.4] - 2025-10-08
|
||||
|
||||
Bundled whisper.cpp: `1.8.0`
|
||||
|
||||
- Added `Whisper::Params#max_len`.
|
||||
([#3365](https://github.com/ggml-org/whisper.cpp/pull/3365))
|
||||
- Fixed negative model-download padding values and made installation tests less
|
||||
sensitive to output formatting.
|
||||
([#3389](https://github.com/ggml-org/whisper.cpp/pull/3389),
|
||||
[#3448](https://github.com/ggml-org/whisper.cpp/pull/3448))
|
||||
|
||||
## [1.3.3] - 2025-07-12
|
||||
|
||||
Bundled whisper.cpp: `1.7.6`
|
||||
|
||||
- Added Core ML build support.
|
||||
([#3214](https://github.com/ggml-org/whisper.cpp/pull/3214))
|
||||
- Added parallel transcription through `Whisper::Context#full_parallel`.
|
||||
([#3222](https://github.com/ggml-org/whisper.cpp/pull/3222))
|
||||
- Added higher-level segment iteration and text, SRT, VTT, CSV, and JSON output
|
||||
helpers.
|
||||
([#3237](https://github.com/ggml-org/whisper.cpp/pull/3237))
|
||||
- Added `Whisper::VERSION`.
|
||||
([#3292](https://github.com/ggml-org/whisper.cpp/pull/3292))
|
||||
- Improved install-time build option handling and Apple framework linkage.
|
||||
([#3206](https://github.com/ggml-org/whisper.cpp/pull/3206),
|
||||
[#3270](https://github.com/ggml-org/whisper.cpp/pull/3270))
|
||||
|
||||
## [1.3.2] - 2025-05-28
|
||||
|
||||
Bundled whisper.cpp: `1.7.5`
|
||||
|
||||
- Added VAD-enabled transcription and `Whisper::VAD::Params`.
|
||||
([#3197](https://github.com/ggml-org/whisper.cpp/pull/3197))
|
||||
- Moved native extension builds to CMake and added install-time CMake build
|
||||
options.
|
||||
([#3043](https://github.com/ggml-org/whisper.cpp/pull/3043),
|
||||
[#3056](https://github.com/ggml-org/whisper.cpp/pull/3056))
|
||||
- Added encoder-begin callback APIs and refined model download caching.
|
||||
([#3076](https://github.com/ggml-org/whisper.cpp/pull/3076),
|
||||
[#3109](https://github.com/ggml-org/whisper.cpp/pull/3109))
|
||||
- Expanded context initialization and segment retrieval APIs.
|
||||
([#2749](https://github.com/ggml-org/whisper.cpp/pull/2749))
|
||||
|
||||
## [1.3.1] - 2024-12-20
|
||||
|
||||
Bundled whisper.cpp: `1.7.3`
|
||||
|
||||
- Added Metal build support and new-segment callbacks.
|
||||
([#2516](https://github.com/ggml-org/whisper.cpp/pull/2516),
|
||||
[#2506](https://github.com/ggml-org/whisper.cpp/pull/2506))
|
||||
- Expanded context, model, parameter, and segment APIs, including low-level
|
||||
transcription methods.
|
||||
([#2518](https://github.com/ggml-org/whisper.cpp/pull/2518),
|
||||
[#2551](https://github.com/ggml-org/whisper.cpp/pull/2551),
|
||||
[#2585](https://github.com/ggml-org/whisper.cpp/pull/2585))
|
||||
- Added model download support and `no_speech_thold`.
|
||||
([#2617](https://github.com/ggml-org/whisper.cpp/pull/2617),
|
||||
[#2641](https://github.com/ggml-org/whisper.cpp/pull/2641))
|
||||
|
||||
## [1.3.0] - 2024-05-16
|
||||
|
||||
Bundled whisper.cpp: `1.5.5`-era source snapshot
|
||||
|
||||
- Refreshed the packaged whisper.cpp and ggml sources for the newer backend
|
||||
architecture.
|
||||
- Updated the native extension for upstream context initialization changes and
|
||||
fixed builds against the newer source layout.
|
||||
- Expanded the Rake packaging and test tasks used to produce the gem.
|
||||
|
||||
## [1.2.0.2] - 2023-02-27
|
||||
|
||||
Bundled whisper.cpp: `1.2.0`-era source snapshot
|
||||
|
||||
- Added Rake build, clean, package, and test tasks.
|
||||
- Changed long-running transcription to run without Ruby's Global VM Lock and
|
||||
added interruption handling.
|
||||
|
||||
## [1.2.0.1] - 2023-02-25
|
||||
|
||||
Bundled whisper.cpp: `1.2.0`-era source snapshot
|
||||
|
||||
- Fixed gem packaging so `LICENSE` and `README.md` were included at valid
|
||||
in-package paths.
|
||||
|
||||
## [1.2.0] - 2023-02-25
|
||||
|
||||
Bundled whisper.cpp: `1.2.0`-era source snapshot
|
||||
|
||||
- First published `whispercpp` Ruby gem, based on the initial Ruby binding.
|
||||
([#500](https://github.com/ggml-org/whisper.cpp/pull/500))
|
||||
|
||||
[1.3.8]: https://rubygems.org/gems/whispercpp/versions/1.3.8
|
||||
[1.3.7]: https://rubygems.org/gems/whispercpp/versions/1.3.7
|
||||
[1.3.6]: https://rubygems.org/gems/whispercpp/versions/1.3.6
|
||||
[1.3.5]: https://rubygems.org/gems/whispercpp/versions/1.3.5
|
||||
[1.3.4]: https://rubygems.org/gems/whispercpp/versions/1.3.4
|
||||
[1.3.3]: https://rubygems.org/gems/whispercpp/versions/1.3.3
|
||||
[1.3.2]: https://rubygems.org/gems/whispercpp/versions/1.3.2
|
||||
[1.3.1]: https://rubygems.org/gems/whispercpp/versions/1.3.1
|
||||
[1.3.0]: https://rubygems.org/gems/whispercpp/versions/1.3.0
|
||||
[1.2.0.2]: https://rubygems.org/gems/whispercpp/versions/1.2.0.2
|
||||
[1.2.0.1]: https://rubygems.org/gems/whispercpp/versions/1.2.0.1
|
||||
[1.2.0]: https://rubygems.org/gems/whispercpp/versions/1.2.0
|
||||
497
bindings/ruby/README.md
Normal file
497
bindings/ruby/README.md
Normal file
@@ -0,0 +1,497 @@
|
||||
whispercpp
|
||||
==========
|
||||
|
||||

|
||||
|
||||
Ruby bindings for [whisper.cpp][], an interface of automatic speech recognition model.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```ruby
|
||||
require "whisper"
|
||||
|
||||
whisper = Whisper::Context.new("base")
|
||||
|
||||
params = Whisper::Params.new(
|
||||
language: "en",
|
||||
offset: 10_000,
|
||||
duration: 60_000,
|
||||
max_text_tokens: 300,
|
||||
translate: true,
|
||||
print_timestamps: false,
|
||||
initial_prompt: "Initial prompt here.",
|
||||
carry_initial_prompt: true
|
||||
)
|
||||
|
||||
whisper.transcribe("path/to/audio.wav", params) do |whole_text|
|
||||
puts whole_text
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
### Preparing model ###
|
||||
|
||||
Some models are prepared up-front:
|
||||
|
||||
You also can use shorthand for pre-converted models:
|
||||
|
||||
```ruby
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
```
|
||||
|
||||
You can see the list of prepared model names by `Whisper::Model.pre_converted_models.keys`:
|
||||
|
||||
```ruby
|
||||
puts Whisper::Model.pre_converted_models.keys
|
||||
# tiny
|
||||
# tiny.en
|
||||
# tiny-q5_1
|
||||
# tiny.en-q5_1
|
||||
# tiny-q8_0
|
||||
# base
|
||||
# base.en
|
||||
# base-q5_1
|
||||
# base.en-q5_1
|
||||
# base-q8_0
|
||||
# :
|
||||
# :
|
||||
```
|
||||
|
||||
You can also retrieve each model:
|
||||
|
||||
```ruby
|
||||
base_en = Whisper::Model.pre_converted_models["base.en"]
|
||||
whisper = Whisper::Context.new(base_en)
|
||||
```
|
||||
|
||||
At first time you use a model, it is downloaded automatically. After that, downloaded cached file is used. To clear cache, call `#clear_cache`:
|
||||
|
||||
```ruby
|
||||
Whisper::Model.pre_converted_models["base"].clear_cache
|
||||
```
|
||||
|
||||
You can also use local model files you prepared:
|
||||
|
||||
```ruby
|
||||
whisper = Whisper::Context.new("path/to/your/model.bin")
|
||||
```
|
||||
|
||||
Or, you can download model files:
|
||||
|
||||
```ruby
|
||||
whisper = Whisper::Context.new("https://example.net/uri/of/your/model.bin")
|
||||
# Or
|
||||
uri = URI("https://example.net/uri/of/your/model.bin")
|
||||
whisper = Whisper::Context.new(uri)
|
||||
```
|
||||
|
||||
See [models][] page for details.
|
||||
|
||||
### Preparing audio file ###
|
||||
|
||||
Currently, whisper.cpp accepts only 16-bit WAV files.
|
||||
|
||||
### Voice Activity Detection (VAD) ###
|
||||
|
||||
Support for Voice Activity Detection (VAD) can be enabled by setting `Whisper::Params`'s `vad` argument to `true` and specifying VAD model:
|
||||
|
||||
```ruby
|
||||
Whisper::Params.new(
|
||||
vad: true,
|
||||
vad_model_path: "silero-v6.2.0",
|
||||
# other arguments...
|
||||
)
|
||||
```
|
||||
|
||||
When you pass the model name (`"silero-v6.2.0"`) or URI (`https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v6.2.0.bin`), it will be downloaded automatically.
|
||||
Currently, "silero-v6.2.0" is registered as pre-converted model like ASR models. You also specify file path or URI of model.
|
||||
|
||||
If you need configure VAD behavior, pass params for that:
|
||||
|
||||
```ruby
|
||||
Whisper::Params.new(
|
||||
vad: true,
|
||||
vad_model_path: "silero-v6.2.0",
|
||||
vad_params: Whisper::VAD::Params.new(
|
||||
threshold: 1.0, # defaults to 0.5
|
||||
min_speech_duration_ms: 500, # defaults to 250
|
||||
min_silence_duration_ms: 200, # defaults to 100
|
||||
max_speech_duration_s: 30000.0, # default is FLT_MAX,
|
||||
speech_pad_ms: 50, # defaults to 30
|
||||
samples_overlap: 0.5 # defaults to 0.1
|
||||
),
|
||||
# other arguments...
|
||||
)
|
||||
```
|
||||
|
||||
For details on VAD, see [whisper.cpp's README](https://github.com/ggml-org/whisper.cpp?tab=readme-ov-file#voice-activity-detection-vad).
|
||||
|
||||
### Output ###
|
||||
|
||||
whispercpp supports SRT and WebVTT output:
|
||||
|
||||
```ruby
|
||||
puts whisper.transcribe("path/to/audio.wav", Whisper::Params.new).to_webvtt
|
||||
# =>
|
||||
WEBVTT
|
||||
|
||||
1
|
||||
00:00:00.000 --> 00:00:03.860
|
||||
My thought I have nobody by a beauty and will as you poured.
|
||||
|
||||
2
|
||||
00:00:03.860 --> 00:00:09.840
|
||||
Mr. Rochester is sub in that so-don't find simplest, and devoted about, to let might in
|
||||
|
||||
3
|
||||
00:00:09.840 --> 00:00:09.940
|
||||
a
|
||||
|
||||
```
|
||||
|
||||
You may call `#to_srt`, too
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install the gem and add to the application's Gemfile by executing:
|
||||
|
||||
$ bundle add whispercpp
|
||||
|
||||
If bundler is not being used to manage dependencies, install the gem by executing:
|
||||
|
||||
$ gem install whispercpp
|
||||
|
||||
You can pass build options for whisper.cpp, for instance:
|
||||
|
||||
$ bundle config build.whispercpp --enable-ggml-cuda
|
||||
|
||||
or,
|
||||
|
||||
$ gem install whispercpp -- --enable-ggml-cuda
|
||||
|
||||
See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/README.md) for available options. You need convert options present in the README to Ruby-style options, for example:
|
||||
|
||||
Boolean options:
|
||||
|
||||
* `-DGGML_BLAS=1` -> `--enable-ggml-blas`
|
||||
* `-DWHISPER_COREML=OFF` -> `--disable-whisper-coreml`
|
||||
|
||||
Argument options:
|
||||
|
||||
* `-DGGML_CUDA_COMPRESSION_MODE=size` -> `--ggml-cuda-compression-mode=size`
|
||||
|
||||
Combination:
|
||||
|
||||
* `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake-cuda-architectures="86"`
|
||||
|
||||
For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.
|
||||
For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
### Transcription ###
|
||||
|
||||
By default, `Whisper::Context#transcribe` works in a single thread. You can make it work in parallel by passing `n_processors` option:
|
||||
|
||||
```ruby
|
||||
whisper.transcribe("path/to/audio.wav", params, n_processors: Etc.nprocessors)
|
||||
```
|
||||
|
||||
Note that transcription occasionally might be low accuracy when it works in parallel.
|
||||
|
||||
If n_processors is greater than 1, you cannot set some callbacks including encoder_begin_callback, abort_callback. Also, progress_callback is ignored even if it's set.
|
||||
|
||||
### Segments ###
|
||||
|
||||
Once `Whisper::Context#transcribe` called, you can retrieve segments by `#each_segment`:
|
||||
|
||||
```ruby
|
||||
def format_time(time_ms)
|
||||
sec, decimal_part = time_ms.divmod(1000)
|
||||
min, sec = sec.divmod(60)
|
||||
hour, min = min.divmod(60)
|
||||
"%02d:%02d:%02d.%03d" % [hour, min, sec, decimal_part]
|
||||
end
|
||||
|
||||
whisper
|
||||
.transcribe("path/to/audio.wav", params)
|
||||
.each_segment.with_index do |segment, index|
|
||||
line = "[%{nth}: %{st} --> %{ed}] %{text}" % {
|
||||
nth: index + 1,
|
||||
st: format_time(segment.start_time),
|
||||
ed: format_time(segment.end_time),
|
||||
text: segment.text
|
||||
}
|
||||
line << " (speaker turned)" if segment.speaker_turn_next?
|
||||
puts line
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
You can also add hook to params called on new segment:
|
||||
|
||||
```ruby
|
||||
# Add hook before calling #transcribe
|
||||
params.on_new_segment do |segment|
|
||||
line = "[%{st} --> %{ed}] %{text}" % {
|
||||
st: format_time(segment.start_time),
|
||||
ed: format_time(segment.end_time),
|
||||
text: segment.text
|
||||
}
|
||||
line << " (speaker turned)" if segment.speaker_turn_next?
|
||||
puts line
|
||||
end
|
||||
|
||||
whisper.transcribe("path/to/audio.wav", params)
|
||||
|
||||
```
|
||||
|
||||
### Tokens ###
|
||||
|
||||
Each segment has tokens.
|
||||
|
||||
To enable token timestamps, you need to set `Whisper::Params#token_timestamps = true`. Then, retrieve tokens from segments using `Whisper::Segment#each_token`.
|
||||
|
||||
```ruby
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new(token_timestamps: true)
|
||||
whisper
|
||||
.transcribe("path/to/audio.wav", params)
|
||||
.each_segment do |segment|
|
||||
segment.each_token do |token|
|
||||
token => {start_time:, end_time:, text:, probability:}
|
||||
st = "%05.2fs" % (start_time / 1000.0)
|
||||
et = "%05.2fs" % (end_time / 1000.0)
|
||||
prob = "%.1f%%" % (probability * 100)
|
||||
puts "[#{st} --> #{et}] #{text} (#{prob})"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```
|
||||
[00.00s --> 00.00s] [_BEG_] (84.2%)
|
||||
[00.32s --> 00.37s] And (71.2%)
|
||||
[00.37s --> 00.53s] so (98.5%)
|
||||
[00.69s --> 00.85s] my (70.7%)
|
||||
[00.85s --> 01.59s] fellow (99.5%)
|
||||
[01.59s --> 02.10s] Americans (90.1%)
|
||||
[02.85s --> 03.30s] , (28.4%)
|
||||
[03.30s --> 04.14s] ask (79.8%)
|
||||
[04.14s --> 04.28s] not (78.9%)
|
||||
[05.03s --> 05.35s] what (93.3%)
|
||||
[05.41s --> 05.74s] your (98.8%)
|
||||
[05.74s --> 06.41s] country (99.6%)
|
||||
[06.41s --> 06.74s] can (97.7%)
|
||||
[06.74s --> 06.92s] do (99.0%)
|
||||
[07.00s --> 07.00s] for (95.8%)
|
||||
[07.01s --> 07.52s] you (98.5%)
|
||||
[07.81s --> 08.05s] , (49.3%)
|
||||
[08.19s --> 08.37s] ask (65.6%)
|
||||
[08.37s --> 08.75s] what (98.8%)
|
||||
[08.91s --> 09.04s] you (98.2%)
|
||||
[09.04s --> 09.32s] can (96.9%)
|
||||
[09.32s --> 09.38s] do (90.3%)
|
||||
[09.44s --> 09.76s] for (91.8%)
|
||||
[09.76s --> 09.99s] your (98.2%)
|
||||
[10.02s --> 10.36s] country (99.6%)
|
||||
[10.51s --> 10.99s] . (87.0%)
|
||||
[11.00s --> 11.00s] [_TT_550] (7.6%)
|
||||
```
|
||||
|
||||
### Models ###
|
||||
|
||||
You can see model information:
|
||||
|
||||
```ruby
|
||||
whisper = Whisper::Context.new("base")
|
||||
model = whisper.model
|
||||
|
||||
model.n_vocab # => 51864
|
||||
model.n_audio_ctx # => 1500
|
||||
model.n_audio_state # => 512
|
||||
model.n_audio_head # => 8
|
||||
model.n_audio_layer # => 6
|
||||
model.n_text_ctx # => 448
|
||||
model.n_text_state # => 512
|
||||
model.n_text_head # => 8
|
||||
model.n_text_layer # => 6
|
||||
model.n_mels # => 80
|
||||
model.ftype # => 1
|
||||
model.type # => "base"
|
||||
|
||||
```
|
||||
|
||||
### Logging ###
|
||||
|
||||
You can set log callback:
|
||||
|
||||
```ruby
|
||||
prefix = "[MyApp] "
|
||||
log_callback = ->(level, buffer, user_data) {
|
||||
case level
|
||||
when Whisper::LOG_LEVEL_NONE
|
||||
puts "#{user_data}none: #{buffer}"
|
||||
when Whisper::LOG_LEVEL_INFO
|
||||
puts "#{user_data}info: #{buffer}"
|
||||
when Whisper::LOG_LEVEL_WARN
|
||||
puts "#{user_data}warn: #{buffer}"
|
||||
when Whisper::LOG_LEVEL_ERROR
|
||||
puts "#{user_data}error: #{buffer}"
|
||||
when Whisper::LOG_LEVEL_DEBUG
|
||||
puts "#{user_data}debug: #{buffer}"
|
||||
when Whisper::LOG_LEVEL_CONT
|
||||
puts "#{user_data}same to previous: #{buffer}"
|
||||
end
|
||||
}
|
||||
Whisper.log_set log_callback, prefix
|
||||
```
|
||||
|
||||
Using this feature, you are also able to suppress log:
|
||||
|
||||
```ruby
|
||||
Whisper.log_set ->(level, buffer, user_data) {
|
||||
# do nothing
|
||||
}, nil
|
||||
Whisper::Context.new("base")
|
||||
```
|
||||
|
||||
### Low-level API to transcribe ###
|
||||
|
||||
You can also call `Whisper::Context#full` and `#full_parallel` with a Ruby array as samples. Although `#transcribe` with audio file path is recommended because it extracts PCM samples in C++ and is fast, `#full` and `#full_parallel` give you flexibility. Unlike `#transcribe`, these methods requires 16,000 Hz, 32-bit float audio.
|
||||
|
||||
```ruby
|
||||
require "whisper"
|
||||
require "wavefile"
|
||||
|
||||
reader = WaveFile::Reader.new("path/to/audio.wav", WaveFile::Format.new(:mono, :float, 16000))
|
||||
samples = reader.enum_for(:each_buffer).map(&:samples).flatten
|
||||
|
||||
whisper = Whisper::Context.new("base")
|
||||
whisper
|
||||
.full(Whisper::Params.new, samples)
|
||||
.each_segment do |segment|
|
||||
puts segment.text
|
||||
end
|
||||
```
|
||||
|
||||
The second argument `samples` may be an array, an object with `length` and `each` method, or a MemoryView.
|
||||
|
||||
If you can prepare audio data as C array and export it as a MemoryView, whispercpp accepts and works with it with zero copy.
|
||||
|
||||
```ruby
|
||||
require "torchaudio"
|
||||
require "ndav/torch/tensor"
|
||||
require "whisper"
|
||||
|
||||
waveform, sample_rate = TorchAudio.load("test/fixtures/jfk.wav")
|
||||
# Convert Torch::Tensor to NDAV
|
||||
samples = waveform.squeeze.to_ndav
|
||||
|
||||
whisper = Whisper::Context.new("base")
|
||||
whisper
|
||||
# NDAV exports MemoryView
|
||||
.full(Whisper::Params.new, samples)
|
||||
```
|
||||
|
||||
### Parakeet ###
|
||||
|
||||
whispercpp gem now supports NVIDIA's ASR model Parakeet.
|
||||
|
||||
If you want to use Parakeet instead of Whisper, the API should feel familiar.
|
||||
In most cases, replace `Whisper::Context` and `Whisper::Params` with `Whisper::Parakeet::Context` and `Whisper::Parakeet::Params`, then use `#transcribe`, `#full`, `#each_segment`, and `#each_token` in the same way.
|
||||
|
||||
```ruby
|
||||
require "whisper"
|
||||
|
||||
# It's useful to assign Whisper::Parakeet to top-level Parakeet constant unless you use Parakeet gem.
|
||||
Parakeet = Whisper::Parakeet
|
||||
|
||||
parakeet = Parakeet::Context.new("path/to/model")
|
||||
|
||||
params = Parakeet::Params.new(
|
||||
no_context: true
|
||||
)
|
||||
|
||||
parakeet
|
||||
.transcribe("path/to/audio.wav", params)
|
||||
.each_segment do |segment|
|
||||
puts "[#{segment.start_time} --> #{segment.end_time}] #{segment.text}"
|
||||
end
|
||||
```
|
||||
|
||||
The main differences are:
|
||||
|
||||
* Namespace is `Whisper::Parakeet`.
|
||||
* Parakeet also supports `on_new_token` / `new_token_callback` in addition to segment and progress callbacks.
|
||||
|
||||
Custom context params
|
||||
---------------------
|
||||
|
||||
You can use customize `Whisper::Context`'s behavior using `Whisper::Context::Params`.
|
||||
|
||||
```ruby
|
||||
context_params = Whisper::Context::Params.new(
|
||||
use_gpu: false,
|
||||
flash_attn: false,
|
||||
# etc
|
||||
)
|
||||
whisper = Whisper::Context.new("base", context_params)
|
||||
```
|
||||
|
||||
Using VAD separately from ASR
|
||||
-----------------------------
|
||||
|
||||
VAD feature itself is useful. You can use it separately from ASR:
|
||||
|
||||
```ruby
|
||||
vad = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
vad
|
||||
.detect("path/to/audio.wav", Whisper::VAD::Params.new)
|
||||
.each.with_index do |segment, index|
|
||||
segment => {start_time: st, end_time: ed} # `Segment` responds to `#deconstruct_keys`
|
||||
|
||||
puts "[%{nth}: %{st} --> %{ed}]" % {nth: index + 1, st:, ed:}
|
||||
end
|
||||
```
|
||||
|
||||
You may also low level API `Whisper::VAD::Context#segments_from_samples` as such `Whisper::Context#full`:
|
||||
|
||||
```ruby
|
||||
# Ruby Array
|
||||
reader = WaveFile::Reader.new("path/to/audio.wav", WaveFile::Format.new(:mono, :float, 16000))
|
||||
samples = reader.enum_for(:each_buffer).map(&:samples).flatten
|
||||
|
||||
# Or, object which exports MemoryView
|
||||
waveform, sample_rate = TorchAudio.load("test/fixtures/jfk.wav")
|
||||
samples = waveform.squeeze.numo.to_arrow.to_arrow_array
|
||||
|
||||
segments = vad.segments_from_samples(Whisper::VAD::Params.new, samples)
|
||||
```
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
% git clone https://github.com/ggml-org/whisper.cpp.git
|
||||
% cd whisper.cpp/bindings/ruby
|
||||
% rake test
|
||||
|
||||
First call of `rake test` builds an extension and downloads a model for testing. After that, you add tests in `test` directory and modify `ext/*.{h,c,cpp}`.
|
||||
|
||||
If something seems wrong on build, running `rake clean` solves some cases.
|
||||
|
||||
### Need help ###
|
||||
|
||||
* Windows support
|
||||
* Check of compilation with various hardware
|
||||
* Refinement of C/C++ code, especially memory management
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The same to [whisper.cpp][].
|
||||
|
||||
[whisper.cpp]: https://github.com/ggml-org/whisper.cpp
|
||||
[models]: https://github.com/ggml-org/whisper.cpp/tree/master/models
|
||||
111
bindings/ruby/Rakefile
Normal file
111
bindings/ruby/Rakefile
Normal file
@@ -0,0 +1,111 @@
|
||||
require 'rake/clean'
|
||||
require "bundler/gem_tasks"
|
||||
require "rake/testtask"
|
||||
require_relative "extsources"
|
||||
|
||||
SOURCES_DIR = "ext/sources"
|
||||
|
||||
SOURCES = FileList[]
|
||||
|
||||
EXTSOURCES.each do |src|
|
||||
basename = src.pathmap("%f")
|
||||
dest = basename == "LICENSE" ? basename
|
||||
: src.pathmap("%{\\.\\./\\.\\.,#{SOURCES_DIR}}p")
|
||||
.pathmap("%{\\.\\./javascript,#{SOURCES_DIR}/bindings/javascript}p")
|
||||
dir = dest.pathmap("%d")
|
||||
file src
|
||||
directory dir
|
||||
file dest => [src, dir] do |t|
|
||||
copy t.source, t.name
|
||||
end
|
||||
SOURCES.include dest
|
||||
end
|
||||
|
||||
CLEAN.include SOURCES
|
||||
|
||||
SRC = FileList["ext/*.{c,cpp,h}"]
|
||||
|
||||
task build: SOURCES
|
||||
|
||||
directory "pkg"
|
||||
CLOBBER.include "pkg"
|
||||
|
||||
LIB_NAME = "whisper".ext(RbConfig::CONFIG["DLEXT"])
|
||||
SO_FILE = File.join("ext", LIB_NAME)
|
||||
LIB_FILE = File.join("lib", LIB_NAME)
|
||||
|
||||
file "ext/Makefile" => SRC + SOURCES + FileList["ext/*.rb"] do |t|
|
||||
chdir "ext" do
|
||||
ruby "extconf.rb"
|
||||
end
|
||||
end
|
||||
if File.exist? "ext/Makefile"
|
||||
task :make_clean do
|
||||
cd "ext" do
|
||||
sh "make", "clean"
|
||||
end
|
||||
end
|
||||
task clean: :make_clean
|
||||
task :make_distclean do
|
||||
cd "ext" do
|
||||
sh "make", "distclean"
|
||||
end
|
||||
end
|
||||
task clobber: :make_distclean
|
||||
end
|
||||
|
||||
file SO_FILE => "ext/Makefile" do |t|
|
||||
chdir "ext" do
|
||||
sh "make"
|
||||
end
|
||||
end
|
||||
CLEAN.include SO_FILE
|
||||
|
||||
directory "lib"
|
||||
file LIB_FILE => [SO_FILE, "lib"] do |t|
|
||||
copy t.source, t.name
|
||||
end
|
||||
CLEAN.include LIB_FILE
|
||||
|
||||
Rake::TestTask.new
|
||||
|
||||
TEST_FIXTURE_AUDIO = "test/fixtures/jfk.wav"
|
||||
TEST_FIXTURE_AUDIO_SRC = File.expand_path(File.join(__dir__, "..", "..", "samples", "jfk.wav"))
|
||||
TEST_FIXTURE_AUDIO_DIR = TEST_FIXTURE_AUDIO.pathmap("%d")
|
||||
directory TEST_FIXTURE_AUDIO_DIR
|
||||
if File.exist? TEST_FIXTURE_AUDIO_SRC
|
||||
file TEST_FIXTURE_AUDIO => [TEST_FIXTURE_AUDIO_SRC, TEST_FIXTURE_AUDIO_DIR] do |t|
|
||||
symlink t.source, t.name
|
||||
end
|
||||
else
|
||||
require "open-uri"
|
||||
file TEST_FIXTURE_AUDIO => TEST_FIXTURE_AUDIO_DIR do |t|
|
||||
File.write t.name, URI("https://github.com/ggml-org/whisper.cpp/raw/refs/heads/master/samples/jfk.wav").read
|
||||
end
|
||||
end
|
||||
|
||||
TEST_PARAKEET_MODEL = "test/fixtures/for-tests-ggml-parakeet-tdt.bin"
|
||||
TEST_PARAKEET_MODEL_SRC = File.expand_path(File.join(__dir__, "..", "..", "models", "for-tests-ggml-parakeet-tdt.bin"))
|
||||
TEST_PARAKEET_MODEL_DIR = TEST_PARAKEET_MODEL.pathmap("%d")
|
||||
directory TEST_PARAKEET_MODEL_DIR
|
||||
if File.exist? TEST_PARAKEET_MODEL_SRC
|
||||
file TEST_PARAKEET_MODEL => [TEST_PARAKEET_MODEL_SRC, TEST_PARAKEET_MODEL_DIR] do |t|
|
||||
symlink t.source, t.name
|
||||
end
|
||||
else
|
||||
require "open-uri"
|
||||
file TEST_PARAKEET_MODEL => TEST_PARAKEET_MODEL_DIR do |t|
|
||||
File.write t.name, URI("https://github.com/ggml-org/whisper.cpp/raw/refs/heads/master/models/for-tests-ggml-parakeet-tdt.bin").read
|
||||
end
|
||||
end
|
||||
|
||||
TEST_MEMORY_VIEW = "test/jfk_reader/jfk_reader.#{RbConfig::CONFIG['DLEXT']}"
|
||||
file TEST_MEMORY_VIEW => "test/jfk_reader/jfk_reader.c" do |t|
|
||||
chdir "test/jfk_reader" do
|
||||
ruby "extconf.rb"
|
||||
sh "make"
|
||||
end
|
||||
end
|
||||
CLEAN.include TEST_MEMORY_VIEW
|
||||
|
||||
task test: [LIB_FILE, TEST_MEMORY_VIEW, TEST_FIXTURE_AUDIO, TEST_PARAKEET_MODEL]
|
||||
9
bindings/ruby/ext/.gitignore
vendored
Normal file
9
bindings/ruby/ext/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Makefile
|
||||
whisper.so
|
||||
whisper.bundle
|
||||
whisper.dll
|
||||
*.o
|
||||
*.a
|
||||
sources/*
|
||||
!sources/CMakeGraphVizOptions.cmake
|
||||
mkmf.log
|
||||
79
bindings/ruby/ext/dependencies.rb
Normal file
79
bindings/ruby/ext/dependencies.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
require "tsort"
|
||||
|
||||
class Dependencies
|
||||
include TSort
|
||||
|
||||
def initialize(cmake, options)
|
||||
@cmake = cmake
|
||||
@options = options
|
||||
@static_lib_shape = nil
|
||||
@nodes = {}
|
||||
@graph = Hash.new {|h, k| h[k] = []}
|
||||
|
||||
generate_dot
|
||||
parse_dot
|
||||
end
|
||||
|
||||
def libs
|
||||
tsort.filter_map {|node|
|
||||
label, shape = @nodes[node]
|
||||
if shape == @static_lib_shape
|
||||
label.gsub(/\\n\([^)]+\)/, '')
|
||||
else
|
||||
nil
|
||||
end
|
||||
}.reverse.collect {|lib| "#{prefix(lib)}#{lib}.#{RbConfig::CONFIG['LIBEXT']}"}
|
||||
end
|
||||
|
||||
def to_s
|
||||
libs.join(" ")
|
||||
end
|
||||
|
||||
def local_libs
|
||||
to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dot_path
|
||||
File.join(__dir__, "build", "whisper.cpp.dot")
|
||||
end
|
||||
|
||||
def generate_dot
|
||||
system @cmake, "-S", "sources", "-B", "build", *@options.graphviz_cmake_args, "--graphviz", dot_path, *@options, exception: true
|
||||
end
|
||||
|
||||
def parse_dot
|
||||
File.open(dot_path).each_line do |line|
|
||||
case line
|
||||
when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
|
||||
@static_lib_shape = $~[:shape]
|
||||
when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
|
||||
node = $~[:node]
|
||||
label = $~[:label]
|
||||
shape = $~[:shape]
|
||||
@nodes[node] = [label, shape]
|
||||
when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
|
||||
depender = $~[:depender]
|
||||
dependee = $~[:dependee]
|
||||
@graph[depender] << dependee
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def prefix(lib)
|
||||
"lib"
|
||||
end
|
||||
|
||||
def tsort_each_node
|
||||
@nodes.each_key do |node|
|
||||
yield node
|
||||
end
|
||||
end
|
||||
|
||||
def tsort_each_child(node)
|
||||
@graph[node].each do |child|
|
||||
yield child
|
||||
end
|
||||
end
|
||||
end
|
||||
17
bindings/ruby/ext/dependencies_for_windows.rb
Normal file
17
bindings/ruby/ext/dependencies_for_windows.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
require_relative "dependencies"
|
||||
|
||||
class DependenciesForWindows < Dependencies
|
||||
def local_libs
|
||||
libs.collect {|lib| %|"#{lib_path(lib)}"|}.join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prefix(lib)
|
||||
lib.start_with?("ggml") ? "" : "lib"
|
||||
end
|
||||
|
||||
def lib_path(lib)
|
||||
File.join(__dir__, lib).tr("\\", "/")
|
||||
end
|
||||
end
|
||||
35
bindings/ruby/ext/extconf.rb
Normal file
35
bindings/ruby/ext/extconf.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
require "mkmf"
|
||||
|
||||
if RUBY_PLATFORM.match? /mswin|mingw|ucrt/
|
||||
require_relative "options_for_windows"
|
||||
require_relative "dependencies_for_windows"
|
||||
|
||||
Opts = OptionsForWindows
|
||||
Deps = DependenciesForWindows
|
||||
else
|
||||
require_relative "options"
|
||||
require_relative "dependencies"
|
||||
|
||||
Opts = Options
|
||||
Deps = Dependencies
|
||||
end
|
||||
|
||||
cmake = find_executable("cmake") || abort
|
||||
options = Opts.new(cmake)
|
||||
have_library("gomp") rescue nil
|
||||
libs = Deps.new(cmake, options)
|
||||
|
||||
append_cflags ["-O3", "-march=native"]
|
||||
$INCFLAGS << " -Isources/include -Isources/ggml/include -Isources/examples"
|
||||
$LOCAL_LIBS << " #{libs.local_libs}"
|
||||
$cleanfiles << " build #{libs}"
|
||||
|
||||
create_makefile "whisper" do |conf|
|
||||
conf << <<~EOF
|
||||
$(TARGET_SO): #{libs}
|
||||
#{libs}: cmake-targets
|
||||
cmake-targets:
|
||||
#{"\t"}"#{cmake}" -S sources -B build #{options}
|
||||
#{"\t"}"#{cmake}" --build build --config Release --target common whisper parakeet
|
||||
EOF
|
||||
end
|
||||
125
bindings/ruby/ext/options.rb
Normal file
125
bindings/ruby/ext/options.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
require "fileutils"
|
||||
|
||||
class Options
|
||||
def initialize(cmake="cmake")
|
||||
@cmake = cmake
|
||||
@options = {}
|
||||
|
||||
configure
|
||||
write_cache_file
|
||||
end
|
||||
|
||||
def to_a
|
||||
[
|
||||
"-D", "BUILD_SHARED_LIBS=OFF",
|
||||
"-D", "WHISPER_BUILD_TESTS=OFF",
|
||||
"-D", "CMAKE_ARCHIVE_OUTPUT_DIRECTORY=#{__dir__}",
|
||||
"-D", "CMAKE_POSITION_INDEPENDENT_CODE=ON",
|
||||
"-C", cache_path
|
||||
]
|
||||
end
|
||||
|
||||
def to_s
|
||||
command_line(*to_a)
|
||||
end
|
||||
|
||||
def graphviz_cmake_args
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cmake_options
|
||||
@cmake_options ||= cmake_options_output.lines.drop_while {|line| line.chomp != "-- Cache values"}.drop(1)
|
||||
.filter_map {|line|
|
||||
option, value = line.chomp.split("=", 2)
|
||||
name, type = option.split(":", 2)
|
||||
[
|
||||
name,
|
||||
[
|
||||
type,
|
||||
type == "BOOL" ? value == "ON" : value
|
||||
]
|
||||
]
|
||||
}.to_h
|
||||
end
|
||||
|
||||
def cmake_options_output
|
||||
Dir.chdir(__dir__) do
|
||||
IO.popen([@cmake, "-S", "sources", "-B", "build", "-L"]) {|io| io.read}
|
||||
end
|
||||
end
|
||||
|
||||
def configure
|
||||
cmake_options.each_pair do |name, (type, default_value)|
|
||||
option = option_name(name)
|
||||
value = type == "BOOL" ? enable_config(option) : arg_config("--#{option}")
|
||||
@options[name] = [type, value]
|
||||
end
|
||||
|
||||
configure_accelerate
|
||||
configure_metal
|
||||
configure_coreml
|
||||
end
|
||||
|
||||
# See ggml/src/ggml-cpu/CMakeLists.txt
|
||||
def configure_accelerate
|
||||
if RUBY_PLATFORM.match?(/darwin/) && enabled?("GGML_ACCELERATE")
|
||||
$LDFLAGS << " -framework Accelerate"
|
||||
end
|
||||
end
|
||||
|
||||
# See ggml/src/ggml-metal/CMakeLists.txt
|
||||
def configure_metal
|
||||
$LDFLAGS << " -framework Foundation -framework Metal -framework MetalKit" if enabled?("GGML_METAL")
|
||||
end
|
||||
|
||||
# See src/CmakeLists.txt
|
||||
def configure_coreml
|
||||
if enabled?("WHISPER_COREML")
|
||||
$LDFLAGS << " -framework Foundation -framework CoreML"
|
||||
$defs << "-DRUBY_WHISPER_USE_COREML"
|
||||
end
|
||||
end
|
||||
|
||||
def option_name(name)
|
||||
name.downcase.gsub("_", "-")
|
||||
end
|
||||
|
||||
def enabled?(option)
|
||||
op = @options[option]
|
||||
return false unless op
|
||||
return false unless op[0] == "BOOL"
|
||||
if op[1].nil?
|
||||
cmake_options[option][1]
|
||||
else
|
||||
op[1]
|
||||
end
|
||||
end
|
||||
|
||||
def cache_path
|
||||
File.join(__dir__, "sources", "Options.cmake")
|
||||
end
|
||||
|
||||
def write_cache_file
|
||||
FileUtils.mkpath File.dirname(cache_path)
|
||||
File.open cache_path, "w" do |file|
|
||||
@options.reject {|name, (type, value)| value.nil?}.each do |name, (type, value)|
|
||||
line = 'set(%<name>s %<value>s CACHE %<type>s "" FORCE)' %{
|
||||
name:,
|
||||
type:,
|
||||
value: value == true ? "ON" : value == false ? "OFF" : escape_cmake(value)
|
||||
}
|
||||
file.puts line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def escape_cmake(str)
|
||||
str.gsub(/[\\"]/, '\\\\\&')
|
||||
end
|
||||
|
||||
def command_line(*args)
|
||||
args.collect {|arg| %|"#{arg.to_s.gsub(/[\\"]/, '\\\\\&')}"|}.join(" ")
|
||||
end
|
||||
end
|
||||
51
bindings/ruby/ext/options_for_windows.rb
Normal file
51
bindings/ruby/ext/options_for_windows.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
require_relative "options"
|
||||
|
||||
class OptionsForWindows < Options
|
||||
def to_s
|
||||
command_line(*generator_args, *to_a)
|
||||
end
|
||||
|
||||
def graphviz_cmake_args
|
||||
generator_args
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def arm?
|
||||
RbConfig::CONFIG["host_cpu"].to_s.downcase.match?(/\A(?:arm64|aarch64)\z/)
|
||||
end
|
||||
|
||||
def cmake_options_output
|
||||
Dir.chdir(__dir__) do
|
||||
IO.popen([@cmake, "-S", "sources", "-B", "build", *generator_args, "-L"]) {|io| io.read}
|
||||
end
|
||||
end
|
||||
|
||||
def generator_args
|
||||
generator = cmake_generator
|
||||
["-G", generator] if generator && !generator.empty?
|
||||
end
|
||||
|
||||
def cmake_generator
|
||||
return @cmake_generator if defined?(@cmake_generator)
|
||||
|
||||
generator = ENV["CMAKE_GENERATOR"]
|
||||
abort "CMAKE_GENERATOR=#{generator} is unsupported for mingw/ucrt Ruby" if visual_studio_generator_name?(generator)
|
||||
return @cmake_generator = generator unless generator.nil? || generator.empty?
|
||||
|
||||
ninja = find_executable("ninja")
|
||||
return @cmake_generator = "Ninja" if ninja
|
||||
|
||||
make = find_executable("make")
|
||||
return @cmake_generator = "MSYS Makefiles" if make
|
||||
|
||||
mingw32_make = find_executable("mingw32-make")
|
||||
return @cmake_generator = "MinGW Makefiles" if mingw32_make
|
||||
|
||||
@cmake_generator = nil
|
||||
end
|
||||
|
||||
def visual_studio_generator_name?(generator)
|
||||
generator && generator.start_with?("Visual Studio")
|
||||
end
|
||||
end
|
||||
198
bindings/ruby/ext/ruby_whisper.c
Normal file
198
bindings/ruby/ext/ruby_whisper.c
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
VALUE mWhisper;
|
||||
VALUE mLogSettable;
|
||||
VALUE mVAD;
|
||||
VALUE mParakeet;
|
||||
VALUE cContext;
|
||||
VALUE cParams;
|
||||
VALUE cVADContext;
|
||||
VALUE cVADParams;
|
||||
VALUE cVADSegments;
|
||||
VALUE cVADSegment;
|
||||
VALUE cParakeetContext;
|
||||
VALUE cParakeetContextParams;
|
||||
VALUE cParakeetParams;
|
||||
VALUE cParakeetSegment;
|
||||
VALUE cParakeetModel;
|
||||
VALUE eError;
|
||||
|
||||
VALUE cSegment;
|
||||
VALUE cToken;
|
||||
VALUE cModel;
|
||||
|
||||
VALUE mOutputContext;
|
||||
VALUE mOutputSegment;
|
||||
|
||||
ID id_to_s;
|
||||
ID id_call;
|
||||
ID id___method__;
|
||||
ID id_to_enum;
|
||||
ID id_length;
|
||||
ID id_next;
|
||||
ID id_new;
|
||||
ID id_to_path;
|
||||
ID id_URI;
|
||||
ID id_pre_converted_models;
|
||||
ID id_coreml_compiled_models;
|
||||
ID id_cache;
|
||||
ID id_n_processors;
|
||||
ID id_extended;
|
||||
ID id_start_log_callback_thread;
|
||||
ID id_log_callback_thread;
|
||||
ID id_alive_p;
|
||||
ID id_join;
|
||||
|
||||
// High level API
|
||||
extern VALUE ruby_whisper_segment_allocate(VALUE klass);
|
||||
|
||||
extern VALUE init_ruby_whisper_context(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_context_params(VALUE *cContext);
|
||||
extern void init_ruby_whisper_params(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_error(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_segment(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_token(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_model(VALUE *mWhisper);
|
||||
extern void init_ruby_whisper_vad_params(VALUE *mVAD);
|
||||
extern void init_ruby_whisper_vad_context(VALUE *mVAD);
|
||||
extern void init_ruby_whisper_vad_segment(VALUE *mVAD);
|
||||
extern void init_ruby_whisper_vad_segments(VALUE *mVAD);
|
||||
extern void init_ruby_whisper_parakeet(VALUE *mWhisper);
|
||||
extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context);
|
||||
|
||||
static ruby_whisper_log_queue whisper_log_queue;
|
||||
|
||||
LOG_SETTABLE_SETUP(whisper_log_queue, mWhisper, whisper_log_set)
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* lang_max_id -> Integer
|
||||
*/
|
||||
static VALUE ruby_whisper_s_lang_max_id(VALUE self) {
|
||||
return INT2NUM(whisper_lang_max_id());
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* lang_id(lang_name) -> Integer
|
||||
*/
|
||||
static VALUE ruby_whisper_s_lang_id(VALUE self, VALUE lang) {
|
||||
const char * lang_str = StringValueCStr(lang);
|
||||
const int id = whisper_lang_id(lang_str);
|
||||
if (-1 == id) {
|
||||
rb_raise(rb_eArgError, "language not found: %s", lang_str);
|
||||
}
|
||||
return INT2NUM(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* lang_str(lang_id) -> String
|
||||
*/
|
||||
static VALUE ruby_whisper_s_lang_str(VALUE self, VALUE id) {
|
||||
const int lang_id = NUM2INT(id);
|
||||
const char * str = whisper_lang_str(lang_id);
|
||||
if (NULL == str) {
|
||||
rb_raise(rb_eIndexError, "id %d outside of language id", lang_id);
|
||||
}
|
||||
return rb_str_new2(str);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* lang_str(lang_id) -> String
|
||||
*/
|
||||
static VALUE ruby_whisper_s_lang_str_full(VALUE self, VALUE id) {
|
||||
const int lang_id = NUM2INT(id);
|
||||
const char * str_full = whisper_lang_str_full(lang_id);
|
||||
if (NULL == str_full) {
|
||||
rb_raise(rb_eIndexError, "id %d outside of language id", lang_id);
|
||||
}
|
||||
return rb_str_new2(str_full);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* system_info_str -> String
|
||||
*/
|
||||
static VALUE ruby_whisper_s_system_info_str(VALUE self) {
|
||||
return rb_str_new2(whisper_print_system_info());
|
||||
}
|
||||
|
||||
void Init_whisper() {
|
||||
id_to_s = rb_intern("to_s");
|
||||
id_call = rb_intern("call");
|
||||
id___method__ = rb_intern("__method__");
|
||||
id_to_enum = rb_intern("to_enum");
|
||||
id_length = rb_intern("length");
|
||||
id_next = rb_intern("next");
|
||||
id_new = rb_intern("new");
|
||||
id_to_path = rb_intern("to_path");
|
||||
id_URI = rb_intern("URI");
|
||||
id_pre_converted_models = rb_intern("pre_converted_models");
|
||||
id_coreml_compiled_models = rb_intern("coreml_compiled_models");
|
||||
id_cache = rb_intern("cache");
|
||||
id_n_processors = rb_intern("n_processors");
|
||||
id_start_log_callback_thread = rb_intern("start_log_callback_thread");
|
||||
id_log_callback_thread = rb_intern("@log_callback_thread");
|
||||
id_alive_p = rb_intern("alive?");
|
||||
id_join = rb_intern("join");
|
||||
|
||||
mWhisper = rb_define_module("Whisper");
|
||||
rb_require("whisper/log_settable");
|
||||
mLogSettable = rb_path2class("Whisper::LogSettable");
|
||||
mVAD = rb_define_module_under(mWhisper, "VAD");
|
||||
rb_require("whisper/output");
|
||||
mOutputContext = rb_path2class("Whisper::Output::Context");
|
||||
mOutputSegment = rb_path2class("Whisper::Output::Segment");
|
||||
|
||||
rb_define_const(mWhisper, "VERSION", rb_str_new2(whisper_version()));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_NONE", INT2NUM(GGML_LOG_LEVEL_NONE));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_INFO", INT2NUM(GGML_LOG_LEVEL_INFO));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_WARN", INT2NUM(GGML_LOG_LEVEL_WARN));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_ERROR", INT2NUM(GGML_LOG_LEVEL_ERROR));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_DEBUG", INT2NUM(GGML_LOG_LEVEL_DEBUG));
|
||||
rb_define_const(mWhisper, "LOG_LEVEL_CONT", INT2NUM(GGML_LOG_LEVEL_CONT));
|
||||
|
||||
rb_define_const(mWhisper, "AHEADS_NONE", INT2NUM(WHISPER_AHEADS_NONE));
|
||||
rb_define_const(mWhisper, "AHEADS_N_TOP_MOST", INT2NUM(WHISPER_AHEADS_N_TOP_MOST));
|
||||
rb_define_const(mWhisper, "AHEADS_CUSTOM", INT2NUM(WHISPER_AHEADS_CUSTOM));
|
||||
rb_define_const(mWhisper, "AHEADS_TINY_EN", INT2NUM(WHISPER_AHEADS_TINY_EN));
|
||||
rb_define_const(mWhisper, "AHEADS_TINY", INT2NUM(WHISPER_AHEADS_TINY));
|
||||
rb_define_const(mWhisper, "AHEADS_BASE_EN", INT2NUM(WHISPER_AHEADS_BASE_EN));
|
||||
rb_define_const(mWhisper, "AHEADS_BASE", INT2NUM(WHISPER_AHEADS_BASE));
|
||||
rb_define_const(mWhisper, "AHEADS_SMALL_EN", INT2NUM(WHISPER_AHEADS_SMALL_EN));
|
||||
rb_define_const(mWhisper, "AHEADS_SMALL", INT2NUM(WHISPER_AHEADS_SMALL));
|
||||
rb_define_const(mWhisper, "AHEADS_MEDIUM_EN", INT2NUM(WHISPER_AHEADS_MEDIUM_EN));
|
||||
rb_define_const(mWhisper, "AHEADS_MEDIUM", INT2NUM(WHISPER_AHEADS_MEDIUM));
|
||||
rb_define_const(mWhisper, "AHEADS_LARGE_V1", INT2NUM(WHISPER_AHEADS_LARGE_V1));
|
||||
rb_define_const(mWhisper, "AHEADS_LARGE_V2", INT2NUM(WHISPER_AHEADS_LARGE_V2));
|
||||
rb_define_const(mWhisper, "AHEADS_LARGE_V3", INT2NUM(WHISPER_AHEADS_LARGE_V3));
|
||||
rb_define_const(mWhisper, "AHEADS_LARGE_V3_TURBO", INT2NUM(WHISPER_AHEADS_LARGE_V3_TURBO));
|
||||
|
||||
rb_define_singleton_method(mWhisper, "lang_max_id", ruby_whisper_s_lang_max_id, 0);
|
||||
rb_define_singleton_method(mWhisper, "lang_id", ruby_whisper_s_lang_id, 1);
|
||||
rb_define_singleton_method(mWhisper, "lang_str", ruby_whisper_s_lang_str, 1);
|
||||
rb_define_singleton_method(mWhisper, "lang_str_full", ruby_whisper_s_lang_str_full, 1);
|
||||
rb_define_singleton_method(mWhisper, "system_info_str", ruby_whisper_s_system_info_str, 0);
|
||||
|
||||
LOG_SETTABLE_INIT(whisper_log_queue, mWhisper)
|
||||
|
||||
cContext = init_ruby_whisper_context(&mWhisper);
|
||||
init_ruby_whisper_context_params(&cContext);
|
||||
init_ruby_whisper_params(&mWhisper);
|
||||
init_ruby_whisper_error(&mWhisper);
|
||||
init_ruby_whisper_segment(&mWhisper);
|
||||
init_ruby_whisper_token(&mWhisper);
|
||||
init_ruby_whisper_model(&mWhisper);
|
||||
init_ruby_whisper_vad_params(&mVAD);
|
||||
init_ruby_whisper_vad_segment(&mVAD);
|
||||
init_ruby_whisper_vad_segments(&mVAD);
|
||||
init_ruby_whisper_vad_context(&mVAD);
|
||||
init_ruby_whisper_parakeet(&mWhisper);
|
||||
|
||||
rb_require("whisper/model/uri");
|
||||
|
||||
rb_include_module(cContext, mOutputContext);
|
||||
rb_include_module(cSegment, mOutputSegment);
|
||||
}
|
||||
248
bindings/ruby/ext/ruby_whisper.h
Normal file
248
bindings/ruby/ext/ruby_whisper.h
Normal file
@@ -0,0 +1,248 @@
|
||||
#ifndef RUBY_WHISPER_H
|
||||
#define RUBY_WHISPER_H
|
||||
|
||||
#include <ruby.h>
|
||||
#include <ruby/version.h>
|
||||
#include <ruby/util.h>
|
||||
#include <ruby/thread.h>
|
||||
#include <ruby/thread_native.h>
|
||||
#include <ruby/atomic.h>
|
||||
#include <ruby/memory_view.h>
|
||||
#include "whisper.h"
|
||||
#include "parakeet.h"
|
||||
#include "ruby_whisper_log_settable.h"
|
||||
|
||||
#if RUBY_API_VERSION_MAJOR < 4
|
||||
// Exists but not declared as public API
|
||||
int ruby_thread_has_gvl_p(void);
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
VALUE *context;
|
||||
VALUE user_data;
|
||||
VALUE callback;
|
||||
VALUE callbacks;
|
||||
} ruby_whisper_callback_container;
|
||||
|
||||
typedef struct ruby_whisper_abort_callback_user_data {
|
||||
volatile rb_atomic_t is_interrupted;
|
||||
ruby_whisper_callback_container *callback_container;
|
||||
} ruby_whisper_abort_callback_user_data;
|
||||
|
||||
typedef struct ruby_whisper_log {
|
||||
enum ggml_log_level level;
|
||||
char *text;
|
||||
size_t length;
|
||||
size_t capacity;
|
||||
} ruby_whisper_log;
|
||||
|
||||
typedef struct ruby_whisper_log_queue {
|
||||
rb_nativethread_lock_t lock;
|
||||
rb_nativethread_cond_t cond;
|
||||
bool is_open;
|
||||
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t size;
|
||||
ruby_whisper_log *logs;
|
||||
} ruby_whisper_log_queue;
|
||||
|
||||
typedef struct {
|
||||
struct whisper_context *context;
|
||||
} ruby_whisper;
|
||||
|
||||
typedef struct ruby_whisper_context_params {
|
||||
struct whisper_context_params params;
|
||||
} ruby_whisper_context_params;
|
||||
|
||||
typedef struct {
|
||||
struct whisper_full_params params;
|
||||
bool diarize;
|
||||
ruby_whisper_callback_container *new_segment_callback_container;
|
||||
ruby_whisper_callback_container *progress_callback_container;
|
||||
ruby_whisper_callback_container *encoder_begin_callback_container;
|
||||
ruby_whisper_callback_container *abort_callback_container;
|
||||
VALUE vad_params;
|
||||
} ruby_whisper_params;
|
||||
|
||||
typedef struct {
|
||||
struct whisper_vad_params params;
|
||||
} ruby_whisper_vad_params;
|
||||
|
||||
typedef struct {
|
||||
VALUE context;
|
||||
int index;
|
||||
} ruby_whisper_segment;
|
||||
|
||||
typedef struct {
|
||||
whisper_token_data *token_data;
|
||||
VALUE text;
|
||||
} ruby_whisper_token;
|
||||
|
||||
typedef struct {
|
||||
VALUE context;
|
||||
} ruby_whisper_model;
|
||||
|
||||
typedef struct {
|
||||
struct whisper_vad_segments *segments;
|
||||
} ruby_whisper_vad_segments;
|
||||
|
||||
typedef struct {
|
||||
VALUE segments;
|
||||
int index;
|
||||
} ruby_whisper_vad_segment;
|
||||
|
||||
typedef struct {
|
||||
struct whisper_vad_context *context;
|
||||
} ruby_whisper_vad_context;
|
||||
|
||||
typedef struct parsed_samples_t {
|
||||
float *samples;
|
||||
int n_samples;
|
||||
rb_memory_view_t memview;
|
||||
bool memview_exported;
|
||||
} parsed_samples_t;
|
||||
|
||||
typedef struct {
|
||||
VALUE *context;
|
||||
VALUE *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
} ruby_whisper_full_args;
|
||||
|
||||
typedef struct segments_from_samples_args {
|
||||
VALUE *context;
|
||||
VALUE *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
} segments_from_samples_args;
|
||||
|
||||
typedef struct ruby_whisper_full_parallel_args {
|
||||
VALUE *context;
|
||||
VALUE *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
int n_processors;
|
||||
} ruby_whisper_full_parallel_args;
|
||||
|
||||
typedef struct {
|
||||
struct parakeet_full_params params;
|
||||
ruby_whisper_callback_container *new_segment_callback_container;
|
||||
ruby_whisper_callback_container *new_token_callback_container;
|
||||
ruby_whisper_callback_container *progress_callback_container;
|
||||
ruby_whisper_callback_container *encoder_begin_callback_container;
|
||||
ruby_whisper_callback_container *abort_callback_container;
|
||||
} ruby_whisper_parakeet_params;
|
||||
|
||||
typedef struct {
|
||||
struct parakeet_context_params params;
|
||||
} ruby_whisper_parakeet_context_params;
|
||||
|
||||
typedef struct {
|
||||
struct parakeet_context *context;
|
||||
} ruby_whisper_parakeet_context;
|
||||
|
||||
typedef struct {
|
||||
VALUE context;
|
||||
int index;
|
||||
} ruby_whisper_parakeet_segment;
|
||||
|
||||
typedef struct {
|
||||
parakeet_token_data *token_data;
|
||||
VALUE text;
|
||||
} ruby_whisper_parakeet_token;
|
||||
|
||||
typedef struct {
|
||||
VALUE context;
|
||||
} ruby_whisper_parakeet_model;
|
||||
|
||||
extern ID id_log_callback_thread;
|
||||
extern ID id_start_log_callback_thread;
|
||||
extern ID id_alive_p;
|
||||
extern ID id_join;
|
||||
extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue);
|
||||
extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue);
|
||||
extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue);
|
||||
extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text);
|
||||
extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue);
|
||||
|
||||
#define GetContext(obj, rw) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \
|
||||
if ((rw)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetContextParams(obj, rwcp) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_context_params, &ruby_whisper_context_params_type, (rwcp)); \
|
||||
} while (0)
|
||||
|
||||
#define GetToken(obj, rwt) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_token, &ruby_whisper_token_type, (rwt)); \
|
||||
if ((rwt)->token_data == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetVADContext(obj, rwvc) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_vad_context, &ruby_whisper_vad_context_type, (rwvc)); \
|
||||
if ((rwvc)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetVADParams(obj, rwvp) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_vad_params, &ruby_whisper_vad_params_type, (rwvp)); \
|
||||
} while (0)
|
||||
|
||||
#define GetVADSegments(obj, rwvss) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, (rwvss)); \
|
||||
if ((rwvss)->segments == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetContextParams(obj, rwpcp) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, (rwpcp)); \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetContext(obj, rwpc) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, (rwpc)); \
|
||||
if ((rwpc)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetParams(obj, rwpp) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, (rwpp)); \
|
||||
if (!(rwpp)->new_segment_callback_container || \
|
||||
!(rwpp)->new_token_callback_container || \
|
||||
!(rwpp)->progress_callback_container || \
|
||||
!(rwpp)->encoder_begin_callback_container || \
|
||||
!(rwpp)->abort_callback_container) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetSegment(obj, rwps) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, (rwps)); \
|
||||
if (!(rwps)->context) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetToken(obj, rwpt) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, (rwpt)); \
|
||||
if (!(rwpt)->token_data) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define GetParakeetModel(obj, rwpm) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, (rwpm)); \
|
||||
if (NIL_P((rwpm)->context)) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
941
bindings/ruby/ext/ruby_whisper_context.c
Normal file
941
bindings/ruby/ext/ruby_whisper_context.c
Normal file
@@ -0,0 +1,941 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
#define IS_BIGENDIAN true
|
||||
#else
|
||||
#define IS_BIGENDIAN false
|
||||
#endif
|
||||
|
||||
extern ID id_to_s;
|
||||
extern ID id___method__;
|
||||
extern ID id_to_enum;
|
||||
extern ID id_length;
|
||||
extern ID id_next;
|
||||
extern ID id_new;
|
||||
extern ID id_to_path;
|
||||
extern ID id_URI;
|
||||
extern ID id_pre_converted_models;
|
||||
extern ID id_coreml_compiled_models;
|
||||
extern ID id_cache;
|
||||
extern ID id_n_processors;
|
||||
|
||||
extern VALUE cContext;
|
||||
extern VALUE eError;
|
||||
extern VALUE cModel;
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_params_type;
|
||||
extern const rb_data_type_t ruby_whisper_context_params_type;
|
||||
extern VALUE ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self);
|
||||
extern VALUE rb_whisper_model_s_new(VALUE context);
|
||||
extern VALUE rb_whisper_segment_s_new(VALUE context, int index);
|
||||
extern void prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors, ruby_whisper_abort_callback_user_data *abort_callback_user_data);
|
||||
|
||||
ID transcribe_option_names[1];
|
||||
|
||||
typedef struct fill_samples_args {
|
||||
float *dest;
|
||||
VALUE *src;
|
||||
int n_samples;
|
||||
} fill_samples_args;
|
||||
|
||||
typedef struct full_without_gvl_args {
|
||||
struct whisper_context *context;
|
||||
struct whisper_full_params *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
int result;
|
||||
} full_without_gvl_args;
|
||||
|
||||
typedef struct full_parallel_without_gvl_args {
|
||||
struct whisper_context *context;
|
||||
struct whisper_full_params *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
int n_processors;
|
||||
int result;
|
||||
} full_parallel_without_gvl_args;
|
||||
|
||||
typedef struct full_ubf_args {
|
||||
ruby_whisper_abort_callback_user_data *abort_callback_user_data;
|
||||
} full_ubf_args;
|
||||
|
||||
static void
|
||||
ruby_whisper_free(ruby_whisper *rw)
|
||||
{
|
||||
if (rw->context) {
|
||||
whisper_free(rw->context);
|
||||
rw->context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rb_whisper_mark(ruby_whisper *rw)
|
||||
{
|
||||
// call rb_gc_mark on any ruby references in rw
|
||||
}
|
||||
|
||||
void
|
||||
rb_whisper_free(void *p)
|
||||
{
|
||||
ruby_whisper *rw = (ruby_whisper *)p;
|
||||
ruby_whisper_free(rw);
|
||||
free(rw);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper *rw = (const ruby_whisper *)p;
|
||||
size_t size = sizeof(*rw);
|
||||
if (!rw) {
|
||||
return 0;
|
||||
}
|
||||
if (rw->context) {
|
||||
size += sizeof(rw->context);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_type = {
|
||||
"ruby_whisper",
|
||||
{0, rb_whisper_free, ruby_whisper_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper, &ruby_whisper_type, rw);
|
||||
rw->context = NULL;
|
||||
return obj;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_normalize_model_path(VALUE model_path)
|
||||
{
|
||||
VALUE pre_converted_models = rb_funcall(cModel, id_pre_converted_models, 0);
|
||||
VALUE pre_converted_model = rb_hash_aref(pre_converted_models, model_path);
|
||||
if (!NIL_P(pre_converted_model)) {
|
||||
model_path = pre_converted_model;
|
||||
#ifdef RUBY_WHISPER_USE_COREML
|
||||
VALUE coreml_converted_models = rb_funcall(cModel, id_coreml_compiled_models, 0);
|
||||
VALUE coreml_converted_model = rb_hash_aref(coreml_converted_models, pre_converted_model);
|
||||
if (!NIL_P(coreml_converted_model)) {
|
||||
rb_funcall(coreml_converted_model, id_cache, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (TYPE(model_path) == T_STRING) {
|
||||
const char * model_path_str = StringValueCStr(model_path);
|
||||
if (strncmp("http://", model_path_str, 7) == 0 || strncmp("https://", model_path_str, 8) == 0) {
|
||||
VALUE uri_class = rb_const_get(cModel, id_URI);
|
||||
model_path = rb_class_new_instance(1, &model_path, uri_class);
|
||||
}
|
||||
}
|
||||
else if (rb_obj_is_kind_of(model_path, rb_path2class("URI::HTTP"))) {
|
||||
VALUE uri_class = rb_const_get(cModel, id_URI);
|
||||
model_path = rb_class_new_instance(1, &model_path, uri_class);
|
||||
}
|
||||
if (rb_respond_to(model_path, id_to_path)) {
|
||||
model_path = rb_funcall(model_path, id_to_path, 0);
|
||||
}
|
||||
|
||||
return model_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* new("base.en") -> Whisper::Context
|
||||
* new("path/to/model.bin") -> Whisper::Context
|
||||
* new(Whisper::Model::URI.new("https://example.net/uri/of/model.bin")) -> Whisper::Context
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
VALUE whisper_model_file_path;
|
||||
VALUE context_params;
|
||||
struct whisper_context_params params;
|
||||
|
||||
// TODO: we can support init from buffer here too maybe another ruby object to expose
|
||||
rb_scan_args(argc, argv, "11", &whisper_model_file_path, &context_params);
|
||||
TypedData_Get_Struct(self, ruby_whisper, &ruby_whisper_type, rw);
|
||||
|
||||
whisper_model_file_path = ruby_whisper_normalize_model_path(whisper_model_file_path);
|
||||
if (!rb_respond_to(whisper_model_file_path, id_to_s)) {
|
||||
rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Whisper::Context");
|
||||
}
|
||||
if (NIL_P(context_params)) {
|
||||
params = whisper_context_default_params();
|
||||
} else {
|
||||
ruby_whisper_context_params *rwcp;
|
||||
GetContextParams(context_params, rwcp);
|
||||
params = rwcp->params;
|
||||
}
|
||||
rw->context = whisper_init_from_file_with_params(StringValueCStr(whisper_model_file_path), params);
|
||||
if (rw->context == NULL) {
|
||||
rb_raise(rb_eRuntimeError, "error: failed to initialize whisper context");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_vocab -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_vocab(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_vocab(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_audio_ctx -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_audio_ctx(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_audio_ctx(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_audio_state -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_audio_state(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_audio_state(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_audio_head -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_audio_head(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_audio_head(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_audio_layer -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_audio_layer(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_audio_layer(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_text_ctx -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_text_ctx(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_text_ctx(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_text_state -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_text_state(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_text_state(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_text_head -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_text_head(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_text_head(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_text_layer -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_text_layer(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_text_layer(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_n_mels -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_n_mels(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_n_mels(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_ftype -> Integer
|
||||
*/
|
||||
VALUE ruby_whisper_model_ftype(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_model_ftype(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model_type -> String
|
||||
*/
|
||||
VALUE ruby_whisper_model_type(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return rb_str_new2(whisper_model_type_readable(rw->context));
|
||||
}
|
||||
|
||||
static bool
|
||||
check_memory_view(rb_memory_view_t *memview)
|
||||
{
|
||||
rb_memory_view_prepare_item_desc(memview);
|
||||
|
||||
if (!memview->format) {
|
||||
rb_warn("format is required");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memview->item_desc.length != 1) {
|
||||
rb_warn("format must be exact one character");
|
||||
return false;
|
||||
}
|
||||
|
||||
rb_memory_view_item_component_t component = memview->item_desc.components[0];
|
||||
|
||||
if (component.offset) {
|
||||
rb_warn("format has offset");
|
||||
return false;
|
||||
}
|
||||
if (component.repeat != 1) {
|
||||
rb_warn("format repeated");
|
||||
return false;
|
||||
}
|
||||
switch (component.format) {
|
||||
case 'f':
|
||||
// accept
|
||||
break;
|
||||
case 'e':
|
||||
if (IS_BIGENDIAN) {
|
||||
rb_warn("currently format \"e\" is only supported on little-endian environment");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
if (!IS_BIGENDIAN) {
|
||||
rb_warn("currently format \"g\" is only supported on big-endian environment");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rb_warn("currently only format \"f\", \"e\" on little-endian environment and \"g\" on big-endian environment are supported for MemoryView, but given: %c", component.format);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memview->ndim != 1 && !(memview->ndim == 2 && memview->shape[1] == 1)) {
|
||||
// TODO: Accept ndim == 2 with shape [n_samples, channels] and channels > 1 by averaging the samples in different channels or just taking the first channel
|
||||
rb_warn("currently only 1 dimensional MemoryView is supported, but given: %zd", memview->ndim);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
fill_samples(VALUE rb_args)
|
||||
{
|
||||
fill_samples_args *args = (fill_samples_args *)rb_args;
|
||||
|
||||
if (RB_TYPE_P(*args->src, T_ARRAY)) {
|
||||
for (int i = 0; i < args->n_samples; i++) {
|
||||
VALUE sample = rb_ary_entry(*args->src, i);
|
||||
if (!RB_FLOAT_TYPE_P(sample)) {
|
||||
sample = rb_to_float(sample);
|
||||
}
|
||||
args->dest[i] = RFLOAT_VALUE(sample);
|
||||
}
|
||||
} else {
|
||||
// TODO: use rb_block_call
|
||||
VALUE iter = rb_funcall(*args->src, id_to_enum, 1, rb_str_new2("each"));
|
||||
for (int i = 0; i < args->n_samples; i++) {
|
||||
// TODO: check if iter is exhausted and raise ArgumentError appropriately
|
||||
VALUE sample = rb_funcall(iter, id_next, 0);
|
||||
if (!RB_FLOAT_TYPE_P(sample)) {
|
||||
sample = rb_to_float(sample);
|
||||
}
|
||||
args->dest[i] = RFLOAT_VALUE(sample);
|
||||
}
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
parsed_samples_t
|
||||
parse_samples(VALUE *samples, VALUE *n_samples)
|
||||
{
|
||||
bool memview_available = rb_memory_view_available_p(*samples);
|
||||
struct parsed_samples_t parsed = {0};
|
||||
parsed.memview_exported = false;
|
||||
const bool is_array = RB_TYPE_P(*samples, T_ARRAY);
|
||||
|
||||
if (!NIL_P(*n_samples)) {
|
||||
parsed.n_samples = NUM2INT(*n_samples);
|
||||
if (is_array) {
|
||||
if (RARRAY_LEN(*samples) < parsed.n_samples) {
|
||||
rb_raise(rb_eArgError, "samples length %ld is less than n_samples %d", RARRAY_LEN(*samples), parsed.n_samples);
|
||||
}
|
||||
}
|
||||
// Should check when samples.respond_to?(:length)?
|
||||
} else {
|
||||
if (is_array) {
|
||||
if (RARRAY_LEN(*samples) > INT_MAX) {
|
||||
rb_raise(rb_eArgError, "samples are too long");
|
||||
}
|
||||
parsed.n_samples = (int)RARRAY_LEN(*samples);
|
||||
} else if (memview_available) {
|
||||
bool memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_FORMAT | RUBY_MEMORY_VIEW_ROW_MAJOR);
|
||||
if (memview_got) {
|
||||
parsed.memview_exported = check_memory_view(&parsed.memview);
|
||||
if (!parsed.memview_exported) {
|
||||
rb_memory_view_release(&parsed.memview);
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
}
|
||||
} else {
|
||||
// Sometimes MemoryView producers accept only SIMPLE flag even when they provide suitable MemoryView
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
bool simple_memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_SIMPLE);
|
||||
if (simple_memview_got) {
|
||||
parsed.memview_exported = check_memory_view(&parsed.memview);
|
||||
if (!parsed.memview_exported) {
|
||||
rb_memory_view_release(&parsed.memview);
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parsed.memview_exported) {
|
||||
ssize_t n_samples_size = parsed.memview.byte_size / parsed.memview.item_size;
|
||||
if (n_samples_size > INT_MAX) {
|
||||
rb_memory_view_release(&parsed.memview);
|
||||
rb_raise(rb_eArgError, "samples are too long: %zd", n_samples_size);
|
||||
}
|
||||
parsed.n_samples = (int)n_samples_size;
|
||||
} else {
|
||||
rb_warn("unable to get a memory view. falls back to Ruby object");
|
||||
if (rb_respond_to(*samples, id_length)) {
|
||||
parsed.n_samples = NUM2INT(rb_funcall(*samples, id_length, 0));
|
||||
} else {
|
||||
rb_raise(rb_eArgError, "samples must respond to :length");
|
||||
}
|
||||
}
|
||||
} else if (rb_respond_to(*samples, id_length)) {
|
||||
parsed.n_samples = NUM2INT(rb_funcall(*samples, id_length, 0));
|
||||
} else {
|
||||
rb_raise(rb_eArgError, "samples must respond to :length or be a MemoryView of an array of float when n_samples is not given");
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.memview_exported) {
|
||||
parsed.samples = (float *)parsed.memview.data;
|
||||
} else {
|
||||
parsed.samples = ALLOC_N(float, parsed.n_samples);
|
||||
fill_samples_args args = {
|
||||
parsed.samples,
|
||||
samples,
|
||||
parsed.n_samples,
|
||||
};
|
||||
int state;
|
||||
rb_protect(fill_samples, (VALUE)&args, &state);
|
||||
if (state) {
|
||||
xfree(parsed.samples);
|
||||
rb_jump_tag(state);
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
VALUE
|
||||
release_samples(VALUE rb_parsed_args)
|
||||
{
|
||||
parsed_samples_t *parsed_args = (parsed_samples_t *)rb_parsed_args;
|
||||
|
||||
if (parsed_args->memview_exported) {
|
||||
rb_memory_view_release(&parsed_args->memview);
|
||||
} else {
|
||||
xfree(parsed_args->samples);
|
||||
}
|
||||
*parsed_args = (parsed_samples_t){0};
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static void*
|
||||
full_without_gvl(void *rb_args)
|
||||
{
|
||||
full_without_gvl_args *args = (full_without_gvl_args *)rb_args;
|
||||
args->result = whisper_full(args->context, *args->params, args->samples, args->n_samples);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
full_ubf(void *rb_args)
|
||||
{
|
||||
full_ubf_args *args = (full_ubf_args *)rb_args;
|
||||
|
||||
RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1);
|
||||
}
|
||||
|
||||
VALUE
|
||||
full_body(VALUE rb_args)
|
||||
{
|
||||
ruby_whisper_full_args *args = (ruby_whisper_full_args *)rb_args;
|
||||
|
||||
ruby_whisper *rw;
|
||||
ruby_whisper_params *rwp;
|
||||
GetContext(*args->context, rw);
|
||||
TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
|
||||
|
||||
ruby_whisper_abort_callback_user_data abort_callback_user_data = {
|
||||
0,
|
||||
NULL,
|
||||
};
|
||||
prepare_transcription(rwp, args->context, 1, &abort_callback_user_data);
|
||||
|
||||
struct full_without_gvl_args full_without_gvl_args = {
|
||||
rw->context,
|
||||
&rwp->params,
|
||||
args->samples,
|
||||
args->n_samples,
|
||||
0,
|
||||
};
|
||||
full_ubf_args full_ubf_args = {
|
||||
&abort_callback_user_data,
|
||||
};
|
||||
rb_thread_call_without_gvl(full_without_gvl, (void *)&full_without_gvl_args, full_ubf, (void *)&full_ubf_args);
|
||||
return INT2NUM(full_without_gvl_args.result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text
|
||||
* Not thread safe for same context
|
||||
* Uses the specified decoding strategy to obtain the text.
|
||||
*
|
||||
* call-seq:
|
||||
* full(params, samples, n_samples) -> nil
|
||||
* full(params, samples) -> nil
|
||||
*
|
||||
* The second argument +samples+ must be an array of samples, respond to :length, or be a MemoryView of an array of float. It must be 32 bit float PCM audio data.
|
||||
*/
|
||||
VALUE ruby_whisper_full(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
if (argc < 2 || argc > 3) {
|
||||
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..3)", argc);
|
||||
}
|
||||
|
||||
VALUE n_samples = argc == 2 ? Qnil : argv[2];
|
||||
|
||||
struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples);
|
||||
ruby_whisper_full_args args = {
|
||||
&self,
|
||||
&argv[0],
|
||||
parsed.samples,
|
||||
parsed.n_samples,
|
||||
};
|
||||
VALUE rb_result = rb_ensure(full_body, (VALUE)&args, release_samples, (VALUE)&parsed);
|
||||
const int result = NUM2INT(rb_result);
|
||||
if (0 == result) {
|
||||
return self;
|
||||
} else {
|
||||
rb_exc_raise(rb_funcall(eError, id_new, 1, result));
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
full_parallel_without_gvl(void *rb_args)
|
||||
{
|
||||
full_parallel_without_gvl_args *args = (full_parallel_without_gvl_args *)rb_args;
|
||||
args->result = whisper_full_parallel(args->context, *args->params, args->samples, args->n_samples, args->n_processors);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VALUE
|
||||
full_parallel_body(VALUE rb_args)
|
||||
{
|
||||
ruby_whisper_full_parallel_args *args = (ruby_whisper_full_parallel_args *)rb_args;
|
||||
|
||||
ruby_whisper *rw;
|
||||
ruby_whisper_params *rwp;
|
||||
GetContext(*args->context, rw);
|
||||
TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
|
||||
|
||||
ruby_whisper_abort_callback_user_data abort_callback_user_data = {
|
||||
0,
|
||||
NULL,
|
||||
};
|
||||
prepare_transcription(rwp, args->context, args->n_processors, &abort_callback_user_data);
|
||||
|
||||
struct full_parallel_without_gvl_args full_parallel_without_gvl_args = {
|
||||
rw->context,
|
||||
&rwp->params,
|
||||
args->samples,
|
||||
args->n_samples,
|
||||
args->n_processors,
|
||||
0,
|
||||
};
|
||||
full_ubf_args full_ubf_args = {
|
||||
&abort_callback_user_data,
|
||||
};
|
||||
rb_thread_call_without_gvl(full_parallel_without_gvl, (void *)&full_parallel_without_gvl_args, full_ubf, (void *)&full_ubf_args);
|
||||
return INT2NUM(full_parallel_without_gvl_args.result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Split the input audio in chunks and process each chunk separately using whisper_full_with_state()
|
||||
* Result is stored in the default state of the context
|
||||
* Not thread safe if executed in parallel on the same context.
|
||||
* It seems this approach can offer some speedup in some cases.
|
||||
* However, the transcription accuracy can be worse at the beginning and end of each chunk.
|
||||
*
|
||||
* call-seq:
|
||||
* full_parallel(params, samples) -> nil
|
||||
* full_parallel(params, samples, n_samples) -> nil
|
||||
* full_parallel(params, samples, n_samples, n_processors) -> nil
|
||||
* full_parallel(params, samples, nil, n_processors) -> nil
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_parallel(int argc, VALUE *argv,VALUE self)
|
||||
{
|
||||
if (argc < 2 || argc > 4) {
|
||||
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..4)", argc);
|
||||
}
|
||||
|
||||
VALUE n_samples = argc == 2 ? Qnil : argv[2];
|
||||
int n_processors;
|
||||
switch (argc) {
|
||||
case 2:
|
||||
n_processors = 1;
|
||||
break;
|
||||
case 3:
|
||||
n_processors = 1;
|
||||
break;
|
||||
case 4:
|
||||
n_processors = NUM2INT(argv[3]);
|
||||
break;
|
||||
}
|
||||
struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples);
|
||||
const ruby_whisper_full_parallel_args args = {
|
||||
&self,
|
||||
&argv[0],
|
||||
parsed.samples,
|
||||
parsed.n_samples,
|
||||
n_processors,
|
||||
};
|
||||
const VALUE rb_result = rb_ensure(full_parallel_body, (VALUE)&args, release_samples, (VALUE)&parsed);
|
||||
const int result = NUM2INT(rb_result);
|
||||
if (0 == result) {
|
||||
return self;
|
||||
} else {
|
||||
rb_exc_raise(rb_funcall(eError, id_new, 1, result));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Number of segments.
|
||||
*
|
||||
* call-seq:
|
||||
* full_n_segments -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_n_segments(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_full_n_segments(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* Language ID, which can be converted to string by Whisper.lang_str and Whisper.lang_str_full.
|
||||
*
|
||||
* call-seq:
|
||||
* full_lang_id -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_lang_id(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
return INT2NUM(whisper_full_lang_id(rw->context));
|
||||
}
|
||||
|
||||
static int ruby_whisper_full_check_segment_index(const ruby_whisper * rw, const VALUE i_segment)
|
||||
{
|
||||
const int c_i_segment = NUM2INT(i_segment);
|
||||
if (c_i_segment < 0 || c_i_segment >= whisper_full_n_segments(rw->context)) {
|
||||
rb_raise(rb_eIndexError, "segment index %d out of range", c_i_segment);
|
||||
}
|
||||
return c_i_segment;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
|
||||
*
|
||||
* full_get_segment_t0(3) # => 1668 (16680 ms)
|
||||
*
|
||||
* call-seq:
|
||||
* full_get_segment_t0(segment_index) -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment_t0(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
|
||||
const int64_t t0 = whisper_full_get_segment_t0(rw->context, c_i_segment);
|
||||
return LONG2NUM(t0);
|
||||
}
|
||||
|
||||
/*
|
||||
* End time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
|
||||
*
|
||||
* full_get_segment_t1(3) # => 1668 (16680 ms)
|
||||
*
|
||||
* call-seq:
|
||||
* full_get_segment_t1(segment_index) -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment_t1(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
|
||||
const int64_t t1 = whisper_full_get_segment_t1(rw->context, c_i_segment);
|
||||
return LONG2NUM(t1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether the next segment indexed by +segment_index+ is predicated as a speaker turn.
|
||||
*
|
||||
* full_get_segment_speacker_turn_next(3) # => true
|
||||
*
|
||||
* call-seq:
|
||||
* full_get_segment_speacker_turn_next(segment_index) -> bool
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment_speaker_turn_next(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
|
||||
const bool speaker_turn_next = whisper_full_get_segment_speaker_turn_next(rw->context, c_i_segment);
|
||||
return speaker_turn_next ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
/*
|
||||
* Text of a segment indexed by +segment_index+.
|
||||
*
|
||||
* full_get_segment_text(3) # => "ask not what your country can do for you, ..."
|
||||
*
|
||||
* call-seq:
|
||||
* full_get_segment_text(segment_index) -> String
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment_text(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
|
||||
const char * text = whisper_full_get_segment_text(rw->context, c_i_segment);
|
||||
return rb_str_new2(text);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* full_get_segment_no_speech_prob(segment_index) -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment_no_speech_prob(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
|
||||
const float no_speech_prob = whisper_full_get_segment_no_speech_prob(rw->context, c_i_segment);
|
||||
return DBL2NUM(no_speech_prob);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_full_n_vad_segments(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
|
||||
return INT2NUM(whisper_full_n_vad_segments(rw->context));
|
||||
}
|
||||
|
||||
static int
|
||||
ruby_whisper_full_check_vad_segment_index(const ruby_whisper *rw, const VALUE i_segment)
|
||||
{
|
||||
const int c_i_segment = NUM2INT(i_segment);
|
||||
if (c_i_segment < 0 || c_i_segment >= whisper_full_n_vad_segments(rw->context)) {
|
||||
rb_raise(rb_eIndexError, "segment index %d out of range", c_i_segment);
|
||||
}
|
||||
return c_i_segment;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_full_get_vad_segment_t0(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
|
||||
|
||||
return LONG2NUM(whisper_full_get_vad_segment_t0(rw->context, c_i_segment));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_full_get_vad_segment_t1(VALUE self, VALUE i_segment)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
|
||||
|
||||
return LONG2NUM(whisper_full_get_vad_segment_t1(rw->context, c_i_segment));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_free(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
whisper_free(rw->context);
|
||||
rw->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
// High level API
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_full_get_segment(VALUE self, VALUE i_segment)
|
||||
{
|
||||
return rb_whisper_segment_s_new(self, NUM2INT(i_segment));
|
||||
}
|
||||
|
||||
/*
|
||||
* Yields each Whisper::Segment:
|
||||
*
|
||||
* whisper.transcribe("path/to/audio.wav", params)
|
||||
* whisper.each_segment do |segment|
|
||||
* puts segment.text
|
||||
* end
|
||||
*
|
||||
* Returns an Enumerator if no block given:
|
||||
*
|
||||
* whisper.transcribe("path/to/audio.wav", params)
|
||||
* enum = whisper.each_segment
|
||||
* enum.to_a # => [#<Whisper::Segment>, ...]
|
||||
*
|
||||
* call-seq:
|
||||
* each_segment {|segment| ... }
|
||||
* each_segment -> Enumerator
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_each_segment(VALUE self)
|
||||
{
|
||||
if (!rb_block_given_p()) {
|
||||
const VALUE method_name = rb_funcall(self, id___method__, 0);
|
||||
return rb_funcall(self, id_to_enum, 1, method_name);
|
||||
}
|
||||
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
|
||||
const int n_segments = whisper_full_n_segments(rw->context);
|
||||
for (int i = 0; i < n_segments; ++i) {
|
||||
rb_yield(rb_whisper_segment_s_new(self, i));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* model -> Whisper::Model
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_get_model(VALUE self)
|
||||
{
|
||||
return rb_whisper_model_s_new(self);
|
||||
}
|
||||
|
||||
VALUE
|
||||
init_ruby_whisper_context(VALUE *mWhisper)
|
||||
{
|
||||
cContext = rb_define_class_under(*mWhisper, "Context", rb_cObject);
|
||||
|
||||
transcribe_option_names[0] = id_n_processors;
|
||||
|
||||
rb_define_alloc_func(cContext, ruby_whisper_allocate);
|
||||
rb_define_method(cContext, "initialize", ruby_whisper_initialize, -1);
|
||||
|
||||
rb_define_method(cContext, "transcribe", ruby_whisper_transcribe, -1);
|
||||
rb_define_method(cContext, "model_n_vocab", ruby_whisper_model_n_vocab, 0);
|
||||
rb_define_method(cContext, "model_n_audio_ctx", ruby_whisper_model_n_audio_ctx, 0);
|
||||
rb_define_method(cContext, "model_n_audio_state", ruby_whisper_model_n_audio_state, 0);
|
||||
rb_define_method(cContext, "model_n_audio_head", ruby_whisper_model_n_audio_head, 0);
|
||||
rb_define_method(cContext, "model_n_audio_layer", ruby_whisper_model_n_audio_layer, 0);
|
||||
rb_define_method(cContext, "model_n_text_ctx", ruby_whisper_model_n_text_ctx, 0);
|
||||
rb_define_method(cContext, "model_n_text_state", ruby_whisper_model_n_text_state, 0);
|
||||
rb_define_method(cContext, "model_n_text_head", ruby_whisper_model_n_text_head, 0);
|
||||
rb_define_method(cContext, "model_n_text_layer", ruby_whisper_model_n_text_layer, 0);
|
||||
rb_define_method(cContext, "model_n_mels", ruby_whisper_model_n_mels, 0);
|
||||
rb_define_method(cContext, "model_ftype", ruby_whisper_model_ftype, 0);
|
||||
rb_define_method(cContext, "model_type", ruby_whisper_model_type, 0);
|
||||
rb_define_method(cContext, "full_n_segments", ruby_whisper_full_n_segments, 0);
|
||||
rb_define_method(cContext, "full_lang_id", ruby_whisper_full_lang_id, 0);
|
||||
rb_define_method(cContext, "full_get_segment_t0", ruby_whisper_full_get_segment_t0, 1);
|
||||
rb_define_method(cContext, "full_get_segment_t1", ruby_whisper_full_get_segment_t1, 1);
|
||||
rb_define_method(cContext, "full_get_segment_speaker_turn_next", ruby_whisper_full_get_segment_speaker_turn_next, 1);
|
||||
rb_define_method(cContext, "full_get_segment_text", ruby_whisper_full_get_segment_text, 1);
|
||||
rb_define_method(cContext, "full_get_segment_no_speech_prob", ruby_whisper_full_get_segment_no_speech_prob, 1);
|
||||
rb_define_method(cContext, "full_n_vad_segments", ruby_whisper_full_n_vad_segments, 0);
|
||||
rb_define_method(cContext, "full_get_vad_segment_t0", ruby_whisper_full_get_vad_segment_t0, 1);
|
||||
rb_define_method(cContext, "full_get_vad_segment_t1", ruby_whisper_full_get_vad_segment_t1, 1);
|
||||
rb_define_method(cContext, "full", ruby_whisper_full, -1);
|
||||
rb_define_method(cContext, "full_parallel", ruby_whisper_full_parallel, -1);
|
||||
rb_define_method(cContext, "free", ruby_whisper_context_free, 0);
|
||||
|
||||
// High level
|
||||
rb_define_method(cContext, "full_get_segment", ruby_whisper_full_get_segment, 1);
|
||||
rb_define_method(cContext, "each_segment", ruby_whisper_each_segment, 0);
|
||||
|
||||
rb_define_method(cContext, "model", ruby_whisper_get_model, 0);
|
||||
|
||||
return cContext;
|
||||
}
|
||||
163
bindings/ruby/ext/ruby_whisper_context_params.c
Normal file
163
bindings/ruby/ext/ruby_whisper_context_params.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define NUM_PARAMS 6
|
||||
|
||||
#define DEF_BOOLEAN_ATTR_METHOD(name) \
|
||||
static VALUE \
|
||||
ruby_whisper_context_params_get_ ## name(VALUE self) { \
|
||||
ruby_whisper_context_params *rwcp; \
|
||||
GetContextParams(self, rwcp); \
|
||||
return rwcp->params.name ? Qtrue : Qfalse; \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_context_params_set_ ## name(VALUE self, VALUE value) { \
|
||||
ruby_whisper_context_params *rwcp; \
|
||||
GetContextParams(self, rwcp); \
|
||||
rwcp->params.name = RTEST(value); \
|
||||
return value; \
|
||||
}
|
||||
|
||||
#define DEF_INT_ATTR_METHOD(name) \
|
||||
static VALUE \
|
||||
ruby_whisper_context_params_get_ ## name(VALUE self) { \
|
||||
ruby_whisper_context_params *rwcp; \
|
||||
GetContextParams(self, rwcp); \
|
||||
return INT2NUM(rwcp->params.name); \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_context_params_set_ ## name(VALUE self, VALUE value) { \
|
||||
ruby_whisper_context_params *rwcp; \
|
||||
GetContextParams(self, rwcp); \
|
||||
rwcp->params.name = NUM2INT(value); \
|
||||
return value; \
|
||||
}
|
||||
|
||||
#define DEFINE_PARAM(param_name, nth) \
|
||||
id_ ## param_name = rb_intern(#param_name); \
|
||||
param_names[nth] = id_ ## param_name; \
|
||||
rb_define_method(cContextParams, #param_name, ruby_whisper_context_params_get_ ## param_name, 0); \
|
||||
rb_define_method(cContextParams, #param_name "=", ruby_whisper_context_params_set_ ## param_name, 1);
|
||||
|
||||
VALUE cContextParams;
|
||||
|
||||
static ID param_names[NUM_PARAMS];
|
||||
static ID id_use_gpu;
|
||||
static ID id_flash_attn;
|
||||
static ID id_gpu_device;
|
||||
static ID id_dtw_token_timestamps;
|
||||
static ID id_dtw_aheads_preset;
|
||||
static ID id_dtw_n_top;
|
||||
|
||||
static size_t
|
||||
ruby_whisper_context_params_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_context_params *rwcp = (ruby_whisper_context_params *)p;
|
||||
if (!rwcp) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(ruby_whisper_context_params);
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_context_params_type = {
|
||||
"ruby_whisper_context_params",
|
||||
{0, RUBY_DEFAULT_FREE, ruby_whisper_context_params_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_params_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_context_params *rwcp;
|
||||
return TypedData_Make_Struct(klass, ruby_whisper_context_params, &ruby_whisper_context_params_type, rwcp);
|
||||
}
|
||||
|
||||
DEF_BOOLEAN_ATTR_METHOD(use_gpu);
|
||||
DEF_BOOLEAN_ATTR_METHOD(flash_attn);
|
||||
DEF_INT_ATTR_METHOD(gpu_device);
|
||||
DEF_BOOLEAN_ATTR_METHOD(dtw_token_timestamps);
|
||||
DEF_INT_ATTR_METHOD(dtw_aheads_preset);
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_params_get_dtw_n_top(VALUE self) {
|
||||
ruby_whisper_context_params *rwcp;
|
||||
GetContextParams(self, rwcp);
|
||||
|
||||
int dtw_n_top = rwcp->params.dtw_n_top;
|
||||
|
||||
return dtw_n_top == -1 ? Qnil : INT2NUM(dtw_n_top);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_params_set_dtw_n_top(VALUE self, VALUE value) {
|
||||
ruby_whisper_context_params *rwcp;
|
||||
GetContextParams(self, rwcp);
|
||||
|
||||
rwcp->params.dtw_n_top = NIL_P(value) ? -1 : NUM2INT(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#define SET_PARAM_IF_SAME(param_name) \
|
||||
if (id == id_ ## param_name) { \
|
||||
ruby_whisper_context_params_set_ ## param_name(self, value); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_params_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
ruby_whisper_context_params *rwcp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_context_params, &ruby_whisper_context_params_type, rwcp);
|
||||
rwcp->params = whisper_context_default_params();
|
||||
|
||||
VALUE kw_hash;
|
||||
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
|
||||
if (NIL_P(kw_hash)) {
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
VALUE values[NUM_PARAMS] = {Qundef};
|
||||
rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
|
||||
|
||||
ID id;
|
||||
VALUE value;
|
||||
for (int i = 0; i < NUM_PARAMS; i++) {
|
||||
id = param_names[i];
|
||||
value = values[i];
|
||||
if (value == Qundef) {
|
||||
continue;
|
||||
}
|
||||
SET_PARAM_IF_SAME(use_gpu)
|
||||
SET_PARAM_IF_SAME(flash_attn)
|
||||
SET_PARAM_IF_SAME(gpu_device)
|
||||
SET_PARAM_IF_SAME(dtw_token_timestamps)
|
||||
SET_PARAM_IF_SAME(dtw_aheads_preset)
|
||||
SET_PARAM_IF_SAME(dtw_n_top)
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
#undef SET_PARAM_IF_SAME
|
||||
|
||||
void
|
||||
init_ruby_whisper_context_params(VALUE *cContext)
|
||||
{
|
||||
cContextParams = rb_define_class_under(*cContext, "Params", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cContextParams, ruby_whisper_context_params_s_allocate);
|
||||
rb_define_method(cContextParams, "initialize", ruby_whisper_context_params_initialize, -1);
|
||||
|
||||
DEFINE_PARAM(use_gpu, 0)
|
||||
DEFINE_PARAM(flash_attn, 1)
|
||||
DEFINE_PARAM(gpu_device, 2)
|
||||
DEFINE_PARAM(dtw_token_timestamps, 3)
|
||||
DEFINE_PARAM(dtw_aheads_preset, 4)
|
||||
DEFINE_PARAM(dtw_n_top, 5)
|
||||
}
|
||||
|
||||
#undef DEFINE_PARAM
|
||||
#undef DEF_INT_ATTR_METHOD
|
||||
#undef DEF_BOOLEAN_ATTR_METHOD
|
||||
#undef NUM_PARAMS
|
||||
52
bindings/ruby/ext/ruby_whisper_error.c
Normal file
52
bindings/ruby/ext/ruby_whisper_error.c
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <ruby.h>
|
||||
|
||||
extern VALUE eError;
|
||||
|
||||
VALUE ruby_whisper_error_initialize(VALUE self, VALUE code)
|
||||
{
|
||||
const int c_code = NUM2INT(code);
|
||||
const char *raw_message;
|
||||
switch (c_code) {
|
||||
case -2:
|
||||
raw_message = "failed to compute log mel spectrogram";
|
||||
break;
|
||||
case -3:
|
||||
raw_message = "failed to auto-detect language";
|
||||
break;
|
||||
case -4:
|
||||
raw_message = "too many decoders requested";
|
||||
break;
|
||||
case -5:
|
||||
raw_message = "audio_ctx is larger than the maximum allowed";
|
||||
break;
|
||||
case -6:
|
||||
raw_message = "failed to encode";
|
||||
break;
|
||||
case -7:
|
||||
raw_message = "whisper_kv_cache_init() failed for self-attention cache";
|
||||
break;
|
||||
case -8:
|
||||
raw_message = "failed to decode";
|
||||
break;
|
||||
case -9:
|
||||
raw_message = "failed to decode";
|
||||
break;
|
||||
default:
|
||||
raw_message = "unknown error";
|
||||
break;
|
||||
}
|
||||
const VALUE message = rb_str_new2(raw_message);
|
||||
rb_call_super(1, &message);
|
||||
rb_iv_set(self, "@code", code);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_error(VALUE *mWhisper)
|
||||
{
|
||||
eError = rb_define_class_under(*mWhisper, "Error", rb_eStandardError);
|
||||
|
||||
rb_define_attr(eError, "code", true, false);
|
||||
rb_define_method(eError, "initialize", ruby_whisper_error_initialize, 1);
|
||||
}
|
||||
180
bindings/ruby/ext/ruby_whisper_log_queue.c
Normal file
180
bindings/ruby/ext/ruby_whisper_log_queue.c
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define LOG_QUEUE_CAPACITY 256
|
||||
#define LOG_DEFAULT_CAPACITY 1024
|
||||
|
||||
void
|
||||
ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue)
|
||||
{
|
||||
rb_nativethread_lock_initialize(&log_queue->lock);
|
||||
rb_native_cond_initialize(&log_queue->cond);
|
||||
log_queue->head = 0;
|
||||
log_queue->tail = 0;
|
||||
log_queue->size = 0;
|
||||
log_queue->is_open = true;
|
||||
log_queue->logs = ALLOC_N(ruby_whisper_log, LOG_QUEUE_CAPACITY);
|
||||
for (size_t i = 0; i < LOG_QUEUE_CAPACITY; i++) {
|
||||
// we cannot call Ruby API like ALLOC_N because this slot may be realloced without GVL
|
||||
// this doesn't be freed because log queue lives until the end of process
|
||||
char *slot = malloc(sizeof(char) * LOG_QUEUE_CAPACITY);
|
||||
if (!slot) {
|
||||
rb_raise(rb_eRuntimeError, "Could not allocate memory for log text");
|
||||
}
|
||||
ruby_whisper_log log = {
|
||||
0,
|
||||
slot,
|
||||
0,
|
||||
LOG_QUEUE_CAPACITY,
|
||||
};
|
||||
log_queue->logs[i] = log;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue)
|
||||
{
|
||||
rb_nativethread_lock_lock(&log_queue->lock);
|
||||
|
||||
log_queue->is_open = true;
|
||||
|
||||
rb_native_cond_signal(&log_queue->cond);
|
||||
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
}
|
||||
|
||||
void
|
||||
ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue)
|
||||
{
|
||||
rb_nativethread_lock_lock(&log_queue->lock);
|
||||
|
||||
log_queue->is_open = false;
|
||||
rb_native_cond_broadcast(&log_queue->cond);
|
||||
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
}
|
||||
|
||||
static size_t
|
||||
calc_enough_cap(size_t len)
|
||||
{
|
||||
size_t quot = len / LOG_DEFAULT_CAPACITY;
|
||||
size_t rem = len % LOG_DEFAULT_CAPACITY;
|
||||
|
||||
return sizeof(char) * (rem == 0 ? quot : quot + 1) * LOG_DEFAULT_CAPACITY;
|
||||
}
|
||||
|
||||
void
|
||||
ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text)
|
||||
{
|
||||
rb_nativethread_lock_lock(&log_queue->lock);
|
||||
|
||||
if (!log_queue->is_open) {
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(text);
|
||||
ruby_whisper_log *log = &log_queue->logs[log_queue->head];
|
||||
if (len > log->capacity) {
|
||||
size_t new_cap = calc_enough_cap(len);
|
||||
// we cannot call Ruby API like REALLOC_N because this function is called without GVL
|
||||
char *slot = realloc(log->text, new_cap);
|
||||
if (!slot) {
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
return;
|
||||
}
|
||||
log->text = slot;
|
||||
log->capacity = new_cap;
|
||||
}
|
||||
// we cannot call Ruby API like MEMCPY because this function is called without GVL
|
||||
memcpy(log->text, text, sizeof(char) * len);
|
||||
log->length = len;
|
||||
log->level = level;
|
||||
log_queue->head = (log_queue->head + 1) % LOG_QUEUE_CAPACITY;
|
||||
bool is_full = log_queue->size >= LOG_QUEUE_CAPACITY;
|
||||
log_queue->size = is_full ? LOG_QUEUE_CAPACITY : log_queue->size + 1;
|
||||
if (is_full) {
|
||||
log_queue->tail = log_queue->head;
|
||||
}
|
||||
|
||||
rb_native_cond_signal(&log_queue->cond);
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
}
|
||||
|
||||
static void*
|
||||
ruby_whisper_log_queue_wait(void *args)
|
||||
{
|
||||
ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args;
|
||||
|
||||
rb_native_cond_wait(&log_queue->cond, &log_queue->lock);
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_log_queue_wait_ubf(void *args)
|
||||
{
|
||||
ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args;
|
||||
|
||||
rb_native_cond_broadcast(&log_queue->cond);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
enum ggml_log_level level;
|
||||
size_t length;
|
||||
char *text;
|
||||
} log_snapshot;
|
||||
|
||||
VALUE
|
||||
ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue)
|
||||
{
|
||||
log_snapshot logs[LOG_QUEUE_CAPACITY];
|
||||
|
||||
rb_nativethread_lock_lock(&log_queue->lock);
|
||||
|
||||
while (log_queue->size == 0 && log_queue->is_open) {
|
||||
rb_thread_call_without_gvl(ruby_whisper_log_queue_wait, (void *)log_queue, ruby_whisper_log_queue_wait_ubf, (void *)log_queue);
|
||||
rb_nativethread_lock_lock(&log_queue->lock);
|
||||
}
|
||||
|
||||
if (log_queue->size == 0 && !log_queue->is_open) {
|
||||
rb_native_cond_broadcast(&log_queue->cond);
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
size_t size = log_queue->size;
|
||||
ruby_whisper_log *log;
|
||||
size_t i;
|
||||
for (i = 0; i < size; i++) {
|
||||
log = &log_queue->logs[(log_queue->tail + i) % LOG_QUEUE_CAPACITY];
|
||||
logs[i].level = log->level;
|
||||
logs[i].length = log->length;
|
||||
char *text = malloc(log->length);
|
||||
if (!text) {
|
||||
logs[i].text = NULL;
|
||||
continue;
|
||||
}
|
||||
logs[i].text = text;
|
||||
memcpy(logs[i].text, log->text, log->length);
|
||||
}
|
||||
log_queue->size = 0;
|
||||
log_queue->tail = log_queue->head;
|
||||
|
||||
rb_native_cond_signal(&log_queue->cond);
|
||||
|
||||
rb_nativethread_lock_unlock(&log_queue->lock);
|
||||
|
||||
VALUE rb_logs = rb_ary_new2(size);
|
||||
VALUE rb_text;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!logs[i].text) {
|
||||
continue;
|
||||
}
|
||||
rb_text = rb_str_new(logs[i].text, logs[i].length);
|
||||
free(logs[i].text);
|
||||
rb_ary_push(rb_logs, rb_ary_new3(2, INT2NUM(logs[i].level), rb_text));
|
||||
}
|
||||
|
||||
return rb_logs;
|
||||
}
|
||||
46
bindings/ruby/ext/ruby_whisper_log_settable.h
Normal file
46
bindings/ruby/ext/ruby_whisper_log_settable.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef RUBY_WHISPER_LOG_SETTABLE_H
|
||||
#define RUBY_WHISPER_LOG_SETTABLE_H
|
||||
|
||||
#define LOG_SETTABLE_SETUP(log_queue, mod, log_set) \
|
||||
static VALUE \
|
||||
ruby_whisper_##log_queue##_s_drain_logs(VALUE self) \
|
||||
{ \
|
||||
return ruby_whisper_log_queue_drain(&log_queue); \
|
||||
} \
|
||||
static void \
|
||||
ruby_whisper_##log_queue##_log_callback(enum ggml_log_level level, const char *text, void *user_data) \
|
||||
{ \
|
||||
ruby_whisper_log_queue_enqueue(&log_queue, level, text); \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_##log_queue##_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) \
|
||||
{ \
|
||||
rb_iv_set(self, "@log_callback", log_callback); \
|
||||
rb_iv_set(self, "@log_callback_user_data", user_data); \
|
||||
if (NIL_P(log_callback)) { \
|
||||
log_set(NULL, NULL); \
|
||||
} else { \
|
||||
ruby_whisper_log_queue_open(&log_queue); \
|
||||
rb_funcall((mod), id_start_log_callback_thread, 0); \
|
||||
log_set(ruby_whisper_##log_queue##_log_callback, NULL); \
|
||||
} \
|
||||
return Qnil; \
|
||||
} \
|
||||
static void \
|
||||
ruby_whisper_##log_queue##_end_proc(VALUE args) \
|
||||
{ \
|
||||
ruby_whisper_log_queue_close(&log_queue); \
|
||||
VALUE log_callback_thread = rb_ivar_get(mod, id_log_callback_thread); \
|
||||
if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive_p, 0))) { \
|
||||
rb_funcall(log_callback_thread, id_join, 0); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_SETTABLE_INIT(log_queue, mod) \
|
||||
ruby_whisper_log_queue_initialize(&log_queue); \
|
||||
rb_define_singleton_method(mod, "drain_logs", ruby_whisper_##log_queue##_s_drain_logs, 0); \
|
||||
rb_define_singleton_method(mod, "log_set", ruby_whisper_##log_queue##_s_log_set, 2); \
|
||||
rb_set_end_proc(ruby_whisper_##log_queue##_end_proc, Qnil); \
|
||||
rb_extend_object(mod, mLogSettable);
|
||||
|
||||
#endif
|
||||
231
bindings/ruby/ext/ruby_whisper_model.c
Normal file
231
bindings/ruby/ext/ruby_whisper_model.c
Normal file
@@ -0,0 +1,231 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_type;
|
||||
|
||||
extern VALUE cModel;
|
||||
|
||||
static void rb_whisper_model_mark(void *p) {
|
||||
ruby_whisper_model *rwm = (ruby_whisper_model *)p;
|
||||
if (rwm->context) {
|
||||
rb_gc_mark(rwm->context);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_model_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_model *rwm = (const ruby_whisper_model *)p;
|
||||
size_t size = sizeof(rwm);
|
||||
if (!rwm) {
|
||||
return 0;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static const rb_data_type_t rb_whisper_model_type = {
|
||||
"ruby_whisper_model",
|
||||
{rb_whisper_model_mark, RUBY_DEFAULT_FREE, ruby_whisper_model_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE ruby_whisper_model_allocate(VALUE klass) {
|
||||
ruby_whisper_model *rwm;
|
||||
return TypedData_Make_Struct(klass, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
}
|
||||
|
||||
VALUE rb_whisper_model_s_new(VALUE context) {
|
||||
ruby_whisper_model *rwm;
|
||||
const VALUE model = ruby_whisper_model_allocate(cModel);
|
||||
TypedData_Get_Struct(model, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
rwm->context = context;
|
||||
return model;
|
||||
};
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_vocab -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_vocab(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_vocab(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_audio_ctx -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_audio_ctx(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_audio_ctx(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_audio_state -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_audio_state(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_audio_state(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_audio_head -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_audio_head(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_audio_head(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_audio_layer -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_audio_layer(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_audio_layer(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_text_ctx -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_text_ctx(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_text_ctx(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_text_state -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_text_state(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_text_state(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_text_head -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_text_head(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_text_head(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_text_layer -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_text_layer(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_text_layer(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* n_mels -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_n_mels(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_n_mels(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* ftype -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_ftype(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return INT2NUM(whisper_model_ftype(rw->context));
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* type -> String
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_model_type(VALUE self)
|
||||
{
|
||||
ruby_whisper_model *rwm;
|
||||
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rwm->context, rw);
|
||||
return rb_str_new2(whisper_model_type_readable(rw->context));
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_model(VALUE *mWhisper)
|
||||
{
|
||||
cModel = rb_define_class_under(*mWhisper, "Model", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cModel, ruby_whisper_model_allocate);
|
||||
rb_define_method(cModel, "n_vocab", ruby_whisper_model_n_vocab, 0);
|
||||
rb_define_method(cModel, "n_audio_ctx", ruby_whisper_model_n_audio_ctx, 0);
|
||||
rb_define_method(cModel, "n_audio_state", ruby_whisper_model_n_audio_state, 0);
|
||||
rb_define_method(cModel, "n_audio_head", ruby_whisper_model_n_audio_head, 0);
|
||||
rb_define_method(cModel, "n_audio_layer", ruby_whisper_model_n_audio_layer, 0);
|
||||
rb_define_method(cModel, "n_text_ctx", ruby_whisper_model_n_text_ctx, 0);
|
||||
rb_define_method(cModel, "n_text_state", ruby_whisper_model_n_text_state, 0);
|
||||
rb_define_method(cModel, "n_text_head", ruby_whisper_model_n_text_head, 0);
|
||||
rb_define_method(cModel, "n_text_layer", ruby_whisper_model_n_text_layer, 0);
|
||||
rb_define_method(cModel, "n_mels", ruby_whisper_model_n_mels, 0);
|
||||
rb_define_method(cModel, "ftype", ruby_whisper_model_ftype, 0);
|
||||
rb_define_method(cModel, "type", ruby_whisper_model_type, 0);
|
||||
}
|
||||
49
bindings/ruby/ext/ruby_whisper_parakeet.c
Normal file
49
bindings/ruby/ext/ruby_whisper_parakeet.c
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "ruby_whisper.h"
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern VALUE mParakeet;
|
||||
extern VALUE mLogSettable;
|
||||
extern VALUE cParakeetContext;
|
||||
extern VALUE cParakeetSegment;
|
||||
extern VALUE mOutputContext;
|
||||
extern VALUE mOutputSegment;
|
||||
|
||||
extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet);
|
||||
extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet);
|
||||
extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet);
|
||||
extern VALUE init_ruby_whisper_parakeet_context(VALUE *mParakeet);
|
||||
extern void init_ruby_whisper_parakeet_context_params(VALUE *cParakeetContext);
|
||||
extern void init_ruby_whisper_parakeet_model(VALUE *mParakeet);
|
||||
|
||||
static ruby_whisper_log_queue parakeet_log_queue;
|
||||
|
||||
LOG_SETTABLE_SETUP(parakeet_log_queue, mParakeet, parakeet_log_set)
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_s_system_info_str(VALUE self)
|
||||
{
|
||||
return rb_str_new2(parakeet_print_system_info());
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet(VALUE *mWhisper)
|
||||
{
|
||||
mParakeet = rb_define_module_under(*mWhisper, "Parakeet");
|
||||
|
||||
rb_define_const(mParakeet, "VERSION", rb_str_new2(parakeet_version()));
|
||||
|
||||
LOG_SETTABLE_INIT(parakeet_log_queue, mParakeet)
|
||||
|
||||
rb_define_singleton_method(mParakeet, "system_info_str", ruby_whisper_parakeet_s_system_info_str, 0);
|
||||
|
||||
init_ruby_whisper_parakeet_params(&mParakeet);
|
||||
init_ruby_whisper_parakeet_token(&mParakeet);
|
||||
init_ruby_whisper_parakeet_segment(&mParakeet);
|
||||
cParakeetContext = init_ruby_whisper_parakeet_context(&mParakeet);
|
||||
init_ruby_whisper_parakeet_context_params(&cParakeetContext);
|
||||
init_ruby_whisper_parakeet_model(&mParakeet);
|
||||
|
||||
rb_include_module(cParakeetContext, mOutputContext);
|
||||
rb_include_module(cParakeetSegment, mOutputSegment);
|
||||
}
|
||||
316
bindings/ruby/ext/ruby_whisper_parakeet_context.c
Normal file
316
bindings/ruby/ext/ruby_whisper_parakeet_context.c
Normal file
@@ -0,0 +1,316 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_SEGMENT_ATTRS(ITERATOR) \
|
||||
ITERATOR(get_segment_t0, LONG) \
|
||||
ITERATOR(get_segment_t1, LONG) \
|
||||
ITERATOR(get_segment_text, STRING) \
|
||||
ITERATOR(n_tokens, INT)
|
||||
|
||||
#define ITERATE_TOKEN_ATTRS(ITERATOR) \
|
||||
ITERATOR(get_token_text, STRING) \
|
||||
ITERATOR(get_token_id, INT) \
|
||||
ITERATOR(get_token_p, FLOAT)
|
||||
|
||||
#define VAL_FROM_LONG(v) LONG2NUM(v)
|
||||
#define VAL_FROM_STRING(v) rb_utf8_str_new_cstr(v)
|
||||
#define VAL_FROM_INT(v) INT2NUM(v)
|
||||
#define VAL_FROM_FLOAT(v) DBL2NUM(v)
|
||||
#define READER(type) VAL_FROM_##type
|
||||
|
||||
extern ID id_to_s;
|
||||
extern ID id___method__;
|
||||
extern ID id_to_enum;
|
||||
extern ID id_new;
|
||||
|
||||
extern VALUE cParakeetContext;
|
||||
extern VALUE eError;
|
||||
|
||||
extern VALUE ruby_whisper_normalize_model_path(VALUE model_path);
|
||||
extern VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params);
|
||||
extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index);
|
||||
extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples);
|
||||
extern VALUE release_samples(VALUE rb_parsed_args);
|
||||
extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data);
|
||||
extern rb_data_type_t ruby_whisper_parakeet_params_type;
|
||||
extern rb_data_type_t ruby_whisper_parakeet_context_params_type;
|
||||
extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data);
|
||||
extern VALUE ruby_whisper_parakeet_model_s_new(VALUE context);
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_context_free(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc = (ruby_whisper_parakeet_context *)p;
|
||||
if (rwpc->context) {
|
||||
parakeet_free(rwpc->context);
|
||||
rwpc->context = NULL;
|
||||
}
|
||||
xfree(rwpc);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_context_memsize(const void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc = (ruby_whisper_parakeet_context *)p;
|
||||
if (!rwpc) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = sizeof(*rwpc);
|
||||
return size;
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_parakeet_context_type = {
|
||||
"ruby_whisper_parakeet_context",
|
||||
{0, ruby_whisper_parakeet_context_free, ruby_whisper_parakeet_context_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, rwpc);
|
||||
rwpc->context = NULL;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
struct parakeet_context **context;
|
||||
char *model_path;
|
||||
struct parakeet_context_params params;
|
||||
} ruby_whisper_parakeet_context_init_args;
|
||||
|
||||
static void*
|
||||
ruby_whisper_parakeet_context_init_without_gvl(void *args)
|
||||
{
|
||||
ruby_whisper_parakeet_context_init_args *init_args = (ruby_whisper_parakeet_context_init_args *)args;
|
||||
*init_args->context = parakeet_init_from_file_with_params(init_args->model_path, init_args->params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
VALUE model_path;
|
||||
VALUE context_params;
|
||||
struct parakeet_context_params params;
|
||||
|
||||
rb_scan_args(argc, argv, "11", &model_path, &context_params);
|
||||
TypedData_Get_Struct(self, ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, rwpc);
|
||||
|
||||
model_path = ruby_whisper_normalize_model_path(model_path);
|
||||
if (!rb_respond_to(model_path, id_to_s)) {
|
||||
rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Parakeet::Context");
|
||||
}
|
||||
if (NIL_P(context_params)) {
|
||||
params = parakeet_context_default_params();
|
||||
} else {
|
||||
ruby_whisper_parakeet_context_params *rwpcp;
|
||||
GetParakeetContextParams(context_params, rwpcp);
|
||||
params = rwpcp->params;
|
||||
}
|
||||
ruby_whisper_parakeet_context_init_args init_args = {
|
||||
&rwpc->context,
|
||||
StringValueCStr(model_path),
|
||||
params,
|
||||
};
|
||||
rb_thread_call_without_gvl(ruby_whisper_parakeet_context_init_without_gvl, (void *)&init_args, NULL, NULL);
|
||||
if (rwpc->context == NULL) {
|
||||
rb_raise(rb_eRuntimeError, "Failed to load model");
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_full_n_segments(VALUE self)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(self, rwpc);
|
||||
|
||||
return INT2NUM(parakeet_full_n_segments(rwpc->context));
|
||||
}
|
||||
|
||||
#define DEF_SEGMENT_ATTR(name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_context_full_##name(VALUE self, VALUE i_segment) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_context *rwpc; \
|
||||
GetParakeetContext(self, rwpc); \
|
||||
return READER(type)(parakeet_full_##name(rwpc->context, NUM2INT(i_segment))); \
|
||||
}
|
||||
|
||||
ITERATE_SEGMENT_ATTRS(DEF_SEGMENT_ATTR)
|
||||
|
||||
#define DEF_TOKEN_ATTR(name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_context_full_##name(VALUE self, VALUE i_segment, VALUE i_token) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_context *rwpc; \
|
||||
GetParakeetContext(self, rwpc); \
|
||||
return READER(type)(parakeet_full_##name(rwpc->context, NUM2INT(i_segment), NUM2INT(i_token))); \
|
||||
}
|
||||
|
||||
ITERATE_TOKEN_ATTRS(DEF_TOKEN_ATTR)
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_full_get_token_data(VALUE self, VALUE i_segment, VALUE i_token)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(self, rwpc);
|
||||
parakeet_token_data token_data = parakeet_full_get_token_data(rwpc->context, NUM2INT(i_segment), NUM2INT(i_token));
|
||||
|
||||
return ruby_whisper_parakeet_token_s_from_token_data(rwpc->context, &token_data);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_each_segment(VALUE self)
|
||||
{
|
||||
if (!rb_block_given_p()) {
|
||||
const VALUE method_name = rb_funcall(self, id___method__, 0);
|
||||
return rb_funcall(self, id_to_enum, 1, method_name);
|
||||
}
|
||||
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(self, rwpc);
|
||||
|
||||
const int n_segments = parakeet_full_n_segments(rwpc->context);
|
||||
for (int i = 0; i < n_segments; ++i) {
|
||||
rb_yield(ruby_whisper_parakeet_segment_init(self, i));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
struct parakeet_context *context;
|
||||
struct parakeet_full_params *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
int result;
|
||||
} parakeet_full_without_gvl_args;
|
||||
|
||||
static void*
|
||||
parakeet_full_without_gvl(void *rb_args)
|
||||
{
|
||||
parakeet_full_without_gvl_args *args = (parakeet_full_without_gvl_args *)rb_args;
|
||||
args->result = parakeet_full(args->context, *args->params, args->samples, args->n_samples);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
ruby_whisper_abort_callback_user_data *abort_callback_user_data;
|
||||
} parakeet_full_ubf_args;
|
||||
|
||||
static void
|
||||
parakeet_full_ubf(void *rb_args)
|
||||
{
|
||||
parakeet_full_ubf_args *args = (parakeet_full_ubf_args *)rb_args;
|
||||
|
||||
RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1);
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_context_full_body(VALUE rb_args)
|
||||
{
|
||||
ruby_whisper_full_args *args = (ruby_whisper_full_args *)rb_args;
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(*args->context, rwpc);
|
||||
ruby_whisper_parakeet_params *rwpp;
|
||||
GetParakeetParams(*args->params, rwpp);
|
||||
|
||||
ruby_whisper_abort_callback_user_data abort_callback_user_data = {
|
||||
0,
|
||||
NULL,
|
||||
};
|
||||
ruby_whisper_parakeet_prepare_transcription(rwpp, args->context, &abort_callback_user_data);
|
||||
|
||||
parakeet_full_without_gvl_args full_without_gvl_args = {
|
||||
rwpc->context,
|
||||
&rwpp->params,
|
||||
args->samples,
|
||||
args->n_samples,
|
||||
0
|
||||
};
|
||||
parakeet_full_ubf_args full_ubf_args = {
|
||||
&abort_callback_user_data,
|
||||
};
|
||||
rb_thread_call_without_gvl(parakeet_full_without_gvl, (void *)&full_without_gvl_args, parakeet_full_ubf, (void *)&full_ubf_args);
|
||||
|
||||
return INT2NUM(full_without_gvl_args.result);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
if (argc < 2 || argc > 3) {
|
||||
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..3)", argc);
|
||||
}
|
||||
|
||||
VALUE n_samples = argc == 2 ? Qnil : argv[2];
|
||||
|
||||
struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples);
|
||||
ruby_whisper_full_args args = {
|
||||
&self,
|
||||
&argv[0],
|
||||
parsed.samples,
|
||||
parsed.n_samples,
|
||||
};
|
||||
VALUE rb_result = rb_ensure(ruby_whisper_parakeet_context_full_body, (VALUE)&args, release_samples, (VALUE)&parsed);
|
||||
const int result = NUM2INT(rb_result);
|
||||
if (result == 0) {
|
||||
return self;
|
||||
} else {
|
||||
rb_exc_raise(rb_funcall(eError, id_new, 1, rb_result));
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_free(VALUE self)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(self, rwpc);
|
||||
parakeet_free(rwpc->context);
|
||||
rwpc->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_get_model(VALUE self)
|
||||
{
|
||||
return ruby_whisper_parakeet_model_s_new(self);
|
||||
}
|
||||
|
||||
VALUE
|
||||
init_ruby_whisper_parakeet_context(VALUE *mParakeet)
|
||||
{
|
||||
cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cParakeetContext, ruby_whisper_parakeet_context_allocate);
|
||||
|
||||
rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1);
|
||||
rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2);
|
||||
rb_define_method(cParakeetContext, "full_n_segments", ruby_whisper_parakeet_context_full_n_segments, 0);
|
||||
rb_define_method(cParakeetContext, "full_get_token_data", ruby_whisper_parakeet_context_full_get_token_data, 2);
|
||||
rb_define_method(cParakeetContext, "model", ruby_whisper_parakeet_context_get_model, 0);
|
||||
rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0);
|
||||
rb_define_method(cParakeetContext, "full", ruby_whisper_parakeet_context_full, -1);
|
||||
rb_define_method(cParakeetContext, "free", ruby_whisper_parakeet_free, 0);
|
||||
|
||||
#define REGISTER_SEGMENT_ATTR(name, type) \
|
||||
rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 1);
|
||||
|
||||
ITERATE_SEGMENT_ATTRS(REGISTER_SEGMENT_ATTR)
|
||||
|
||||
#define REGISTER_TOKEN_ATTR(name, type) \
|
||||
rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 2);
|
||||
|
||||
ITERATE_TOKEN_ATTRS(REGISTER_TOKEN_ATTR)
|
||||
|
||||
return cParakeetContext;
|
||||
}
|
||||
117
bindings/ruby/ext/ruby_whisper_parakeet_context_params.c
Normal file
117
bindings/ruby/ext/ruby_whisper_parakeet_context_params.c
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_ATTRS(ITERATOR) \
|
||||
ITERATOR(use_gpu, BOOL) \
|
||||
ITERATOR(gpu_device, INT)
|
||||
|
||||
#define VAL_FROM_BOOL(v) ((v) ? Qtrue : Qfalse)
|
||||
#define VAL_TO_BOOL(v) (RTEST(v))
|
||||
#define VAL_FROM_INT(v) (INT2NUM(v))
|
||||
#define VAL_TO_INT(v) (NUM2INT(v))
|
||||
#define READER(type) VAL_FROM_##type
|
||||
#define WRITER(type) VAL_TO_##type
|
||||
|
||||
#define DEF_ATTR(name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_context_params_get_##name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_context_params *rwpcp; \
|
||||
GetParakeetContextParams(self, rwpcp); \
|
||||
return READER(type)(rwpcp->params.name); \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_context_params_set_##name(VALUE self, VALUE val) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_context_params *rwpcp; \
|
||||
GetParakeetContextParams(self, rwpcp); \
|
||||
rwpcp->params.name = WRITER(type)(val); \
|
||||
return val; \
|
||||
}
|
||||
|
||||
enum {
|
||||
#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_CONTEXT_PARAMS_##name,
|
||||
|
||||
ITERATE_ATTRS(DEF_IDX)
|
||||
RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS
|
||||
};
|
||||
|
||||
extern VALUE cParakeetContextParams;
|
||||
|
||||
typedef VALUE (*param_writer_t)(VALUE, VALUE);
|
||||
|
||||
static ID param_names[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS];
|
||||
static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS];
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_context_params_memsize(const void *p)
|
||||
{
|
||||
if (!p) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(ruby_whisper_parakeet_context_params);
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_parakeet_context_params_type = {
|
||||
"ruby_whisper_parakeet_context_params",
|
||||
{0, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_context_params_memsize,},
|
||||
0, 0,
|
||||
0,
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_params_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_context_params *rwpcp;
|
||||
return TypedData_Make_Struct(klass, ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, rwpcp);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_params_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
VALUE kw_hash;
|
||||
VALUE values[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS] = {Qundef};
|
||||
VALUE value;
|
||||
ruby_whisper_parakeet_context_params *rwpcp;
|
||||
int i;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, rwpcp);
|
||||
rwpcp->params = parakeet_context_default_params();
|
||||
|
||||
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
|
||||
if (NIL_P(kw_hash)) {
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS, values);
|
||||
for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS; i++) {
|
||||
value = values[i];
|
||||
if (value == Qundef) {
|
||||
continue;
|
||||
}
|
||||
param_writers[i](self, value);
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
ITERATE_ATTRS(DEF_ATTR)
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet_context_params(VALUE *cParakeetContext)
|
||||
{
|
||||
cParakeetContextParams = rb_define_class_under(*cParakeetContext, "Params", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cParakeetContextParams, ruby_whisper_parakeet_context_params_s_allocate);
|
||||
|
||||
rb_define_method(cParakeetContextParams, "initialize", ruby_whisper_parakeet_context_params_initialize, -1);
|
||||
|
||||
int i = 0;
|
||||
#define REGISTER_ATTR(name, type) \
|
||||
param_names[i] = rb_intern(#name); \
|
||||
param_writers[i] = ruby_whisper_parakeet_context_params_set_##name; \
|
||||
rb_define_method(cParakeetContextParams, #name, ruby_whisper_parakeet_context_params_get_##name, 0); \
|
||||
rb_define_method(cParakeetContextParams, #name "=", ruby_whisper_parakeet_context_params_set_##name, 1); \
|
||||
i++;
|
||||
|
||||
ITERATE_ATTRS(REGISTER_ATTR)
|
||||
}
|
||||
84
bindings/ruby/ext/ruby_whisper_parakeet_model.c
Normal file
84
bindings/ruby/ext/ruby_whisper_parakeet_model.c
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_ATTRS(ITERATOR) \
|
||||
ITERATOR(n_vocab) \
|
||||
ITERATOR(n_audio_ctx) \
|
||||
ITERATOR(n_audio_state) \
|
||||
ITERATOR(n_audio_head) \
|
||||
ITERATOR(n_audio_layer) \
|
||||
ITERATOR(n_mels) \
|
||||
ITERATOR(ftype)
|
||||
|
||||
extern rb_data_type_t ruby_whisper_parakeet_context_type;
|
||||
extern VALUE cParakeetModel;
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_model_mark(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_model *rwpm = (ruby_whisper_parakeet_model *)p;
|
||||
if (!NIL_P(rwpm->context)) {
|
||||
rb_gc_mark(rwpm->context);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_model_memsize(const void *p)
|
||||
{
|
||||
if (!p) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(ruby_whisper_parakeet_model);
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_parakeet_model_type = {
|
||||
"ruby_whisper_parakeet_model",
|
||||
{ruby_whisper_parakeet_model_mark, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_model_memsize},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_model_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_model *rwpm;
|
||||
VALUE model = TypedData_Make_Struct(klass, ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, rwpm);
|
||||
rwpm->context = Qnil;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_model_s_new(VALUE context)
|
||||
{
|
||||
const VALUE model = ruby_whisper_parakeet_model_s_allocate(cParakeetModel);
|
||||
ruby_whisper_parakeet_model *rwpm;
|
||||
TypedData_Get_Struct(model, ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, rwpm);
|
||||
rwpm->context = context;
|
||||
return model;
|
||||
}
|
||||
|
||||
#define DEF_ATTR(name) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_model_get_##name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_model *rwpm; \
|
||||
ruby_whisper_parakeet_context *rwpc; \
|
||||
GetParakeetModel(self, rwpm); \
|
||||
GetParakeetContext(rwpm->context, rwpc); \
|
||||
return INT2NUM(parakeet_model_##name(rwpc->context)); \
|
||||
}
|
||||
|
||||
ITERATE_ATTRS(DEF_ATTR)
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet_model(VALUE *mParakeet)
|
||||
{
|
||||
cParakeetModel = rb_define_class_under(*mParakeet, "Model", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cParakeetModel, ruby_whisper_parakeet_model_s_allocate);
|
||||
|
||||
#define REGISTER_ATTR(name) \
|
||||
rb_define_method(cParakeetModel, #name, ruby_whisper_parakeet_model_get_##name, 0);
|
||||
|
||||
ITERATE_ATTRS(REGISTER_ATTR)
|
||||
}
|
||||
548
bindings/ruby/ext/ruby_whisper_parakeet_params.c
Normal file
548
bindings/ruby/ext/ruby_whisper_parakeet_params.c
Normal file
@@ -0,0 +1,548 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_PARAMS(ITERATOR) \
|
||||
ITERATOR(n_threads, INT) \
|
||||
ITERATOR(offset_ms, INT) \
|
||||
ITERATOR(duration_ms, INT) \
|
||||
ITERATOR(no_context, BOOL) \
|
||||
ITERATOR(audio_ctx, INT)
|
||||
|
||||
#define ITERATE_NORMAL_CALLBACK_NAMES(ITERATOR, DATA) \
|
||||
ITERATOR(new_segment, DATA) \
|
||||
ITERATOR(new_token, DATA) \
|
||||
ITERATOR(progress, DATA) \
|
||||
ITERATOR(encoder_begin, DATA)
|
||||
|
||||
#define ITERATE_NORMAL_CALLBACK_PARAM(name, ITERATOR) ITERATOR(name##_callback)
|
||||
#define ITERATE_NORMAL_CALLBACK_PARAMS(ITERATOR) \
|
||||
ITERATE_NORMAL_CALLBACK_NAMES(ITERATE_NORMAL_CALLBACK_PARAM, ITERATOR)
|
||||
|
||||
#define ITERATE_CALLBACK_PARAMS(ITERATOR) \
|
||||
ITERATE_NORMAL_CALLBACK_PARAMS(ITERATOR) \
|
||||
ITERATOR(abort_callback)
|
||||
|
||||
enum {
|
||||
#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_PARAM_##name,
|
||||
#define DEF_IDX_CALLBACK(name) RUBY_WHISPER_PARAKEET_PARAM_##name,
|
||||
#define DEF_IDX_USER_DATA(name) RUBY_WHISPER_PARAKEET_PARAM_##name##_user_data,
|
||||
ITERATE_PARAMS(DEF_IDX)
|
||||
ITERATE_CALLBACK_PARAMS(DEF_IDX_CALLBACK)
|
||||
ITERATE_CALLBACK_PARAMS(DEF_IDX_USER_DATA)
|
||||
|
||||
RUBY_WHISPER_PARAKEET_NUM_PARAMS
|
||||
};
|
||||
|
||||
#define VAL_TO_INT(v) (NUM2INT(v))
|
||||
#define VAL_FROM_INT(v) (INT2NUM(v))
|
||||
#define VAL_TO_BOOL(v) (RTEST(v))
|
||||
#define VAL_FROM_BOOL(v) (v ? Qtrue : Qfalse)
|
||||
|
||||
extern VALUE cParakeetParams;
|
||||
extern ID id_call;
|
||||
|
||||
extern void ruby_whisper_callback_container_mark(ruby_whisper_callback_container *rwc);
|
||||
extern ruby_whisper_callback_container* ruby_whisper_callback_container_allocate(void);
|
||||
extern bool ruby_whisper_callback_container_is_present(const ruby_whisper_callback_container *container);
|
||||
extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index);
|
||||
extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data);
|
||||
|
||||
static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS];
|
||||
typedef VALUE (*param_writer_t)(VALUE, VALUE);
|
||||
static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS];
|
||||
|
||||
typedef struct {
|
||||
const ruby_whisper_callback_container *container;
|
||||
struct parakeet_state *state;
|
||||
int n_new;
|
||||
} call_parakeet_new_segment_callbacks_args;
|
||||
|
||||
static void*
|
||||
call_parakeet_new_segment_callbacks(void *v_args)
|
||||
{
|
||||
call_parakeet_new_segment_callbacks_args *args = (call_parakeet_new_segment_callbacks_args *)v_args;
|
||||
const ruby_whisper_callback_container *container = args->container;
|
||||
|
||||
if (!NIL_P(container->callback)) {
|
||||
rb_funcall(container->callback, id_call, 4, *container->context, Qnil, INT2NUM(args->n_new), container->user_data);
|
||||
}
|
||||
if (NIL_P(container->callbacks)) {
|
||||
return NULL;
|
||||
}
|
||||
const long n_callbacks = RARRAY_LEN(container->callbacks);
|
||||
if (n_callbacks == 0) {
|
||||
return NULL;
|
||||
}
|
||||
const int n_segments = parakeet_full_n_segments_from_state(args->state);
|
||||
for (int i = args->n_new; i > 0; i--) {
|
||||
int i_segment = n_segments - i;
|
||||
VALUE segment = ruby_whisper_parakeet_segment_init(*container->context, i_segment);
|
||||
for (int j = 0; j < n_callbacks; j++) {
|
||||
VALUE cb = rb_ary_entry(container->callbacks, j);
|
||||
rb_funcall(cb, id_call, 1, segment);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_new_segment_callback(struct parakeet_context *context, struct parakeet_state *state, int n_new, void *user_data)
|
||||
{
|
||||
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||
if (!ruby_whisper_callback_container_is_present(container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_parakeet_new_segment_callbacks_args args = {
|
||||
container,
|
||||
state,
|
||||
n_new,
|
||||
};
|
||||
rb_thread_call_with_gvl(call_parakeet_new_segment_callbacks, (void *)&args);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const ruby_whisper_callback_container *container;
|
||||
struct parakeet_context *context;
|
||||
struct parakeet_state *state;
|
||||
const parakeet_token_data *token_data;
|
||||
} call_parakeet_new_token_callbacks_args;
|
||||
|
||||
static void*
|
||||
call_parakeet_new_token_callbacks(void *v_args)
|
||||
{
|
||||
call_parakeet_new_token_callbacks_args *args = (call_parakeet_new_token_callbacks_args *)v_args;
|
||||
VALUE token = Qnil;
|
||||
const ruby_whisper_callback_container *container = args->container;
|
||||
|
||||
if (!NIL_P(container->callback)) {
|
||||
token = ruby_whisper_parakeet_token_s_from_token_data(args->context, args->token_data);
|
||||
rb_funcall(container->callback, id_call, 4, *container->context, Qnil, token, container->user_data);
|
||||
}
|
||||
if (NIL_P(container->callbacks)) {
|
||||
return NULL;
|
||||
}
|
||||
const long n_callbacks = RARRAY_LEN(container->callbacks);
|
||||
if (n_callbacks == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (NIL_P(token)) {
|
||||
token = ruby_whisper_parakeet_token_s_from_token_data(args->context, args->token_data);
|
||||
}
|
||||
for (int i = 0; i < n_callbacks; i++) {
|
||||
VALUE cb = rb_ary_entry(container->callbacks, i);
|
||||
rb_funcall(cb, id_call, 1, token);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_new_token_callback(struct parakeet_context *context, struct parakeet_state *state, const parakeet_token_data *token_data, void *user_data)
|
||||
{
|
||||
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||
if (!ruby_whisper_callback_container_is_present(container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_parakeet_new_token_callbacks_args args = {
|
||||
container,
|
||||
context,
|
||||
state,
|
||||
token_data,
|
||||
};
|
||||
rb_thread_call_with_gvl(call_parakeet_new_token_callbacks, (void *)&args);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const ruby_whisper_callback_container *container;
|
||||
struct parakeet_state *state;
|
||||
int progress;
|
||||
} call_parakeet_progress_callbacks_args;
|
||||
|
||||
static void*
|
||||
call_parakeet_progress_callback(void *v_args)
|
||||
{
|
||||
call_parakeet_progress_callbacks_args *args = (call_parakeet_progress_callbacks_args *)v_args;
|
||||
const ruby_whisper_callback_container *container = args->container;
|
||||
|
||||
if (!NIL_P(container->callback)) {
|
||||
rb_funcall(container->callback, id_call, 4, *container->context, Qnil, INT2NUM(args->progress), container->user_data);
|
||||
}
|
||||
if (NIL_P(container->callbacks)) {
|
||||
return NULL;
|
||||
}
|
||||
const long n_callbacks = RARRAY_LEN(container->callbacks);
|
||||
if (n_callbacks == 0) {
|
||||
return NULL;
|
||||
}
|
||||
for (long i = 0; i < n_callbacks; i++) {
|
||||
VALUE cb = rb_ary_entry(container->callbacks, i);
|
||||
rb_funcall(cb, id_call, 1, INT2NUM(args->progress));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_progress_callback(struct parakeet_context *context, struct parakeet_state *state, int progress, void *user_data)
|
||||
{
|
||||
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||
if (!ruby_whisper_callback_container_is_present(container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_parakeet_progress_callbacks_args args = {
|
||||
container,
|
||||
state,
|
||||
progress,
|
||||
};
|
||||
rb_thread_call_with_gvl(call_parakeet_progress_callback, (void *)&args);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const ruby_whisper_callback_container *container;
|
||||
struct parakeet_state *state;
|
||||
bool is_continued;
|
||||
} call_parakeet_encoder_begin_callbacks_args;
|
||||
|
||||
static void*
|
||||
call_parakeet_encoder_begin_callbacks(void *v_args)
|
||||
{
|
||||
call_parakeet_encoder_begin_callbacks_args *args = (call_parakeet_encoder_begin_callbacks_args *)v_args;
|
||||
const ruby_whisper_callback_container *container = args->container;
|
||||
VALUE result = Qnil;
|
||||
|
||||
if (!NIL_P(container->callback)) {
|
||||
result = rb_funcall(container->callback, id_call, 3, *container->context, Qnil, container->user_data);
|
||||
if (result == Qfalse) {
|
||||
args->is_continued = false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (NIL_P(container->callbacks)) {
|
||||
return NULL;
|
||||
}
|
||||
const long n_callbacks = RARRAY_LEN(container->callbacks);
|
||||
if (n_callbacks == 0) {
|
||||
return NULL;
|
||||
}
|
||||
for (long i = 0; i < n_callbacks; i++) {
|
||||
VALUE cb = rb_ary_entry(container->callbacks, i);
|
||||
result = rb_funcall(cb, id_call, 0);
|
||||
if (result == Qfalse) {
|
||||
args->is_continued = false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
ruby_whisper_parakeet_encoder_begin_callback(struct parakeet_context *context, struct parakeet_state *state, void *user_data)
|
||||
{
|
||||
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||
if (!ruby_whisper_callback_container_is_present(container)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
call_parakeet_encoder_begin_callbacks_args args = {
|
||||
container,
|
||||
state,
|
||||
true,
|
||||
};
|
||||
rb_thread_call_with_gvl(call_parakeet_encoder_begin_callbacks, (void *)&args);
|
||||
|
||||
return args.is_continued;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const ruby_whisper_callback_container *container;
|
||||
bool is_interrupted;
|
||||
} call_parakeet_abort_callbacks_args;
|
||||
|
||||
static void*
|
||||
call_parakeet_abort_callbacks(void *v_args)
|
||||
{
|
||||
call_parakeet_abort_callbacks_args *args = (call_parakeet_abort_callbacks_args *)v_args;
|
||||
const ruby_whisper_callback_container *container = args->container;
|
||||
VALUE result = Qnil;
|
||||
|
||||
if (!NIL_P(container->callback)) {
|
||||
result = rb_funcall(container->callback, id_call, 1, container->user_data);
|
||||
if (RTEST(result)) {
|
||||
args->is_interrupted = true;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (NIL_P(container->callbacks)) {
|
||||
return NULL;
|
||||
}
|
||||
const long n_callbacks = RARRAY_LEN(container->callbacks);
|
||||
if (n_callbacks == 0) {
|
||||
return NULL;
|
||||
}
|
||||
VALUE cb;
|
||||
for (long i = 0; i < n_callbacks; i++) {
|
||||
cb = rb_ary_entry(container->callbacks, i);
|
||||
result = rb_funcall(cb, id_call, 0);
|
||||
if (RTEST(result)) {
|
||||
args->is_interrupted = true;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
ruby_whisper_parakeet_abort_callback(void *user_data)
|
||||
{
|
||||
ruby_whisper_abort_callback_user_data *data = (ruby_whisper_abort_callback_user_data *)user_data;
|
||||
|
||||
int is_interrupted = RUBY_ATOMIC_LOAD(data->is_interrupted);
|
||||
if (is_interrupted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(data->callback_container) || !ruby_whisper_callback_container_is_present(data->callback_container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
call_parakeet_abort_callbacks_args args = {
|
||||
data->callback_container,
|
||||
false,
|
||||
};
|
||||
rb_thread_call_with_gvl(call_parakeet_abort_callbacks, (void *)&args);
|
||||
|
||||
return args.is_interrupted;
|
||||
}
|
||||
|
||||
#define CALLBACK_CONTAINER_NAME(name) name ## _container
|
||||
|
||||
void
|
||||
ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data)
|
||||
{
|
||||
#define PARAM_NAME(name) name
|
||||
#define USER_DATA_NAME(name) name##_user_data
|
||||
#define REGISTER_CALLBACK(name) \
|
||||
if (ruby_whisper_callback_container_is_present(rwpp->CALLBACK_CONTAINER_NAME(name))) { \
|
||||
rwpp->CALLBACK_CONTAINER_NAME(name)->context = context; \
|
||||
rwpp->params.PARAM_NAME(name) = ruby_whisper_parakeet_##name; \
|
||||
rwpp->params.USER_DATA_NAME(name) = rwpp->CALLBACK_CONTAINER_NAME(name); \
|
||||
}
|
||||
|
||||
ITERATE_NORMAL_CALLBACK_PARAMS(REGISTER_CALLBACK)
|
||||
|
||||
if (ruby_whisper_callback_container_is_present(rwpp->abort_callback_container)) {
|
||||
abort_callback_user_data->callback_container = rwpp->abort_callback_container;
|
||||
}
|
||||
rwpp->params.abort_callback = ruby_whisper_parakeet_abort_callback;
|
||||
rwpp->params.abort_callback_user_data = (void *)abort_callback_user_data;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_params_mark(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p;
|
||||
|
||||
#define MARK_CONTAINER(name) \
|
||||
if (rwpp->name##_container) { \
|
||||
ruby_whisper_callback_container_mark(rwpp->name##_container); \
|
||||
}
|
||||
|
||||
ITERATE_CALLBACK_PARAMS(MARK_CONTAINER)
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_params_free(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p;
|
||||
|
||||
#define FREE_CONTAINER(name) \
|
||||
if (rwpp->name##_container) { \
|
||||
xfree(rwpp->name##_container); \
|
||||
}
|
||||
|
||||
ITERATE_CALLBACK_PARAMS(FREE_CONTAINER)
|
||||
|
||||
xfree(rwpp);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_params_memsize(const void *p)
|
||||
{
|
||||
const struct ruby_whisper_parakeet_params *params = p;
|
||||
if (!params) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(ruby_whisper_parakeet_params);
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_parakeet_params_type = {
|
||||
"ruby_whisper_parakeet_params",
|
||||
{ruby_whisper_parakeet_params_mark, ruby_whisper_parakeet_params_free, ruby_whisper_parakeet_params_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
#define READER(type) VAL_FROM_##type
|
||||
#define WRITER(type) VAL_TO_##type
|
||||
#define DEF_PARAM_ATTR(name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_get_##name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
return READER(type)(rwpp->params.name); \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
rwpp->params.name = WRITER(type)(val); \
|
||||
return val; \
|
||||
}
|
||||
|
||||
#define DEF_CALLBACK_PARAM_ATTR(name) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_get_##name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
return rwpp->CALLBACK_CONTAINER_NAME(name)->callback; \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
rwpp->CALLBACK_CONTAINER_NAME(name)->callback = (val); \
|
||||
return val; \
|
||||
}
|
||||
|
||||
#define DEF_USER_DATA_PARAM_ATTR(name) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_get_##name##_user_data(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
return rwpp->CALLBACK_CONTAINER_NAME(name)->user_data; \
|
||||
} \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_set_##name##_user_data(VALUE self, VALUE val) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
rwpp->CALLBACK_CONTAINER_NAME(name)->user_data = val; \
|
||||
return val; \
|
||||
}
|
||||
|
||||
#define DEF_HOOK(name, data) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_params_on_##name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_params *rwpp; \
|
||||
GetParakeetParams(self, rwpp); \
|
||||
const VALUE blk = rb_block_proc(); \
|
||||
if (NIL_P(rwpp->name##_callback_container->callbacks)) { \
|
||||
rwpp->name##_callback_container->callbacks = rb_ary_new(); \
|
||||
} \
|
||||
rb_ary_push(rwpp->name##_callback_container->callbacks, blk); \
|
||||
return Qnil; \
|
||||
}
|
||||
|
||||
ITERATE_PARAMS(DEF_PARAM_ATTR)
|
||||
ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR)
|
||||
ITERATE_CALLBACK_PARAMS(DEF_USER_DATA_PARAM_ATTR)
|
||||
ITERATE_NORMAL_CALLBACK_NAMES(DEF_HOOK, _)
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_params_abort_on(VALUE self)
|
||||
{
|
||||
ruby_whisper_parakeet_params *rwpp;
|
||||
GetParakeetParams(self, rwpp);
|
||||
const VALUE blk = rb_block_proc();
|
||||
if (NIL_P(rwpp->abort_callback_container->callbacks)) {
|
||||
rwpp->abort_callback_container->callbacks = rb_ary_new();
|
||||
}
|
||||
rb_ary_push(rwpp->abort_callback_container->callbacks, blk);
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_params_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_params *rwpp;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp);
|
||||
rwpp->params = parakeet_full_default_params(PARAKEET_SAMPLING_GREEDY);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
VALUE kw_hash;
|
||||
VALUE values[RUBY_WHISPER_PARAKEET_NUM_PARAMS] = {Qundef};
|
||||
VALUE value;
|
||||
ruby_whisper_parakeet_params *rwpp;
|
||||
int i;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp);
|
||||
|
||||
#define INIT_CONTAINER(name) rwpp->name##_container = ruby_whisper_callback_container_allocate();
|
||||
|
||||
ITERATE_CALLBACK_PARAMS(INIT_CONTAINER)
|
||||
|
||||
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
|
||||
if (NIL_P(kw_hash)) {
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_PARAMS, values);
|
||||
|
||||
for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) {
|
||||
value = values[i];
|
||||
if (value == Qundef) {
|
||||
continue;
|
||||
}
|
||||
param_writers[i](self, value);
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet_params(VALUE *mParakeet)
|
||||
{
|
||||
cParakeetParams = rb_define_class_under(*mParakeet, "Params", rb_cObject);
|
||||
rb_define_alloc_func(cParakeetParams, ruby_whisper_parakeet_params_s_allocate);
|
||||
|
||||
rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1);
|
||||
|
||||
int i = 0;
|
||||
#define REGISTER_PARAM(name) \
|
||||
param_names[i] = rb_intern(#name); \
|
||||
param_writers[i] = ruby_whisper_parakeet_params_set_##name; \
|
||||
rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \
|
||||
rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \
|
||||
i++;
|
||||
|
||||
#define REGISTER_PARAM_ATTR(name, type) REGISTER_PARAM(name)
|
||||
#define REGISTER_CALLBACK_PARAM_ATTR(name) REGISTER_PARAM(name)
|
||||
#define REGISTER_USER_DATA_PARAM_ATTR(name) REGISTER_PARAM(name##_user_data)
|
||||
|
||||
ITERATE_PARAMS(REGISTER_PARAM_ATTR)
|
||||
ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR)
|
||||
ITERATE_CALLBACK_PARAMS(REGISTER_USER_DATA_PARAM_ATTR)
|
||||
|
||||
#define REGISTER_HOOK(name, data) \
|
||||
rb_define_method(cParakeetParams, "on_" #name, ruby_whisper_parakeet_params_on_##name, 0);
|
||||
|
||||
ITERATE_NORMAL_CALLBACK_NAMES(REGISTER_HOOK, _)
|
||||
|
||||
rb_define_method(cParakeetParams, "abort_on", ruby_whisper_parakeet_params_abort_on, 0);
|
||||
}
|
||||
157
bindings/ruby/ext/ruby_whisper_parakeet_segment.c
Normal file
157
bindings/ruby/ext/ruby_whisper_parakeet_segment.c
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_ATTRS(ITERATOR) \
|
||||
ITERATOR(start_time, t0, TIME) \
|
||||
ITERATOR(end_time, t1, TIME) \
|
||||
ITERATOR(text, text, STRING)
|
||||
|
||||
enum {
|
||||
#define DEF_IDX(name, c_name, type) RUBY_WHISPER_PARAKEET_SEGMENT_##name,
|
||||
|
||||
ITERATE_ATTRS(DEF_IDX)
|
||||
RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS,
|
||||
};
|
||||
|
||||
#define VAL_FROM_TIME(v) (LONG2NUM((v) * 10))
|
||||
#define VAL_FROM_STRING(v) (rb_str_new2(v))
|
||||
#define READER(type) VAL_FROM_##type
|
||||
#define DEF_ATTR(rb_name, c_name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_get_##rb_name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_segment *rwps; \
|
||||
GetParakeetSegment(self, rwps); \
|
||||
ruby_whisper_parakeet_context *rwpc; \
|
||||
GetParakeetContext(rwps->context, rwpc); \
|
||||
return READER(type)(parakeet_full_get_segment_##c_name(rwpc->context, rwps->index)); \
|
||||
}
|
||||
|
||||
extern ID id___method__;
|
||||
extern ID id_to_enum;
|
||||
extern VALUE cParakeetSegment;
|
||||
extern VALUE sym_start_time;
|
||||
extern VALUE sym_end_time;
|
||||
extern VALUE sym_text;
|
||||
extern const rb_data_type_t ruby_whisper_parakeet_context_type;
|
||||
extern VALUE ruby_whisper_parakeet_token_s_from_index(struct parakeet_context *context, int i_segment, int i_token);
|
||||
|
||||
static void
|
||||
rb_whisper_parakeet_segment_mark(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_segment *rwps = (ruby_whisper_parakeet_segment *)p;
|
||||
rb_gc_mark(rwps->context);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_segment_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_parakeet_segment *rwps = (const ruby_whisper_parakeet_segment *)p;
|
||||
if (!rwps) {
|
||||
return 0;
|
||||
}
|
||||
return sizeof(*rwps);
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_parakeet_segment_type = {
|
||||
"ruby_whisper_parakeet_segment",
|
||||
{rb_whisper_parakeet_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_segment_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_segment_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_segment *rwps;
|
||||
return TypedData_Make_Struct(klass, ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, rwps);
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_segment_init(VALUE context, int index)
|
||||
{
|
||||
ruby_whisper_parakeet_segment *rwps;
|
||||
|
||||
const VALUE segment = ruby_whisper_parakeet_segment_s_allocate(cParakeetSegment);
|
||||
TypedData_Get_Struct(segment, ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, rwps);
|
||||
rwps->context = context;
|
||||
rwps->index = index;
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
ITERATE_ATTRS(DEF_ATTR)
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_segment_each_token(VALUE self)
|
||||
{
|
||||
if (!rb_block_given_p()) {
|
||||
const VALUE method_name = rb_funcall(self, id___method__, 0);
|
||||
return rb_funcall(self, id_to_enum, 1, method_name);
|
||||
}
|
||||
|
||||
ruby_whisper_parakeet_segment *rwps;
|
||||
GetParakeetSegment(self, rwps);
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(rwps->context, rwpc);
|
||||
|
||||
const int n_tokens = parakeet_full_n_tokens(rwpc->context, rwps->index);
|
||||
for (int i = 0; i < n_tokens; i++) {
|
||||
rb_yield(ruby_whisper_parakeet_token_s_from_index(rwpc->context, rwps->index, i));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_segment_deconstruct_keys(VALUE self, VALUE keys)
|
||||
{
|
||||
ruby_whisper_parakeet_segment *rwps;
|
||||
GetParakeetSegment(self, rwps);
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(rwps->context, rwpc);
|
||||
|
||||
VALUE hash = rb_hash_new();
|
||||
long n_keys;
|
||||
if (NIL_P(keys)) {
|
||||
keys = rb_ary_new3(
|
||||
RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS,
|
||||
sym_start_time,
|
||||
sym_end_time,
|
||||
sym_text
|
||||
);
|
||||
n_keys = RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS;
|
||||
} else {
|
||||
n_keys = RARRAY_LEN(keys);
|
||||
if (n_keys > RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS) {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n_keys; i++) {
|
||||
VALUE key = rb_ary_entry(keys, i);
|
||||
|
||||
#define CHECK_AND_SET_KEY(rb_name, c_name, type) \
|
||||
if (key == sym_##rb_name) { \
|
||||
rb_hash_aset(hash, key, ruby_whisper_parakeet_get_##rb_name(self)); \
|
||||
}
|
||||
|
||||
ITERATE_ATTRS(CHECK_AND_SET_KEY)
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet_segment(VALUE *mParakeet)
|
||||
{
|
||||
cParakeetSegment = rb_define_class_under(*mParakeet, "Segment", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cParakeetSegment, ruby_whisper_parakeet_segment_s_allocate);
|
||||
|
||||
#define REGISTER_ATTR(rb_name, c_name, type) \
|
||||
rb_define_method(cParakeetSegment, #rb_name, ruby_whisper_parakeet_get_##rb_name, 0);
|
||||
|
||||
ITERATE_ATTRS(REGISTER_ATTR)
|
||||
|
||||
rb_define_method(cParakeetSegment, "each_token", ruby_whisper_parakeet_segment_each_token, 0);
|
||||
rb_define_method(cParakeetSegment, "deconstruct_keys", ruby_whisper_parakeet_segment_deconstruct_keys, 1);
|
||||
}
|
||||
188
bindings/ruby/ext/ruby_whisper_parakeet_token.c
Normal file
188
bindings/ruby/ext/ruby_whisper_parakeet_token.c
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define ITERATE_MEMBERS(ITERATOR) \
|
||||
ITERATOR(id, id, id, id, INT) \
|
||||
ITERATOR(duration_idx, duration_idx, duration_idx, duration_idx, INT) \
|
||||
ITERATOR(duration_value, duration_value, duration_value, duration_value, INT) \
|
||||
ITERATOR(frame_index, frame_index, frame_index, frame_index, INT) \
|
||||
ITERATOR(probability, probability, p, p, FLOAT) \
|
||||
ITERATOR(log_probability, log_probability, plog, plog, FLOAT) \
|
||||
ITERATOR(start_time, start_time, start_time, t0, TIME) \
|
||||
ITERATOR(end_time, end_time, end_time, t1, TIME) \
|
||||
ITERATOR(word_start?, word_start, word_start_p, is_word_start, BOOL)
|
||||
|
||||
#define ITERATE_ATTRS(ITERATOR) \
|
||||
ITERATOR(text, text, text, text, STRING)
|
||||
|
||||
enum {
|
||||
#define DEF_IDX(rb_name, s_key, c_name, p_name, type) RUBY_WHISPER_PARAKEET_TOKEN_##c_name,
|
||||
|
||||
ITERATE_MEMBERS(DEF_IDX)
|
||||
ITERATE_ATTRS(DEF_IDX)
|
||||
RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS,
|
||||
};
|
||||
|
||||
#define VAL_FROM_INT(v) (INT2NUM(v))
|
||||
#define VAL_FROM_FLOAT(v) (DBL2NUM(v))
|
||||
#define VAL_FROM_TIME(v) (LONG2NUM(v * 10))
|
||||
#define VAL_FROM_BOOL(v) ((v) ? Qtrue : Qfalse)
|
||||
#define VAL_FROM_STRING(v) (rb_str_new2(v))
|
||||
|
||||
#define READER(type) VAL_FROM_##type
|
||||
#define MEMBER_NAME(name) name
|
||||
#define DEF_MEMBER_ATTR(rb_name, s_key, c_name, p_name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_token_get_##c_name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_token *rwpt; \
|
||||
GetParakeetToken(self, rwpt); \
|
||||
return READER(type)(rwpt->token_data->MEMBER_NAME(p_name)); \
|
||||
}
|
||||
|
||||
#define DEF_ATTR(rb_name, s_key, c_name, p_name, type) \
|
||||
static VALUE \
|
||||
ruby_whisper_parakeet_token_get_##c_name(VALUE self) \
|
||||
{ \
|
||||
ruby_whisper_parakeet_token *rwpt; \
|
||||
GetParakeetToken(self, rwpt); \
|
||||
return rwpt->p_name; \
|
||||
}
|
||||
|
||||
VALUE cParakeetToken;
|
||||
|
||||
#define DEC_ATTR_SYMS(rb_name, s_key, c_name, p_name, type) static VALUE sym_##s_key;
|
||||
|
||||
ITERATE_MEMBERS(DEC_ATTR_SYMS)
|
||||
ITERATE_ATTRS(DEC_ATTR_SYMS)
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_token_mark(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p;
|
||||
rb_gc_mark(rwpt->text);
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_parakeet_token_free(void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p;
|
||||
if (rwpt->token_data) {
|
||||
xfree(rwpt->token_data);
|
||||
rwpt->token_data = NULL;
|
||||
}
|
||||
xfree(rwpt);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_parakeet_token_memsize(const void *p)
|
||||
{
|
||||
ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p;
|
||||
if (!rwpt) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = sizeof(*rwpt);
|
||||
if (rwpt->token_data) {
|
||||
size += sizeof(*rwpt->token_data);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_parakeet_token_type = {
|
||||
"ruby_whisper_parakeet_token",
|
||||
{ruby_whisper_parakeet_token_mark, ruby_whisper_parakeet_token_free, ruby_whisper_parakeet_token_memsize},
|
||||
0, 0,
|
||||
0,
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_token_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_parakeet_token *rwpt;
|
||||
VALUE token = TypedData_Make_Struct(klass, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt);
|
||||
|
||||
rwpt->token_data = NULL;
|
||||
rwpt->text = Qnil;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data)
|
||||
{
|
||||
const VALUE token = ruby_whisper_parakeet_token_s_allocate(cParakeetToken);
|
||||
ruby_whisper_parakeet_token *rwpt;
|
||||
TypedData_Get_Struct(token, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt);
|
||||
|
||||
rwpt->token_data = ALLOC(parakeet_token_data);
|
||||
*rwpt->token_data = *token_data;
|
||||
rwpt->text = rb_utf8_str_new_cstr(parakeet_token_to_str(context, token_data->id));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_token_s_from_index(struct parakeet_context *context, int i_segment, int i_token)
|
||||
{
|
||||
parakeet_token_data token_data = parakeet_full_get_token_data(context, i_segment, i_token);
|
||||
return ruby_whisper_parakeet_token_s_from_token_data(context, &token_data);
|
||||
}
|
||||
|
||||
ITERATE_MEMBERS(DEF_MEMBER_ATTR)
|
||||
// Define #text using parakeet_token_to_str or parakeet_token_to_text
|
||||
ITERATE_ATTRS(DEF_ATTR)
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_token_deconstruct_keys(VALUE self, VALUE keys)
|
||||
{
|
||||
ruby_whisper_parakeet_token *rwpt;
|
||||
GetParakeetToken(self, rwpt);
|
||||
|
||||
VALUE hash = rb_hash_new();
|
||||
long n_keys = 0;
|
||||
|
||||
if (NIL_P(keys)) {
|
||||
VALUE attrs[] = {
|
||||
#define LIST_SYMS(rb_name, s_key, c_name, p_name, type) sym_##s_key,
|
||||
|
||||
ITERATE_MEMBERS(LIST_SYMS)
|
||||
ITERATE_ATTRS(LIST_SYMS)
|
||||
};
|
||||
keys = rb_ary_new_from_values(RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS, attrs);
|
||||
n_keys = RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS;
|
||||
} else {
|
||||
n_keys = RARRAY_LEN(keys);
|
||||
if (n_keys > RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS) {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
for (long i = 0; i < n_keys; i++) {
|
||||
VALUE key = rb_ary_entry(keys, i);
|
||||
|
||||
#define CHECK_AND_SET_KEY(rb_name, s_key, c_name, p_name, type) \
|
||||
if (key == sym_##s_key) { \
|
||||
rb_hash_aset(hash, key, ruby_whisper_parakeet_token_get_##c_name(self)); \
|
||||
}
|
||||
|
||||
ITERATE_MEMBERS(CHECK_AND_SET_KEY)
|
||||
ITERATE_ATTRS(CHECK_AND_SET_KEY)
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_parakeet_token(VALUE *mParakeet)
|
||||
{
|
||||
cParakeetToken = rb_define_class_under(*mParakeet, "Token", rb_cObject);
|
||||
rb_define_alloc_func(cParakeetToken, ruby_whisper_parakeet_token_s_allocate);
|
||||
|
||||
#define REGISTER_ATTR(rb_name, s_key, c_name, p_name, type) \
|
||||
sym_##s_key = ID2SYM(rb_intern(#s_key)); \
|
||||
rb_define_method(cParakeetToken, #rb_name, ruby_whisper_parakeet_token_get_##c_name, 0);
|
||||
|
||||
ITERATE_MEMBERS(REGISTER_ATTR)
|
||||
ITERATE_ATTRS(REGISTER_ATTR)
|
||||
|
||||
rb_define_method(cParakeetToken, "deconstruct_keys", ruby_whisper_parakeet_token_deconstruct_keys, 1);
|
||||
}
|
||||
58
bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp
Normal file
58
bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "ruby_whisper.h"
|
||||
#include "common-whisper.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_parakeet_context_type;
|
||||
extern const rb_data_type_t ruby_whisper_parakeet_params_type;
|
||||
|
||||
extern VALUE ruby_whisper_parakeet_context_full_body(VALUE rb_args);
|
||||
|
||||
extern ID id_to_path;
|
||||
extern ID id_new;
|
||||
|
||||
extern VALUE eError;
|
||||
|
||||
VALUE
|
||||
ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params)
|
||||
{
|
||||
if (rb_respond_to(audio_path, id_to_path)) {
|
||||
audio_path = rb_funcall(audio_path, id_to_path, 0);
|
||||
}
|
||||
|
||||
std::string fname = StringValueCStr(audio_path);
|
||||
std::vector<float> pcmf32;
|
||||
std::vector<std::vector<float>> pcmf32s;
|
||||
|
||||
if (!read_audio_data(fname, pcmf32, pcmf32s, false)) {
|
||||
rb_raise(rb_eRuntimeError, "Failed to open %s", fname.c_str());
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
ruby_whisper_parakeet_params *rwpp;
|
||||
GetParakeetContext(self, rwpc);
|
||||
GetParakeetParams(params, rwpp);
|
||||
|
||||
ruby_whisper_full_args args = {
|
||||
&self,
|
||||
¶ms,
|
||||
pcmf32.data(),
|
||||
(int)pcmf32.size(),
|
||||
};
|
||||
VALUE rb_result = ruby_whisper_parakeet_context_full_body((VALUE)&args);
|
||||
const int result = NUM2INT(rb_result);
|
||||
if (result == 0) {
|
||||
return self;
|
||||
} else {
|
||||
rb_exc_raise(rb_funcall(eError, id_new, 1, rb_result));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1548
bindings/ruby/ext/ruby_whisper_params.c
Normal file
1548
bindings/ruby/ext/ruby_whisper_params.c
Normal file
File diff suppressed because it is too large
Load Diff
284
bindings/ruby/ext/ruby_whisper_segment.c
Normal file
284
bindings/ruby/ext/ruby_whisper_segment.c
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define N_KEY_NAMES 6
|
||||
|
||||
extern ID id___method__;
|
||||
extern ID id_to_enum;
|
||||
VALUE sym_start_time;
|
||||
VALUE sym_end_time;
|
||||
VALUE sym_text;
|
||||
VALUE sym_no_speech_prob;
|
||||
VALUE sym_speaker_turn_next;
|
||||
VALUE sym_n_tokens;
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_type;
|
||||
|
||||
extern VALUE cSegment;
|
||||
|
||||
extern VALUE ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int index);
|
||||
|
||||
static void
|
||||
rb_whisper_segment_mark(void *p)
|
||||
{
|
||||
ruby_whisper_segment *rws = (ruby_whisper_segment *)p;
|
||||
rb_gc_mark(rws->context);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_segment_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_segment *rws = (const ruby_whisper_segment *)p;
|
||||
size_t size = sizeof(rws);
|
||||
if (!rws) {
|
||||
return 0;
|
||||
}
|
||||
if (rws->index) {
|
||||
size += sizeof(rws->index);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_segment_type = {
|
||||
"ruby_whisper_segment",
|
||||
{rb_whisper_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_segment_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
VALUE
|
||||
ruby_whisper_segment_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
return TypedData_Make_Struct(klass, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_whisper_segment_s_new(VALUE context, int index)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
const VALUE segment = ruby_whisper_segment_allocate(cSegment);
|
||||
TypedData_Get_Struct(segment, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
rws->context = context;
|
||||
rws->index = index;
|
||||
return segment;
|
||||
};
|
||||
|
||||
/*
|
||||
* Start time in milliseconds.
|
||||
*
|
||||
* call-seq:
|
||||
* start_time -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_start_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
const int64_t t0 = whisper_full_get_segment_t0(rw->context, rws->index);
|
||||
// able to multiply 10 without overflow because to_timestamp() in whisper.cpp does it
|
||||
return LONG2NUM(t0 * 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* End time in milliseconds.
|
||||
*
|
||||
* call-seq:
|
||||
* end_time -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_end_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
const int64_t t1 = whisper_full_get_segment_t1(rw->context, rws->index);
|
||||
// able to multiply 10 without overflow because to_timestamp() in whisper.cpp does it
|
||||
return LONG2NUM(t1 * 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether the next segment is predicted as a speaker turn.
|
||||
*
|
||||
* call-seq:
|
||||
* speaker_turn_next? -> bool
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_speaker_turn_next(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
return whisper_full_get_segment_speaker_turn_next(rw->context, rws->index) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* text -> String
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_text(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
const char * text = whisper_full_get_segment_text(rw->context, rws->index);
|
||||
return rb_str_new2(text);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* no_speech_prob -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_no_speech_prob(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
return DBL2NUM(whisper_full_get_segment_no_speech_prob(rw->context, rws->index));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get number of tokens in the segment
|
||||
*
|
||||
* call-seq:
|
||||
* n_tokens -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_get_n_tokens(VALUE self)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
return INT2NUM(whisper_full_n_tokens(rw->context, rws->index));
|
||||
}
|
||||
|
||||
/*
|
||||
* Yields each Whisper::Token:
|
||||
*
|
||||
* whisper.each_segment.first.each_token do |token|
|
||||
* p token
|
||||
* end
|
||||
*
|
||||
* Returns an Enumerator if no block is given:
|
||||
*
|
||||
* whisper.each_segment.first.each_token.to_a # => [#<Whisper::Token>, ...]
|
||||
*
|
||||
* call-seq:
|
||||
* each_token {|token| ... }
|
||||
* each_token -> Enumerator
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_each_token(VALUE self)
|
||||
{
|
||||
if (!rb_block_given_p()) {
|
||||
const VALUE method_name = rb_funcall(self, id___method__, 0);
|
||||
return rb_funcall(self, id_to_enum, 1, method_name);
|
||||
}
|
||||
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
|
||||
const int n_tokens = whisper_full_n_tokens(rw->context, rws->index);
|
||||
for (int i = 0; i < n_tokens; ++i) {
|
||||
rb_yield(ruby_whisper_token_s_init(rw->context, rws->index, i));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* deconstruct_keys(keys) -> hash
|
||||
*
|
||||
* Possible keys: :start_time, :end_time, :text, :no_speech_prob, :speaker_turn_next, :n_tokens
|
||||
*
|
||||
* whisper.each_segment do |segment|
|
||||
* segment => {start_time:, end_time:, text:, no_speech_prob:, speaker_turn_next:}
|
||||
*
|
||||
* puts "[#{start_time} --> #{end_time}] #{text} (no speech prob: #{no_speech_prob}#{speaker_turn_next ? ', speaker turns next' : ''})"
|
||||
* end
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_segment_deconstruct_keys(VALUE self, VALUE keys)
|
||||
{
|
||||
ruby_whisper_segment *rws;
|
||||
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
|
||||
ruby_whisper *rw;
|
||||
GetContext(rws->context, rw);
|
||||
|
||||
VALUE hash = rb_hash_new();
|
||||
long n_keys;
|
||||
if (NIL_P(keys)) {
|
||||
keys = rb_ary_new3(
|
||||
N_KEY_NAMES,
|
||||
sym_start_time,
|
||||
sym_end_time,
|
||||
sym_text,
|
||||
sym_no_speech_prob,
|
||||
sym_speaker_turn_next
|
||||
);
|
||||
n_keys = N_KEY_NAMES;
|
||||
} else {
|
||||
n_keys = RARRAY_LEN(keys);
|
||||
if (n_keys > N_KEY_NAMES) {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n_keys; i++) {
|
||||
VALUE key = rb_ary_entry(keys, i);
|
||||
if (key == sym_start_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_start_time(self));
|
||||
}
|
||||
if (key == sym_end_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_end_time(self));
|
||||
}
|
||||
if (key == sym_text) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_text(self));
|
||||
}
|
||||
if (key == sym_no_speech_prob) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_no_speech_prob(self));
|
||||
}
|
||||
if (key == sym_speaker_turn_next) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_speaker_turn_next(self));
|
||||
}
|
||||
if (key == sym_n_tokens) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_segment_get_n_tokens(self));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_segment(VALUE *mWhisper)
|
||||
{
|
||||
cSegment = rb_define_class_under(*mWhisper, "Segment", rb_cObject);
|
||||
|
||||
sym_start_time = ID2SYM(rb_intern("start_time"));
|
||||
sym_end_time = ID2SYM(rb_intern("end_time"));
|
||||
sym_text = ID2SYM(rb_intern("text"));
|
||||
sym_no_speech_prob = ID2SYM(rb_intern("no_speech_prob"));
|
||||
sym_speaker_turn_next = ID2SYM(rb_intern("speaker_turn_next"));
|
||||
sym_n_tokens = ID2SYM(rb_intern("n_tokens"));
|
||||
|
||||
rb_define_alloc_func(cSegment, ruby_whisper_segment_allocate);
|
||||
rb_define_method(cSegment, "start_time", ruby_whisper_segment_get_start_time, 0);
|
||||
rb_define_method(cSegment, "end_time", ruby_whisper_segment_get_end_time, 0);
|
||||
rb_define_method(cSegment, "speaker_turn_next?", ruby_whisper_segment_get_speaker_turn_next, 0);
|
||||
rb_define_method(cSegment, "text", ruby_whisper_segment_get_text, 0);
|
||||
rb_define_method(cSegment, "no_speech_prob", ruby_whisper_segment_get_no_speech_prob, 0);
|
||||
rb_define_method(cSegment, "n_tokens", ruby_whisper_segment_get_n_tokens, 0);
|
||||
rb_define_method(cSegment, "each_token", ruby_whisper_segment_each_token, 0);
|
||||
rb_define_method(cSegment, "deconstruct_keys", ruby_whisper_segment_deconstruct_keys, 1);
|
||||
}
|
||||
#undef N_KEY_NAMES
|
||||
371
bindings/ruby/ext/ruby_whisper_token.c
Normal file
371
bindings/ruby/ext/ruby_whisper_token.c
Normal file
@@ -0,0 +1,371 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define N_KEY_NAMES 11
|
||||
|
||||
extern VALUE cToken;
|
||||
extern const rb_data_type_t ruby_whisper_type;
|
||||
|
||||
static VALUE sym_id;
|
||||
static VALUE sym_tid;
|
||||
static VALUE sym_probability;
|
||||
static VALUE sym_log_probability;
|
||||
static VALUE sym_pt;
|
||||
static VALUE sym_ptsum;
|
||||
static VALUE sym_t_dtw;
|
||||
static VALUE sym_voice_length;
|
||||
static VALUE sym_start_time;
|
||||
static VALUE sym_end_time;
|
||||
static VALUE sym_text;
|
||||
|
||||
static size_t
|
||||
ruby_whisper_token_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_token *rwt = (const ruby_whisper_token *)p;
|
||||
if (!rwt) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = sizeof(*rwt);
|
||||
if (rwt->token_data) {
|
||||
size += sizeof(*rwt->token_data);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_token_mark(void *p)
|
||||
{
|
||||
ruby_whisper_token *rwt = (ruby_whisper_token *)p;
|
||||
rb_gc_mark(rwt->text);
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_token_free(void *p)
|
||||
{
|
||||
ruby_whisper_token *rwt = (ruby_whisper_token *)p;
|
||||
if (rwt->token_data) {
|
||||
xfree(rwt->token_data);
|
||||
rwt->token_data = NULL;
|
||||
}
|
||||
xfree(rwt);
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_token_type = {
|
||||
"ruby_whisper_token",
|
||||
{ruby_whisper_token_mark, ruby_whisper_token_free, ruby_whisper_token_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_token_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
VALUE token = TypedData_Make_Struct(klass, ruby_whisper_token, &ruby_whisper_token_type, rwt);
|
||||
rwt->token_data = NULL;
|
||||
rwt->text = Qnil;
|
||||
return token;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int i_token)
|
||||
{
|
||||
const VALUE token = ruby_whisper_token_allocate(cToken);
|
||||
ruby_whisper_token *rwt;
|
||||
TypedData_Get_Struct(token, ruby_whisper_token, &ruby_whisper_token_type, rwt);
|
||||
rwt->token_data = ALLOC(whisper_token_data);
|
||||
*(rwt->token_data) = whisper_full_get_token_data(context, i_segment, i_token);
|
||||
rwt->text = rb_str_new2(whisper_full_get_token_text(context, i_segment, i_token));
|
||||
return token;
|
||||
}
|
||||
|
||||
/*
|
||||
* Token ID.
|
||||
*
|
||||
* call-seq:
|
||||
* id -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_id(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return INT2NUM(rwt->token_data->id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Forced timestamp token ID.
|
||||
*
|
||||
* call-seq:
|
||||
* tid -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_tid(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return INT2NUM(rwt->token_data->tid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probability of the token.
|
||||
*
|
||||
* call-seq:
|
||||
* probability -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_p(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return DBL2NUM(rwt->token_data->p);
|
||||
}
|
||||
|
||||
/*
|
||||
* Log probability of the token.
|
||||
*
|
||||
* call-seq:
|
||||
* log_probability -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_plog(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return DBL2NUM(rwt->token_data->plog);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probability of the timestamp token.
|
||||
*
|
||||
* call-seq:
|
||||
* pt -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_pt(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return DBL2NUM(rwt->token_data->pt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sum of probability of all timestamp tokens.
|
||||
*
|
||||
* call-seq:
|
||||
* ptsum -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_ptsum(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return DBL2NUM(rwt->token_data->ptsum);
|
||||
}
|
||||
|
||||
/*
|
||||
* [EXPERIMENTAL] Token-level timestamps with DTW
|
||||
*
|
||||
* Do not use if you haven't computed token-level timestamps with dtw.
|
||||
* Roughly corresponds to the moment in audio in which the token was output.
|
||||
*
|
||||
* call-seq:
|
||||
* t_dtw -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_t_dtw(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return LONG2NUM(rwt->token_data->t_dtw);
|
||||
}
|
||||
|
||||
/*
|
||||
* Voice length of the token.
|
||||
*
|
||||
* call-seq:
|
||||
* voice_length -> Float
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_vlen(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return DBL2NUM(rwt->token_data->vlen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the token text of the token.
|
||||
*
|
||||
* call-seq:
|
||||
* text -> String
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_text(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return rwt->text;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start time of the token.
|
||||
*
|
||||
* Token-level timestamp data.
|
||||
* Do not use if you haven't computed token-level timestamps.
|
||||
*
|
||||
* call-seq:
|
||||
* start_time -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_start_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return LONG2NUM(rwt->token_data->t0 * 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* End time of the token.
|
||||
*
|
||||
* Token-level timestamp data.
|
||||
* Do not use if you haven't computed token-level timestamps.
|
||||
*
|
||||
* call-seq:
|
||||
* end_time -> Integer
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_token_get_end_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
return LONG2NUM(rwt->token_data->t1 * 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* deconstruct_keys(keys) -> hash
|
||||
*
|
||||
* Possible keys: :id, :tid, :probability, :log_probability, :pt, :ptsum,
|
||||
* :t_dtw, :voice_length, :start_time, :end_time, :text
|
||||
* segment.each_token do |token|
|
||||
* token => {text:, probability:}
|
||||
puts "#{text} (#{probability})"
|
||||
* end
|
||||
*/
|
||||
static VALUE ruby_whisper_token_deconstruct_keys(VALUE self, VALUE keys)
|
||||
{
|
||||
ruby_whisper_token *rwt;
|
||||
GetToken(self, rwt);
|
||||
VALUE hash = rb_hash_new();
|
||||
long n_keys = 0;
|
||||
|
||||
if (NIL_P(keys)) {
|
||||
keys = rb_ary_new3(
|
||||
N_KEY_NAMES,
|
||||
sym_id,
|
||||
sym_tid,
|
||||
sym_probability,
|
||||
sym_log_probability,
|
||||
sym_pt,
|
||||
sym_ptsum,
|
||||
sym_t_dtw,
|
||||
sym_voice_length,
|
||||
sym_start_time,
|
||||
sym_end_time,
|
||||
sym_text
|
||||
);
|
||||
n_keys = N_KEY_NAMES;
|
||||
} else {
|
||||
n_keys = RARRAY_LEN(keys);
|
||||
if (n_keys > N_KEY_NAMES) {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_keys; i++) {
|
||||
VALUE key = rb_ary_entry(keys, i);
|
||||
if (key == sym_start_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_start_time(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_end_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_end_time(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_text) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_text(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_probability) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_p(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_id) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_id(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_tid) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_tid(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_log_probability) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_plog(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_pt) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_pt(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_ptsum) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_ptsum(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_t_dtw) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_t_dtw(self));
|
||||
continue;
|
||||
}
|
||||
if (key == sym_voice_length) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_token_get_vlen(self));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
init_ruby_whisper_token(VALUE *mWhisper)
|
||||
{
|
||||
cToken = rb_define_class_under(*mWhisper, "Token", rb_cObject);
|
||||
|
||||
rb_define_alloc_func(cToken, ruby_whisper_token_allocate);
|
||||
|
||||
sym_id = ID2SYM(rb_intern("id"));
|
||||
sym_tid = ID2SYM(rb_intern("tid"));
|
||||
sym_probability = ID2SYM(rb_intern("probability"));
|
||||
sym_log_probability = ID2SYM(rb_intern("log_probability"));
|
||||
sym_pt = ID2SYM(rb_intern("pt"));
|
||||
sym_ptsum = ID2SYM(rb_intern("ptsum"));
|
||||
sym_t_dtw = ID2SYM(rb_intern("t_dtw"));
|
||||
sym_voice_length = ID2SYM(rb_intern("voice_length"));
|
||||
sym_start_time = ID2SYM(rb_intern("start_time"));
|
||||
sym_end_time = ID2SYM(rb_intern("end_time"));
|
||||
sym_text = ID2SYM(rb_intern("text"));
|
||||
|
||||
rb_define_method(cToken, "id", ruby_whisper_token_get_id, 0);
|
||||
rb_define_method(cToken, "tid", ruby_whisper_token_get_tid, 0);
|
||||
rb_define_method(cToken, "probability", ruby_whisper_token_get_p, 0);
|
||||
rb_define_method(cToken, "log_probability", ruby_whisper_token_get_plog, 0);
|
||||
rb_define_method(cToken, "pt", ruby_whisper_token_get_pt, 0);
|
||||
rb_define_method(cToken, "ptsum", ruby_whisper_token_get_ptsum, 0);
|
||||
rb_define_method(cToken, "t_dtw", ruby_whisper_token_get_t_dtw, 0);
|
||||
rb_define_method(cToken, "voice_length", ruby_whisper_token_get_vlen, 0);
|
||||
rb_define_method(cToken, "start_time", ruby_whisper_token_get_start_time, 0);
|
||||
rb_define_method(cToken, "end_time", ruby_whisper_token_get_end_time, 0);
|
||||
rb_define_method(cToken, "text", ruby_whisper_token_get_text, 0);
|
||||
|
||||
rb_define_method(cToken, "deconstruct_keys", ruby_whisper_token_deconstruct_keys, 1);
|
||||
}
|
||||
|
||||
#undef N_KEY_NAMES
|
||||
104
bindings/ruby/ext/ruby_whisper_transcribe.cpp
Normal file
104
bindings/ruby/ext/ruby_whisper_transcribe.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "ruby_whisper.h"
|
||||
#include "common-whisper.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_type;
|
||||
extern const rb_data_type_t ruby_whisper_params_type;
|
||||
|
||||
extern ID id_to_s;
|
||||
extern ID id_call;
|
||||
extern ID id_to_path;
|
||||
extern ID transcribe_option_names[1];
|
||||
|
||||
extern VALUE full_body(VALUE rb_args);
|
||||
extern VALUE full_parallel_body(VALUE rb_args);
|
||||
|
||||
/*
|
||||
* transcribe a single file
|
||||
* can emit to a block results
|
||||
*
|
||||
* params = Whisper::Params.new
|
||||
* params.duration = 60_000
|
||||
* whisper.transcribe "path/to/audio.wav", params do |text|
|
||||
* puts text
|
||||
* end
|
||||
*
|
||||
* call-seq:
|
||||
* transcribe(path_to_audio, params) {|text| ...}
|
||||
**/
|
||||
VALUE
|
||||
ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) {
|
||||
ruby_whisper *rw;
|
||||
ruby_whisper_params *rwp;
|
||||
VALUE wave_file_path, blk, params, kws;
|
||||
VALUE opts[1];
|
||||
|
||||
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "2:&", &wave_file_path, ¶ms, &kws, &blk);
|
||||
rb_get_kwargs(kws, transcribe_option_names, 0, 1, opts);
|
||||
|
||||
int n_processors = opts[0] == Qundef ? 1 : NUM2INT(opts[0]);
|
||||
|
||||
GetContext(self, rw);
|
||||
TypedData_Get_Struct(params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
|
||||
|
||||
if (!rb_respond_to(wave_file_path, id_to_s)) {
|
||||
rb_raise(rb_eRuntimeError, "Expected file path to wave file");
|
||||
}
|
||||
|
||||
if (rb_respond_to(wave_file_path, id_to_path)) {
|
||||
wave_file_path = rb_funcall(wave_file_path, id_to_path, 0);
|
||||
}
|
||||
std::string fname_inp = StringValueCStr(wave_file_path);
|
||||
|
||||
std::vector<float> pcmf32; // mono-channel F32 PCM
|
||||
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
||||
|
||||
if (!read_audio_data(fname_inp, pcmf32, pcmf32s, rwp->diarize)) {
|
||||
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str());
|
||||
return self;
|
||||
}
|
||||
|
||||
VALUE rb_result;
|
||||
if (n_processors == 1) {
|
||||
ruby_whisper_full_args args = {
|
||||
&self,
|
||||
¶ms,
|
||||
pcmf32.data(),
|
||||
(int)pcmf32.size(),
|
||||
};
|
||||
rb_result = full_body((VALUE)&args);
|
||||
} else {
|
||||
ruby_whisper_full_parallel_args parallel_args = {
|
||||
&self,
|
||||
¶ms,
|
||||
pcmf32.data(),
|
||||
(int)pcmf32.size(),
|
||||
n_processors,
|
||||
};
|
||||
rb_result = full_parallel_body((VALUE)¶llel_args);
|
||||
}
|
||||
const int result = NUM2INT(rb_result);
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "failed to process audio\n");
|
||||
return self;
|
||||
}
|
||||
if (NIL_P(blk)) {
|
||||
return self;
|
||||
}
|
||||
const int n_segments = whisper_full_n_segments(rw->context);
|
||||
VALUE output = rb_str_new2("");
|
||||
for (int i = 0; i < n_segments; ++i) {
|
||||
const char * text = whisper_full_get_segment_text(rw->context, i);
|
||||
output = rb_str_concat(output, rb_str_new2(text));
|
||||
}
|
||||
rb_funcall(blk, id_call, 1, output);
|
||||
return self;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
154
bindings/ruby/ext/ruby_whisper_vad_context.c
Normal file
154
bindings/ruby/ext/ruby_whisper_vad_context.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
extern ID id_to_s;
|
||||
|
||||
extern VALUE cVADContext;
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_vad_params_type;
|
||||
extern VALUE ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params);
|
||||
extern VALUE ruby_whisper_normalize_model_path(VALUE model_path);
|
||||
extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples);
|
||||
extern VALUE release_samples(VALUE parsed);
|
||||
|
||||
extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
|
||||
|
||||
static size_t
|
||||
ruby_whisper_vad_context_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_vad_context *rwvc = p;
|
||||
size_t size = sizeof(rwvc);
|
||||
if (!rwvc) {
|
||||
return 0;
|
||||
}
|
||||
if (rwvc->context) {
|
||||
size += sizeof(rwvc->context);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_vad_context_free(void *p)
|
||||
{
|
||||
ruby_whisper_vad_context *rwvc = (ruby_whisper_vad_context *)p;
|
||||
if (rwvc->context) {
|
||||
whisper_vad_free(rwvc->context);
|
||||
rwvc->context = NULL;
|
||||
}
|
||||
xfree(rwvc);
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_vad_context_type = {
|
||||
"ruby_whisper_vad_context",
|
||||
{0, ruby_whisper_vad_context_free, ruby_whisper_vad_context_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_context_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_context, &ruby_whisper_vad_context_type, rwvc);
|
||||
rwvc->context = NULL;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_context_initialize(VALUE self, VALUE model_path)
|
||||
{
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
struct whisper_vad_context *context;
|
||||
|
||||
model_path = ruby_whisper_normalize_model_path(model_path);
|
||||
context = whisper_vad_init_from_file_with_params(StringValueCStr(model_path), whisper_vad_default_context_params());
|
||||
if (context == NULL) {
|
||||
rb_raise(rb_eRuntimeError, "Failed to initialize whisper VAD context");
|
||||
}
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_context, &ruby_whisper_vad_context_type, rwvc);
|
||||
rwvc->context = context;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
struct whisper_vad_context *context;
|
||||
struct whisper_vad_params *params;
|
||||
float *samples;
|
||||
int n_samples;
|
||||
struct whisper_vad_segments *result;
|
||||
} segments_from_samples_without_gvl_args;
|
||||
|
||||
static void*
|
||||
segments_from_samples_without_gvl(void *rb_args)
|
||||
{
|
||||
segments_from_samples_without_gvl_args *args = (segments_from_samples_without_gvl_args *)rb_args;
|
||||
args->result = whisper_vad_segments_from_samples(args->context, *args->params, args->samples, args->n_samples);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VALUE
|
||||
segments_from_samples_body(VALUE rb_args)
|
||||
{
|
||||
segments_from_samples_args *args = (segments_from_samples_args *)rb_args;
|
||||
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
GetVADContext(*args->context, rwvc);
|
||||
GetVADParams(*args->params, rwvp);
|
||||
|
||||
segments_from_samples_without_gvl_args segments_from_samples_without_gvl_args = {
|
||||
rwvc->context,
|
||||
&rwvp->params,
|
||||
args->samples,
|
||||
args->n_samples,
|
||||
NULL
|
||||
};
|
||||
rb_thread_call_without_gvl(segments_from_samples_without_gvl, (void *)&segments_from_samples_without_gvl_args, NULL, NULL);
|
||||
if (!segments_from_samples_without_gvl_args.result) {
|
||||
rb_raise(rb_eRuntimeError, "Failed to process audio");
|
||||
}
|
||||
|
||||
return ruby_whisper_vad_segments_s_init(segments_from_samples_without_gvl_args.result);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segments_from_samples(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
if (argc < 2 || argc > 3) {
|
||||
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..3)", argc);
|
||||
}
|
||||
|
||||
VALUE n_samples = argc == 2 ? Qnil : argv[2];
|
||||
struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples);
|
||||
segments_from_samples_args args = {
|
||||
&self,
|
||||
&argv[0],
|
||||
parsed.samples,
|
||||
parsed.n_samples,
|
||||
};
|
||||
VALUE segments = rb_ensure(segments_from_samples_body, (VALUE)&args, release_samples, (VALUE)&parsed);
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_free(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
GetVADContext(self, rwvc);
|
||||
whisper_vad_free(rwvc->context);
|
||||
rwvc->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
void init_ruby_whisper_vad_context(VALUE *mVAD)
|
||||
{
|
||||
cVADContext = rb_define_class_under(*mVAD, "Context", rb_cObject);
|
||||
rb_define_alloc_func(cVADContext, ruby_whisper_vad_context_s_allocate);
|
||||
rb_define_method(cVADContext, "initialize", ruby_whisper_vad_context_initialize, 1);
|
||||
rb_define_method(cVADContext, "segments_from_samples", ruby_whisper_vad_segments_from_samples, -1);
|
||||
rb_define_method(cVADContext, "detect", ruby_whisper_vad_detect, 2);
|
||||
rb_define_method(cVADContext, "free", ruby_whisper_vad_free, 0);
|
||||
}
|
||||
50
bindings/ruby/ext/ruby_whisper_vad_context_detect.cpp
Normal file
50
bindings/ruby/ext/ruby_whisper_vad_context_detect.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "ruby_whisper.h"
|
||||
#include "common-whisper.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern ID id_to_path;
|
||||
|
||||
extern VALUE cVADSegments;
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_vad_context_type;
|
||||
extern const rb_data_type_t ruby_whisper_vad_params_type;
|
||||
extern const rb_data_type_t ruby_whisper_vad_segments_type;
|
||||
|
||||
extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
|
||||
extern VALUE segments_from_samples_body(VALUE rb_args);
|
||||
|
||||
VALUE
|
||||
ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params) {
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
std::string cpp_file_path;
|
||||
std::vector<float> pcmf32;
|
||||
std::vector<std::vector<float>> pcmf32s;
|
||||
|
||||
GetVADContext(self, rwvc);
|
||||
|
||||
if (rb_respond_to(file_path, id_to_path)) {
|
||||
file_path = rb_funcall(file_path, id_to_path, 0);
|
||||
}
|
||||
cpp_file_path = StringValueCStr(file_path);
|
||||
|
||||
if (!read_audio_data(cpp_file_path, pcmf32, pcmf32s, false)) {
|
||||
rb_raise(rb_eRuntimeError, "Failed to open '%s' as WAV file\n", cpp_file_path.c_str());
|
||||
}
|
||||
|
||||
segments_from_samples_args args = {
|
||||
&self,
|
||||
¶ms,
|
||||
pcmf32.data(),
|
||||
(int)pcmf32.size(),
|
||||
};
|
||||
return segments_from_samples_body((VALUE)&args);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
287
bindings/ruby/ext/ruby_whisper_vad_params.c
Normal file
287
bindings/ruby/ext/ruby_whisper_vad_params.c
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define DEFINE_PARAM(param_name, nth) \
|
||||
id_ ## param_name = rb_intern(#param_name); \
|
||||
param_names[nth] = id_ ## param_name; \
|
||||
rb_define_method(cVADParams, #param_name, ruby_whisper_vad_params_get_ ## param_name, 0); \
|
||||
rb_define_method(cVADParams, #param_name "=", ruby_whisper_vad_params_set_ ## param_name, 1);
|
||||
|
||||
#define NUM_PARAMS 6
|
||||
|
||||
extern VALUE cVADParams;
|
||||
|
||||
static size_t
|
||||
ruby_whisper_vad_params_memsize(const void *p)
|
||||
{
|
||||
const struct ruby_whisper_vad_params *params = p;
|
||||
size_t size = sizeof(params);
|
||||
if (!params) {
|
||||
return 0;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static ID param_names[NUM_PARAMS];
|
||||
static ID id_threshold;
|
||||
static ID id_min_speech_duration_ms;
|
||||
static ID id_min_silence_duration_ms;
|
||||
static ID id_max_speech_duration_s;
|
||||
static ID id_speech_pad_ms;
|
||||
static ID id_samples_overlap;
|
||||
|
||||
const rb_data_type_t ruby_whisper_vad_params_type = {
|
||||
"ruby_whisper_vad_params",
|
||||
{0, RUBY_DEFAULT_FREE, ruby_whisper_vad_params_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params = whisper_vad_default_params();
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* Probability threshold to consider as speech.
|
||||
*
|
||||
* call-seq:
|
||||
* threshold = th -> th
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_threshold(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.threshold = NUM2DBL(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_threshold(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return DBL2NUM(rwvp->params.threshold);
|
||||
}
|
||||
|
||||
/*
|
||||
* Min duration for a valid speech segment.
|
||||
*
|
||||
* call-seq:
|
||||
* min_speech_duration_ms = duration_ms -> duration_ms
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_min_speech_duration_ms(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.min_speech_duration_ms = NUM2INT(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_min_speech_duration_ms(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return INT2NUM(rwvp->params.min_speech_duration_ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* Min silence duration to consider speech as ended.
|
||||
*
|
||||
* call-seq:
|
||||
* min_silence_duration_ms = duration_ms -> duration_ms
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_min_silence_duration_ms(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.min_silence_duration_ms = NUM2INT(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_min_silence_duration_ms(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return INT2NUM(rwvp->params.min_silence_duration_ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* Max duration of a speech segment before forcing a new segment.
|
||||
*
|
||||
* call-seq:
|
||||
* max_speech_duration_s = duration_s -> duration_s
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_max_speech_duration_s(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.max_speech_duration_s = NUM2DBL(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_max_speech_duration_s(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return DBL2NUM(rwvp->params.max_speech_duration_s);
|
||||
}
|
||||
|
||||
/*
|
||||
* Padding added before and after speech segments.
|
||||
*
|
||||
* call-seq:
|
||||
* speech_pad_ms = pad_ms -> pad_ms
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_speech_pad_ms(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.speech_pad_ms = NUM2INT(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_speech_pad_ms(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return INT2NUM(rwvp->params.speech_pad_ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* Overlap in seconds when copying audio samples from speech segment.
|
||||
*
|
||||
* call-seq:
|
||||
* samples_overlap = overlap -> overlap
|
||||
*/
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_set_samples_overlap(VALUE self, VALUE value)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
rwvp->params.samples_overlap = NUM2DBL(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_get_samples_overlap(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
return DBL2NUM(rwvp->params.samples_overlap);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_params_equal(VALUE self, VALUE other)
|
||||
{
|
||||
ruby_whisper_vad_params *rwvp1;
|
||||
ruby_whisper_vad_params *rwvp2;
|
||||
|
||||
if (self == other) {
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
if (!rb_obj_is_kind_of(other, cVADParams)) {
|
||||
return Qfalse;
|
||||
}
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp1);
|
||||
TypedData_Get_Struct(other, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp2);
|
||||
|
||||
if (rwvp1->params.threshold != rwvp2->params.threshold) {
|
||||
return Qfalse;
|
||||
}
|
||||
if (rwvp1->params.min_speech_duration_ms != rwvp2->params.min_speech_duration_ms) {
|
||||
return Qfalse;
|
||||
}
|
||||
if (rwvp1->params.min_silence_duration_ms != rwvp2->params.min_silence_duration_ms) {
|
||||
return Qfalse;
|
||||
}
|
||||
if (rwvp1->params.max_speech_duration_s != rwvp2->params.max_speech_duration_s) {
|
||||
return Qfalse;
|
||||
}
|
||||
if (rwvp1->params.speech_pad_ms != rwvp2->params.speech_pad_ms) {
|
||||
return Qfalse;
|
||||
}
|
||||
if (rwvp1->params.samples_overlap != rwvp2->params.samples_overlap) {
|
||||
return Qfalse;
|
||||
}
|
||||
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
#define SET_PARAM_IF_SAME(param_name) \
|
||||
if (id == id_ ## param_name) { \
|
||||
ruby_whisper_vad_params_set_ ## param_name(self, value); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_vad_params_initialize(int argc, VALUE *argv, VALUE self)
|
||||
{
|
||||
VALUE kw_hash;
|
||||
VALUE values[NUM_PARAMS] = {Qundef};
|
||||
VALUE value;
|
||||
ruby_whisper_vad_params *rwvp;
|
||||
ID id;
|
||||
int i;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
|
||||
|
||||
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
|
||||
if (NIL_P(kw_hash)) {
|
||||
return self;
|
||||
}
|
||||
|
||||
rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
|
||||
|
||||
for (i = 0; i < NUM_PARAMS; i++) {
|
||||
id = param_names[i];
|
||||
value = values[i];
|
||||
if (value == Qundef) {
|
||||
continue;
|
||||
}
|
||||
SET_PARAM_IF_SAME(threshold)
|
||||
SET_PARAM_IF_SAME(min_speech_duration_ms)
|
||||
SET_PARAM_IF_SAME(min_silence_duration_ms)
|
||||
SET_PARAM_IF_SAME(max_speech_duration_s)
|
||||
SET_PARAM_IF_SAME(speech_pad_ms)
|
||||
SET_PARAM_IF_SAME(samples_overlap)
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#undef SET_PARAM_IF_SAME
|
||||
|
||||
void
|
||||
init_ruby_whisper_vad_params(VALUE *mVAD)
|
||||
{
|
||||
cVADParams = rb_define_class_under(*mVAD, "Params", rb_cObject);
|
||||
rb_define_alloc_func(cVADParams, ruby_whisper_vad_params_s_allocate);
|
||||
rb_define_method(cVADParams, "initialize", ruby_whisper_vad_params_initialize, -1);
|
||||
|
||||
DEFINE_PARAM(threshold, 0)
|
||||
DEFINE_PARAM(min_speech_duration_ms, 1)
|
||||
DEFINE_PARAM(min_silence_duration_ms, 2)
|
||||
DEFINE_PARAM(max_speech_duration_s, 3)
|
||||
DEFINE_PARAM(speech_pad_ms, 4)
|
||||
DEFINE_PARAM(samples_overlap, 5)
|
||||
|
||||
rb_define_method(cVADParams, "==", ruby_whisper_vad_params_equal, 1);
|
||||
}
|
||||
|
||||
#undef DEFINE_PARAM
|
||||
#undef NUM_PARAMS
|
||||
138
bindings/ruby/ext/ruby_whisper_vad_segment.c
Normal file
138
bindings/ruby/ext/ruby_whisper_vad_segment.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
#define N_KEY_NAMES 2
|
||||
|
||||
extern VALUE cVADSegment;
|
||||
|
||||
extern const rb_data_type_t ruby_whisper_vad_segments_type;
|
||||
|
||||
static VALUE sym_start_time;
|
||||
static VALUE sym_end_time;
|
||||
|
||||
static void
|
||||
rb_whisper_vad_segment_mark(void *p)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs = (ruby_whisper_vad_segment *)p;
|
||||
rb_gc_mark(rwvs->segments);
|
||||
}
|
||||
|
||||
static size_t
|
||||
ruby_whisper_vad_segment_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_vad_segment *rwvs = p;
|
||||
size_t size = sizeof(rwvs);
|
||||
if (!rwvs) {
|
||||
return 0;
|
||||
}
|
||||
if (rwvs->index) {
|
||||
size += sizeof(rwvs->index);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static const rb_data_type_t ruby_whisper_vad_segment_type = {
|
||||
"ruby_whisper_vad_segment",
|
||||
{rb_whisper_vad_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_vad_segment_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segment_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
|
||||
rwvs->segments = Qnil;
|
||||
rwvs->index = -1;
|
||||
return obj;
|
||||
}
|
||||
|
||||
VALUE
|
||||
rb_whisper_vad_segment_s_new(VALUE segments, int index)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs;
|
||||
const VALUE segment = ruby_whisper_vad_segment_s_allocate(cVADSegment);
|
||||
TypedData_Get_Struct(segment, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
|
||||
rwvs->segments = segments;
|
||||
rwvs->index = index;
|
||||
return segment;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segment_get_start_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs;
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
float t0;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
|
||||
TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
|
||||
t0 = whisper_vad_segments_get_segment_t0(rwvss->segments, rwvs->index);
|
||||
return LONG2NUM(t0 * 10);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segment_get_end_time(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs;
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
float t1;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
|
||||
TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
|
||||
t1 = whisper_vad_segments_get_segment_t1(rwvss->segments, rwvs->index);
|
||||
return LONG2NUM(t1 * 10);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segment_deconstruct_keys(VALUE self, VALUE keys)
|
||||
{
|
||||
ruby_whisper_vad_segment *rwvs;
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
VALUE hash, key;
|
||||
long n_keys;
|
||||
int i;
|
||||
|
||||
TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
|
||||
TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
|
||||
|
||||
hash = rb_hash_new();
|
||||
if (NIL_P(keys)) {
|
||||
keys = rb_ary_new3(
|
||||
N_KEY_NAMES,
|
||||
sym_start_time,
|
||||
sym_end_time
|
||||
);
|
||||
n_keys = N_KEY_NAMES;
|
||||
} else {
|
||||
n_keys = RARRAY_LEN(keys);
|
||||
if (n_keys > N_KEY_NAMES) {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
key = rb_ary_entry(keys, i);
|
||||
if (key == sym_start_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_vad_segment_get_start_time(self));
|
||||
}
|
||||
if (key == sym_end_time) {
|
||||
rb_hash_aset(hash, key, ruby_whisper_vad_segment_get_end_time(self));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_vad_segment(VALUE *mVAD)
|
||||
{
|
||||
cVADSegment = rb_define_class_under(*mVAD, "Segment", rb_cObject);
|
||||
|
||||
sym_start_time = ID2SYM(rb_intern("start_time"));
|
||||
sym_end_time = ID2SYM(rb_intern("end_time"));
|
||||
|
||||
rb_define_alloc_func(cVADSegment, ruby_whisper_vad_segment_s_allocate);
|
||||
rb_define_method(cVADSegment, "start_time", ruby_whisper_vad_segment_get_start_time, 0);
|
||||
rb_define_method(cVADSegment, "end_time", ruby_whisper_vad_segment_get_end_time, 0);
|
||||
rb_define_method(cVADSegment, "deconstruct_keys", ruby_whisper_vad_segment_deconstruct_keys, 1);
|
||||
}
|
||||
105
bindings/ruby/ext/ruby_whisper_vad_segments.c
Normal file
105
bindings/ruby/ext/ruby_whisper_vad_segments.c
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "ruby_whisper.h"
|
||||
|
||||
extern ID id___method__;
|
||||
extern ID id_to_enum;
|
||||
|
||||
extern VALUE cVADSegments;
|
||||
|
||||
extern VALUE rb_whisper_vad_segment_s_new(VALUE segments, int index);
|
||||
|
||||
static size_t
|
||||
ruby_whisper_vad_segments_memsize(const void *p)
|
||||
{
|
||||
const ruby_whisper_vad_segments *rwvss = p;
|
||||
size_t size = sizeof(rwvss);
|
||||
if (!rwvss) {
|
||||
return 0;
|
||||
}
|
||||
if (rwvss->segments) {
|
||||
size += sizeof(rwvss->segments);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static void
|
||||
ruby_whisper_vad_segments_free(void *p)
|
||||
{
|
||||
ruby_whisper_vad_segments *rwvss = (ruby_whisper_vad_segments *)p;
|
||||
if (rwvss->segments) {
|
||||
whisper_vad_free_segments(rwvss->segments);
|
||||
rwvss->segments = NULL;
|
||||
}
|
||||
xfree(rwvss);
|
||||
}
|
||||
|
||||
const rb_data_type_t ruby_whisper_vad_segments_type = {
|
||||
"ruby_whisper_vad_segments",
|
||||
{0, ruby_whisper_vad_segments_free, ruby_whisper_vad_segments_memsize,},
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segments_s_allocate(VALUE klass)
|
||||
{
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
|
||||
rwvss->segments = NULL;
|
||||
return obj;
|
||||
}
|
||||
|
||||
VALUE
|
||||
ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments)
|
||||
{
|
||||
VALUE rb_segments;
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
|
||||
rb_segments = ruby_whisper_vad_segments_s_allocate(cVADSegments);
|
||||
TypedData_Get_Struct(rb_segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
|
||||
rwvss->segments = segments;
|
||||
|
||||
return rb_segments;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segments_each(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
VALUE method_name;
|
||||
int n_segments, i;
|
||||
|
||||
if (!rb_block_given_p()) {
|
||||
method_name = rb_funcall(self, id___method__, 0);
|
||||
return rb_funcall(self, id_to_enum, 1, method_name);
|
||||
}
|
||||
|
||||
GetVADSegments(self, rwvss);
|
||||
n_segments = whisper_vad_segments_n_segments(rwvss->segments);
|
||||
for (i = 0; i < n_segments; ++i) {
|
||||
rb_yield(rb_whisper_vad_segment_s_new(self, i));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_segments_get_length(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_segments *rwvss;
|
||||
int n_segments;
|
||||
|
||||
GetVADSegments(self, rwvss);
|
||||
n_segments = whisper_vad_segments_n_segments(rwvss->segments);
|
||||
|
||||
return INT2NUM(n_segments);
|
||||
}
|
||||
|
||||
void
|
||||
init_ruby_whisper_vad_segments(VALUE *mVAD)
|
||||
{
|
||||
cVADSegments = rb_define_class_under(*mVAD, "Segments", rb_cObject);
|
||||
rb_define_alloc_func(cVADSegments, ruby_whisper_vad_segments_s_allocate);
|
||||
rb_define_method(cVADSegments, "each", ruby_whisper_vad_segments_each, 0);
|
||||
rb_define_method(cVADSegments, "length", ruby_whisper_vad_segments_get_length, 0);
|
||||
rb_include_module(cVADSegments, rb_path2class("Enumerable"));
|
||||
}
|
||||
8
bindings/ruby/ext/sources/CMakeGraphVizOptions.cmake
Normal file
8
bindings/ruby/ext/sources/CMakeGraphVizOptions.cmake
Normal file
@@ -0,0 +1,8 @@
|
||||
set(GRAPHVIZ_EXECUTABLES FALSE)
|
||||
set(GRAPHVIZ_STATIC_LIBS TRUE)
|
||||
set(GRAPHVIZ_SHARED_LIBS FALSE)
|
||||
set(GRAPHVIZ_MODULE_LIBS FALSE)
|
||||
set(GRAPHVIZ_INTERFACE_LIBS FALSE)
|
||||
set(GRAPHVIZ_OBJECT_LIBS FALSE)
|
||||
set(GRAPHVIZ_UNKNOWN_LIBS FALSE)
|
||||
set(GRAPHVIZ_GENERATE_DEPENDERS FALSE)
|
||||
57
bindings/ruby/extsources.rb
Normal file
57
bindings/ruby/extsources.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
require "pathname"
|
||||
|
||||
root = Pathname("..")/".."
|
||||
ignored_dirs = %w[
|
||||
.devops
|
||||
.github
|
||||
ci
|
||||
examples/addon.node
|
||||
examples/bench.wasm
|
||||
examples/command
|
||||
examples/command.wasm
|
||||
examples/lsp
|
||||
examples/main
|
||||
examples/python
|
||||
examples/stream
|
||||
examples/stream.wasm
|
||||
examples/sycl
|
||||
examples/talk-llama
|
||||
examples/wchess
|
||||
examples/whisper.android
|
||||
examples/whisper.android.java
|
||||
examples/whisper.nvim
|
||||
examples/whisper.objc
|
||||
examples/whisper.swiftui
|
||||
examples/whisper.wasm
|
||||
grammars
|
||||
models
|
||||
samples
|
||||
scripts
|
||||
tests
|
||||
].collect {|dir| root/dir}
|
||||
ignored_files = %w[
|
||||
AUTHORS
|
||||
Makefile
|
||||
.gitignore
|
||||
.gitmodules
|
||||
.dockerignore
|
||||
]
|
||||
ignored_exts = %w[
|
||||
.yml
|
||||
.sh
|
||||
.md
|
||||
.py
|
||||
.js
|
||||
.nvim
|
||||
]
|
||||
|
||||
EXTSOURCES =
|
||||
`git ls-files -z #{root}`.split("\x0")
|
||||
.collect {|file| Pathname(file)}
|
||||
.reject {|file|
|
||||
ignored_exts.include?(file.extname) ||
|
||||
ignored_files.include?(file.basename.to_path) ||
|
||||
ignored_dirs.any? {|dir| file.descend.any? {|desc| desc == dir}} ||
|
||||
(file.descend.to_a[1] != root && file != Pathname("..")/"javascript"/"package-tmpl.json")
|
||||
}
|
||||
.collect(&:to_path)
|
||||
33
bindings/ruby/lib/whisper/log_settable.rb
Normal file
33
bindings/ruby/lib/whisper/log_settable.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
module Whisper
|
||||
module LogSettable
|
||||
private
|
||||
|
||||
def start_log_callback_thread
|
||||
return if @log_callback_thread&.alive?
|
||||
|
||||
@log_callback_thread = Thread.new {
|
||||
begin
|
||||
while logs = drain_logs
|
||||
begin
|
||||
callback, user_data = synchronize {[@log_callback, @log_callback_user_data]}
|
||||
next if callback.nil?
|
||||
|
||||
logs.each do |(level, text)|
|
||||
callback.call level, text, user_data
|
||||
end
|
||||
rescue => err
|
||||
$stderr.puts err
|
||||
end
|
||||
end
|
||||
rescue => err
|
||||
$stderr.puts err
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def synchronize(&block)
|
||||
@mutex ||= Thread::Mutex.new
|
||||
@mutex.synchronize &block
|
||||
end
|
||||
end
|
||||
end
|
||||
237
bindings/ruby/lib/whisper/model/uri.rb
Normal file
237
bindings/ruby/lib/whisper/model/uri.rb
Normal file
@@ -0,0 +1,237 @@
|
||||
require "uri"
|
||||
require "net/http"
|
||||
require "time"
|
||||
require "pathname"
|
||||
require "io/console/size"
|
||||
|
||||
module Whisper
|
||||
class Model
|
||||
class URI
|
||||
def initialize(uri)
|
||||
@uri = URI(uri)
|
||||
end
|
||||
|
||||
def to_path
|
||||
cache
|
||||
cache_path.to_path
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
path = cache_path
|
||||
path.delete if path.exist?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_path
|
||||
base_cache_dir/@uri.host/@uri.path[1..]
|
||||
end
|
||||
|
||||
def base_cache_dir
|
||||
base = case RUBY_PLATFORM
|
||||
when /mswin|mingw/
|
||||
ENV.key?("LOCALAPPDATA") ? Pathname(ENV["LOCALAPPDATA"]) : Pathname(Dir.home)/"AppData/Local"
|
||||
when /darwin/
|
||||
Pathname(Dir.home)/"Library/Caches"
|
||||
else
|
||||
ENV.key?("XDG_CACHE_HOME") ? Pathname(ENV["XDG_CACHE_HOME"]) : Pathname(Dir.home)/".cache"
|
||||
end
|
||||
base/"whisper.cpp"
|
||||
end
|
||||
|
||||
def cache
|
||||
path = cache_path
|
||||
return path if cache_path.exist?
|
||||
|
||||
headers = {}
|
||||
request @uri, headers
|
||||
path
|
||||
end
|
||||
|
||||
def request(uri, headers)
|
||||
Net::HTTP.start uri.host, uri.port, use_ssl: uri.scheme == "https" do |http|
|
||||
request = Net::HTTP::Get.new(uri, headers)
|
||||
http.request request do |response|
|
||||
case response
|
||||
when Net::HTTPOK
|
||||
download response
|
||||
when Net::HTTPRedirection
|
||||
request URI(response["location"]), headers
|
||||
else
|
||||
raise "#{response.code} #{response.message}\n#{response.body}"
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => err
|
||||
if cache_path.exist?
|
||||
warn err
|
||||
# Use cache file
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def download(response)
|
||||
path = cache_path
|
||||
path.dirname.mkpath unless path.dirname.exist?
|
||||
downloading_path = Pathname("#{path}.downloading")
|
||||
size = response.content_length
|
||||
downloading_path.open "wb" do |file|
|
||||
downloaded = 0
|
||||
response.read_body do |chunk|
|
||||
file << chunk
|
||||
downloaded += chunk.bytesize
|
||||
show_progress downloaded, size
|
||||
end
|
||||
$stderr.puts
|
||||
end
|
||||
downloading_path.rename path
|
||||
end
|
||||
|
||||
def show_progress(current, size)
|
||||
line_size = 47
|
||||
progress_rate_available = size && $stderr.tty? && $stderr.winsize[1] >= line_size
|
||||
|
||||
unless @prev
|
||||
@prev = Time.now
|
||||
$stderr.puts "Downloading #{@uri} to #{cache_path}"
|
||||
end
|
||||
|
||||
now = Time.now
|
||||
|
||||
if progress_rate_available
|
||||
return if now - @prev < 1 && current < size
|
||||
|
||||
progress_width = 20
|
||||
progress = current.to_f / size
|
||||
arrow_length = progress * progress_width
|
||||
arrow = "=" * (arrow_length - 1) + ">" + " " * (progress_width - arrow_length)
|
||||
line = "[#{arrow}] (#{format_bytesize(current)} / #{format_bytesize(size)})"
|
||||
padding = ' ' * ($stderr.winsize[1] - line.size)
|
||||
$stderr.print "\r#{line}#{padding}"
|
||||
else
|
||||
return if now - @prev < 1
|
||||
|
||||
$stderr.print "."
|
||||
end
|
||||
@prev = now
|
||||
end
|
||||
|
||||
def format_bytesize(bytesize)
|
||||
return "0.0 B" if bytesize.zero?
|
||||
|
||||
units = %w[B KiB MiB GiB TiB]
|
||||
exp = (Math.log(bytesize) / Math.log(1024)).to_i
|
||||
format("%.1f %s", bytesize.to_f / 1024 ** exp, units[exp])
|
||||
end
|
||||
end
|
||||
|
||||
class ZipURI < URI
|
||||
def cache
|
||||
zip_path = super
|
||||
dest = unzipped_path
|
||||
return if dest.exist? && dest.mtime >= zip_path.mtime
|
||||
escaping dest do
|
||||
system "unzip", "-q", "-d", zip_path.dirname.to_path, zip_path.to_path, exception: true
|
||||
end
|
||||
zip_path
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
super
|
||||
unzipped_path.rmtree if unzipped_path.exist?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unzipped_path
|
||||
cache_path.sub_ext("")
|
||||
end
|
||||
|
||||
def escaping(path)
|
||||
escaped = Pathname("#{path}.removing")
|
||||
if path.exist?
|
||||
escaped.rmtree if escaped.exist?
|
||||
path.rename escaped
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
if path.exist?
|
||||
escaped.rmtree if escaped.exist?
|
||||
else
|
||||
escaped.rename path if escaped.exist?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@pre_converted_models = %w[
|
||||
tiny
|
||||
tiny.en
|
||||
tiny-q5_1
|
||||
tiny.en-q5_1
|
||||
tiny-q8_0
|
||||
base
|
||||
base.en
|
||||
base-q5_1
|
||||
base.en-q5_1
|
||||
base-q8_0
|
||||
small
|
||||
small.en
|
||||
small-q5_1
|
||||
small.en-q5_1
|
||||
small-q8_0
|
||||
medium
|
||||
medium.en
|
||||
medium-q5_0
|
||||
medium.en-q5_0
|
||||
medium-q8_0
|
||||
large-v1
|
||||
large-v2
|
||||
large-v2-q5_0
|
||||
large-v2-q8_0
|
||||
large-v3
|
||||
large-v3-q5_0
|
||||
large-v3-turbo
|
||||
large-v3-turbo-q5_0
|
||||
large-v3-turbo-q8_0
|
||||
].each_with_object({}) {|name, models|
|
||||
models[name] = URI.new("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-#{name}.bin")
|
||||
}
|
||||
|
||||
%w[
|
||||
small.en-tdrz
|
||||
].each do |name|
|
||||
@pre_converted_models[name] = URI.new("https://huggingface.co/akashmjn/tinydiarize-whisper.cpp/resolve/main/ggml-#{name}.bin")
|
||||
end
|
||||
|
||||
%w[
|
||||
silero-v5.1.2
|
||||
silero-v6.2.0
|
||||
].each do |name|
|
||||
@pre_converted_models[name] = URI.new("https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-#{name}.bin")
|
||||
end
|
||||
|
||||
%w[
|
||||
parakeet-tdt-0.6b-v3-f16
|
||||
parakeet-tdt-0.6b-v3-f32
|
||||
parakeet-tdt-0.6b-v3-q4_0
|
||||
parakeet-tdt-0.6b-v3-q4_k
|
||||
parakeet-tdt-0.6b-v3-q8_0
|
||||
].each do |name|
|
||||
@pre_converted_models[name] = URI.new("https://huggingface.co/ggml-org/parakeet-GGUF/resolve/main/ggml-#{name}.bin")
|
||||
end
|
||||
|
||||
@coreml_compiled_models = @pre_converted_models.each_with_object({}) {|(name, uri), models|
|
||||
next if name.end_with?("-tdrz") || name.start_with?("silero-") || name.start_with?("parakeet-")
|
||||
|
||||
if matched = name.match(/\A(?<name>.*)-q\d_\d\z/)
|
||||
name = matched[:name]
|
||||
end
|
||||
models[uri] = ZipURI.new("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-#{name}-encoder.mlmodelc.zip")
|
||||
}
|
||||
|
||||
class << self
|
||||
attr_reader :pre_converted_models, :coreml_compiled_models
|
||||
end
|
||||
end
|
||||
end
|
||||
74
bindings/ruby/lib/whisper/output.rb
Normal file
74
bindings/ruby/lib/whisper/output.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
module Whisper
|
||||
module Output
|
||||
module Context
|
||||
def to_srt
|
||||
each_segment.with_index.reduce("") {|srt, (segment, index)|
|
||||
srt << "#{index + 1}\n#{segment.to_srt_cue}\n"
|
||||
}
|
||||
end
|
||||
|
||||
def to_webvtt
|
||||
each_segment.with_index.reduce("WEBVTT\n\n") {|webvtt, (segment, index)|
|
||||
webvtt << "#{index + 1}\n#{segment.to_webvtt_cue}\n"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module Segment
|
||||
SRT_ESCAPES = {
|
||||
"&" => "&",
|
||||
"<" => "<",
|
||||
">" => ">",
|
||||
}
|
||||
SRT_ESCAPES_RE = Regexp.union(SRT_ESCAPES.keys)
|
||||
private_constant :SRT_ESCAPES, :SRT_ESCAPES_RE
|
||||
|
||||
def to_srt_cue
|
||||
"#{srt_start_time} --> #{srt_end_time}\n#{srt_text}\n"
|
||||
end
|
||||
|
||||
def to_webvtt_cue
|
||||
"#{webvtt_start_time} --> #{webvtt_end_time}\n#{webvtt_text}\n"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_to_a(time)
|
||||
sec, decimal_part = time.divmod(1000)
|
||||
min, sec = sec.divmod(60)
|
||||
hour, min = min.divmod(60)
|
||||
[hour, min, sec, decimal_part]
|
||||
end
|
||||
|
||||
def srt_time(time)
|
||||
"%02d:%02d:%02d,%03d" % time_to_a(time)
|
||||
end
|
||||
|
||||
def srt_start_time
|
||||
srt_time(start_time)
|
||||
end
|
||||
|
||||
def srt_end_time
|
||||
srt_time(end_time)
|
||||
end
|
||||
|
||||
def srt_text
|
||||
text.gsub(SRT_ESCAPES_RE, SRT_ESCAPES)
|
||||
end
|
||||
|
||||
def webvtt_time(time)
|
||||
"%02d:%02d:%02d.%03d" % time_to_a(time)
|
||||
end
|
||||
|
||||
def webvtt_start_time
|
||||
webvtt_time(start_time)
|
||||
end
|
||||
|
||||
def webvtt_end_time
|
||||
webvtt_time(end_time)
|
||||
end
|
||||
|
||||
alias webvtt_text srt_text
|
||||
end
|
||||
end
|
||||
end
|
||||
1036
bindings/ruby/sig/whisper.rbs
Normal file
1036
bindings/ruby/sig/whisper.rbs
Normal file
File diff suppressed because it is too large
Load Diff
26
bindings/ruby/test/helper.rb
Normal file
26
bindings/ruby/test/helper.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
require "test/unit"
|
||||
require "whisper"
|
||||
require_relative "jfk_reader/jfk_reader"
|
||||
|
||||
class TestBase < Test::Unit::TestCase
|
||||
AUDIO = File.join(__dir__, "fixtures", "jfk.wav")
|
||||
|
||||
Parakeet = Whisper::Parakeet
|
||||
|
||||
class << self
|
||||
def whisper
|
||||
return @whisper if @whisper
|
||||
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new
|
||||
params.print_timestamps = false
|
||||
@whisper.transcribe(TestBase::AUDIO, params)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def whisper
|
||||
self.class.whisper
|
||||
end
|
||||
end
|
||||
5
bindings/ruby/test/jfk_reader/.gitignore
vendored
Normal file
5
bindings/ruby/test/jfk_reader/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Makefile
|
||||
jfk_reader.o
|
||||
jfk_reader.so
|
||||
jfk_reader.bundle
|
||||
jfk_reader.dll
|
||||
3
bindings/ruby/test/jfk_reader/extconf.rb
Normal file
3
bindings/ruby/test/jfk_reader/extconf.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
require "mkmf"
|
||||
|
||||
create_makefile("jfk_reader")
|
||||
111
bindings/ruby/test/jfk_reader/jfk_reader.c
Normal file
111
bindings/ruby/test/jfk_reader/jfk_reader.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <ruby.h>
|
||||
#include <ruby/memory_view.h>
|
||||
#include <ruby/encoding.h>
|
||||
|
||||
typedef struct {
|
||||
VALUE audio_path;
|
||||
int n_samples;
|
||||
const char *audio_path_str;
|
||||
float *data;
|
||||
short *samples;
|
||||
} jfk_alloc_args;
|
||||
|
||||
static VALUE
|
||||
jfk_reader_alloc_resources(VALUE arg)
|
||||
{
|
||||
jfk_alloc_args *a = (jfk_alloc_args *)arg;
|
||||
a->audio_path_str = StringValueCStr(a->audio_path);
|
||||
a->data = ALLOC_N(float, a->n_samples);
|
||||
a->samples = ALLOC_N(short, a->n_samples);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
jfk_reader_initialize(VALUE self, VALUE audio_path)
|
||||
{
|
||||
rb_iv_set(self, "audio_path", audio_path);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static bool
|
||||
jfk_reader_get_memory_view(const VALUE obj, rb_memory_view_t *view, int flags)
|
||||
{
|
||||
VALUE audio_path = rb_iv_get(obj, "audio_path");
|
||||
// n_samples is a fixed constant (not derived from user input).
|
||||
const int n_samples = 176000;
|
||||
|
||||
jfk_alloc_args args = {
|
||||
.audio_path = audio_path,
|
||||
.n_samples = n_samples,
|
||||
.audio_path_str = NULL,
|
||||
.data = NULL,
|
||||
.samples = NULL,
|
||||
};
|
||||
|
||||
int state;
|
||||
rb_protect(jfk_reader_alloc_resources, (VALUE)&args, &state);
|
||||
if (state) {
|
||||
if (args.samples) xfree(args.samples);
|
||||
if (args.data) xfree(args.data);
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *file = fopen(args.audio_path_str, "rb");
|
||||
if (file == NULL) {
|
||||
xfree(args.samples);
|
||||
xfree(args.data);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(file, 78, SEEK_SET);
|
||||
fread(args.samples, sizeof(short), n_samples, file);
|
||||
fclose(file);
|
||||
for (int i = 0; i < n_samples; i++) {
|
||||
args.data[i] = args.samples[i] / 32768.0;
|
||||
}
|
||||
xfree(args.samples);
|
||||
|
||||
view->obj = obj;
|
||||
view->data = (void *)args.data;
|
||||
view->byte_size = sizeof(float) * n_samples;
|
||||
view->readonly = true;
|
||||
view->format = "e";
|
||||
view->item_size = sizeof(float);
|
||||
view->item_desc.components = NULL;
|
||||
view->item_desc.length = 0;
|
||||
view->ndim = 1;
|
||||
view->shape = NULL;
|
||||
view->sub_offsets = NULL;
|
||||
view->private_data = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
jfk_reader_release_memory_view(const VALUE obj, rb_memory_view_t *view)
|
||||
{
|
||||
if (view->data) {
|
||||
xfree(view->data);
|
||||
view->data = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
jfk_reader_memory_view_available_p(const VALUE obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static const rb_memory_view_entry_t jfk_reader_view_entry = {
|
||||
jfk_reader_get_memory_view,
|
||||
jfk_reader_release_memory_view,
|
||||
jfk_reader_memory_view_available_p
|
||||
};
|
||||
|
||||
void Init_jfk_reader(void)
|
||||
{
|
||||
VALUE cJFKReader = rb_define_class("JFKReader", rb_cObject);
|
||||
rb_memory_view_register(cJFKReader, &jfk_reader_view_entry);
|
||||
rb_define_method(cJFKReader, "initialize", jfk_reader_initialize, 1);
|
||||
}
|
||||
203
bindings/ruby/test/test_callback.rb
Normal file
203
bindings/ruby/test/test_callback.rb
Normal file
@@ -0,0 +1,203 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestCallback < TestBase
|
||||
def setup
|
||||
GC.start
|
||||
@params = Whisper::Params.new
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
@audio = File.join(AUDIO)
|
||||
end
|
||||
|
||||
def test_new_segment_callback
|
||||
@params.new_segment_callback = ->(context, state, n_new, user_data) {
|
||||
assert_kind_of Integer, n_new
|
||||
assert n_new > 0
|
||||
assert_same @whisper, context
|
||||
|
||||
n_segments = context.full_n_segments
|
||||
n_new.times do |i|
|
||||
i_segment = n_segments - 1 + i
|
||||
start_time = context.full_get_segment_t0(i_segment) * 10
|
||||
end_time = context.full_get_segment_t1(i_segment) * 10
|
||||
text = context.full_get_segment_text(i_segment)
|
||||
|
||||
assert_kind_of Integer, start_time
|
||||
assert start_time >= 0
|
||||
assert_kind_of Integer, end_time
|
||||
assert end_time > 0
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text) if i_segment == 0
|
||||
end
|
||||
}
|
||||
|
||||
@whisper.transcribe(@audio, @params)
|
||||
end
|
||||
|
||||
def test_new_segment_callback_closure
|
||||
search_word = "what"
|
||||
@params.new_segment_callback = ->(context, state, n_new, user_data) {
|
||||
n_segments = context.full_n_segments
|
||||
n_new.times do |i|
|
||||
i_segment = n_segments - 1 + i
|
||||
text = context.full_get_segment_text(i_segment)
|
||||
if text.include?(search_word)
|
||||
t0 = context.full_get_segment_t0(i_segment)
|
||||
t1 = context.full_get_segment_t1(i_segment)
|
||||
raise "search word '#{search_word}' found at between #{t0} and #{t1}"
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
assert_raise RuntimeError do
|
||||
@whisper.transcribe(@audio, @params)
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_segment_callback_user_data
|
||||
udata = Object.new
|
||||
@params.new_segment_callback_user_data = udata
|
||||
@params.new_segment_callback = ->(context, state, n_new, user_data) {
|
||||
assert_same udata, user_data
|
||||
}
|
||||
|
||||
@whisper.transcribe(@audio, @params)
|
||||
end
|
||||
|
||||
def test_new_segment_callback_user_data_gc
|
||||
@params.new_segment_callback_user_data = "My user data"
|
||||
@params.new_segment_callback = ->(context, state, n_new, user_data) {
|
||||
assert_equal "My user data", user_data
|
||||
}
|
||||
GC.start
|
||||
|
||||
assert_same @whisper, @whisper.transcribe(@audio, @params)
|
||||
end
|
||||
|
||||
def test_progress_callback
|
||||
first = nil
|
||||
last = nil
|
||||
@params.progress_callback = ->(context, state, progress, user_data) {
|
||||
assert_kind_of Integer, progress
|
||||
assert 0 <= progress && progress <= 100
|
||||
assert_same @whisper, context
|
||||
first = progress if first.nil?
|
||||
last = progress
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert_equal 0, first
|
||||
assert_equal 100, last
|
||||
end
|
||||
|
||||
def test_progress_callback_user_data
|
||||
udata = Object.new
|
||||
@params.progress_callback_user_data = udata
|
||||
@params.progress_callback = ->(context, state, n_new, user_data) {
|
||||
assert_same udata, user_data
|
||||
}
|
||||
|
||||
@whisper.transcribe(@audio, @params)
|
||||
end
|
||||
|
||||
def test_on_progress
|
||||
first = nil
|
||||
last = nil
|
||||
@params.on_progress do |progress|
|
||||
assert_kind_of Integer, progress
|
||||
assert 0 <= progress && progress <= 100
|
||||
first = progress if first.nil?
|
||||
last = progress
|
||||
end
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert_equal 0, first
|
||||
assert_equal 100, last
|
||||
end
|
||||
|
||||
def test_encoder_begin_callback
|
||||
i = 0
|
||||
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||
i += 1
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert i > 0
|
||||
end
|
||||
|
||||
def test_encoder_begin_callback_abort
|
||||
logs = []
|
||||
Whisper.log_set -> (level, buffer, user_data) {
|
||||
logs << buffer if level == Whisper::LOG_LEVEL_ERROR
|
||||
}, logs
|
||||
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||
return false
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
sleep 0.5 # wait for logs dequeued
|
||||
assert_match(/encoder_begin_callback returned false - aborting/, logs.join)
|
||||
Whisper.log_set ->(level, buffer, user_data) {}, nil
|
||||
end
|
||||
|
||||
def test_encoder_begin_callback_user_data
|
||||
udata = Object.new
|
||||
@params.encoder_begin_callback_user_data = udata
|
||||
yielded = nil
|
||||
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||
yielded = user_data
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert_same udata, yielded
|
||||
end
|
||||
|
||||
def test_on_encoder_begin
|
||||
i = 0
|
||||
@params.on_encoder_begin do
|
||||
i += 1
|
||||
end
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert i > 0
|
||||
end
|
||||
|
||||
def test_abort_callback
|
||||
i = 0
|
||||
@params.abort_callback = ->(user_data) {
|
||||
assert_nil user_data
|
||||
i += 1
|
||||
return false
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert i > 0
|
||||
end
|
||||
|
||||
def test_abort_callback_abort
|
||||
i = 0
|
||||
@params.abort_callback = ->(user_data) {
|
||||
i += 1
|
||||
return i == 3
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert_equal 3, i
|
||||
end
|
||||
|
||||
def test_abort_callback_user_data
|
||||
udata = Object.new
|
||||
@params.abort_callback_user_data = udata
|
||||
yielded = nil
|
||||
@params.abort_callback = ->(user_data) {
|
||||
yielded = user_data
|
||||
}
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert_same udata, yielded
|
||||
end
|
||||
|
||||
def test_abort_on
|
||||
do_abort = false
|
||||
_aborted_from_callback = false
|
||||
@params.on_new_segment do |segment|
|
||||
do_abort = true if segment.text.match?(/ask/)
|
||||
end
|
||||
i = 0
|
||||
@params.abort_on do
|
||||
i += 1
|
||||
do_abort
|
||||
end
|
||||
@whisper.transcribe(@audio, @params)
|
||||
assert i > 0
|
||||
end
|
||||
end
|
||||
82
bindings/ruby/test/test_context_params.rb
Normal file
82
bindings/ruby/test/test_context_params.rb
Normal file
@@ -0,0 +1,82 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestContextParams < TestBase
|
||||
PARAM_NAMES = [
|
||||
:use_gpu,
|
||||
:flash_attn,
|
||||
:gpu_device,
|
||||
:dtw_token_timestamps,
|
||||
:dtw_aheads_preset,
|
||||
:dtw_n_top
|
||||
]
|
||||
|
||||
def test_new
|
||||
params = Whisper::Context::Params.new
|
||||
assert_instance_of Whisper::Context::Params, params
|
||||
end
|
||||
|
||||
def test_attributes
|
||||
params = Whisper::Context::Params.new
|
||||
|
||||
assert_true params.use_gpu
|
||||
params.use_gpu = false
|
||||
assert_false params.use_gpu
|
||||
|
||||
assert_true params.flash_attn
|
||||
params.flash_attn = false
|
||||
assert_false params.flash_attn
|
||||
|
||||
assert_equal 0, params.gpu_device
|
||||
params.gpu_device = 1
|
||||
assert_equal 1, params.gpu_device
|
||||
|
||||
assert_false params.dtw_token_timestamps
|
||||
params.dtw_token_timestamps = true
|
||||
assert_true params.dtw_token_timestamps
|
||||
|
||||
assert_equal Whisper::AHEADS_NONE, params.dtw_aheads_preset
|
||||
params.dtw_aheads_preset =Whisper::AHEADS_BASE
|
||||
assert_equal Whisper::AHEADS_BASE, params.dtw_aheads_preset
|
||||
|
||||
assert_nil params.dtw_n_top
|
||||
params.dtw_n_top = 6
|
||||
assert_equal 6, params.dtw_n_top
|
||||
params.dtw_n_top = nil
|
||||
assert_nil params.dtw_n_top
|
||||
end
|
||||
|
||||
def test_new_with_kw_args
|
||||
params = Whisper::Context::Params.new(use_gpu: false)
|
||||
assert_false params.use_gpu
|
||||
end
|
||||
|
||||
def test_new_with_kw_wargs_non_existent
|
||||
assert_raise ArgumentError do
|
||||
Whisper::Context::Params.new(non_existent: "value")
|
||||
end
|
||||
end
|
||||
|
||||
data(PARAM_NAMES.collect {|param| [param, param]}.to_h)
|
||||
def test_new_with_kw_args_default_values(param)
|
||||
default_params = Whisper::Context::Params.new
|
||||
default_value = default_params.send(param)
|
||||
value = if param == :dtw_n_top
|
||||
6
|
||||
else
|
||||
case default_value
|
||||
in true | false
|
||||
!default_value
|
||||
in Integer
|
||||
default_value + 1
|
||||
end
|
||||
end
|
||||
params = Whisper::Context::Params.new(param => value)
|
||||
assert_equal value, params.send(param)
|
||||
|
||||
PARAM_NAMES.reject {|name| name == param}.each do |name|
|
||||
expected = default_params.send(name)
|
||||
actual = params.send(name)
|
||||
assert_equal expected, actual
|
||||
end
|
||||
end
|
||||
end
|
||||
20
bindings/ruby/test/test_error.rb
Normal file
20
bindings/ruby/test/test_error.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestError < TestBase
|
||||
def test_error
|
||||
error = Whisper::Error.new(-2)
|
||||
assert_equal "failed to compute log mel spectrogram", error.message
|
||||
assert_equal(-2, error.code)
|
||||
end
|
||||
|
||||
def test_unknown_error
|
||||
error = Whisper::Error.new(-20)
|
||||
assert_equal "unknown error", error.message
|
||||
end
|
||||
|
||||
def test_non_int_code
|
||||
assert_raise TypeError do
|
||||
_error = Whisper::Error.new("non int")
|
||||
end
|
||||
end
|
||||
end
|
||||
118
bindings/ruby/test/test_model.rb
Normal file
118
bindings/ruby/test/test_model.rb
Normal file
@@ -0,0 +1,118 @@
|
||||
require_relative "helper"
|
||||
require "pathname"
|
||||
|
||||
class TestModel < TestBase
|
||||
def test_model
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
assert_instance_of Whisper::Model, whisper.model
|
||||
end
|
||||
|
||||
def test_attributes
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
model = whisper.model
|
||||
|
||||
assert_equal 51864, model.n_vocab
|
||||
assert_equal 1500, model.n_audio_ctx
|
||||
assert_equal 512, model.n_audio_state
|
||||
assert_equal 8, model.n_audio_head
|
||||
assert_equal 6, model.n_audio_layer
|
||||
assert_equal 448, model.n_text_ctx
|
||||
assert_equal 512, model.n_text_state
|
||||
assert_equal 8, model.n_text_head
|
||||
assert_equal 6, model.n_text_layer
|
||||
assert_equal 80, model.n_mels
|
||||
assert_equal 1, model.ftype
|
||||
assert_equal "base", model.type
|
||||
end
|
||||
|
||||
def test_gc
|
||||
model = Whisper::Context.new("base.en").model
|
||||
GC.start
|
||||
|
||||
assert_equal 51864, model.n_vocab
|
||||
assert_equal 1500, model.n_audio_ctx
|
||||
assert_equal 512, model.n_audio_state
|
||||
assert_equal 8, model.n_audio_head
|
||||
assert_equal 6, model.n_audio_layer
|
||||
assert_equal 448, model.n_text_ctx
|
||||
assert_equal 512, model.n_text_state
|
||||
assert_equal 8, model.n_text_head
|
||||
assert_equal 6, model.n_text_layer
|
||||
assert_equal 80, model.n_mels
|
||||
assert_equal 1, model.ftype
|
||||
assert_equal "base", model.type
|
||||
end
|
||||
|
||||
def test_pathname
|
||||
path = Pathname(Whisper::Model.pre_converted_models["base.en"].to_path)
|
||||
whisper = Whisper::Context.new(path)
|
||||
model = whisper.model
|
||||
|
||||
assert_equal 51864, model.n_vocab
|
||||
assert_equal 1500, model.n_audio_ctx
|
||||
assert_equal 512, model.n_audio_state
|
||||
assert_equal 8, model.n_audio_head
|
||||
assert_equal 6, model.n_audio_layer
|
||||
assert_equal 448, model.n_text_ctx
|
||||
assert_equal 512, model.n_text_state
|
||||
assert_equal 8, model.n_text_head
|
||||
assert_equal 6, model.n_text_layer
|
||||
assert_equal 80, model.n_mels
|
||||
assert_equal 1, model.ftype
|
||||
assert_equal "base", model.type
|
||||
end
|
||||
|
||||
def test_auto_download
|
||||
path = Whisper::Model.pre_converted_models["base.en"].to_path
|
||||
|
||||
assert_path_exist path
|
||||
assert_equal 147964211, File.size(path)
|
||||
end
|
||||
|
||||
def test_uri_string
|
||||
path = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"
|
||||
whisper = Whisper::Context.new(path)
|
||||
model = whisper.model
|
||||
|
||||
assert_equal 51864, model.n_vocab
|
||||
assert_equal 1500, model.n_audio_ctx
|
||||
assert_equal 512, model.n_audio_state
|
||||
assert_equal 8, model.n_audio_head
|
||||
assert_equal 6, model.n_audio_layer
|
||||
assert_equal 448, model.n_text_ctx
|
||||
assert_equal 512, model.n_text_state
|
||||
assert_equal 8, model.n_text_head
|
||||
assert_equal 6, model.n_text_layer
|
||||
assert_equal 80, model.n_mels
|
||||
assert_equal 1, model.ftype
|
||||
assert_equal "base", model.type
|
||||
end
|
||||
|
||||
def test_uri
|
||||
path = URI("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin")
|
||||
whisper = Whisper::Context.new(path)
|
||||
model = whisper.model
|
||||
|
||||
assert_equal 51864, model.n_vocab
|
||||
assert_equal 1500, model.n_audio_ctx
|
||||
assert_equal 512, model.n_audio_state
|
||||
assert_equal 8, model.n_audio_head
|
||||
assert_equal 6, model.n_audio_layer
|
||||
assert_equal 448, model.n_text_ctx
|
||||
assert_equal 512, model.n_text_state
|
||||
assert_equal 8, model.n_text_head
|
||||
assert_equal 6, model.n_text_layer
|
||||
assert_equal 80, model.n_mels
|
||||
assert_equal 1, model.ftype
|
||||
assert_equal "base", model.type
|
||||
end
|
||||
|
||||
def test_coreml_model_auto_download
|
||||
uri = Whisper::Model.coreml_compiled_models[Whisper::Model.pre_converted_models["tiny"]]
|
||||
model_path = Pathname(uri.to_path).sub_ext("")
|
||||
model_path.rmtree if model_path.exist?
|
||||
|
||||
uri.cache
|
||||
assert_path_exist model_path
|
||||
end
|
||||
end
|
||||
52
bindings/ruby/test/test_package.rb
Normal file
52
bindings/ruby/test/test_package.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
require_relative "helper"
|
||||
require 'tempfile'
|
||||
require 'tmpdir'
|
||||
require 'open3'
|
||||
|
||||
class TestPackage < TestBase
|
||||
def test_build
|
||||
Tempfile.create do |file|
|
||||
assert system("gem", "build", "whispercpp.gemspec", "--output", file.to_path, exception: true)
|
||||
assert file.size > 0
|
||||
assert_path_exist file.to_path
|
||||
end
|
||||
end
|
||||
|
||||
sub_test_case "Building binary on installation" do
|
||||
def setup
|
||||
system "rake", "build", exception: true
|
||||
end
|
||||
|
||||
def test_install
|
||||
gemspec = Gem::Specification.load("whispercpp.gemspec")
|
||||
Dir.mktmpdir do |dir|
|
||||
system "gem", "install", "--install-dir", dir, "--no-document", File.join("pkg", gemspec.file_name), exception: true
|
||||
assert_installed dir, gemspec.version
|
||||
end
|
||||
end
|
||||
|
||||
def test_install_with_coreml
|
||||
omit_unless RUBY_PLATFORM.match?(/darwin/) do
|
||||
gemspec = Gem::Specification.load("whispercpp.gemspec")
|
||||
Dir.mktmpdir do |dir|
|
||||
system "gem", "install", "--install-dir", dir, "--no-document", File.join("pkg", gemspec.file_name), "--", "--enable-whisper-coreml", exception: true
|
||||
assert_installed dir, gemspec.version
|
||||
libdir = File.join(dir, "gems", "#{gemspec.name}-#{gemspec.version}", "lib")
|
||||
assert_nothing_raised do
|
||||
system "ruby", "-I", libdir, "-r", "whisper", "-e", "Whisper::Context.new('tiny')", exception: true
|
||||
end
|
||||
output, status = Open3.capture2("ruby", "-I", libdir, "-r", "whisper", "-e", "puts Whisper.system_info_str")
|
||||
assert_match /COREML = 1/, output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_installed(dir, version)
|
||||
assert_path_exist File.join(dir, "gems/whispercpp-#{version}/lib", "whisper.#{RbConfig::CONFIG["DLEXT"]}")
|
||||
assert_path_exist File.join(dir, "gems/whispercpp-#{version}/LICENSE")
|
||||
assert_path_not_exist File.join(dir, "gems/whispercpp-#{version}/ext/build")
|
||||
end
|
||||
end
|
||||
end
|
||||
28
bindings/ruby/test/test_parakeet.rb
Normal file
28
bindings/ruby/test/test_parakeet.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require_relative "helper"
|
||||
require "stringio"
|
||||
|
||||
class TestParakeet < TestBase
|
||||
def test_log_set
|
||||
log_callback = Parakeet.instance_variable_get("@log_callback")
|
||||
user_data = Parakeet.instance_variable_get("@log_callback_user_data")
|
||||
|
||||
$stdout = StringIO.new
|
||||
Parakeet.log_set proc {|level, message, _| puts [level, message].join(": ")}, nil
|
||||
Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin")
|
||||
sleep 0.1
|
||||
$stdout.rewind
|
||||
logs = $stdout.string
|
||||
assert_match /loading model from/, logs
|
||||
ensure
|
||||
$stdout = STDOUT
|
||||
Parakeet.log_set log_callback, user_data
|
||||
end
|
||||
|
||||
def test_system_info_str
|
||||
assert_match /\APARAKEET : /, Parakeet.system_info_str
|
||||
end
|
||||
|
||||
def test_version
|
||||
assert_instance_of String, Parakeet::VERSION
|
||||
end
|
||||
end
|
||||
107
bindings/ruby/test/test_parakeet_callback.rb
Normal file
107
bindings/ruby/test/test_parakeet_callback.rb
Normal file
@@ -0,0 +1,107 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParakeetCallback < TestBase
|
||||
def setup
|
||||
omit "Skip not to download large model" if ENV["CI"]
|
||||
|
||||
Whisper.instance_variable_set "@whisper", nil
|
||||
GC.start
|
||||
@params = Parakeet::Params.new
|
||||
@parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0")
|
||||
end
|
||||
|
||||
def test_new_segment_callback
|
||||
@params.new_segment_callback = ->(context, state, n_new, user_data) {
|
||||
assert_kind_of Integer, n_new
|
||||
assert n_new > 0
|
||||
assert_same @parakeet, context
|
||||
|
||||
n_segments = context.full_n_segments
|
||||
n_new.times do |i|
|
||||
i_segment = n_segments - 1 + i
|
||||
start_time = context.full_get_segment_t0(i_segment) * 10
|
||||
end_time = context.full_get_segment_t1(i_segment) * 10
|
||||
text = context.full_get_segment_text(i_segment)
|
||||
|
||||
assert_kind_of Integer, start_time
|
||||
assert start_time >= 0
|
||||
assert_kind_of Integer, end_time
|
||||
assert end_time > 0
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your/, text) if i_segment == 0
|
||||
end
|
||||
}
|
||||
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
end
|
||||
|
||||
def test_on_new_segment
|
||||
seg = nil
|
||||
index = 0
|
||||
@params.on_new_segment do |segment|
|
||||
assert_instance_of Parakeet::Segment, segment
|
||||
if index == 0
|
||||
seg = segment
|
||||
assert_equal 0, segment.start_time
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your/, segment.text)
|
||||
end
|
||||
index += 1
|
||||
end
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
assert_equal 0, seg.start_time
|
||||
assert_match /ask not what your country can do for you, ask what you can do for your/, seg.text
|
||||
end
|
||||
|
||||
def test_on_new_token
|
||||
index = 0
|
||||
@params.on_new_token do |token|
|
||||
assert_instance_of Parakeet::Token, token
|
||||
if index == 0
|
||||
assert_instance_of Integer, token.start_time
|
||||
assert_match "▁And", token.text
|
||||
end
|
||||
index += 1
|
||||
end
|
||||
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
end
|
||||
|
||||
def test_on_progress
|
||||
first = nil
|
||||
@params.on_progress do |progress|
|
||||
assert_kind_of Integer, progress
|
||||
assert 0 <= progress && progress <= 100
|
||||
first = progress if first.nil?
|
||||
end
|
||||
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
|
||||
assert_equal 0, first
|
||||
end
|
||||
|
||||
def test_on_encoder_begin
|
||||
i = 0
|
||||
@params.on_encoder_begin do
|
||||
i += 1
|
||||
end
|
||||
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
|
||||
assert i > 0
|
||||
end
|
||||
|
||||
def test_abort_on
|
||||
do_abort = false
|
||||
@params.on_new_segment do |segment|
|
||||
do_abort = true if segment.text.match?(/ask/)
|
||||
end
|
||||
i = 0
|
||||
@params.abort_on do
|
||||
i += 1
|
||||
do_abort
|
||||
end
|
||||
|
||||
@parakeet.transcribe(AUDIO, @params) rescue nil
|
||||
|
||||
assert i > 0
|
||||
end
|
||||
end
|
||||
123
bindings/ruby/test/test_parakeet_context.rb
Normal file
123
bindings/ruby/test/test_parakeet_context.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
require_relative "helper"
|
||||
require "stringio"
|
||||
|
||||
class TestParakeetContext < TestBase
|
||||
def setup
|
||||
omit "Skip not to download large model" if ENV["CI"]
|
||||
|
||||
Whisper.instance_variable_set "@whisper", nil
|
||||
GC.start
|
||||
|
||||
@parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0")
|
||||
@params = Parakeet::Params.new
|
||||
end
|
||||
|
||||
def test_new
|
||||
assert_instance_of Parakeet::Context, @parakeet
|
||||
end
|
||||
|
||||
def test_new_with_params
|
||||
log_callback = Parakeet.instance_variable_get(:@log_callback)
|
||||
user_data = Parakeet.instance_variable_get(:@log_callback_user_data)
|
||||
begin
|
||||
logs = ""
|
||||
Parakeet.log_set proc {|level, message| logs << message}, nil
|
||||
params = Parakeet::Context::Params.new(use_gpu: false)
|
||||
parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0", params)
|
||||
assert_instance_of Parakeet::Context, parakeet
|
||||
assert_match /use gpu\s+=\s+0/, logs
|
||||
ensure
|
||||
Parakeet.log_set log_callback, user_data
|
||||
end
|
||||
end
|
||||
|
||||
sub_test_case "full" do
|
||||
def setup
|
||||
super
|
||||
@samples = File.read(AUDIO, nil, 78).unpack("s<*").collect {|i| i.to_f / 2**15}
|
||||
end
|
||||
|
||||
def test_full
|
||||
@parakeet.full @params, @samples, @samples.length
|
||||
|
||||
segments = @parakeet.each_segment.to_a
|
||||
assert_equal 1, segments.length
|
||||
assert_match /ask not what your country can do for you, ask what you can do for your/, segments.first.text
|
||||
end
|
||||
|
||||
def test_full_without_length
|
||||
@parakeet.full(@params, @samples)
|
||||
|
||||
segments = @parakeet.each_segment.to_a
|
||||
assert_equal 1, segments.length
|
||||
assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text
|
||||
end
|
||||
|
||||
def test_full_enumerator
|
||||
samples = @samples.each
|
||||
@parakeet.full @params, samples, @samples.length
|
||||
|
||||
segments = @parakeet.each_segment.to_a
|
||||
assert_equal 1, segments.length
|
||||
assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text
|
||||
end
|
||||
|
||||
def test_full_enumerator_without_length
|
||||
samples = @samples.each
|
||||
assert_raise ArgumentError do
|
||||
@parakeet.full @params, samples
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_enumerator_with_too_large_length
|
||||
samples = @samples.each.take(10).to_enum
|
||||
assert_raise StopIteration do
|
||||
@parakeet.full @params, samples, 11
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_with_memory_view
|
||||
samples = JFKReader.new(AUDIO)
|
||||
@parakeet.full @params, samples
|
||||
|
||||
segments = @parakeet.each_segment.to_a
|
||||
assert_equal 1, segments.length
|
||||
assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text
|
||||
end
|
||||
|
||||
def test_full_with_memroy_view_gc
|
||||
samples = JFKReader.new(AUDIO)
|
||||
@parakeet.full(@params, samples)
|
||||
GC.start
|
||||
require "fiddle"
|
||||
Fiddle::MemoryView.export samples do |view|
|
||||
assert_equal 176000, view.to_s.unpack("#{view.format}*").length
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_transcribe
|
||||
assert_nothing_raised do
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
end
|
||||
end
|
||||
|
||||
def test_transcribe_with_pathname
|
||||
assert_nothing_raised do
|
||||
@parakeet.transcribe Pathname(AUDIO), @params
|
||||
end
|
||||
end
|
||||
|
||||
def test_transcribe_with_nothing
|
||||
assert_raise_message(/open/) do
|
||||
@parakeet.transcribe "nothing", @params
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
@parakeet.free
|
||||
assert_raise RuntimeError do
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
end
|
||||
end
|
||||
end
|
||||
24
bindings/ruby/test/test_parakeet_context_params.rb
Normal file
24
bindings/ruby/test/test_parakeet_context_params.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParakeetContextParams < TestBase
|
||||
def setup
|
||||
@params = Parakeet::Context::Params.new
|
||||
end
|
||||
|
||||
def test_new
|
||||
assert_instance_of Parakeet::Context::Params, @params
|
||||
end
|
||||
|
||||
def test_attributes
|
||||
assert_true @params.use_gpu
|
||||
assert_instance_of Integer, @params.gpu_device
|
||||
end
|
||||
|
||||
def test_attribute_writer
|
||||
@params.use_gpu = false
|
||||
assert_false @params.use_gpu
|
||||
|
||||
@params.gpu_device = 2
|
||||
assert_equal 2, @params.gpu_device
|
||||
end
|
||||
end
|
||||
21
bindings/ruby/test/test_parakeet_model.rb
Normal file
21
bindings/ruby/test/test_parakeet_model.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParakeetModel < TestBase
|
||||
def test_model
|
||||
parakeet = Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin")
|
||||
assert_instance_of Parakeet::Model, parakeet.model
|
||||
end
|
||||
|
||||
def test_attributes
|
||||
parakeet = Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin")
|
||||
model = parakeet.model
|
||||
|
||||
assert_equal 10, model.n_vocab
|
||||
assert_equal 3200, model.n_audio_ctx
|
||||
assert_equal 8, model.n_audio_state
|
||||
assert_equal 2, model.n_audio_head
|
||||
assert_equal 1, model.n_audio_layer
|
||||
assert_equal 16, model.n_mels
|
||||
assert_equal 0, model.ftype
|
||||
end
|
||||
end
|
||||
78
bindings/ruby/test/test_parakeet_params.rb
Normal file
78
bindings/ruby/test/test_parakeet_params.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
require_relative "helper"
|
||||
require "etc"
|
||||
|
||||
class TestParakeetParams < TestBase
|
||||
PARAM_NAMES = [
|
||||
:n_threads,
|
||||
:offset_ms,
|
||||
:duration_ms,
|
||||
:no_context,
|
||||
:audio_ctx
|
||||
]
|
||||
|
||||
def setup
|
||||
@params = Parakeet::Params.new
|
||||
end
|
||||
|
||||
def test_new
|
||||
assert_instance_of Parakeet::Params, @params
|
||||
end
|
||||
|
||||
def test_n_threads
|
||||
assert_equal [4, Etc.nprocessors].min, @params.n_threads
|
||||
|
||||
@params.n_threads = 1
|
||||
assert_equal 1, @params.n_threads
|
||||
end
|
||||
|
||||
def test_offset_ms
|
||||
assert_equal 0, @params.offset_ms
|
||||
|
||||
@params.offset_ms = 10_000
|
||||
assert_equal 10_000, @params.offset_ms
|
||||
end
|
||||
|
||||
def test_duration_ms
|
||||
assert_equal 0, @params.duration_ms
|
||||
|
||||
@params.duration_ms = 60_000
|
||||
assert_equal 60_000, @params.duration_ms
|
||||
end
|
||||
|
||||
def test_no_context
|
||||
assert_equal true, @params.no_context
|
||||
|
||||
@params.no_context = false
|
||||
assert_equal false, @params.no_context
|
||||
end
|
||||
|
||||
def test_audio_ctx
|
||||
assert_equal 0, @params.audio_ctx
|
||||
|
||||
@params.audio_ctx = 1
|
||||
assert_equal 1, @params.audio_ctx
|
||||
end
|
||||
|
||||
def test_new_with_kw_args
|
||||
params = Parakeet::Params.new(n_threads: 1)
|
||||
assert_equal 1, params.n_threads
|
||||
assert_equal 0, params.offset_ms
|
||||
end
|
||||
|
||||
data(PARAM_NAMES.collect {|param| [param, param]}.to_h)
|
||||
def test_new_with_kw_args_default_values(param)
|
||||
default_value = @params.send(param)
|
||||
value = case [param, default_value]
|
||||
in [*, true | false]
|
||||
!default_value
|
||||
in [*, Integer]
|
||||
default_value + 1
|
||||
end
|
||||
params = Parakeet::Params.new(param => value)
|
||||
assert_equal value, params.send(param)
|
||||
|
||||
PARAM_NAMES.reject {|name| name == param}.each do |name|
|
||||
assert_equal @params.send(name), params.send(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
42
bindings/ruby/test/test_parakeet_segment.rb
Normal file
42
bindings/ruby/test/test_parakeet_segment.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParakeetSegment < TestBase
|
||||
def setup
|
||||
omit "Skip not to download large model" if ENV["CI"]
|
||||
|
||||
@parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0")
|
||||
@parakeet.transcribe AUDIO, Parakeet::Params.new
|
||||
end
|
||||
|
||||
def test_segment
|
||||
whole_text = ""
|
||||
@parakeet.each_segment do |segment|
|
||||
assert_instance_of Parakeet::Segment, segment
|
||||
assert_kind_of Integer, segment.start_time
|
||||
assert segment.end_time >= segment.start_time
|
||||
assert_kind_of String, segment.text
|
||||
whole_text << segment.text
|
||||
end
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, whole_text)
|
||||
end
|
||||
|
||||
def test_deconstruct_keys
|
||||
segment = @parakeet.each_segment.first
|
||||
expected = {
|
||||
start_time: segment.start_time,
|
||||
end_time: segment.end_time,
|
||||
text: segment.text
|
||||
}
|
||||
assert_equal expected, segment.deconstruct_keys([:start_time, :end_time, :text])
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_with_nil
|
||||
segment = @parakeet.each_segment.first
|
||||
expected = {
|
||||
start_time: segment.start_time,
|
||||
end_time: segment.end_time,
|
||||
text: segment.text
|
||||
}
|
||||
assert_equal expected, segment.deconstruct_keys(nil)
|
||||
end
|
||||
end
|
||||
73
bindings/ruby/test/test_parakeet_token.rb
Normal file
73
bindings/ruby/test/test_parakeet_token.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParakeetToken < TestBase
|
||||
ATTRS = %i[
|
||||
id
|
||||
duration_idx
|
||||
duration_value
|
||||
frame_index
|
||||
probability
|
||||
log_probability
|
||||
start_time
|
||||
end_time
|
||||
word_start?
|
||||
text
|
||||
]
|
||||
|
||||
def setup
|
||||
omit "Skip not to download large model" if ENV["CI"]
|
||||
|
||||
Whisper.instance_variable_set "@whisper", nil
|
||||
GC.start
|
||||
|
||||
parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0")
|
||||
params = Parakeet::Params.new
|
||||
parakeet.transcribe AUDIO, params
|
||||
@segment = parakeet.each_segment.first
|
||||
end
|
||||
|
||||
def test_each_token
|
||||
i = 0
|
||||
@segment.each_token do |token|
|
||||
i += 1
|
||||
assert_instance_of Parakeet::Token, token
|
||||
end
|
||||
assert_equal 38, i
|
||||
end
|
||||
|
||||
def test_each_token_without_block
|
||||
assert_instance_of Enumerator, @segment.each_token
|
||||
end
|
||||
|
||||
def test_token
|
||||
token = @segment.each_token.first
|
||||
|
||||
assert_instance_of Parakeet::Token, token
|
||||
assert_instance_of Integer, token.id
|
||||
assert_instance_of Integer, token.duration_idx
|
||||
assert_instance_of Integer, token.duration_value
|
||||
assert_instance_of Integer, token.frame_index
|
||||
assert_instance_of Float, token.probability
|
||||
assert_instance_of Float, token.log_probability
|
||||
assert_instance_of Integer, token.start_time
|
||||
assert_instance_of Integer, token.end_time
|
||||
assert_instance_of String, token.text
|
||||
end
|
||||
|
||||
def test_text
|
||||
assert_equal ["▁And", "▁so", ",", "▁my", "▁f", "ell", "ow", "▁Amer", "ic", "ans", ",", "▁a", "sk", "▁not", "▁what", "▁your", "▁co", "un", "tr", "y", "▁can", "▁do", "▁for", "▁you", ",", "▁a", "sk", "▁what", "▁you", "▁can", "▁do", "▁for", "▁your", "▁co", "un", "tr", "y", "."],
|
||||
@segment.each_token.collect(&:text)
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_with_nil
|
||||
token = @segment.each_token.first
|
||||
expected = ATTRS.collect {|attr| [attr.to_s.sub(/\?\z/, "").intern, token.send(attr)]}.to_h
|
||||
assert_equal expected, token.deconstruct_keys(nil)
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_with_keys
|
||||
token = @segment.each_token.first
|
||||
expected = ATTRS.collect {|attr| [attr.to_s.sub(/\?\z/, "").intern, token.send(attr)]}.to_h
|
||||
assert_equal expected, token.deconstruct_keys(expected.keys)
|
||||
end
|
||||
end
|
||||
315
bindings/ruby/test/test_params.rb
Normal file
315
bindings/ruby/test/test_params.rb
Normal file
@@ -0,0 +1,315 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestParams < TestBase
|
||||
PARAM_NAMES = [
|
||||
:language,
|
||||
:translate,
|
||||
:no_context,
|
||||
:single_segment,
|
||||
:print_special,
|
||||
:print_progress,
|
||||
:print_realtime,
|
||||
:print_timestamps,
|
||||
:suppress_blank,
|
||||
:suppress_nst,
|
||||
:token_timestamps,
|
||||
:max_len,
|
||||
:split_on_word,
|
||||
:initial_prompt,
|
||||
:carry_initial_prompt,
|
||||
:diarize,
|
||||
:offset,
|
||||
:duration,
|
||||
:max_text_tokens,
|
||||
:temperature,
|
||||
:max_initial_ts,
|
||||
:length_penalty,
|
||||
:temperature_inc,
|
||||
:entropy_thold,
|
||||
:logprob_thold,
|
||||
:no_speech_thold,
|
||||
:new_segment_callback,
|
||||
:new_segment_callback_user_data,
|
||||
:progress_callback,
|
||||
:progress_callback_user_data,
|
||||
:abort_callback,
|
||||
:abort_callback_user_data,
|
||||
:vad,
|
||||
:vad_model_path,
|
||||
:vad_params,
|
||||
]
|
||||
|
||||
def setup
|
||||
@params = Whisper::Params.new
|
||||
end
|
||||
|
||||
def test_language
|
||||
@params.language = "en"
|
||||
assert_equal @params.language, "en"
|
||||
GC.compact
|
||||
assert_equal @params.language, "en"
|
||||
@params.language = "auto"
|
||||
assert_equal @params.language, "auto"
|
||||
end
|
||||
|
||||
def test_offset
|
||||
@params.offset = 10_000
|
||||
assert_equal @params.offset, 10_000
|
||||
@params.offset = 0
|
||||
assert_equal @params.offset, 0
|
||||
end
|
||||
|
||||
def test_duration
|
||||
@params.duration = 60_000
|
||||
assert_equal @params.duration, 60_000
|
||||
@params.duration = 0
|
||||
assert_equal @params.duration, 0
|
||||
end
|
||||
|
||||
def test_max_text_tokens
|
||||
@params.max_text_tokens = 300
|
||||
assert_equal @params.max_text_tokens, 300
|
||||
@params.max_text_tokens = 0
|
||||
assert_equal @params.max_text_tokens, 0
|
||||
end
|
||||
|
||||
def test_translate
|
||||
@params.translate = true
|
||||
assert @params.translate
|
||||
@params.translate = false
|
||||
assert !@params.translate
|
||||
end
|
||||
|
||||
def test_no_context
|
||||
@params.no_context = true
|
||||
assert @params.no_context
|
||||
@params.no_context = false
|
||||
assert !@params.no_context
|
||||
end
|
||||
|
||||
def test_single_segment
|
||||
@params.single_segment = true
|
||||
assert @params.single_segment
|
||||
@params.single_segment = false
|
||||
assert !@params.single_segment
|
||||
end
|
||||
|
||||
def test_print_special
|
||||
@params.print_special = true
|
||||
assert @params.print_special
|
||||
@params.print_special = false
|
||||
assert !@params.print_special
|
||||
end
|
||||
|
||||
def test_print_progress
|
||||
@params.print_progress = true
|
||||
assert @params.print_progress
|
||||
@params.print_progress = false
|
||||
assert !@params.print_progress
|
||||
end
|
||||
|
||||
def test_print_realtime
|
||||
@params.print_realtime = true
|
||||
assert @params.print_realtime
|
||||
@params.print_realtime = false
|
||||
assert !@params.print_realtime
|
||||
end
|
||||
|
||||
def test_print_timestamps
|
||||
@params.print_timestamps = true
|
||||
assert @params.print_timestamps
|
||||
@params.print_timestamps = false
|
||||
assert !@params.print_timestamps
|
||||
end
|
||||
|
||||
def test_carry_initial_prompt
|
||||
@params.carry_initial_prompt = true
|
||||
assert @params.carry_initial_prompt
|
||||
@params.carry_initial_prompt = false
|
||||
assert !@params.carry_initial_prompt
|
||||
end
|
||||
|
||||
def test_suppress_blank
|
||||
@params.suppress_blank = true
|
||||
assert @params.suppress_blank
|
||||
@params.suppress_blank = false
|
||||
assert !@params.suppress_blank
|
||||
end
|
||||
|
||||
def test_suppress_nst
|
||||
@params.suppress_nst = true
|
||||
assert @params.suppress_nst
|
||||
@params.suppress_nst = false
|
||||
assert !@params.suppress_nst
|
||||
end
|
||||
|
||||
def test_token_timestamps
|
||||
@params.token_timestamps = true
|
||||
assert @params.token_timestamps
|
||||
@params.token_timestamps = false
|
||||
assert !@params.token_timestamps
|
||||
end
|
||||
|
||||
def test_max_len
|
||||
@params.max_len = 42
|
||||
assert_equal @params.max_len, 42
|
||||
@params.max_len = 0
|
||||
assert_equal @params.max_len, 0
|
||||
end
|
||||
|
||||
def test_split_on_word
|
||||
@params.split_on_word = true
|
||||
assert @params.split_on_word
|
||||
@params.split_on_word = false
|
||||
assert !@params.split_on_word
|
||||
end
|
||||
|
||||
def test_initial_prompt
|
||||
assert_nil @params.initial_prompt
|
||||
@params.initial_prompt = "You are a polite person."
|
||||
assert_equal "You are a polite person.", @params.initial_prompt
|
||||
end
|
||||
|
||||
def test_temperature
|
||||
assert_equal 0.0, @params.temperature
|
||||
@params.temperature = 0.5
|
||||
assert_equal 0.5, @params.temperature
|
||||
end
|
||||
|
||||
def test_max_initial_ts
|
||||
assert_equal 1.0, @params.max_initial_ts
|
||||
@params.max_initial_ts = 600.0
|
||||
assert_equal 600.0, @params.max_initial_ts
|
||||
end
|
||||
|
||||
def test_length_penalty
|
||||
assert_equal(-1.0, @params.length_penalty)
|
||||
@params.length_penalty = 0.5
|
||||
assert_equal 0.5, @params.length_penalty
|
||||
end
|
||||
|
||||
def test_temperature_inc
|
||||
assert_in_delta 0.2, @params.temperature_inc
|
||||
@params.temperature_inc = 0.5
|
||||
assert_in_delta 0.5, @params.temperature_inc
|
||||
end
|
||||
|
||||
def test_entropy_thold
|
||||
assert_in_delta 2.4, @params.entropy_thold
|
||||
@params.entropy_thold = 3.0
|
||||
assert_in_delta 3.0, @params.entropy_thold
|
||||
end
|
||||
|
||||
def test_logprob_thold
|
||||
assert_in_delta(-1.0, @params.logprob_thold)
|
||||
@params.logprob_thold = -0.5
|
||||
assert_in_delta(-0.5, @params.logprob_thold)
|
||||
end
|
||||
|
||||
def test_no_speech_thold
|
||||
assert_in_delta 0.6, @params.no_speech_thold
|
||||
@params.no_speech_thold = 0.2
|
||||
assert_in_delta 0.2, @params.no_speech_thold
|
||||
end
|
||||
|
||||
def test_vad
|
||||
assert_false @params.vad
|
||||
@params.vad = true
|
||||
assert_true @params.vad
|
||||
end
|
||||
|
||||
def test_vad_model_path
|
||||
assert_nil @params.vad_model_path
|
||||
@params.vad_model_path = "silero-v6.2.0"
|
||||
assert_equal Whisper::Model.pre_converted_models["silero-v6.2.0"].to_path, @params.vad_model_path
|
||||
end
|
||||
|
||||
def test_vad_model_path_with_nil
|
||||
@params.vad_model_path = "silero-v6.2.0"
|
||||
@params.vad_model_path = nil
|
||||
assert_nil @params.vad_model_path
|
||||
end
|
||||
|
||||
def test_vad_model_path_with_invalid
|
||||
assert_raise TypeError do
|
||||
@params.vad_model_path = Object.new
|
||||
end
|
||||
end
|
||||
|
||||
def test_vad_model_path_with_URI_string
|
||||
@params.vad_model_path = "https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v6.2.0.bin"
|
||||
assert_equal @params.vad_model_path, Whisper::Model.pre_converted_models["silero-v6.2.0"].to_path
|
||||
end
|
||||
|
||||
def test_vad_model_path_with_URI
|
||||
@params.vad_model_path = URI("https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v6.2.0.bin")
|
||||
assert_equal @params.vad_model_path, Whisper::Model.pre_converted_models["silero-v6.2.0"].to_path
|
||||
end
|
||||
|
||||
def test_vad_params
|
||||
assert_kind_of Whisper::VAD::Params, @params.vad_params
|
||||
default_params = @params.vad_params
|
||||
assert_same default_params, @params.vad_params
|
||||
assert_equal 0.5, default_params.threshold
|
||||
new_params = Whisper::VAD::Params.new
|
||||
@params.vad_params = new_params
|
||||
assert_same new_params, @params.vad_params
|
||||
end
|
||||
|
||||
def test_new_with_kw_args
|
||||
params = Whisper::Params.new(language: "es")
|
||||
assert_equal "es", params.language
|
||||
assert_equal 1.0, params.max_initial_ts
|
||||
end
|
||||
|
||||
def test_new_with_kw_args_non_existent
|
||||
assert_raise ArgumentError do
|
||||
Whisper::Params.new(non_existent: "value")
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_with_kw_args_wrong_type
|
||||
assert_raise TypeError do
|
||||
Whisper::Params.new(language: 3)
|
||||
end
|
||||
end
|
||||
|
||||
data(PARAM_NAMES.collect {|param| [param, param]}.to_h)
|
||||
def test_new_with_kw_args_default_values(param)
|
||||
default_value = @params.send(param)
|
||||
value = case [param, default_value]
|
||||
in [*, true | false]
|
||||
!default_value
|
||||
in [*, Integer | Float]
|
||||
default_value + 1
|
||||
in [:language, *]
|
||||
"es"
|
||||
in [:initial_prompt, *]
|
||||
"Initial prompt"
|
||||
in [/_callback\Z/, *]
|
||||
proc {}
|
||||
in [/_user_data\Z/, *]
|
||||
Object.new
|
||||
in [:vad_model_path, *]
|
||||
Whisper::Model.pre_converted_models["silero-v6.2.0"].to_path
|
||||
in [:vad_params, *]
|
||||
Whisper::VAD::Params.new
|
||||
end
|
||||
params = Whisper::Params.new(param => value)
|
||||
if Float === value
|
||||
assert_in_delta value, params.send(param)
|
||||
else
|
||||
assert_equal value, params.send(param)
|
||||
end
|
||||
|
||||
PARAM_NAMES.reject {|name| name == param}.each do |name|
|
||||
expected = @params.send(name)
|
||||
actual = params.send(name)
|
||||
if Float === expected
|
||||
assert_in_delta expected, actual
|
||||
else
|
||||
assert_equal expected, actual
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
145
bindings/ruby/test/test_segment.rb
Normal file
145
bindings/ruby/test/test_segment.rb
Normal file
@@ -0,0 +1,145 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestSegment < TestBase
|
||||
def test_iteration
|
||||
whisper.each_segment do |segment|
|
||||
assert_instance_of Whisper::Segment, segment
|
||||
end
|
||||
end
|
||||
|
||||
def test_enumerator
|
||||
enum = whisper.each_segment
|
||||
assert_instance_of Enumerator, enum
|
||||
enum.to_a.each_with_index do |segment, index|
|
||||
assert_instance_of Whisper::Segment, segment
|
||||
assert_kind_of Integer, index
|
||||
end
|
||||
end
|
||||
|
||||
def test_start_time
|
||||
i = 0
|
||||
whisper.each_segment do |segment|
|
||||
assert_equal 0, segment.start_time if i == 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
def test_end_time
|
||||
i = 0
|
||||
whisper.each_segment do |segment|
|
||||
assert_equal whisper.full_get_segment_t1(i) * 10, segment.end_time
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
def test_no_speech_prob
|
||||
no_speech_prob = nil
|
||||
whisper.each_segment do |segment|
|
||||
no_speech_prob = segment.no_speech_prob
|
||||
end
|
||||
assert no_speech_prob > 0.0
|
||||
end
|
||||
|
||||
def test_on_new_segment
|
||||
params = Whisper::Params.new
|
||||
seg = nil
|
||||
index = 0
|
||||
params.on_new_segment do |segment|
|
||||
assert_instance_of Whisper::Segment, segment
|
||||
if index == 0
|
||||
seg = segment
|
||||
assert_equal 0, segment.start_time
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||
end
|
||||
index += 1
|
||||
end
|
||||
whisper.transcribe(AUDIO, params)
|
||||
assert_equal 0, seg.start_time
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, seg.text)
|
||||
end
|
||||
|
||||
def test_on_new_segment_twice
|
||||
params = Whisper::Params.new
|
||||
seg = nil
|
||||
params.on_new_segment do |segment|
|
||||
seg = segment
|
||||
return
|
||||
end
|
||||
params.on_new_segment do |segment|
|
||||
assert_same seg, segment
|
||||
return
|
||||
end
|
||||
whisper.transcribe(AUDIO, params)
|
||||
end
|
||||
|
||||
def test_transcription_after_segment_retrieved
|
||||
segment = whisper.each_segment.first
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||
|
||||
whisper.transcribe(AUDIO, Whisper::Params.new(offset: 5000))
|
||||
assert_not_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||
assert_match(/what you can do for your country/i, segment.text)
|
||||
end
|
||||
|
||||
def test_pattern_matching
|
||||
segment = whisper.each_segment.first
|
||||
segment => {start_time:, end_time:, text:, no_speech_prob:, speaker_turn_next:}
|
||||
|
||||
assert_equal segment.start_time, start_time
|
||||
assert_equal segment.end_time, end_time
|
||||
assert_equal segment.text, text
|
||||
assert_equal segment.no_speech_prob, no_speech_prob
|
||||
assert_equal segment.speaker_turn_next?, speaker_turn_next
|
||||
end
|
||||
|
||||
def test_pattern_matching_partial
|
||||
segment = whisper.each_segment.first
|
||||
segment => {start_time:, end_time:, text:}
|
||||
|
||||
assert_equal segment.start_time, start_time
|
||||
assert_equal segment.end_time, end_time
|
||||
assert_equal segment.text, text
|
||||
end
|
||||
|
||||
def test_deconstruct_keys
|
||||
segment = whisper.each_segment.first
|
||||
expected = {
|
||||
start_time: segment.start_time,
|
||||
end_time: segment.end_time,
|
||||
text: segment.text,
|
||||
no_speech_prob: segment.no_speech_prob,
|
||||
speaker_turn_next: segment.speaker_turn_next?
|
||||
}
|
||||
assert_equal expected, segment.deconstruct_keys([:start_time, :end_time, :text, :no_speech_prob, :speaker_turn_next])
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_non_existent
|
||||
omit "Undefined behavior"
|
||||
|
||||
segment = whisper.each_segment.first
|
||||
|
||||
assert_equal({}, segment.deconstruct_keys([:non_existent]))
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_too_many_keys
|
||||
omit "Undefined behavior"
|
||||
|
||||
segment = whisper.each_segment.first
|
||||
|
||||
assert_equal({}, segment.deconstruct_keys([:start_time, :end_time, :text, :no_speech_prob, :speaker_turn_next, :extra_key]))
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_includes_non_existent_keys_not_too_many
|
||||
omit "Undefined behavior"
|
||||
|
||||
segment = whisper.each_segment.first
|
||||
|
||||
expected = {
|
||||
start_time: segment.start_time,
|
||||
end_time: segment.end_time,
|
||||
text: segment.text,
|
||||
no_speech_prob: segment.no_speech_prob
|
||||
}
|
||||
assert_equal(expected, segment.deconstruct_keys([:start_time, :end_time, :text, :no_speech_prob, :non_existent]))
|
||||
end
|
||||
end
|
||||
81
bindings/ruby/test/test_token.rb
Normal file
81
bindings/ruby/test/test_token.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestToken < TestBase
|
||||
def setup
|
||||
@segment = whisper.each_segment.first
|
||||
@token = @segment.each_token.first
|
||||
end
|
||||
|
||||
def test_n_tokens
|
||||
assert_equal 27, @segment.n_tokens
|
||||
end
|
||||
|
||||
def test_allocate
|
||||
token = Whisper::Token.allocate
|
||||
assert_raise do
|
||||
token.id
|
||||
end
|
||||
end
|
||||
|
||||
def test_each_token
|
||||
i = 0
|
||||
@segment.each_token do |token|
|
||||
i += 1
|
||||
assert_instance_of Whisper::Token, token
|
||||
end
|
||||
assert_equal 27, i
|
||||
end
|
||||
|
||||
def test_each_token_without_block
|
||||
assert_instance_of Enumerator, @segment.each_token
|
||||
end
|
||||
|
||||
def test_token
|
||||
assert_instance_of Whisper::Token, @token
|
||||
|
||||
assert_instance_of Integer, @token.id
|
||||
assert_instance_of Float, @token.probability
|
||||
assert_instance_of Float, @token.log_probability
|
||||
|
||||
assert_instance_of Integer, @token.tid
|
||||
assert_instance_of Float, @token.pt
|
||||
assert_instance_of Float, @token.ptsum
|
||||
|
||||
assert_instance_of Integer, @token.start_time
|
||||
assert_instance_of Integer, @token.end_time
|
||||
|
||||
assert_instance_of Integer, @token.t_dtw
|
||||
|
||||
assert_instance_of Float, @token.voice_length
|
||||
|
||||
assert_instance_of String, @token.text
|
||||
end
|
||||
|
||||
def test_text
|
||||
assert_equal ["[_BEG_]", " And", " so", " my", " fellow", " Americans", ",", " ask", " not", " what", " your", " country", " can", " do", " for", " you", ",", " ask", " what", " you", " can", " do", " for", " your", " country", ".", "[_TT_550]"],
|
||||
@segment.each_token.collect(&:text)
|
||||
end
|
||||
|
||||
def test_token_timestamps
|
||||
params = Whisper::Params.new(token_timestamps: true)
|
||||
whisper.transcribe(TestBase::AUDIO, params)
|
||||
prev = -1
|
||||
whisper.each_segment.first.each_token do |token|
|
||||
assert token.start_time >= prev
|
||||
assert token.end_time >= token.start_time
|
||||
prev = token.end_time
|
||||
end
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_with_nil
|
||||
keys = %i[id tid probability log_probability pt ptsum t_dtw voice_length start_time end_time text]
|
||||
expected = keys.collect {|key| [key, @token.send(key)] }.to_h
|
||||
assert_equal(expected, @token.deconstruct_keys(nil))
|
||||
end
|
||||
|
||||
def test_deconstruct_keys_with_keys
|
||||
keys = %i[id tid probability log_probability pt ptsum t_dtw voice_length start_time end_time text]
|
||||
expected = keys.collect {|key| [key, @token.send(key)] }.to_h
|
||||
assert_equal expected, @token.deconstruct_keys(keys)
|
||||
end
|
||||
end
|
||||
28
bindings/ruby/test/test_vad.rb
Normal file
28
bindings/ruby/test/test_vad.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestVAD < TestBase
|
||||
def setup
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
vad_params = Whisper::VAD::Params.new
|
||||
@params = Whisper::Params.new(
|
||||
vad: true,
|
||||
vad_model_path: "silero-v6.2.0",
|
||||
vad_params:
|
||||
)
|
||||
end
|
||||
|
||||
def test_transcribe
|
||||
@whisper.transcribe(TestBase::AUDIO, @params) do |text|
|
||||
assert_match(/ask not what your country can do for you[,.] ask what you can do for your country/i, text)
|
||||
end
|
||||
end
|
||||
|
||||
def test_vad_segments_api
|
||||
@whisper.transcribe TestBase::AUDIO, @params
|
||||
|
||||
assert_equal 4, @whisper.full_n_vad_segments
|
||||
@whisper.full_n_vad_segments.times do |i|
|
||||
assert @whisper.full_get_vad_segment_t0(i) < @whisper.full_get_vad_segment_t1(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
108
bindings/ruby/test/test_vad_context.rb
Normal file
108
bindings/ruby/test/test_vad_context.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestVADContext < TestBase
|
||||
def test_initialize
|
||||
context = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
assert_instance_of Whisper::VAD::Context, context
|
||||
end
|
||||
|
||||
def test_detect
|
||||
context = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
segments = context.detect(AUDIO, Whisper::VAD::Params.new)
|
||||
assert_segments segments
|
||||
end
|
||||
|
||||
def test_invalid_model_type
|
||||
assert_raise TypeError do
|
||||
Whisper::VAD::Context.new(Object.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_allocate
|
||||
vad = Whisper::VAD::Context.allocate
|
||||
assert_raise do
|
||||
vad.detect(AUDIO, Whisper::VAD::Params.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
vad = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
vad.free
|
||||
assert_raise RuntimeError do
|
||||
vad.detect(AUDIO, Whisper::VAD::Params.new)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_segments(segments)
|
||||
assert_instance_of Whisper::VAD::Segments, segments
|
||||
|
||||
i = 0
|
||||
segments.each do |segment|
|
||||
i += 1
|
||||
assert_instance_of Whisper::VAD::Segment, segment
|
||||
end
|
||||
assert i > 0
|
||||
|
||||
segments.each_with_index do |segment, index|
|
||||
assert_instance_of Integer, index
|
||||
end
|
||||
|
||||
assert_instance_of Enumerator, segments.each
|
||||
|
||||
segment = segments.each.first
|
||||
assert_instance_of Integer, segment.start_time
|
||||
assert_instance_of Integer, segment.end_time
|
||||
|
||||
segment => {start_time:, end_time:}
|
||||
assert_equal segment.start_time, start_time
|
||||
assert_equal segment.end_time, end_time
|
||||
|
||||
assert_equal 4, segments.length
|
||||
end
|
||||
|
||||
sub_test_case "from samples" do
|
||||
def setup
|
||||
super
|
||||
@vad = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
@samples = File.read(AUDIO, nil, 78).unpack("s<*").collect {|i| i.to_f / 2**15}
|
||||
end
|
||||
|
||||
def test_segments_from_samples
|
||||
segments = @vad.segments_from_samples(Whisper::VAD::Params.new, @samples, @samples.length)
|
||||
assert_segments segments
|
||||
end
|
||||
|
||||
def test_segments_from_samples_without_length
|
||||
segments = @vad.segments_from_samples(Whisper::VAD::Params.new, @samples)
|
||||
assert_segments segments
|
||||
end
|
||||
|
||||
def test_segments_from_samples_enumerator
|
||||
samples = @samples.each
|
||||
segments = @vad.segments_from_samples(Whisper::VAD::Params.new, samples, @samples.length)
|
||||
assert_segments segments
|
||||
end
|
||||
|
||||
def test_segments_from_samples_enumerator_without_length
|
||||
samples = @samples.each
|
||||
assert_raise ArgumentError do
|
||||
@vad.segments_from_samples(Whisper::VAD::Params.new, samples)
|
||||
end
|
||||
end
|
||||
|
||||
def test_segments_from_samples_enumerator_with_too_large_length
|
||||
samples = @samples.each.take(10).to_enum
|
||||
assert_raise StopIteration do
|
||||
@vad.segments_from_samples(Whisper::VAD::Params.new, samples, 11)
|
||||
end
|
||||
end
|
||||
|
||||
def test_segments_from_samples_with_memory_view
|
||||
samples = JFKReader.new(AUDIO)
|
||||
segments = @vad.segments_from_samples(Whisper::VAD::Params.new, samples)
|
||||
assert_segments segments
|
||||
end
|
||||
end
|
||||
end
|
||||
103
bindings/ruby/test/test_vad_params.rb
Normal file
103
bindings/ruby/test/test_vad_params.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestVADParams < TestBase
|
||||
PARAM_NAMES = [
|
||||
:threshold,
|
||||
:min_speech_duration_ms,
|
||||
:min_silence_duration_ms,
|
||||
:max_speech_duration_s,
|
||||
:speech_pad_ms,
|
||||
:samples_overlap
|
||||
]
|
||||
|
||||
def setup
|
||||
@params = Whisper::VAD::Params.new
|
||||
end
|
||||
|
||||
def test_new
|
||||
params = Whisper::VAD::Params.new
|
||||
assert_kind_of Whisper::VAD::Params, params
|
||||
end
|
||||
|
||||
def test_threshold
|
||||
assert_in_delta @params.threshold, 0.5
|
||||
@params.threshold = 0.7
|
||||
assert_in_delta @params.threshold, 0.7
|
||||
end
|
||||
|
||||
def test_min_speech_duration
|
||||
pend
|
||||
end
|
||||
|
||||
def test_min_speech_duration_ms
|
||||
assert_equal 250, @params.min_speech_duration_ms
|
||||
@params.min_speech_duration_ms = 500
|
||||
assert_equal 500, @params.min_speech_duration_ms
|
||||
end
|
||||
|
||||
def test_min_silence_duration_ms
|
||||
assert_equal 100, @params.min_silence_duration_ms
|
||||
@params.min_silence_duration_ms = 200
|
||||
assert_equal 200, @params.min_silence_duration_ms
|
||||
end
|
||||
|
||||
def test_max_speech_duration
|
||||
pend
|
||||
end
|
||||
|
||||
def test_max_speech_duration_s
|
||||
assert @params.max_speech_duration_s >= 10e37 # Defaults to FLT_MAX
|
||||
@params.max_speech_duration_s = 60.0
|
||||
assert_equal 60.0, @params.max_speech_duration_s
|
||||
end
|
||||
|
||||
def test_speech_pad_ms
|
||||
assert_equal 30, @params.speech_pad_ms
|
||||
@params.speech_pad_ms = 50
|
||||
assert_equal 50, @params.speech_pad_ms
|
||||
end
|
||||
|
||||
def test_samples_overlap
|
||||
assert_in_delta @params.samples_overlap, 0.1
|
||||
@params.samples_overlap = 0.5
|
||||
assert_in_delta @params.samples_overlap, 0.5
|
||||
end
|
||||
|
||||
def test_equal
|
||||
assert_equal @params, Whisper::VAD::Params.new
|
||||
end
|
||||
|
||||
def test_new_with_kw_args
|
||||
params = Whisper::VAD::Params.new(threshold: 0.7)
|
||||
assert_in_delta params.threshold, 0.7
|
||||
assert_equal 250, params.min_speech_duration_ms
|
||||
end
|
||||
|
||||
def test_new_with_kw_args_non_existent
|
||||
assert_raise ArgumentError do
|
||||
Whisper::VAD::Params.new(non_existent: "value")
|
||||
end
|
||||
end
|
||||
|
||||
data(PARAM_NAMES.collect {|param| [param, param]}.to_h)
|
||||
def test_new_with_kw_args_default_values(param)
|
||||
default_value = @params.send(param)
|
||||
value = default_value + 1
|
||||
params = Whisper::VAD::Params.new(param => value)
|
||||
if Float === value
|
||||
assert_in_delta value, params.send(param)
|
||||
else
|
||||
assert_equal value, params.send(param)
|
||||
end
|
||||
|
||||
PARAM_NAMES.reject {|name| name == param}.each do |name|
|
||||
expected = @params.send(name)
|
||||
actual = params.send(name)
|
||||
if Float === expected
|
||||
assert_in_delta expected, actual
|
||||
else
|
||||
assert_equal expected, actual
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
bindings/ruby/test/test_vad_segment.rb
Normal file
19
bindings/ruby/test/test_vad_segment.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestVADSegment < TestBase
|
||||
def test_initialize
|
||||
segment = Whisper::VAD::Segment.new
|
||||
|
||||
assert_raise do
|
||||
segment.start_time
|
||||
end
|
||||
|
||||
assert_raise do
|
||||
segment.end_time
|
||||
end
|
||||
|
||||
assert_raise do
|
||||
segment => {start_time:, end_time:}
|
||||
end
|
||||
end
|
||||
end
|
||||
16
bindings/ruby/test/test_vad_segments.rb
Normal file
16
bindings/ruby/test/test_vad_segments.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require_relative "helper"
|
||||
|
||||
class TestVADSegments < TestBase
|
||||
def test_initialize
|
||||
segments = Whisper::VAD::Segments.new
|
||||
|
||||
assert_raise do
|
||||
segments.each do |segment|
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise do
|
||||
segments.length
|
||||
end
|
||||
end
|
||||
end
|
||||
350
bindings/ruby/test/test_whisper.rb
Normal file
350
bindings/ruby/test/test_whisper.rb
Normal file
@@ -0,0 +1,350 @@
|
||||
require_relative "helper"
|
||||
require "stringio"
|
||||
require "etc"
|
||||
require "pathname"
|
||||
|
||||
# Exists to detect memory-related bug
|
||||
Whisper.log_set ->(level, buffer, user_data) {}, nil
|
||||
|
||||
class TestWhisper < TestBase
|
||||
def setup
|
||||
@params = Whisper::Params.new
|
||||
end
|
||||
|
||||
def test_whisper
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new
|
||||
params.print_timestamps = false
|
||||
|
||||
@whisper.transcribe(AUDIO, params) {|text|
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text)
|
||||
}
|
||||
end
|
||||
|
||||
def test_whisper_pathname
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new
|
||||
|
||||
@whisper.transcribe(Pathname(AUDIO), params) {|text|
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text)
|
||||
}
|
||||
end
|
||||
|
||||
def test_transcribe_non_parallel
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new
|
||||
|
||||
@whisper.transcribe(AUDIO, params, n_processors: 1) {|text|
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text)
|
||||
}
|
||||
end
|
||||
|
||||
def test_transcribe_n_processors
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
params = Whisper::Params.new
|
||||
|
||||
without_log_callback do
|
||||
@whisper.transcribe(AUDIO, params, n_processors: 4) {|text|
|
||||
assert_match(/what you can do for your country/i, text)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
whisper.free
|
||||
|
||||
assert_raise RuntimeError do
|
||||
whisper.model_type
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def without_log_callback
|
||||
Whisper.log_set nil, nil
|
||||
yield
|
||||
ensure
|
||||
Whisper.log_set ->(level, buffer, user_data) {}, nil
|
||||
end
|
||||
|
||||
sub_test_case "After transcription" do
|
||||
def test_full_n_segments
|
||||
assert_equal 1, whisper.full_n_segments
|
||||
end
|
||||
|
||||
def test_full_lang_id
|
||||
assert_equal 0, whisper.full_lang_id
|
||||
end
|
||||
|
||||
def test_full_get_segment
|
||||
segment = whisper.full_get_segment(0)
|
||||
assert_equal 0, segment.start_time
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||
end
|
||||
|
||||
def test_full_get_segment_t0
|
||||
assert_equal 0, whisper.full_get_segment_t0(0)
|
||||
assert_raise IndexError do
|
||||
whisper.full_get_segment_t0(whisper.full_n_segments)
|
||||
end
|
||||
assert_raise IndexError do
|
||||
whisper.full_get_segment_t0(-1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_get_segment_t1
|
||||
t1 = whisper.full_get_segment_t1(0)
|
||||
assert_kind_of Integer, t1
|
||||
assert t1 > 0
|
||||
assert_raise IndexError do
|
||||
whisper.full_get_segment_t1(whisper.full_n_segments)
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_get_segment_speaker_turn_next
|
||||
assert_false whisper.full_get_segment_speaker_turn_next(0)
|
||||
end
|
||||
|
||||
def test_full_get_segment_text
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, whisper.full_get_segment_text(0))
|
||||
end
|
||||
|
||||
def test_full_get_segment_no_speech_prob
|
||||
prob = whisper.full_get_segment_no_speech_prob(0)
|
||||
assert prob > 0.0
|
||||
assert prob < 1.0
|
||||
end
|
||||
end
|
||||
|
||||
def test_lang_max_id
|
||||
assert_kind_of Integer, Whisper.lang_max_id
|
||||
end
|
||||
|
||||
def test_lang_id
|
||||
assert_equal 0, Whisper.lang_id("en")
|
||||
assert_raise ArgumentError do
|
||||
Whisper.lang_id("non existing language")
|
||||
end
|
||||
end
|
||||
|
||||
def test_lang_str
|
||||
assert_equal "en", Whisper.lang_str(0)
|
||||
assert_raise IndexError do
|
||||
Whisper.lang_str(Whisper.lang_max_id + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_lang_str_full
|
||||
assert_equal "english", Whisper.lang_str_full(0)
|
||||
assert_raise IndexError do
|
||||
Whisper.lang_str_full(Whisper.lang_max_id + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_system_info_str
|
||||
assert_match(/\AWHISPER : COREML = \d | OPENVINO = \d |/, Whisper.system_info_str)
|
||||
end
|
||||
|
||||
def test_version
|
||||
assert_kind_of String, Whisper::VERSION
|
||||
end
|
||||
|
||||
def test_log_set
|
||||
user_data = Object.new
|
||||
logs = []
|
||||
log_callback = ->(level, buffer, udata) {
|
||||
logs << [level, buffer, udata]
|
||||
}
|
||||
Whisper.log_set log_callback, user_data
|
||||
Whisper::Context.new("base.en")
|
||||
sleep 0.1 # wait for logs dequeued
|
||||
|
||||
assert logs.length > 30
|
||||
logs.each do |log|
|
||||
assert_include [Whisper::LOG_LEVEL_DEBUG, Whisper::LOG_LEVEL_INFO, Whisper::LOG_LEVEL_WARN], log[0]
|
||||
assert_same user_data, log[2]
|
||||
end
|
||||
end
|
||||
|
||||
def test_log_suppress
|
||||
stderr = $stderr
|
||||
Whisper.log_set ->(level, buffer, user_data) {
|
||||
# do nothing
|
||||
}, nil
|
||||
dev = StringIO.new("")
|
||||
$stderr = dev
|
||||
Whisper::Context.new("base.en")
|
||||
assert_empty dev.string
|
||||
ensure
|
||||
$stderr = stderr
|
||||
end
|
||||
|
||||
def test_access_attribute_without_initialization
|
||||
whisper = Whisper::Context.allocate
|
||||
assert_raise do
|
||||
whisper.model_type
|
||||
end
|
||||
end
|
||||
|
||||
sub_test_case "full" do
|
||||
def setup
|
||||
super
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
@samples = File.read(AUDIO, nil, 78).unpack("s<*").collect {|i| i.to_f / 2**15}
|
||||
end
|
||||
|
||||
def test_full
|
||||
@whisper.full(@params, @samples, @samples.length)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||
end
|
||||
|
||||
def test_full_without_length
|
||||
@whisper.full(@params, @samples)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||
end
|
||||
|
||||
def test_full_enumerator
|
||||
samples = @samples.each
|
||||
@whisper.full(@params, samples, @samples.length)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||
end
|
||||
|
||||
def test_full_enumerator_without_length
|
||||
samples = @samples.each
|
||||
assert_raise ArgumentError do
|
||||
@whisper.full(@params, samples)
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_enumerator_with_too_large_length
|
||||
samples = @samples.each.take(10).to_enum
|
||||
assert_raise StopIteration do
|
||||
@whisper.full(@params, samples, 11)
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_with_memory_view
|
||||
samples = JFKReader.new(AUDIO)
|
||||
@whisper.full(@params, samples)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||
end
|
||||
|
||||
def test_full_with_memroy_view_gc
|
||||
samples = JFKReader.new(AUDIO)
|
||||
@whisper.full(@params, samples)
|
||||
GC.start
|
||||
require "fiddle"
|
||||
Fiddle::MemoryView.export samples do |view|
|
||||
assert_equal 176000, view.to_s.unpack("#{view.format}*").length
|
||||
end
|
||||
end
|
||||
|
||||
def test_full_parallel
|
||||
nprocessors = 2
|
||||
without_log_callback do
|
||||
@whisper.full_parallel(@params, @samples, @samples.length, nprocessors)
|
||||
end
|
||||
|
||||
assert_equal nprocessors, @whisper.full_n_segments
|
||||
text = @whisper.each_segment.collect(&:text).join
|
||||
assert_match(/ask what you can do/i, text)
|
||||
assert_match(/for your country/i, text)
|
||||
end
|
||||
|
||||
def test_full_parallel_with_memory_view
|
||||
nprocessors = 2
|
||||
samples = JFKReader.new(AUDIO)
|
||||
without_log_callback do
|
||||
@whisper.full_parallel(@params, samples, nil, nprocessors)
|
||||
end
|
||||
|
||||
assert_equal nprocessors, @whisper.full_n_segments
|
||||
text = @whisper.each_segment.collect(&:text).join
|
||||
assert_match(/ask what you can do/i, text)
|
||||
assert_match(/for your country/i, text)
|
||||
end
|
||||
|
||||
def test_full_parallel_without_length_and_n_processors
|
||||
@whisper.full_parallel(@params, @samples)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
text = @whisper.each_segment.collect(&:text).join
|
||||
assert_match(/ask what you can do/i, text)
|
||||
assert_match(/for your country/i, text)
|
||||
end
|
||||
|
||||
def test_full_parallel_without_length
|
||||
nprocessors = 2
|
||||
without_log_callback do
|
||||
@whisper.full_parallel(@params, @samples, nil, nprocessors)
|
||||
end
|
||||
|
||||
assert_equal nprocessors, @whisper.full_n_segments
|
||||
text = @whisper.each_segment.collect(&:text).join
|
||||
assert_match(/ask what you can do/i, text)
|
||||
assert_match(/for your country/i, text)
|
||||
end
|
||||
|
||||
def test_full_parallel_without_n_processors
|
||||
@whisper.full_parallel(@params, @samples, @samples.length)
|
||||
|
||||
assert_equal 1, @whisper.full_n_segments
|
||||
text = @whisper.each_segment.collect(&:text).join
|
||||
assert_match(/ask what you can do/i, text)
|
||||
assert_match(/for your country/i, text)
|
||||
end
|
||||
end
|
||||
|
||||
def test_to_srt
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
whisper.transcribe AUDIO, @params
|
||||
|
||||
lines = whisper.to_srt.lines
|
||||
assert_match(/\A\d+\n/, lines[0])
|
||||
assert_match(/\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}\n/, lines[1])
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, lines[2])
|
||||
end
|
||||
|
||||
def test_to_webvtt
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
whisper.transcribe AUDIO, @params
|
||||
|
||||
lines = whisper.to_webvtt.lines
|
||||
assert_equal "WEBVTT\n", lines[0]
|
||||
assert_equal "\n", lines[1]
|
||||
assert_match(/\A\d+\n/, lines[2])
|
||||
assert_match(/\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}\n/, lines[3])
|
||||
assert_match(/ask not what your country can do for you, ask what you can do for your country/, lines[4])
|
||||
end
|
||||
|
||||
sub_test_case "Format needs escape" do
|
||||
def setup
|
||||
@whisper = Whisper::Context.new("base.en")
|
||||
@whisper.transcribe AUDIO, Whisper::Params.new
|
||||
segment = @whisper.each_segment.first
|
||||
segment.define_singleton_method :text do
|
||||
"& so my fellow Americans --> ask not what your country can do for you <-- ask what you can do for your country."
|
||||
end
|
||||
@whisper.define_singleton_method :each_segment do
|
||||
Enumerator.new(3) {|yielder| 3.times {yielder << segment}}
|
||||
end
|
||||
end
|
||||
|
||||
def test_to_srt_escape
|
||||
assert_equal "& so my fellow Americans --> ask not what your country can do for you <-- ask what you can do for your country.\n", @whisper.to_srt.lines[2]
|
||||
end
|
||||
|
||||
def test_to_webvtt_escape
|
||||
assert_equal "& so my fellow Americans --> ask not what your country can do for you <-- ask what you can do for your country.\n", @whisper.to_webvtt.lines[4]
|
||||
end
|
||||
end
|
||||
end
|
||||
36
bindings/ruby/whispercpp.gemspec
Normal file
36
bindings/ruby/whispercpp.gemspec
Normal file
@@ -0,0 +1,36 @@
|
||||
require_relative "extsources"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "whispercpp"
|
||||
s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
|
||||
s.version = '1.3.9'
|
||||
s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
|
||||
s.email = 'todd.fisher@gmail.com'
|
||||
s.extra_rdoc_files = ['LICENSE', 'README.md']
|
||||
|
||||
s.files = `git ls-files . -z`.split("\x0") +
|
||||
EXTSOURCES.collect {|file|
|
||||
basename = File.basename(file)
|
||||
if s.extra_rdoc_files.include?(basename)
|
||||
basename
|
||||
else
|
||||
file.sub("../..", "ext/sources")
|
||||
.sub("../javascript", "ext/sources/bindings/javascript")
|
||||
end
|
||||
}
|
||||
|
||||
s.summary = %q{Ruby whisper.cpp bindings}
|
||||
s.test_files = s.files.select {|file| file.start_with? "test/"}
|
||||
|
||||
s.extensions << 'ext/extconf.rb'
|
||||
s.required_ruby_version = '>= 3.3.0'
|
||||
|
||||
#### Documentation and testing.
|
||||
s.homepage = 'https://github.com/ggml-org/whisper.cpp'
|
||||
s.rdoc_options = ['--main', 'README.md']
|
||||
|
||||
|
||||
s.platform = Gem::Platform::RUBY
|
||||
|
||||
s.licenses = ['MIT']
|
||||
end
|
||||
Reference in New Issue
Block a user