Compare commits
3 Commits
go/v0.3.50
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fc584f266d | |||
| ad6bbd3331 | |||
| 2dd9ea9aee |
@@ -10,6 +10,7 @@ add_library(vosk
|
||||
src/recognizer.cc
|
||||
src/spk_model.cc
|
||||
src/vosk_api.cc
|
||||
src/postprocessor.cc
|
||||
)
|
||||
|
||||
find_package(kaldi REQUIRED)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Vosk Speech Recognition Toolkit
|
||||
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition for 20+ languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino,
|
||||
Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish.
|
||||
More to come.
|
||||
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
vocabulary and speaker identification.
|
||||
|
||||
Speech recognition bindings implemented for various programming languages
|
||||
like Python, Java, Node.JS, C#, C++, Rust, Go and others.
|
||||
|
||||
Vosk supplies speech recognition for chatbots, smart home appliances,
|
||||
virtual assistants. It can also create subtitles for movies,
|
||||
transcription for lectures and interviews.
|
||||
|
||||
Vosk scales from small devices like Raspberry Pi or Android smartphone to
|
||||
big clusters.
|
||||
|
||||
# Documentation
|
||||
|
||||
For installation instructions, examples and documentation visit [Vosk
|
||||
Website](https://alphacephei.com/vosk).
|
||||
@@ -1,27 +1,21 @@
|
||||
<!-- WEHUB_ZH_README -->
|
||||
> [!NOTE]
|
||||
> 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
|
||||
> [English](./README.en.md) · [原始项目](https://github.com/alphacep/vosk-api) · [上游 README](https://github.com/alphacep/vosk-api/blob/HEAD/README.md)
|
||||
> 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。
|
||||
|
||||
# Vosk Speech Recognition Toolkit
|
||||
|
||||
Vosk is an offline open source speech recognition toolkit. It enables
|
||||
speech recognition for 20+ languages and dialects - English, Indian
|
||||
English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish,
|
||||
Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino,
|
||||
Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish.
|
||||
More to come.
|
||||
Vosk 是一款离线开源语音识别工具包。它支持 20 多种语言及方言的语音识别——英语、印度英语、德语、法语、西班牙语、葡萄牙语、中文、俄语、土耳其语、越南语、意大利语、荷兰语、加泰罗尼亚语、阿拉伯语、希腊语、波斯语、菲律宾语、乌克兰语、哈萨克语、瑞典语、日语、世界语、印地语、捷克语、波兰语。更多语言即将推出。
|
||||
|
||||
Vosk models are small (50 Mb) but provide continuous large vocabulary
|
||||
transcription, zero-latency response with streaming API, reconfigurable
|
||||
vocabulary and speaker identification.
|
||||
Vosk 模型体积小(50 Mb),但可提供连续大词汇量转写、通过流式 API(streaming API)实现零延迟响应、可重新配置的词汇表以及说话人识别。
|
||||
|
||||
Speech recognition bindings implemented for various programming languages
|
||||
like Python, Java, Node.JS, C#, C++, Rust, Go and others.
|
||||
已为 Python、Java、Node.JS、C#、C++、Rust、Go 等多种编程语言实现语音识别绑定。
|
||||
|
||||
Vosk supplies speech recognition for chatbots, smart home appliances,
|
||||
virtual assistants. It can also create subtitles for movies,
|
||||
transcription for lectures and interviews.
|
||||
Vosk 为聊天机器人、智能家居设备、虚拟助手提供语音识别,还可为电影生成字幕,为讲座和访谈提供转写。
|
||||
|
||||
Vosk scales from small devices like Raspberry Pi or Android smartphone to
|
||||
big clusters.
|
||||
Vosk 的部署规模可从小型设备(如 Raspberry Pi 或 Android 智能手机)扩展到大型集群。
|
||||
|
||||
# Documentation
|
||||
# 文档
|
||||
|
||||
For installation instructions, examples and documentation visit [Vosk
|
||||
Website](https://alphacephei.com/vosk).
|
||||
有关安装说明、示例和文档,请访问 [Vosk 网站](https://alphacephei.com/vosk).
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`alphacep/vosk-api`
|
||||
- 原始仓库:https://github.com/alphacep/vosk-api
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -4,13 +4,13 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.24.0'
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.34.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
version = '0.3.50'
|
||||
version = '0.3.75'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@@ -24,7 +24,7 @@ subprojects {
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01, false)
|
||||
publishToMavenCentral()
|
||||
signAllPublications()
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ set -x
|
||||
OS_NAME=`echo $(uname -s) | tr '[:upper:]' '[:lower:]'`
|
||||
ANDROID_TOOLCHAIN_PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64
|
||||
WORKDIR_BASE=`pwd`/build
|
||||
PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin:$PATH
|
||||
PATH=$ANDROID_TOOLCHAIN_PATH/bin:$PATH
|
||||
OPENFST_VERSION=1.8.0
|
||||
|
||||
for arch in armeabi-v7a arm64-v8a x86_64 x86; do
|
||||
@@ -45,6 +45,7 @@ case $arch in
|
||||
CC=armv7a-linux-androideabi21-clang
|
||||
CXX=armv7a-linux-androideabi21-clang++
|
||||
ARCHFLAGS="-mfloat-abi=softfp -mfpu=neon"
|
||||
PAGESIZE_LDFLAGS=""
|
||||
;;
|
||||
arm64-v8a)
|
||||
BLAS_ARCH=ARMV8
|
||||
@@ -54,6 +55,8 @@ case $arch in
|
||||
CC=aarch64-linux-android21-clang
|
||||
CXX=aarch64-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
# Ensure compatibility with 16KiB page size devices
|
||||
PAGESIZE_LDFLAGS="-Wl,-z,common-page-size=4096 -Wl,-z,max-page-size=16384"
|
||||
;;
|
||||
x86_64)
|
||||
BLAS_ARCH=ATOM
|
||||
@@ -63,6 +66,7 @@ case $arch in
|
||||
CC=x86_64-linux-android21-clang
|
||||
CXX=x86_64-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
PAGESIZE_LDFLAGS=""
|
||||
;;
|
||||
x86)
|
||||
BLAS_ARCH=ATOM
|
||||
@@ -72,6 +76,7 @@ case $arch in
|
||||
CC=i686-linux-android21-clang
|
||||
CXX=i686-linux-android21-clang++
|
||||
ARCHFLAGS=""
|
||||
PAGESIZE_LDFLAGS=""
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -79,16 +84,16 @@ mkdir -p $WORKDIR/local/lib
|
||||
|
||||
# openblas first
|
||||
cd $WORKDIR
|
||||
git clone -b v0.3.13 --single-branch https://github.com/xianyi/OpenBLAS
|
||||
make -C OpenBLAS TARGET=$BLAS_ARCH ONLY_CBLAS=1 AR=$AR CC=$CC HOSTCC=gcc ARM_SOFTFP_ABI=1 USE_THREAD=0 NUM_THREADS=1 -j4
|
||||
git clone -b v0.3.20 --single-branch https://github.com/xianyi/OpenBLAS
|
||||
make -C OpenBLAS TARGET=$BLAS_ARCH ONLY_CBLAS=1 AR=$AR CC=$CC HOSTCC=gcc ARM_SOFTFP_ABI=1 USE_THREAD=0 NUM_THREADS=1 -j 8
|
||||
make -C OpenBLAS install PREFIX=$WORKDIR/local
|
||||
|
||||
# CLAPACK
|
||||
cd $WORKDIR
|
||||
git clone -b v3.2.1 --single-branch https://github.com/alphacep/clapack
|
||||
mkdir -p clapack/BUILD && cd clapack/BUILD
|
||||
cmake -DCMAKE_C_FLAGS=$ARCHFLAGS -DCMAKE_C_COMPILER_TARGET=$HOST \
|
||||
-DCMAKE_C_COMPILER=$CC -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_AR=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin/$AR \
|
||||
cmake -DCMAKE_C_FLAGS="$ARCHFLAGS" -DCMAKE_C_COMPILER_TARGET=$HOST \
|
||||
-DCMAKE_C_COMPILER=$CC -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_AR=$ANDROID_TOOLCHAIN_PATH/bin/$AR \
|
||||
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||
-DCMAKE_CROSSCOMPILING=True ..
|
||||
make -j 8 -C F2CLIBS/libf2c
|
||||
@@ -129,7 +134,7 @@ make -j 8 -C ${WORKDIR_BASE}/../../../src \
|
||||
OPENFST_ROOT=${WORKDIR}/local \
|
||||
OPENBLAS_ROOT=${WORKDIR}/local \
|
||||
CXX=$CXX \
|
||||
EXTRA_LDFLAGS="-llog -static-libstdc++ -Wl,-soname,libvosk.so"
|
||||
EXTRA_LDFLAGS="-llog -static-libstdc++ -Wl,-soname,libvosk.so ${PAGESIZE_LDFLAGS}"
|
||||
cp $WORKDIR/vosk/libvosk.so $WORKDIR/../../src/main/jniLibs/$arch/libvosk.so
|
||||
|
||||
done
|
||||
|
||||
@@ -4,14 +4,14 @@ def pomDescription = "Vosk speech recognition library for Android"
|
||||
|
||||
android {
|
||||
namespace 'org.vosk'
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 36
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
targetSdkVersion 36
|
||||
versionCode 10
|
||||
versionName = version
|
||||
archivesBaseName = archiveName
|
||||
ndkVersion = "25.2.9519653"
|
||||
ndkVersion = "28.2.13676358"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -25,7 +25,7 @@ task buildVosk(type: Exec) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'net.java.dev.jna:jna:5.13.0@aar'
|
||||
api 'net.java.dev.jna:jna:5.18.1@aar'
|
||||
}
|
||||
|
||||
//preBuild.dependsOn buildVosk
|
||||
|
||||
@@ -56,8 +56,18 @@ public class LibVosk {
|
||||
|
||||
public static native void vosk_recognizer_reset(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_set_endpointer_mode(Pointer recognizer, int mode);
|
||||
|
||||
public static native void vosk_recognizer_set_endpointer_delays(Pointer recognizer, float t_start_max, float t_end, float t_max);
|
||||
|
||||
public static native void vosk_recognizer_free(Pointer recognizer);
|
||||
|
||||
public static native Pointer vosk_text_processor_new(String verbalizer, String tagger);
|
||||
|
||||
public static native void vosk_text_processor_free(Pointer processor);
|
||||
|
||||
public static native String vosk_text_processor_itn(Pointer processor, String input);
|
||||
|
||||
/**
|
||||
* Set log level for Kaldi messages.
|
||||
*
|
||||
|
||||
@@ -236,6 +236,34 @@ public class Recognizer extends PointerType implements AutoCloseable {
|
||||
LibVosk.vosk_recognizer_reset(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpointer delay mode
|
||||
*/
|
||||
public class EndpointerMode {
|
||||
public static final int DEFAULT = 0;
|
||||
public static final int SHORT = 1;
|
||||
public static final int LONG = 2;
|
||||
public static final int VERY_LONG = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures endpointer mode for recognizer
|
||||
*/
|
||||
public void setEndpointerMode(int mode) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_mode(this.getPointer(), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endpointer delays
|
||||
*
|
||||
* @param t_start_max timeout for stopping recognition in case of initial silence (usually around 5.0)
|
||||
* @param t_end timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
|
||||
* @param t_max timeout for forcing utterance end in milliseconds (usually around 20-30)
|
||||
**/
|
||||
public void setEndpointerDelays(float t_start_max, float t_end, float t_max) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_delays(this.getPointer(), t_start_max, t_end, t_max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases recognizer object.
|
||||
* Underlying model is also unreferenced and if needed, released.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.vosk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class TextProcessor extends PointerType implements AutoCloseable {
|
||||
public TextProcessor() {
|
||||
}
|
||||
|
||||
public TextProcessor(String verbalizer, String tagger) {
|
||||
super(LibVosk.vosk_text_processor_new(verbalizer, tagger));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
LibVosk.vosk_text_processor_free(this.getPointer());
|
||||
}
|
||||
|
||||
public String itn(String input) {
|
||||
return LibVosk.vosk_text_processor_itn(this.getPointer(), input);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,51 @@ public class SpeechService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates speech service with a caller-supplied {@link AudioRecord}.
|
||||
* <p>
|
||||
* Use this when you need to control the audio input device - for example,
|
||||
* to pin recording to the built-in microphone when an external USB device
|
||||
* without a microphone is present:
|
||||
* <pre>
|
||||
* AudioRecord recorder = new AudioRecord.Builder()
|
||||
* .setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
|
||||
* .setAudioFormat(format)
|
||||
* .build();
|
||||
* if (Build.VERSION.SDK_INT >= 28) {
|
||||
* AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
* for (AudioDeviceInfo d : am.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
|
||||
* if (d.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
|
||||
* recorder.setPreferredDevice(d);
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* SpeechService service = new SpeechService(recognizer, 16000f, recorder);
|
||||
* </pre>
|
||||
* <p>
|
||||
* The caller retains ownership of {@code recorder}: if this constructor
|
||||
* throws, the recorder is <em>not</em> released. Call
|
||||
* {@link AudioRecord#release()} yourself in that case.
|
||||
*
|
||||
* @param recognizer the Vosk recognizer
|
||||
* @param sampleRate sample rate in Hz; must match {@code recorder}'s configuration
|
||||
* @param recorder a fully-initialised {@link AudioRecord}
|
||||
* @throws IOException if {@code recorder} is in STATE_UNINITIALIZED
|
||||
*/
|
||||
public SpeechService(Recognizer recognizer, float sampleRate, AudioRecord recorder)
|
||||
throws IOException {
|
||||
this.recognizer = recognizer;
|
||||
this.sampleRate = (int) sampleRate;
|
||||
this.recorder = recorder;
|
||||
|
||||
bufferSize = Math.round(this.sampleRate * BUFFER_SIZE_SECONDS);
|
||||
|
||||
if (recorder.getState() == AudioRecord.STATE_UNINITIALIZED) {
|
||||
throw new IOException(
|
||||
"Failed to initialize recorder. Microphone might be already in use.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition. Does nothing if recognition is active.
|
||||
@@ -137,6 +182,19 @@ public class SpeechService {
|
||||
return stopRecognizerThread();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the audio session ID of the underlying {@link AudioRecord}.
|
||||
* <p>
|
||||
* The session ID can be used to attach audio effects such as
|
||||
* {@link android.media.audiofx.NoiseSuppressor} to the recording session.
|
||||
*
|
||||
* @return audio session ID, or {@link AudioRecord#ERROR} if unavailable
|
||||
*/
|
||||
public int getAudioSessionId() {
|
||||
return recorder.getAudioSessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the recognizer and release the recorder
|
||||
*/
|
||||
|
||||
@@ -4,10 +4,10 @@ def pomDescription = "Small English model for Android"
|
||||
|
||||
android {
|
||||
namespace "org.vosk"
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 36
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
targetSdkVersion 36
|
||||
versionCode 10
|
||||
versionName = version
|
||||
archivesBaseName = archiveName
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
US English model for mobile Vosk applications
|
||||
|
||||
Copyright 2020 Alpha Cephei Inc
|
||||
|
||||
Accuracy: 10.38 (tedlium test) 9.85 (librispeech test-clean)
|
||||
Speed: 0.11xRT (desktop)
|
||||
Latency: 0.15s (right context)
|
||||
@@ -28,6 +28,9 @@ public class VoskDemo
|
||||
{
|
||||
// Demo float array
|
||||
VoskRecognizer rec = new VoskRecognizer(model, 16000.0f);
|
||||
|
||||
rec.SetEndpointerMode(EndpointerMode.LONG);
|
||||
|
||||
using(Stream source = File.OpenRead("test.wav")) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>VoskDemo</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Vosk" Version="0.3.50" />
|
||||
<PackageReference Include="Vosk" Version="0.3.75" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageId>Vosk</PackageId>
|
||||
<Version>0.3.75</Version>
|
||||
<authors>Alpha Cephei Inc</authors>
|
||||
<owners>Alpha Cephei Inc</owners>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CopyFiles" AfterTargets="Build">
|
||||
<Copy SourceFiles="bin/Release/net8.0/Vosk.dll" DestinationFolder="lib/net8.0" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Vosk</id>
|
||||
<version>0.3.50</version>
|
||||
<version>0.3.75</version>
|
||||
<authors>Alpha Cephei Inc</authors>
|
||||
<owners>Alpha Cephei Inc</owners>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
@@ -23,10 +23,10 @@ Vosk scales from small devices like Raspberry Pi or Android smartphone to big cl
|
||||
<copyright>Copyright 2020-2050 Alpha Cephei Inc</copyright>
|
||||
<tags>speech recognition voice stt asr speech-to-text ai offline privacy</tags>
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0"/>
|
||||
<group targetFramework="net8.0"/>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" exclude="src/*.cs;build.sh;**/.keep-me;*.nupkg" />
|
||||
<file src="**" exclude="bin/**;obj/**;build.sh;src/*.cs;*.nupkg;**/.keep-me" />
|
||||
</files>
|
||||
</package>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mcs -out:lib/netstandard2.0/Vosk.dll -target:library src/*.cs
|
||||
nuget pack
|
||||
rm -rf bin lib obj
|
||||
/home/shmyrev/local/dotnet/dotnet pack Vosk.csproj -p:NuspecFile=Vosk.nuspec -o .
|
||||
|
||||
@@ -65,6 +65,12 @@ class VoskPINVOKE {
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_reset")]
|
||||
public static extern void VoskRecognizer_Reset(global::System.Runtime.InteropServices.HandleRef jarg1);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_endpointer_mode")]
|
||||
public static extern void VoskRecognizer_SetEndpointerMode(global::System.Runtime.InteropServices.HandleRef jarg1, int jarg2);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_recognizer_set_endpointer_delays")]
|
||||
public static extern void VoskRecognizer_SetEndpointerDelays(global::System.Runtime.InteropServices.HandleRef jarg1, float jarg2, float jarg3, float jarg4);
|
||||
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint="vosk_set_log_level")]
|
||||
public static extern void SetLogLevel(int jarg1);
|
||||
|
||||
@@ -107,7 +113,6 @@ class VoskPINVOKE {
|
||||
[global::System.Runtime.InteropServices.DllImport("libvosk", EntryPoint = "vosk_batch_recognizer_get_pending_chunks")]
|
||||
public static extern int VoskBatchRecognizer_GetPendingChunks(global::System.Runtime.InteropServices.HandleRef jarg1);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
namespace Vosk {
|
||||
|
||||
public enum EndpointerMode {
|
||||
DEFAULT = 0,
|
||||
SHORT = 1,
|
||||
LONG = 2,
|
||||
VERY_LONG = 3
|
||||
}
|
||||
|
||||
public class VoskRecognizer : System.IDisposable {
|
||||
private System.Runtime.InteropServices.HandleRef handle;
|
||||
|
||||
@@ -91,6 +98,14 @@ public class VoskRecognizer : System.IDisposable {
|
||||
VoskPINVOKE.VoskRecognizer_Reset(handle);
|
||||
}
|
||||
|
||||
public void SetEndpointerMode(EndpointerMode mode) {
|
||||
VoskPINVOKE.VoskRecognizer_SetEndpointerMode(handle, (int) mode);
|
||||
}
|
||||
|
||||
public void SetEndpointerDelays(float t_start_max, float t_end, float t_max) {
|
||||
VoskPINVOKE.VoskRecognizer_SetEndpointerDelays(handle, t_start_max, t_end, t_max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package vosk
|
||||
|
||||
// #cgo CPPFLAGS: -I ${SRCDIR}/../src
|
||||
// #cgo !windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
|
||||
// #cgo windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -lpthread
|
||||
// #include <stdlib.h>
|
||||
// #include <vosk_api.h>
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// VoskBatchModel contains a reference to the C VoskBatchModel
|
||||
type VoskBatchModel struct {
|
||||
model *C.struct_VoskBatchModel
|
||||
}
|
||||
|
||||
// NewBatchModel creates a new VoskBatchModel instance
|
||||
func NewBatchModel(modelPath string) (*VoskBatchModel, error) {
|
||||
cmodelPath := C.CString(modelPath)
|
||||
defer C.free(unsafe.Pointer(cmodelPath))
|
||||
internal := C.vosk_batch_model_new(cmodelPath)
|
||||
model := &VoskBatchModel{model: internal}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (m *VoskBatchModel) Free() {
|
||||
C.vosk_batch_model_free(m.model)
|
||||
}
|
||||
|
||||
func (m *VoskBatchModel) Wait() {
|
||||
C.vosk_batch_model_wait(m.model);
|
||||
}
|
||||
|
||||
func freeBatchModel(model *VoskBatchModel) {
|
||||
C.vosk_batch_model_free(model.model)
|
||||
}
|
||||
|
||||
// VoskBatchRecognizer contains a reference to the C VoskBatchRecognizer
|
||||
type VoskBatchRecognizer struct {
|
||||
rec *C.struct_VoskBatchRecognizer
|
||||
}
|
||||
|
||||
func freeBatchRecognizer(recognizer *VoskBatchRecognizer) {
|
||||
C.vosk_batch_recognizer_free(recognizer.rec)
|
||||
}
|
||||
|
||||
func (r *VoskBatchRecognizer) Free() {
|
||||
C.vosk_batch_recognizer_free(r.rec)
|
||||
}
|
||||
|
||||
// NewBatchRecognizer creates a new VoskBatchRecognizer instance
|
||||
func NewBatchRecognizer(model *VoskBatchModel, sampleRate float64) (*VoskBatchRecognizer, error) {
|
||||
internal := C.vosk_batch_recognizer_new(model.model, C.float(sampleRate))
|
||||
rec := &VoskBatchRecognizer{rec: internal}
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// AcceptWaveform accepts and processes a new chunk of the voice data.
|
||||
func (r *VoskBatchRecognizer) AcceptWaveform(buffer []byte) {
|
||||
cbuf := C.CBytes(buffer)
|
||||
defer C.free(cbuf)
|
||||
C.vosk_batch_recognizer_accept_waveform(r.rec, (*C.char)(cbuf), C.int(len(buffer)))
|
||||
}
|
||||
|
||||
/** Set NLSML output
|
||||
* @param nlsml - boolean value
|
||||
*/
|
||||
//void vosk_batch_recognizer_set_nlsml(VoskBatchRecognizer *recognizer, int nlsml);
|
||||
|
||||
func (r *VoskBatchRecognizer) SetNlsml(nlsml int) {
|
||||
C.vosk_batch_recognizer_set_nlsml(r.rec, C.int(nlsml))
|
||||
}
|
||||
|
||||
/** Closes the stream */
|
||||
//void vosk_batch_recognizer_finish_stream(VoskBatchRecognizer *recognizer);
|
||||
|
||||
func (r *VoskBatchRecognizer) FinishStream() {
|
||||
C.vosk_batch_recognizer_finish_stream(r.rec)
|
||||
}
|
||||
|
||||
/** Return results */
|
||||
//const char *vosk_batch_recognizer_front_result(VoskBatchRecognizer *recognizer);
|
||||
|
||||
func (r *VoskBatchRecognizer) FrontResult() string {
|
||||
return C.GoString(C.vosk_batch_recognizer_front_result(r.rec))
|
||||
}
|
||||
|
||||
/** Release and free first retrieved result */
|
||||
//void vosk_batch_recognizer_pop(VoskBatchRecognizer *recognizer);
|
||||
|
||||
func (r *VoskBatchRecognizer) Pop() {
|
||||
C.vosk_batch_recognizer_pop(r.rec)
|
||||
}
|
||||
|
||||
/** Get amount of pending chunks for more intelligent waiting */
|
||||
//int vosk_batch_recognizer_get_pending_chunks(VoskBatchRecognizer *recognizer);
|
||||
func (r *VoskBatchRecognizer) GetPendingChunks() int {
|
||||
i := C.vosk_batch_recognizer_get_pending_chunks(r.rec)
|
||||
return int(i)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
This example expects a `s16le` converted audio file and converts it to text in a
|
||||
manner that imitates the Python example of [test_gpu_batch.py](../python/example/test_gpu_batch.py).
|
||||
|
||||
Note that the `libvosk.so` must be in the library path. This was successfully tested on
|
||||
Ubuntu 24.04 with Go 1.18, gcc-11, NVIDIA driver 570.172.08.
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
vosk "github.com/alphacep/vosk-api/go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var filename string
|
||||
flag.StringVar(&filename, "f", "", "file to transcribe")
|
||||
flag.Parse()
|
||||
|
||||
vosk.GPUInit()
|
||||
|
||||
model, err := vosk.NewBatchModel("model")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rec, err := vosk.NewBatchRecognizer(model, 16000.0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf := make([]byte, 8000)
|
||||
|
||||
for {
|
||||
if _, err := file.Read(buf); err != nil {
|
||||
if err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
rec.AcceptWaveform(buf)
|
||||
model.Wait()
|
||||
if rec.FrontResult() != "" {
|
||||
fmt.Println(rec.FrontResult())
|
||||
rec.Pop()
|
||||
}
|
||||
}
|
||||
// Is this needed? rec.FinishStream()
|
||||
}
|
||||
@@ -16,6 +16,8 @@ func main() {
|
||||
flag.StringVar(&filename, "f", "", "file to transcribe")
|
||||
flag.Parse()
|
||||
|
||||
vosk.GPUInit()
|
||||
|
||||
model, err := vosk.NewModel("model")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
+10
@@ -125,6 +125,16 @@ func (r *VoskRecognizer) SetPartialWords(words int) {
|
||||
C.vosk_recognizer_set_partial_words(r.rec, C.int(words))
|
||||
}
|
||||
|
||||
// SetEndpointerDelays sets the recognition timeouts, where startMax
|
||||
// is the timeout for stopping recognition in case of initial silence
|
||||
// (usually around 5), end is the timeout for stopping recognition
|
||||
// in milliseconds after we recognized something (usually around 0.5-1.0),
|
||||
// and max is the timeout for forcing utterance end in milliseconds
|
||||
// (usually around 20-30).
|
||||
func (r *VoskRecognizer) SetEndpointerDelays(startMax, end, max float64) {
|
||||
C.vosk_recognizer_set_endpointer_delays(r.rec, C.float(startMax), C.float(end), C.float(max))
|
||||
}
|
||||
|
||||
// AcceptWaveform accepts and processes a new chunk of the voice data.
|
||||
func (r *VoskRecognizer) AcceptWaveform(buffer []byte) int {
|
||||
cbuf := C.CBytes(buffer)
|
||||
|
||||
@@ -11,5 +11,5 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.50'
|
||||
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.75'
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ repositories {
|
||||
|
||||
archivesBaseName = 'vosk'
|
||||
group = 'com.alphacephei'
|
||||
version = '0.3.50'
|
||||
version = '0.3.75'
|
||||
|
||||
mavenPublish {
|
||||
group = 'com.alphacephei'
|
||||
@@ -25,7 +25,7 @@ mavenPublish {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api group: 'net.java.dev.jna', name: 'jna', version: '5.13.0'
|
||||
api group: 'net.java.dev.jna', name: 'jna', version: '5.18.1'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,18 @@ public class LibVosk {
|
||||
|
||||
public static native void vosk_recognizer_reset(Pointer recognizer);
|
||||
|
||||
public static native void vosk_recognizer_set_endpointer_mode(Pointer recognizer, int mode);
|
||||
|
||||
public static native void vosk_recognizer_set_endpointer_delays(Pointer recognizer, float t_start_max, float t_end, float t_max);
|
||||
|
||||
public static native void vosk_recognizer_free(Pointer recognizer);
|
||||
|
||||
public static native Pointer vosk_text_processor_new(String verbalizer, String tagger);
|
||||
|
||||
public static native void vosk_text_processor_free(Pointer processor);
|
||||
|
||||
public static native String vosk_text_processor_itn(Pointer processor, String input);
|
||||
|
||||
/**
|
||||
* Set log level for Kaldi messages.
|
||||
*
|
||||
|
||||
@@ -236,6 +236,34 @@ public class Recognizer extends PointerType implements AutoCloseable {
|
||||
LibVosk.vosk_recognizer_reset(this.getPointer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpointer delay mode
|
||||
*/
|
||||
public class EndpointerMode {
|
||||
public static final int DEFAULT = 0;
|
||||
public static final int SHORT = 1;
|
||||
public static final int LONG = 2;
|
||||
public static final int VERY_LONG = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures endpointer mode for recognizer
|
||||
*/
|
||||
public void setEndpointerMode(int mode) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_mode(this.getPointer(), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endpointer delays
|
||||
*
|
||||
* @param t_start_max timeout for stopping recognition in case of initial silence (usually around 5.0)
|
||||
* @param t_end timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
|
||||
* @param t_max timeout for forcing utterance end in milliseconds (usually around 20-30)
|
||||
**/
|
||||
public void setEndpointerDelays(float t_start_max, float t_end, float t_max) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_delays(this.getPointer(), t_start_max, t_end, t_max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases recognizer object.
|
||||
* Underlying model is also unreferenced and if needed, released.
|
||||
|
||||
@@ -15,8 +15,10 @@ import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import org.vosk.LogLevel;
|
||||
import org.vosk.Recognizer;
|
||||
import org.vosk.Recognizer.EndpointerMode;
|
||||
import org.vosk.LibVosk;
|
||||
import org.vosk.Model;
|
||||
import org.vosk.TextProcessor;
|
||||
|
||||
public class DecoderTest {
|
||||
|
||||
@@ -95,9 +97,24 @@ public class DecoderTest {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoderEndpointerDelays() throws IOException, UnsupportedAudioFileException {
|
||||
try (Model model = new Model("model");
|
||||
Recognizer recognizer = new Recognizer(model, 16000)) {
|
||||
recognizer.setEndpointerMode(EndpointerMode.VERY_LONG);
|
||||
recognizer.setEndpointerDelays(5.0f, 3.0f, 50.0f);
|
||||
}
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void decoderTestException() throws IOException {
|
||||
Model model = new Model("model_missing");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItn() throws IOException {
|
||||
TextProcessor p = new TextProcessor("model/itn/en_itn_tagger.fst", "model/itn/en_itn_verbalizer.fst");
|
||||
System.out.println(p.itn("as easy as one two three"));
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-3
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
vosk-api-kotlin
|
||||
Generated
-127
@@ -1,127 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright &#36;today.year Alpha Cephei Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." />
|
||||
<option name="myName" value="Apache-2.0" />
|
||||
</copyright>
|
||||
</component>
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="Apache-2.0">
|
||||
<module2copyright>
|
||||
<element module="All" copyright="Apache-2.0" />
|
||||
</module2copyright>
|
||||
</settings>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-25
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Embedded JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+30
-16
@@ -1,5 +1,6 @@
|
||||
import org.jetbrains.dokka.gradle.DokkaTask
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
/*
|
||||
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
|
||||
@@ -18,15 +19,15 @@ import org.jetbrains.kotlin.config.JvmTarget
|
||||
*/
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.8.10"
|
||||
kotlin("multiplatform") version "2.0.0"
|
||||
id("com.android.library")
|
||||
`maven-publish`
|
||||
id("org.jetbrains.dokka") version "1.7.20"
|
||||
kotlin("plugin.serialization") version "1.8.10"
|
||||
id("org.jetbrains.dokka") version "1.9.20"
|
||||
kotlin("plugin.serialization") version "2.0.0"
|
||||
}
|
||||
|
||||
group = "com.alphacephei"
|
||||
version = "0.3.50"
|
||||
version = "0.3.75"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
@@ -67,9 +68,11 @@ fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.native(
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = JvmTarget.JVM_11.description
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
environment("MODEL", "VOSK_MODEL")
|
||||
@@ -80,7 +83,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
androidTarget {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
|
||||
@@ -102,6 +105,16 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
applyDefaultHierarchyTemplate {
|
||||
withJvm()
|
||||
withAndroidTarget()
|
||||
|
||||
if (enableNative)
|
||||
withNative()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
withType<MavenPublication> {
|
||||
@@ -130,13 +143,13 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
val jna_version = "5.13.0"
|
||||
val coroutines_version = "1.6.4"
|
||||
val jna_version = "5.14.0"
|
||||
val coroutines_version = "1.7.3"
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
|
||||
}
|
||||
}
|
||||
@@ -161,7 +174,7 @@ kotlin {
|
||||
api("net.java.dev.jna:jna:$jna_version@aar")
|
||||
}
|
||||
}
|
||||
val androidTest by getting {
|
||||
val androidUnitTest by getting {
|
||||
dependencies {
|
||||
implementation("junit:junit:4.13.2")
|
||||
}
|
||||
@@ -170,15 +183,16 @@ kotlin {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
namespace = "com.alphacephei.library"
|
||||
compileSdk = 34
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
targetSdk = 34
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
publishing {
|
||||
multipleVariants {
|
||||
|
||||
@@ -23,7 +23,7 @@ pluginManagement {
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.namespace == "com.android") {
|
||||
useModule("com.android.tools.build:gradle:7.3.0")
|
||||
useModule("com.android.tools.build:gradle:8.3.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2024 Alpha Cephei Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.vosk
|
||||
|
||||
/**
|
||||
* VoskEpMode
|
||||
*/
|
||||
enum class EndPointerMode {
|
||||
ANSWER_DEFAULT,
|
||||
ANSWER_SHORT,
|
||||
ANSWER_LONG,
|
||||
ANSWER_VERY_LONG
|
||||
}
|
||||
@@ -151,6 +151,7 @@ expect class Recognizer : Freeable {
|
||||
* }],
|
||||
* </pre>
|
||||
*
|
||||
* C equivalent = vosk_recognizer_set_words
|
||||
* @param words - boolean value
|
||||
*/
|
||||
fun setOutputWordTimes(words: Boolean)
|
||||
@@ -168,6 +169,23 @@ expect class Recognizer : Freeable {
|
||||
*/
|
||||
fun setNLSML(nlsml: Boolean)
|
||||
|
||||
|
||||
/**
|
||||
* Set endpointer scaling factor
|
||||
*
|
||||
* @param mode Endpointer mode
|
||||
**/
|
||||
fun setEndPointerMode(mode: EndPointerMode)
|
||||
|
||||
/**
|
||||
* Set endpointer delays
|
||||
*
|
||||
* @param tStartMax timeout for stopping recognition in case of initial silence (usually around 5.0)
|
||||
* @param tEnd timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
|
||||
* @param tMax timeout for forcing utterance end in milliseconds (usually around 20-30)
|
||||
**/
|
||||
fun setEndPointerDelays(tStartMax: Float, tEnd: Float, tMax: Float)
|
||||
|
||||
/**
|
||||
* Accept voice data
|
||||
*
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 Alpha Cephei Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.vosk
|
||||
|
||||
/**
|
||||
* Inverse text normalization
|
||||
*
|
||||
* @since 2024/06/19
|
||||
* @constructor Create text processor
|
||||
*/
|
||||
expect class TextProcessor constructor(tagger: Char, verbalizer: Char) : Freeable {
|
||||
|
||||
/** Release text processor */
|
||||
override fun free()
|
||||
|
||||
/** Convert string */
|
||||
fun itn(input: Char): Char
|
||||
}
|
||||
@@ -19,4 +19,4 @@ package org.vosk.exception
|
||||
/**
|
||||
* Internal common IO exception. On JVM this is just a type alias.
|
||||
*/
|
||||
expect open class IOException(message: String? = null) : Exception
|
||||
expect open class IOException(message: String?) : Exception
|
||||
@@ -42,14 +42,13 @@ internal object LibVosk {
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun unpackDll(targetDir: File, lib: String) {
|
||||
val source: InputStream =
|
||||
Vosk::class.java.getResourceAsStream("/win32-x86-64/$lib.dll")!!
|
||||
|
||||
Files.copy(
|
||||
source,
|
||||
File(targetDir, "$lib.dll").toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
Vosk::class.java.getResourceAsStream("/win32-x86-64/$lib.dll")!!.use {
|
||||
Files.copy(
|
||||
it,
|
||||
File(targetDir, "$lib.dll").toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -57,6 +56,7 @@ internal object LibVosk {
|
||||
Platform.isAndroid() -> {
|
||||
Native.register(LibVosk::class.java, "vosk")
|
||||
}
|
||||
|
||||
Platform.isWindows() -> {
|
||||
// We have to unpack dependencies
|
||||
try {
|
||||
@@ -79,6 +79,7 @@ internal object LibVosk {
|
||||
Native.register(LibVosk::class.java, "libvosk");
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Native.register(LibVosk::class.java, "vosk");
|
||||
}
|
||||
@@ -194,4 +195,19 @@ internal object LibVosk {
|
||||
external fun vosk_batch_recognizer_pop(recognizer: BatchRecognizer)
|
||||
|
||||
external fun vosk_batch_recognizer_get_pending_chunks(recognizer: BatchRecognizer): Int
|
||||
|
||||
external fun vosk_text_processor_new(tagger: Char, verbalizer: Char): Pointer
|
||||
|
||||
external fun vosk_text_processor_free(processor: TextProcessor)
|
||||
|
||||
external fun vosk_text_processor_itn(processor: TextProcessor, input: Char): Char
|
||||
|
||||
external fun vosk_recognizer_set_endpointer_mode(recognizer: Recognizer, ordinal: Int)
|
||||
|
||||
external fun vosk_recognizer_set_endpointer_delays(
|
||||
recognizer: Recognizer,
|
||||
tStartMax: Float,
|
||||
tEnd: Float,
|
||||
tMax: Float
|
||||
)
|
||||
}
|
||||
@@ -327,4 +327,27 @@ actual class Recognizer : Freeable, PointerType, AutoCloseable {
|
||||
free()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endpointer scaling factor
|
||||
*
|
||||
* @param mode Endpointer mode
|
||||
**/
|
||||
actual fun setEndPointerMode(mode: EndPointerMode) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_mode(this, mode.ordinal)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set endpointer delays
|
||||
*
|
||||
* @param tStartMax timeout for stopping recognition in case of initial silence (usually around 5.0)
|
||||
* @param tEnd timeout for stopping recognition in milliseconds after we recognized something (usually around 0.5 - 1.0)
|
||||
* @param tMax timeout for forcing utterance end in milliseconds (usually around 20-30)
|
||||
**/
|
||||
actual fun setEndPointerDelays(
|
||||
tStartMax: Float,
|
||||
tEnd: Float,
|
||||
tMax: Float
|
||||
) {
|
||||
LibVosk.vosk_recognizer_set_endpointer_delays(this, tStartMax, tEnd, tMax)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2024 Alpha Cephei Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.vosk
|
||||
|
||||
import com.sun.jna.PointerType
|
||||
|
||||
/**
|
||||
* Inverse text normalization
|
||||
*
|
||||
* @since 2024/06/19
|
||||
*/
|
||||
actual class TextProcessor :
|
||||
Freeable, PointerType, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Create text processor
|
||||
*/
|
||||
actual constructor(tagger: Char, verbalizer: Char) :
|
||||
super(LibVosk.vosk_text_processor_new(tagger, verbalizer))
|
||||
|
||||
|
||||
/** Release text processor */
|
||||
actual override fun free() {
|
||||
LibVosk.vosk_text_processor_free(this)
|
||||
}
|
||||
|
||||
/** Convert string */
|
||||
actual fun itn(input: Char): Char =
|
||||
LibVosk.vosk_text_processor_itn(this, input)
|
||||
|
||||
/**
|
||||
* @see free
|
||||
*/
|
||||
override fun close() {
|
||||
free()
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,4 @@ linkerOpts.linux = \
|
||||
|
||||
linkerOpts.linux_x64 = \
|
||||
-L/usr/lib64/ \
|
||||
-L/usr/local/lib64/
|
||||
-L/usr/local/lib64/
|
||||
|
||||
+19
-7
@@ -69,16 +69,18 @@ const vosk_recognizer_ptr = ref.refType(vosk_recognizer);
|
||||
|
||||
let soname;
|
||||
if (os.platform() == 'win32') {
|
||||
// Update path to load dependent dlls
|
||||
let currentPath = process.env.Path;
|
||||
let dllDirectory = path.resolve(path.join(__dirname, "lib", "win-x86_64"));
|
||||
process.env.Path = dllDirectory + path.delimiter + currentPath;
|
||||
// Update path to load dependent dlls
|
||||
let currentPath = process.env.Path;
|
||||
let dllDirectory = path.resolve(path.join(__dirname, 'lib', 'win-x86_64'));
|
||||
process.env.Path = dllDirectory + path.delimiter + currentPath;
|
||||
|
||||
soname = path.join(__dirname, "lib", "win-x86_64", "libvosk.dll")
|
||||
soname = path.join(__dirname, 'lib', 'win-x86_64', 'libvosk.dll');
|
||||
} else if (os.platform() == 'darwin') {
|
||||
soname = path.join(__dirname, "lib", "osx-universal", "libvosk.dylib")
|
||||
soname = path.join(__dirname, 'lib', 'osx-universal', 'libvosk.dylib');
|
||||
} else if (os.platform() == 'linux' && os.arch() == 'arm64') {
|
||||
soname = path.join(__dirname, 'lib', 'linux-arm64', 'libvosk.so');
|
||||
} else {
|
||||
soname = path.join(__dirname, "lib", "linux-x86_64", "libvosk.so")
|
||||
soname = path.join(__dirname, 'lib', 'linux-x86_64', 'libvosk.so');
|
||||
}
|
||||
|
||||
const libvosk = ffi.Library(soname, {
|
||||
@@ -128,6 +130,9 @@ class Model {
|
||||
* @type {unknown}
|
||||
*/
|
||||
this.handle = libvosk.vosk_model_new(modelPath);
|
||||
if (!this.handle) {
|
||||
throw new Error('Failed to create a model.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,6 +166,9 @@ class SpeakerModel {
|
||||
* @type {unknown}
|
||||
*/
|
||||
this.handle = libvosk.vosk_spk_model_new(modelPath);
|
||||
if (!this.handle) {
|
||||
throw new Error('Failed to create a speaker model.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,6 +243,10 @@ class Recognizer {
|
||||
: hasOwnProperty(param, 'grammar')
|
||||
? libvosk.vosk_recognizer_new_grm(model.handle, sampleRate, JSON.stringify(param.grammar))
|
||||
: libvosk.vosk_recognizer_new(model.handle, sampleRate);
|
||||
|
||||
if (!this.handle) {
|
||||
throw new Error('Failed to create a recognizer.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vosk",
|
||||
"version": "0.3.50",
|
||||
"version": "0.3.75",
|
||||
"description": "Node binding for continuous offline voice recoginition with Vosk library.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ with open("README.md", "rb") as fh:
|
||||
|
||||
setuptools.setup(
|
||||
name="vosk",
|
||||
version="0.3.50",
|
||||
version="0.3.75",
|
||||
author="Alpha Cephei Inc",
|
||||
author_email="contact@alphacephei.com",
|
||||
description="Offline open source speech recognition API based on Kaldi and Vosk",
|
||||
|
||||
@@ -28,7 +28,7 @@ def recognize(line):
|
||||
|
||||
def main():
|
||||
p = Pool(8)
|
||||
texts = p.map(recognize, open(sys.argv[1], encoding="uft-8").readlines())
|
||||
texts = p.map(recognize, open(sys.argv[1], encoding="utf-8").readlines())
|
||||
print ("\n".join(texts))
|
||||
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/.bundle/
|
||||
/.yardoc
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
.idea
|
||||
@@ -0,0 +1,3 @@
|
||||
--format documentation
|
||||
--color
|
||||
--require spec_helper
|
||||
@@ -0,0 +1,53 @@
|
||||
plugins:
|
||||
- rubocop-rake
|
||||
- rubocop-rspec
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.5
|
||||
NewCops: enable
|
||||
|
||||
Style/StringLiterals:
|
||||
EnforcedStyle: double_quotes
|
||||
|
||||
Style/StringLiteralsInInterpolation:
|
||||
EnforcedStyle: double_quotes
|
||||
|
||||
Style/TrailingCommaInArguments:
|
||||
EnforcedStyleForMultiline: consistent_comma
|
||||
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
EnforcedStyleForMultiline: consistent_comma
|
||||
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: consistent_comma
|
||||
|
||||
Style/NumericPredicate:
|
||||
EnforcedStyle: comparison
|
||||
|
||||
Layout/CaseIndentation:
|
||||
EnforcedStyle: end
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 27
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 10
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 10
|
||||
|
||||
Metrics/MethodLength:
|
||||
Max: 20
|
||||
|
||||
Style/SymbolArray:
|
||||
Exclude:
|
||||
- 'lib/vosk/ffi.rb'
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 10
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Max: 10
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 3
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in vosk.gemspec
|
||||
gemspec
|
||||
|
||||
gem "rake", "~> 13.0"
|
||||
|
||||
gem "rspec", "~> 3.0"
|
||||
|
||||
gem "rubocop", "~> 1.21"
|
||||
gem "rubocop-rake", "~> 0.7.1"
|
||||
gem "rubocop-rspec", "~> 3.9"
|
||||
|
||||
gem "wavefile", "~> 1.1"
|
||||
|
||||
gem "webmock", "~> 3.26"
|
||||
@@ -0,0 +1,109 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
vosk (0.3.45)
|
||||
bytesize (~> 0.1)
|
||||
ffi (~> 1.6)
|
||||
fileutils (~> 1.7)
|
||||
httparty (~> 0.21)
|
||||
progressbar (~> 1.13)
|
||||
rubyzip (~> 2.4)
|
||||
srt (~> 0.1.5)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.8)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
ast (2.4.2)
|
||||
bigdecimal (4.0.1)
|
||||
bytesize (0.1.2)
|
||||
crack (1.0.1)
|
||||
bigdecimal
|
||||
rexml
|
||||
csv (3.2.6)
|
||||
diff-lcs (1.5.1)
|
||||
ffi (1.17.0-x86_64-linux-gnu)
|
||||
fileutils (1.7.3)
|
||||
hashdiff (1.2.1)
|
||||
httparty (0.22.0)
|
||||
csv
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
json (2.9.0)
|
||||
language_server-protocol (3.17.0.3)
|
||||
lint_roller (1.1.0)
|
||||
mini_mime (1.1.5)
|
||||
multi_xml (0.6.0)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.10.2)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
prism (1.9.0)
|
||||
progressbar (1.13.0)
|
||||
public_suffix (7.0.2)
|
||||
racc (1.8.1)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
regexp_parser (2.9.3)
|
||||
rexml (3.4.4)
|
||||
rspec (3.13.0)
|
||||
rspec-core (~> 3.13.0)
|
||||
rspec-expectations (~> 3.13.0)
|
||||
rspec-mocks (~> 3.13.0)
|
||||
rspec-core (3.13.2)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.84.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.49.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.49.0)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.7)
|
||||
rubocop-rake (0.7.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1)
|
||||
rubocop-rspec (3.9.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.81)
|
||||
ruby-progressbar (1.13.0)
|
||||
rubyzip (2.4.1)
|
||||
srt (0.1.5)
|
||||
unicode-display_width (3.1.2)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
wavefile (1.1.2)
|
||||
webmock (3.26.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
rake (~> 13.0)
|
||||
rspec (~> 3.0)
|
||||
rubocop (~> 1.21)
|
||||
rubocop-rake (~> 0.7.1)
|
||||
rubocop-rspec (~> 3.9)
|
||||
vosk!
|
||||
wavefile (~> 1.1)
|
||||
webmock (~> 3.26)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.22
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
# Vosk Ruby
|
||||
|
||||
Ruby bindings for [Vosk](https://alphacephei.com/vosk/) — an offline speech recognition toolkit supporting 20+ languages.
|
||||
|
||||
## Installation
|
||||
|
||||
Add to your Gemfile:
|
||||
|
||||
```ruby
|
||||
gem "vosk"
|
||||
```
|
||||
|
||||
Or install directly:
|
||||
|
||||
```bash
|
||||
gem install vosk
|
||||
```
|
||||
|
||||
The gem ships with a precompiled `libvosk` for supported platforms. On other platforms it will attempt to load a system-installed `libvosk`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic transcription
|
||||
|
||||
```ruby
|
||||
require "vosk"
|
||||
require "wavefile"
|
||||
|
||||
# Load a model by language (downloaded automatically if not cached)
|
||||
model = Vosk::Model.new(lang: "en-us")
|
||||
|
||||
# Or by name, or by local path:
|
||||
# model = Vosk::Model.new(model_name: "vosk-model-small-en-us-0.4")
|
||||
# model = Vosk::Model.new(model_path: "/path/to/model")
|
||||
|
||||
WaveFile::Reader.new("audio.wav") do |reader|
|
||||
rec = Vosk::KaldiRecognizer.new(model, reader.format.sample_rate)
|
||||
|
||||
reader.each_buffer(4000) do |buffer|
|
||||
data = buffer.samples.pack(WaveFile::PACK_CODES.dig(:pcm, 16))
|
||||
if rec.accept_waveform(data).nonzero?
|
||||
puts rec.result # JSON: {"text": "..."}
|
||||
else
|
||||
puts rec.partial_result # JSON: {"partial": "..."}
|
||||
end
|
||||
end
|
||||
puts rec.final_result
|
||||
end
|
||||
```
|
||||
|
||||
Audio must be mono, 16-bit PCM WAV. Use [wavefile](https://github.com/jstrait/wavefile) to read it.
|
||||
|
||||
### Grammar / keyword recognition
|
||||
|
||||
Pass a JSON array of phrases as the third argument:
|
||||
|
||||
```ruby
|
||||
rec = Vosk::KaldiRecognizer.new(model, sample_rate, '["one two three", "[unk]"]')
|
||||
```
|
||||
|
||||
### Recognizer options
|
||||
|
||||
```ruby
|
||||
rec.words = true # include per-word timing in results
|
||||
rec.partial_words = true # include per-word timing in partial results
|
||||
rec.max_alternatives = 5 # return n-best list instead of single result
|
||||
rec.nlsml = true # return NLSML instead of JSON
|
||||
```
|
||||
|
||||
### SRT subtitle generation
|
||||
|
||||
`srt_result` reads raw PCM from any IO stream and returns an SRT-formatted string. Use ffmpeg to decode any audio format on the fly:
|
||||
|
||||
```ruby
|
||||
require "vosk"
|
||||
|
||||
SAMPLE_RATE = 16_000
|
||||
|
||||
model = Vosk::Model.new(lang: "en-us")
|
||||
rec = Vosk::KaldiRecognizer.new(model, SAMPLE_RATE)
|
||||
rec.words = true # required for word-level timestamps
|
||||
|
||||
IO.popen(["ffmpeg", "-loglevel", "quiet", "-i", "audio.mp4",
|
||||
"-ar", SAMPLE_RATE.to_s, "-ac", "1", "-f", "s16le", "-",]) do |stream|
|
||||
puts rec.srt_result(stream)
|
||||
end
|
||||
```
|
||||
|
||||
The `words_per_line:` keyword controls how many words appear per subtitle line (default: 7):
|
||||
|
||||
```ruby
|
||||
rec.srt_result(stream, words_per_line: 5)
|
||||
```
|
||||
|
||||
### Speaker identification
|
||||
|
||||
```ruby
|
||||
spk_model = Vosk::SpkModel.new("/path/to/spk-model")
|
||||
rec = Vosk::KaldiRecognizer.new(model, sample_rate)
|
||||
rec.spk_model = spk_model
|
||||
```
|
||||
|
||||
### Listing available models
|
||||
|
||||
```ruby
|
||||
puts Vosk.models # all model names
|
||||
puts Vosk.languages # all supported language codes
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
```ruby
|
||||
Vosk.log_level = -1 # suppress all output
|
||||
Vosk.log_level = 0 # default
|
||||
```
|
||||
|
||||
### Transcriber CLI
|
||||
|
||||
The gem includes a `vosk-transcriber` executable:
|
||||
|
||||
```bash
|
||||
vosk-transcriber audio.wav
|
||||
```
|
||||
|
||||
## Model storage
|
||||
|
||||
Models are cached in `~/.cache/vosk/` by default, or in the directory set by `$VOSK_MODEL_PATH`.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
bundle install
|
||||
bundle exec rake spec
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
require "rubygems/package_task"
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
require "rubocop/rake_task"
|
||||
require "fileutils"
|
||||
|
||||
RuboCop::RakeTask.new
|
||||
|
||||
# TODO: modify 'build' task somehow to include pre-compiled binary
|
||||
# (or add this logic into gemspec)
|
||||
|
||||
spec = Gem::Specification.load("vosk.gemspec")
|
||||
# Tweak spec when building pre-compiled gem:
|
||||
# (see example in https://github.com/oxidize-rb/actions/blob/main/cross-gem/action.yml and https://github.com/oxidize-rb/rb-sys/blob/main/gem/lib/rb_sys/extensiontask.rb)
|
||||
# See also https://github.com/rake-compiler/rake-compiler/blob/master/lib/rake/extensiontask.rb and https://github.com/rake-compiler/rake-compiler-dock
|
||||
# def cross_compile?
|
||||
# # TODO: impl
|
||||
# false
|
||||
# end
|
||||
|
||||
# TODO: maybe it should be a separate task that extends Gem::PackageTask
|
||||
# if cross_compile?
|
||||
# # Can't use 'file' task bc we don't know file names exactly
|
||||
# task :libs do
|
||||
# # TODO
|
||||
# # vosk_source = os.getenv("VOSK_SOURCE", os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
# # "..")))
|
||||
# FileUtils.cp Dir[File.join(vosk_source, "src/lib*.*")], "lib/vosk"
|
||||
# # TODO: check it works properly (spec is modified after `gem` task is already created)
|
||||
# # Matches package_data = {'vosk': ['*.so', '*.dll', '*.dyld']},
|
||||
# spec.files += Dir["lib/vosk/*.{so,dll,dyld}"]
|
||||
# end
|
||||
# task gem: :libs
|
||||
# # TODO: figure out platform
|
||||
# # system = os.environ.get('VOSK_SYSTEM', platform.system())
|
||||
# # architecture = os.environ.get('VOSK_ARCHITECTURE', platform.architecture()[0])
|
||||
# # machine = os.environ.get('VOSK_MACHINE', platform.machine())
|
||||
# spec.platform = Gem::Platform.new(platf)
|
||||
# end
|
||||
|
||||
# add your default gem packing task
|
||||
# Run with `rake build`
|
||||
Gem::PackageTask.new(spec)
|
||||
|
||||
task default: %i[spec rubocop]
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/setup"
|
||||
require "vosk"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
require "irb"
|
||||
IRB.start(__FILE__)
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
set -vx
|
||||
|
||||
bundle install
|
||||
|
||||
# Do any other automated setup that you need to do here
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../python/example/test.wav
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "vosk"
|
||||
require "wavefile"
|
||||
|
||||
# You can set log level to -1 to disable debug messages
|
||||
Vosk.log_level = 0
|
||||
|
||||
WaveFile::Reader.new(ARGV[0]) do |reader|
|
||||
reader_format = reader.format
|
||||
unless reader_format.channels == 1 && reader_format.bits_per_sample == 16 && reader_format.sample_format == :pcm
|
||||
puts("Audio file must be WAV format mono PCM.")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
model = Vosk::Model.new(lang: "en-us")
|
||||
|
||||
# You can also init model by name or with a folder path
|
||||
# model = Model(model_name: "vosk-model-en-us-0.21")
|
||||
# model = Model(model_path: "models/en")
|
||||
|
||||
rec = Vosk::KaldiRecognizer.new(model, reader_format.sample_rate)
|
||||
rec.words = true
|
||||
rec.partial_words = true
|
||||
|
||||
reader.each_buffer(4000) do |buffer|
|
||||
data = buffer.samples.pack(WaveFile::PACK_CODES.dig(:pcm, 16))
|
||||
|
||||
if rec.accept_waveform(data).nonzero?
|
||||
puts rec.result
|
||||
else
|
||||
puts rec.partial_result
|
||||
end
|
||||
end
|
||||
puts rec.final_result
|
||||
end
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "vosk"
|
||||
|
||||
SAMPLE_RATE = 16_000
|
||||
|
||||
Vosk.log_level = -1
|
||||
|
||||
model = Vosk::Model.new(lang: "en-us")
|
||||
rec = Vosk::KaldiRecognizer.new(model, SAMPLE_RATE)
|
||||
rec.words = true
|
||||
|
||||
IO.popen(["ffmpeg", "-loglevel", "quiet", "-i", "audio.mp4",
|
||||
"-ar", SAMPLE_RATE.to_s, "-ac", "1", "-f", "s16le", "-",]) do |stream|
|
||||
puts rec.srt_result(stream)
|
||||
end
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "vosk"
|
||||
require "wavefile"
|
||||
|
||||
WaveFile::Reader.new(ARGV[0]) do |reader|
|
||||
reader_format = reader.format
|
||||
unless reader_format.channels == 1 && reader_format.bits_per_sample == 16 && reader_format.sample_format == :pcm
|
||||
puts("Audio file must be WAV format mono PCM.")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
model = Vosk::Model.new(lang: "en-us")
|
||||
|
||||
# You can also specify the possible word or phrase list as JSON list,
|
||||
# the order doesn't have to be strict
|
||||
rec = Vosk::KaldiRecognizer.new(
|
||||
model, reader_format.sample_rate,
|
||||
'["oh one two three", "four five six", "seven eight nine zero", "[unk]"]',
|
||||
)
|
||||
|
||||
reader.each_buffer(4000) do |buffer|
|
||||
data = buffer.samples.pack(WaveFile::PACK_CODES.dig(:pcm, 16))
|
||||
|
||||
if rec.accept_waveform(data).nonzero?
|
||||
puts rec.result
|
||||
rec.grammar = '["one zero one two three oh", "four five six", "seven eight nine zero", "[unk]"]'
|
||||
else
|
||||
puts rec.partial_result
|
||||
end
|
||||
end
|
||||
puts rec.final_result
|
||||
end
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "vosk"
|
||||
# TODO
|
||||
+343
-3
@@ -1,5 +1,345 @@
|
||||
class Vosk
|
||||
def self.hi
|
||||
puts "Hello world!"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "vosk/version"
|
||||
require "httparty"
|
||||
require_relative "vosk/progressbar"
|
||||
require_relative "vosk/ffi"
|
||||
require "zip"
|
||||
require "fileutils"
|
||||
require "json"
|
||||
require "srt"
|
||||
|
||||
# Vosk speech recognition system
|
||||
module Vosk
|
||||
class Error < StandardError; end
|
||||
|
||||
# Remote location of the models and local folders
|
||||
MODEL_PRE_URL = "https://alphacephei.com/vosk/models/"
|
||||
MODEL_LIST_URL = "#{MODEL_PRE_URL}model-list.json"
|
||||
# TODO: Test on Windows
|
||||
MODEL_DIRS = [
|
||||
ENV.fetch("VOSK_MODEL_PATH", nil), "/usr/share/vosk",
|
||||
File.join(Dir.home, "AppData/Local/vosk"), File.join(Dir.home, ".cache/vosk"),
|
||||
].compact.freeze
|
||||
|
||||
# Different from Python: no need to print inside the method, simply use +puts Vosk.models+
|
||||
def self.models
|
||||
response = HTTParty.get(MODEL_LIST_URL, timeout: 10)
|
||||
response.map { |model| model["name"] }
|
||||
end
|
||||
|
||||
# Different from Python: no need to print inside the method, simply use +puts Vosk.languages+
|
||||
def self.languages
|
||||
response = HTTParty.get(MODEL_LIST_URL, timeout: 10)
|
||||
response.map { |model| model["lang"] }.uniq
|
||||
end
|
||||
|
||||
# Model stores all the data required for recognition
|
||||
# it contains static data and can be shared across processing
|
||||
# threads.
|
||||
class Model
|
||||
attr_reader :handle
|
||||
|
||||
def initialize(model_path: nil, model_name: nil, lang: nil)
|
||||
model_path ||= get_model_path(model_name, lang)
|
||||
@handle = C.vosk_model_new(model_path)
|
||||
end
|
||||
|
||||
def vosk_model_find_word(word)
|
||||
C.vosk_model_find_word(@handle, word)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_model_path(model_name, lang)
|
||||
if model_name
|
||||
get_model_by_name(model_name)
|
||||
else
|
||||
get_model_by_lang(lang)
|
||||
end
|
||||
end
|
||||
|
||||
def get_model_by_name(model_name)
|
||||
MODEL_DIRS.each do |directory|
|
||||
next unless Dir.exist?(directory)
|
||||
|
||||
entry = Dir.entries(directory).find { |f| f == model_name }
|
||||
return File.join(directory, entry) if entry
|
||||
end
|
||||
response = HTTParty.get(MODEL_LIST_URL, timeout: 10)
|
||||
result_model = response.map { |m| m["name"] }.find { |n| n == model_name }
|
||||
unless result_model
|
||||
# It's not common for Ruby gems to exit the whole process, but I decided to match Python behavior
|
||||
puts "model name #{model_name} does not exist"
|
||||
exit(1)
|
||||
end
|
||||
# It always selects the last dir for downloads, ignoring env and windows-specific dir
|
||||
dest = File.join(MODEL_DIRS.last, result_model)
|
||||
download_model(dest)
|
||||
dest
|
||||
end
|
||||
|
||||
def get_model_by_lang(lang)
|
||||
MODEL_DIRS.each do |directory|
|
||||
next unless Dir.exist?(directory)
|
||||
|
||||
entry = Dir.entries(directory).find { |f| f.match?(/\Avosk-model(-small)?-#{Regexp.escape(lang)}/) }
|
||||
return File.join(directory, entry) if entry
|
||||
end
|
||||
response = HTTParty.get(MODEL_LIST_URL, timeout: 10)
|
||||
result_model = response.find do |m|
|
||||
m["lang"] == lang && m["type"] == "small" && m["obsolete"] == "false"
|
||||
end&.dig("name")
|
||||
unless result_model
|
||||
# It's not common for Ruby gems to exit the whole process, but I decided to match Python behavior
|
||||
puts "lang #{lang} does not exist"
|
||||
exit(1)
|
||||
end
|
||||
# It always selects the last dir for downloads, ignoring env and windows-specific dir
|
||||
dest = File.join(MODEL_DIRS.last, result_model)
|
||||
download_model(dest)
|
||||
dest
|
||||
end
|
||||
|
||||
# Python param "model_name" is, in fact, a full path
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def download_model(model_path)
|
||||
dir = File.dirname(model_path)
|
||||
# Python version won't try to create the directory if a file with the same name exists
|
||||
FileUtils.makedirs(dir)
|
||||
model_name = File.basename(model_path)
|
||||
zip_path = "#{model_path}.zip"
|
||||
url = "#{MODEL_PRE_URL}#{model_name}.zip"
|
||||
|
||||
progressbar = ProgressBar.create(
|
||||
# Why add MODEL_PRE_URL and then split it away?
|
||||
title: "#{model_name}.zip",
|
||||
total: nil,
|
||||
progress_mark: "█",
|
||||
format: "%t: %j%%|%B| %s/%z [%d<%o, %r/s]",
|
||||
rate_scale: ->(rate) { ByteSize.new(rate.to_i).to_s },
|
||||
)
|
||||
|
||||
begin
|
||||
download_file(url, zip_path) do |bsize, tsize|
|
||||
progressbar.total = tsize if tsize && tsize >= progressbar.progress
|
||||
progressbar.progress += bsize
|
||||
end
|
||||
progressbar.finish
|
||||
ensure
|
||||
progressbar&.stop
|
||||
end
|
||||
|
||||
Zip::File.open(zip_path) do |zip_file|
|
||||
zip_file.each do |entry|
|
||||
entry_path = File.join(dir, entry.name)
|
||||
FileUtils.makedirs(File.dirname(entry_path))
|
||||
entry.extract(entry_path) { true }
|
||||
end
|
||||
end
|
||||
|
||||
File.unlink(zip_path)
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def download_file(url, dest, &callback)
|
||||
File.open(dest, File::CREAT | File::WRONLY | File::TRUNC | File::BINARY) do |file|
|
||||
response = HTTParty.get(url, stream_body: true) do |fragment|
|
||||
next unless fragment.http_response.is_a?(Net::HTTPSuccess)
|
||||
|
||||
file.write(fragment)
|
||||
callback&.call(fragment.bytesize, fragment.http_response["Content-Length"]&.to_i)
|
||||
end
|
||||
raise HTTParty::ResponseError.new(response), "Code #{response.code}" unless response.success?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Speaker model is the same as model but contains the data
|
||||
# for speaker identification.
|
||||
class SpkModel
|
||||
attr_reader :handle
|
||||
|
||||
def initialize(model_path)
|
||||
@handle = C.vosk_spk_model_new(model_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Endpointer scaling factor
|
||||
class EndpointerMode
|
||||
C::VoskEndpointerMode.symbol_map.each do |name, value|
|
||||
const_set(name.upcase, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Recognizer object is the main object which processes data.
|
||||
# Each recognizer usually runs in own thread and takes audio as input.
|
||||
# Once audio is processed recognizer returns JSON object as a string
|
||||
# which represent decoded information - words, confidences, times, n-best lists,
|
||||
# speaker information and so on
|
||||
class KaldiRecognizer
|
||||
# Python version accepts *args, so in case of a wrong number of arguments it'll raise TypeError,
|
||||
# while Ruby raises ArgumentError
|
||||
def initialize(model, sample_rate, grammar_or_spk_model = nil)
|
||||
@handle = case grammar_or_spk_model
|
||||
when nil
|
||||
C.vosk_recognizer_new(model.handle, sample_rate.to_f)
|
||||
when SpkModel
|
||||
C.vosk_recognizer_new_spk(model.handle, sample_rate.to_f, grammar_or_spk_model.handle)
|
||||
when String
|
||||
C.vosk_recognizer_new_grm(model.handle, sample_rate.to_f, grammar_or_spk_model)
|
||||
else
|
||||
raise TypeError, "Unknown arguments"
|
||||
end
|
||||
end
|
||||
|
||||
def max_alternatives=(max_alternatives)
|
||||
C.vosk_recognizer_set_max_alternatives(@handle, max_alternatives)
|
||||
end
|
||||
|
||||
def words=(enable_words)
|
||||
C.vosk_recognizer_set_words(@handle, enable_words ? 1 : 0)
|
||||
end
|
||||
|
||||
def partial_words=(enable_partial_words)
|
||||
C.vosk_recognizer_set_partial_words(@handle, enable_partial_words ? 1 : 0)
|
||||
end
|
||||
|
||||
def nlsml=(enable_nlsml)
|
||||
C.vosk_recognizer_set_nlsml(@handle, enable_nlsml ? 1 : 0)
|
||||
end
|
||||
|
||||
def endpointer_mode=(mode)
|
||||
C.vosk_recognizer_set_endpointer_mode(@handle, mode.to_i)
|
||||
end
|
||||
|
||||
def set_endpointer_delays(t_start_max, t_end, t_max)
|
||||
C.vosk_recognizer_set_endpointer_delays(@handle, t_start_max.to_f, t_end.to_f, t_max.to_f)
|
||||
end
|
||||
|
||||
def spk_model=(spk_model)
|
||||
C.vosk_recognizer_set_spk_model(@handle, spk_model.handle)
|
||||
end
|
||||
|
||||
def grammar=(grammar)
|
||||
C.vosk_recognizer_set_grm(@handle, grammar)
|
||||
end
|
||||
|
||||
def accept_waveform(data)
|
||||
res = C.vosk_recognizer_accept_waveform(@handle, data, data.bytesize)
|
||||
raise Error, "Failed to process waveform" if res < 0
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def result
|
||||
C.vosk_recognizer_result(@handle)
|
||||
end
|
||||
|
||||
def partial_result
|
||||
C.vosk_recognizer_partial_result(@handle)
|
||||
end
|
||||
|
||||
def final_result
|
||||
C.vosk_recognizer_final_result(@handle)
|
||||
end
|
||||
|
||||
def reset
|
||||
C.vosk_recognizer_reset(@handle)
|
||||
end
|
||||
|
||||
def srt_result(stream, words_per_line: 7)
|
||||
results = []
|
||||
while (data = stream.read(4000))
|
||||
results.push(result) if accept_waveform(data).nonzero?
|
||||
end
|
||||
results.push(final_result)
|
||||
|
||||
create_srt(results, words_per_line)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_srt(results, words_per_line)
|
||||
srt = SRT::File.new
|
||||
results.each do |res|
|
||||
jres = JSON.parse(res)
|
||||
next unless jres.key?("result")
|
||||
|
||||
jres["result"].each_slice(words_per_line) do |line|
|
||||
sub = SRT::Line.new
|
||||
sub.sequence = srt.lines.length + 1
|
||||
sub.start_time = line.first["start"]
|
||||
sub.end_time = line.last["end"]
|
||||
sub.text = line.map { |w| w["word"] }.join(" ")
|
||||
srt.lines.push(sub)
|
||||
end
|
||||
end
|
||||
srt.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Batch model object
|
||||
class BatchModel
|
||||
attr_reader :handle
|
||||
|
||||
# Python version accepts additional ignored args for some reason
|
||||
def initialize(model_path)
|
||||
@handle = C.vosk_batch_model_new(model_path)
|
||||
end
|
||||
|
||||
def wait
|
||||
C.vosk_batch_model_wait(@handle)
|
||||
end
|
||||
end
|
||||
|
||||
# Batch recognizer object
|
||||
class BatchRecognizer
|
||||
# Python version accepts *args, but I don't just use regular arguments
|
||||
def initialize(batch_model, sample_rate)
|
||||
@handle = C.vosk_batch_recognizer_new(batch_model.handle, sample_rate.to_f)
|
||||
end
|
||||
|
||||
def accept_waveform(data)
|
||||
C.vosk_batch_recognizer_accept_waveform(@handle, data, data.bytesize)
|
||||
end
|
||||
|
||||
def result
|
||||
res = C.vosk_batch_recognizer_front_result(@handle)
|
||||
C.vosk_batch_recognizer_pop(@handle)
|
||||
res
|
||||
end
|
||||
|
||||
def finish_stream
|
||||
C.vosk_batch_recognizer_finish_stream(@handle)
|
||||
end
|
||||
|
||||
def pending_chunks
|
||||
C.vosk_batch_recognizer_get_pending_chunks(@handle)
|
||||
end
|
||||
end
|
||||
|
||||
# Inverse text normalization
|
||||
class Processor
|
||||
# Python version accepts *args, but I don't just use regular arguments
|
||||
def initialize(lang, type)
|
||||
@handle = C.vosk_text_processor_new(lang, type)
|
||||
end
|
||||
|
||||
def process(text)
|
||||
C.vosk_text_processor_itn(@handle, text)
|
||||
end
|
||||
end
|
||||
|
||||
def self.log_level=(level)
|
||||
C.vosk_set_log_level(level)
|
||||
end
|
||||
|
||||
def self.gpu_init
|
||||
C.vosk_gpu_init
|
||||
end
|
||||
|
||||
def self.gpu_thread_init
|
||||
C.vosk_gpu_thread_init
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "ffi"
|
||||
|
||||
module Vosk
|
||||
module C # :nodoc: all
|
||||
extend FFI::Library
|
||||
|
||||
# FIXME: Load same way as in Python, test on Windows
|
||||
# This second option, 'vosk', allows system-wide installed library to be loaded.
|
||||
# I see you search /usr/share/vosk, so I guess it's supported somehow.
|
||||
# It'll allow the gem to be used on systems not supported in pre-compiled releases.
|
||||
# (in fact, we only need the first in a pre-compiled release and the second otherwise,
|
||||
# but not worth the effort to put more configuration in the build stage - not possible without hacks)
|
||||
# But when we load a lib not shipped with the gem itself, we (might) need to ensure it's a compatible version
|
||||
# Note: options in the array are alternatives, only the first found is loaded
|
||||
# Note: probably needs RubyInstaller::Runtime.add_dll_directory on Windows - if FFI::Platform.windows?
|
||||
ffi_lib [File.join(__dir__, FFI.map_library_name("vosk")), "vosk"]
|
||||
|
||||
class VoskModel < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create a model" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_model_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
class VoskSpkModel < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create a speaker model" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_spk_model_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
VoskEndpointerMode = enum(
|
||||
:VoskEndpointerMode,
|
||||
[
|
||||
:default, 0,
|
||||
:short, 1,
|
||||
:long, 2,
|
||||
:very_long, 3,
|
||||
],
|
||||
)
|
||||
|
||||
class VoskRecognizer < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create a recognizer" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_recognizer_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
class VoskBatchModel < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create a model" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_batch_model_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
class VoskBatchRecognizer < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create a recognizer" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_batch_recognizer_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
class VoskTextProcessor < FFI::AutoPointer
|
||||
def self.from_native(ptr, _ctx)
|
||||
raise Error, "Failed to create processor" if ptr.null?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def self.release(ptr)
|
||||
C.vosk_text_processor_free(ptr)
|
||||
end
|
||||
end
|
||||
|
||||
attach_function :vosk_model_new, [:string], VoskModel
|
||||
attach_function :vosk_model_free, [VoskModel], :void
|
||||
attach_function :vosk_model_find_word, [VoskModel, :string], :int
|
||||
|
||||
attach_function :vosk_spk_model_new, [:string], VoskSpkModel
|
||||
attach_function :vosk_spk_model_free, [VoskSpkModel], :void
|
||||
|
||||
attach_function :vosk_recognizer_new, [VoskModel, :float], VoskRecognizer
|
||||
attach_function :vosk_recognizer_new_spk, [VoskModel, :float, VoskSpkModel], VoskRecognizer
|
||||
attach_function :vosk_recognizer_new_grm, [VoskModel, :float, :string], VoskRecognizer
|
||||
attach_function :vosk_recognizer_set_spk_model, [VoskRecognizer, VoskSpkModel], :void
|
||||
attach_function :vosk_recognizer_set_grm, [VoskRecognizer, :string], :void
|
||||
attach_function :vosk_recognizer_set_max_alternatives, [VoskRecognizer, :int], :void
|
||||
attach_function :vosk_recognizer_set_words, [VoskRecognizer, :int], :void
|
||||
attach_function :vosk_recognizer_set_partial_words, [VoskRecognizer, :int], :void
|
||||
attach_function :vosk_recognizer_set_nlsml, [VoskRecognizer, :int], :void
|
||||
# TODO: remove this, it was needed only because I used libvosk from Python package
|
||||
if Gem::Version.new(VERSION) >= Gem::Version.new("0.3.46")
|
||||
attach_function :vosk_recognizer_set_endpointer_mode, [VoskRecognizer, VoskEndpointerMode], :void
|
||||
attach_function :vosk_recognizer_set_endpointer_delays, [VoskRecognizer, :float, :float, :float], :void
|
||||
end
|
||||
attach_function :vosk_recognizer_accept_waveform, [VoskRecognizer, :buffer_in, :int], :int
|
||||
# vosk_recognizer_accept_waveform_s; vosk_recognizer_accept_waveform_f - skipped
|
||||
attach_function :vosk_recognizer_result, [VoskRecognizer], :string
|
||||
attach_function :vosk_recognizer_partial_result, [VoskRecognizer], :string
|
||||
attach_function :vosk_recognizer_final_result, [VoskRecognizer], :string
|
||||
attach_function :vosk_recognizer_reset, [VoskRecognizer], :void
|
||||
attach_function :vosk_recognizer_free, [VoskRecognizer], :void
|
||||
|
||||
attach_function :vosk_set_log_level, [:int], :void
|
||||
attach_function :vosk_gpu_init, [], :void
|
||||
attach_function :vosk_gpu_thread_init, [], :void
|
||||
|
||||
attach_function :vosk_batch_model_new, [:string], VoskBatchModel
|
||||
attach_function :vosk_batch_model_free, [VoskBatchModel], :void
|
||||
attach_function :vosk_batch_model_wait, [VoskBatchModel], :void
|
||||
|
||||
attach_function :vosk_batch_recognizer_new, [VoskBatchModel, :float], VoskBatchRecognizer
|
||||
attach_function :vosk_batch_recognizer_free, [VoskBatchRecognizer], :void
|
||||
attach_function :vosk_batch_recognizer_accept_waveform, [VoskBatchRecognizer, :buffer_in, :int], :void
|
||||
# vosk_batch_recognizer_set_nlsml - skipped
|
||||
attach_function :vosk_batch_recognizer_finish_stream, [VoskBatchRecognizer], :void
|
||||
attach_function :vosk_batch_recognizer_front_result, [VoskBatchRecognizer], :string
|
||||
attach_function :vosk_batch_recognizer_pop, [VoskBatchRecognizer], :void
|
||||
attach_function :vosk_batch_recognizer_get_pending_chunks, [VoskBatchRecognizer], :int
|
||||
|
||||
# https://github.com/ffi/ffi/issues/467
|
||||
class OwnedString
|
||||
extend FFI::DataConverter
|
||||
|
||||
native_type :strptr
|
||||
|
||||
def self.to_native(_value, _context)
|
||||
raise TypeError, "owned_string can't be used for input"
|
||||
end
|
||||
|
||||
def self.from_native((str, ptr), _context)
|
||||
C.free(ptr)
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
typedef OwnedString, :owned_string
|
||||
attach_function :free, [:pointer], :void
|
||||
|
||||
# TODO: remove this, it was needed only because I used libvosk from Python package
|
||||
if Gem::Version.new(VERSION) >= Gem::Version.new("0.3.48")
|
||||
attach_function :vosk_text_processor_new, [:string, :string], VoskTextProcessor
|
||||
attach_function :vosk_text_processor_free, [VoskTextProcessor], :void
|
||||
# NOTE: you have a memory leak here in your python version, needs to be
|
||||
# ptr = _c.vosk_text_processor_itn(self._handle, text.encode('utf-8'))
|
||||
# str = _ffi.string(ptr).decode('utf-8')
|
||||
# _ffi.gc(ptr, _c.free) # or call libc free directly
|
||||
attach_function :vosk_text_processor_itn, [VoskTextProcessor, :string], :owned_string
|
||||
end
|
||||
end
|
||||
|
||||
private_constant :C
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "progressbar"
|
||||
require "bytesize"
|
||||
|
||||
# Extends progressbar with tqdm-like output:
|
||||
# %s / %z → human-readable byte sizes: current size / total siZe
|
||||
# %d / %o → compact time: elapsed Duration / Outstanding remaining
|
||||
# %r → rate_scale lambda may return a ByteSize string
|
||||
|
||||
# Override rate_of_change so rate_scale can return a string.
|
||||
# The default format '%i' coerces the result to integer, breaking string values.
|
||||
ProgressBar::Components::Rate.prepend(Module.new do
|
||||
def rate_of_change(format_string = "%s")
|
||||
return "0" if elapsed_seconds <= 0
|
||||
|
||||
format_string % scaled_rate
|
||||
end
|
||||
end)
|
||||
|
||||
class ProgressBar # :nodoc: all
|
||||
# Add ByteSize-formatted progress methods to the Progress component.
|
||||
class Progress
|
||||
def progress_with_precision
|
||||
ByteSize.new(progress).to_s
|
||||
end
|
||||
|
||||
def total_with_unknown_indicator_with_precision
|
||||
total ? ByteSize.new(total).to_s : total_with_unknown_indicator
|
||||
end
|
||||
end
|
||||
|
||||
module Components
|
||||
# Add label-free time methods to the Time component.
|
||||
class Time
|
||||
def elapsed_no_label
|
||||
val = elapsed
|
||||
val.start_with?("00:") ? val[3..-1] : val
|
||||
end
|
||||
|
||||
def estimated_no_label
|
||||
val = estimated_with_friendly_oob.split(": ", 2).last
|
||||
val.start_with?("00:") ? val[3..-1] : val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Extend MOLECULES with new non-overlapping keys — originals are untouched.
|
||||
# remove_const avoids the "already initialized constant" warning.
|
||||
molecules = ProgressBar::Format::Molecule::MOLECULES
|
||||
ProgressBar::Format::Molecule.send(:remove_const, :MOLECULES)
|
||||
ProgressBar::Format::Molecule::MOLECULES = molecules.merge(
|
||||
s: %i[progressable progress_with_precision],
|
||||
z: %i[progressable total_with_unknown_indicator_with_precision],
|
||||
d: %i[time_component elapsed_no_label],
|
||||
o: %i[time_component estimated_no_label],
|
||||
).freeze
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Vosk
|
||||
VERSION = "0.3.45"
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
module Vosk
|
||||
VERSION: String
|
||||
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "vosk"
|
||||
require "webmock/rspec"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
config.example_status_persistence_file_path = ".rspec_status"
|
||||
|
||||
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
end
|
||||
|
||||
Vosk.log_level = -1
|
||||
@@ -0,0 +1,173 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Vosk::Model do
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
let(:en_model_path) { File.join(Dir.home, ".cache/vosk/vosk-model-small-en-us-0.4") }
|
||||
let(:model_name) { "vosk-model-small-en-us-0.4" }
|
||||
let(:model_list) do
|
||||
[{ "name" => model_name, "lang" => "en-us", "type" => "small", "obsolete" => "false" }]
|
||||
end
|
||||
# Allocate an instance without calling initialize — no C library needed.
|
||||
let(:stub_model) { described_class.allocate }
|
||||
|
||||
after { FileUtils.rm_rf(tmpdir) }
|
||||
|
||||
it "raises Vosk::Error on a bad path" do
|
||||
expect do
|
||||
described_class.new(model_path: "/nonexistent/path")
|
||||
end.to raise_error(Vosk::Error, "Failed to create a model")
|
||||
end
|
||||
|
||||
context "when loaded from a path" do
|
||||
subject(:model) { described_class.new(model_path: en_model_path) }
|
||||
|
||||
it "constructs successfully" do
|
||||
expect(model).to be_a(described_class)
|
||||
end
|
||||
|
||||
it "finds a known word" do
|
||||
expect(model.vosk_model_find_word("one")).to be >= 0
|
||||
end
|
||||
|
||||
it "returns -1 for an unknown word" do
|
||||
expect(model.vosk_model_find_word("xyzzy")).to eq(-1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_model_by_name" do
|
||||
context "when the model directory already exists in MODEL_DIRS" do
|
||||
before do
|
||||
FileUtils.makedirs(File.join(tmpdir, model_name))
|
||||
stub_const("Vosk::MODEL_DIRS", [tmpdir])
|
||||
end
|
||||
|
||||
it "returns its path without hitting the network" do
|
||||
expect(stub_model.send(:get_model_by_name, model_name)).to eq(File.join(tmpdir, model_name))
|
||||
end
|
||||
end
|
||||
|
||||
context "when the model is not in any MODEL_DIR" do
|
||||
before do
|
||||
stub_const("Vosk::MODEL_DIRS", [tmpdir])
|
||||
stub_request(:get, Vosk::MODEL_LIST_URL)
|
||||
.to_return(status: 200, body: model_list.to_json, headers: { "Content-Type" => "application/json" })
|
||||
allow(stub_model).to receive(:download_model)
|
||||
end
|
||||
|
||||
it "downloads the model to the last MODEL_DIR" do
|
||||
stub_model.send(:get_model_by_name, model_name)
|
||||
expect(stub_model).to have_received(:download_model).with(File.join(tmpdir, model_name))
|
||||
end
|
||||
|
||||
it "returns the expected path after downloading" do
|
||||
expect(stub_model.send(:get_model_by_name, model_name)).to eq(File.join(tmpdir, model_name))
|
||||
end
|
||||
|
||||
it "exits when the model name is not in the remote list" do
|
||||
expect { stub_model.send(:get_model_by_name, "vosk-model-unknown") }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_model_by_lang" do
|
||||
context "when a matching model directory already exists" do
|
||||
before do
|
||||
FileUtils.makedirs(File.join(tmpdir, model_name))
|
||||
stub_const("Vosk::MODEL_DIRS", [tmpdir])
|
||||
end
|
||||
|
||||
it "returns its path without hitting the network" do
|
||||
expect(stub_model.send(:get_model_by_lang, "en-us")).to eq(File.join(tmpdir, model_name))
|
||||
end
|
||||
|
||||
it "also matches vosk-model-<lang> (without -small-)" do
|
||||
other = "vosk-model-en-us-0.22"
|
||||
FileUtils.makedirs(File.join(tmpdir, other))
|
||||
stub_const("Vosk::MODEL_DIRS", [tmpdir])
|
||||
expect(stub_model.send(:get_model_by_lang, "en-us")).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when no local model exists for the language" do
|
||||
before do
|
||||
stub_const("Vosk::MODEL_DIRS", [tmpdir])
|
||||
stub_request(:get, Vosk::MODEL_LIST_URL)
|
||||
.to_return(status: 200, body: model_list.to_json, headers: { "Content-Type" => "application/json" })
|
||||
allow(stub_model).to receive(:download_model)
|
||||
end
|
||||
|
||||
it "downloads a small model for the language" do
|
||||
stub_model.send(:get_model_by_lang, "en-us")
|
||||
expect(stub_model).to have_received(:download_model).with(File.join(tmpdir, model_name))
|
||||
end
|
||||
|
||||
it "exits when no model is available for the language" do
|
||||
expect { stub_model.send(:get_model_by_lang, "xx-unknown") }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#download_model" do
|
||||
let(:model_path) { File.join(tmpdir, model_name) }
|
||||
let(:zip_content) do
|
||||
Zip::OutputStream.write_buffer do |zip|
|
||||
zip.put_next_entry("#{model_name}/conf/model.conf")
|
||||
zip.write("# model config\n" * 1_000)
|
||||
zip.put_next_entry("#{model_name}/README")
|
||||
zip.write("test model\n")
|
||||
end.string
|
||||
end
|
||||
let(:progress_bar_content) { StringIO.new }
|
||||
let(:progress_bar_stream) do
|
||||
dbl = double
|
||||
allow(dbl).to receive(:tty?).with(no_args).and_return(true)
|
||||
allow(dbl).to receive(:print) do |*args|
|
||||
progress_bar_content.print(*args)
|
||||
end
|
||||
allow(dbl).to receive(:flush).with(no_args).and_return(dbl)
|
||||
allow(dbl).to receive(:winsize).with(no_args).and_return([40, 180])
|
||||
dbl
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const("ProgressBar::Output::DEFAULT_OUTPUT_STREAM", progress_bar_stream)
|
||||
stub_request(:get, "#{Vosk::MODEL_PRE_URL}#{model_name}.zip")
|
||||
.to_return(status: 200, body: zip_content, headers: { "Content-Length" => zip_content.bytesize.to_s })
|
||||
end
|
||||
|
||||
it "extracts the archive into the parent directory" do
|
||||
stub_model.send(:download_model, model_path)
|
||||
expect(File.exist?(File.join(model_path, "conf/model.conf"))).to be(true)
|
||||
end
|
||||
|
||||
it "deletes the zip file after extraction" do
|
||||
stub_model.send(:download_model, model_path)
|
||||
expect(File.exist?("#{model_path}.zip")).to be(false)
|
||||
end
|
||||
|
||||
it "creates the parent directory when it does not yet exist" do
|
||||
nested_path = File.join(tmpdir, "new_subdir", model_name)
|
||||
stub_model.send(:download_model, nested_path)
|
||||
expect(Dir.exist?(File.dirname(nested_path))).to be(true)
|
||||
end
|
||||
|
||||
it "uses the correct download URL" do
|
||||
stub_model.send(:download_model, model_path)
|
||||
expect(WebMock).to have_requested(:get, "#{Vosk::MODEL_PRE_URL}#{model_name}.zip")
|
||||
end
|
||||
|
||||
it "displays a progress bar during download" do
|
||||
stub_model.send(:download_model, model_path)
|
||||
# TODO: test callback when multiple fragments are received
|
||||
expect(progress_bar_content.string).to eq(
|
||||
(" " * 180).concat(
|
||||
"\r" \
|
||||
"vosk-model-small-en-us-0.4.zip: 0%|=---=---=---=---=---=---=---=---=---=---=---=---=---=---=---=---=--" \
|
||||
"-=---=---=---=---=---=---=---=---=---=---=| 0 bytes/?? [00:00<??:??:??, 0/s]\r" \
|
||||
"vosk-model-small-en-us-0.4.zip: 100%|███████████████████████████████████████████████████████████████████" \
|
||||
"████████████████████████████████████| 402 bytes/402 bytes [00:00<00:00, 0/s]\n",
|
||||
),
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
require "srt"
|
||||
require "wavefile"
|
||||
|
||||
RSpec.describe Vosk do
|
||||
let(:en_model_path) { File.join(Dir.home, ".cache/vosk/vosk-model-small-en-us-0.4") }
|
||||
let(:test_wav_path) { File.expand_path("../example/test.wav", __dir__) }
|
||||
let(:wave_reader) { WaveFile::Reader.new(test_wav_path) }
|
||||
let(:wave_chunks) do
|
||||
chunks = []
|
||||
wave_reader.each_buffer(4000) { |buffer| chunks.push(buffer.samples.pack(WaveFile::PACK_CODES.dig(:pcm, 16))) }
|
||||
chunks
|
||||
end
|
||||
|
||||
after { wave_reader.close }
|
||||
|
||||
it "has a version number" do
|
||||
expect(Vosk::VERSION).not_to be_nil
|
||||
end
|
||||
|
||||
describe "EndpointerMode" do
|
||||
it "defines the correct integer constants" do
|
||||
expect([
|
||||
Vosk::EndpointerMode::DEFAULT,
|
||||
Vosk::EndpointerMode::SHORT,
|
||||
Vosk::EndpointerMode::LONG,
|
||||
Vosk::EndpointerMode::VERY_LONG,
|
||||
]).to eq([0, 1, 2, 3])
|
||||
end
|
||||
end
|
||||
|
||||
describe Vosk::SpkModel do
|
||||
it "raises Vosk::Error on a bad path" do
|
||||
expect do
|
||||
described_class.new("/nonexistent/path")
|
||||
end.to raise_error(Vosk::Error, "Failed to create a speaker model")
|
||||
end
|
||||
end
|
||||
|
||||
describe Vosk::KaldiRecognizer do
|
||||
let(:model) { Vosk::Model.new(model_path: en_model_path) }
|
||||
let(:wave_sample_rate) { wave_reader.format.sample_rate }
|
||||
|
||||
describe "initialization" do
|
||||
it "accepts (model, sample_rate)" do
|
||||
expect(described_class.new(model, wave_sample_rate)).to be_a(described_class)
|
||||
end
|
||||
|
||||
it "accepts (model, sample_rate, grammar_string)" do
|
||||
rec = described_class.new(model, wave_sample_rate, '["one two three", "[unk]"]')
|
||||
expect(rec).to be_a(described_class)
|
||||
end
|
||||
|
||||
it "raises TypeError for an unknown third argument type" do
|
||||
expect { described_class.new(model, wave_sample_rate, 42) }.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when processing audio" do
|
||||
subject(:rec) { described_class.new(model, wave_sample_rate) }
|
||||
|
||||
let(:wave_stream) do
|
||||
stream = wave_chunks.each_with_object(StringIO.new) { |chunk, stream| stream.write(chunk) }
|
||||
stream.rewind
|
||||
stream
|
||||
end
|
||||
|
||||
let(:expected_srt) do
|
||||
<<~SRT
|
||||
1
|
||||
00:00:00,870 --> 00:00:02,610
|
||||
what zero zero zero one
|
||||
|
||||
2
|
||||
00:00:03,930 --> 00:00:04,950
|
||||
no no to uno
|
||||
|
||||
3
|
||||
00:00:06,240 --> 00:00:08,010
|
||||
cyril one eight zero three
|
||||
SRT
|
||||
end
|
||||
|
||||
it "accept_waveform returns 0 or 1" do
|
||||
results = wave_chunks.map { |chunk| rec.accept_waveform(chunk) }.uniq
|
||||
expect(results).to contain_exactly(0, 1)
|
||||
end
|
||||
|
||||
it "result returns valid JSON" do
|
||||
rec.accept_waveform(wave_chunks.first)
|
||||
expect { JSON.parse(rec.result) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "partial_result returns valid JSON" do
|
||||
rec.accept_waveform(wave_chunks.first)
|
||||
expect { JSON.parse(rec.partial_result) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "final_result returns valid JSON" do
|
||||
expect { JSON.parse(rec.final_result) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "transcribes the test file to non-empty text" do
|
||||
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
||||
text = JSON.parse(rec.final_result)["text"]
|
||||
expect(text).not_to be_empty
|
||||
end
|
||||
|
||||
it "reset clears the partial result" do
|
||||
rec.accept_waveform(wave_chunks.first)
|
||||
rec.reset
|
||||
expect(JSON.parse(rec.partial_result)["partial"]).to be_nil.or(eq(""))
|
||||
end
|
||||
|
||||
it "set_words does not raise" do
|
||||
expect { rec.words = true }.not_to raise_error
|
||||
end
|
||||
|
||||
it "set_words includes per-word timing in results" do
|
||||
rec.words = true
|
||||
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
||||
result = JSON.parse(rec.final_result)
|
||||
expect(result).to have_key("text")
|
||||
expect(result["result"]).to be_an(Array)
|
||||
end
|
||||
|
||||
it "set_partial_words does not raise" do
|
||||
expect { rec.partial_words = true }.not_to raise_error
|
||||
end
|
||||
|
||||
it "set_max_alternatives produces an alternatives array" do
|
||||
rec.max_alternatives = 5
|
||||
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
||||
result = JSON.parse(rec.final_result)
|
||||
expect(result).to have_key("alternatives")
|
||||
expect(result["alternatives"]).to be_an(Array)
|
||||
end
|
||||
|
||||
it "set_endpointer_mode accepts EndpointerMode constants",
|
||||
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.46") && "requires libvosk >= 0.3.46" do
|
||||
expect { rec.endpointer_mode = :short }.not_to raise_error
|
||||
end
|
||||
|
||||
it "set_endpointer_delays accepts float values",
|
||||
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.46") && "requires libvosk >= 0.3.46" do
|
||||
expect { rec.set_endpointer_delays(0.5, 1.0, 30.0) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "set_grammar changes the active grammar" do
|
||||
expect { rec.grammar = '["one two three", "[unk]"]' }.not_to raise_error
|
||||
end
|
||||
|
||||
it "srt_result produces a valid SRT" do
|
||||
rec.words = true
|
||||
expect(rec.srt_result(wave_stream)).to eq(expected_srt)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a grammar recognizer" do
|
||||
subject(:rec) do
|
||||
described_class.new(
|
||||
model, wave_sample_rate,
|
||||
'["one two three four five six seven eight nine zero", "[unk]"]',
|
||||
)
|
||||
end
|
||||
|
||||
it "produces a result constrained to the grammar vocabulary" do
|
||||
wave_chunks.each { |chunk| rec.accept_waveform(chunk) }
|
||||
expect(JSON.parse(rec.final_result)).to have_key("text")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Vosk::Processor,
|
||||
skip: Gem::Version.new(Vosk::VERSION) < Gem::Version.new("0.3.48") && "requires libvosk >= 0.3.48" do
|
||||
it "raises Vosk::Error on a bad lang/type" do
|
||||
expect { described_class.new("xx_invalid", "itn") }.to raise_error(Vosk::Error, "Failed to create processor")
|
||||
end
|
||||
end
|
||||
end
|
||||
+40
-11
@@ -1,12 +1,41 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "vosk"
|
||||
s.version = "0.3.50"
|
||||
s.summary = "Offline speech recognition API"
|
||||
s.description = "Vosk is an offline open source speech recognition toolkit. It enables speech recognition for 20+ languages and dialects - English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, Esperanto, Hindi, Czech, Polish. More to come."
|
||||
s.authors = ["Alpha Cephei Inc"]
|
||||
s.email = "contact@alphacphei.com"
|
||||
s.files = ["lib/vosk.rb"]
|
||||
s.homepage =
|
||||
"https://rubygems.org/gems/vosk"
|
||||
s.license = "Apache 2.0"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "lib/vosk/version"
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "vosk"
|
||||
spec.version = Vosk::VERSION
|
||||
spec.authors = ["Alpha Cephei Inc", "Vladimir Ulianitsky"]
|
||||
spec.email = ["contact@alphacphei.com", "uvlad7@gmail.com"]
|
||||
|
||||
spec.summary = "Offline speech recognition API"
|
||||
spec.description =
|
||||
"Vosk is an offline open source speech recognition toolkit. " \
|
||||
"It enables speech recognition for 20+ languages and dialects - " \
|
||||
"English, Indian English, German, French, Spanish, Portuguese, Chinese, Russian, Turkish, Vietnamese, " \
|
||||
"Italian, Dutch, Catalan, Arabic, Greek, Farsi, Filipino, Ukrainian, Kazakh, Swedish, Japanese, " \
|
||||
"Esperanto, Hindi, Czech, Polish. More to come."
|
||||
spec.homepage = "https://alphacephei.com/vosk"
|
||||
spec.license = "Apache-2.0"
|
||||
spec.required_ruby_version = ">= 2.5"
|
||||
spec.metadata["homepage_uri"] = spec.homepage
|
||||
spec.metadata["source_code_uri"] = "https://github.com/alphacep/vosk-api"
|
||||
spec.files = [
|
||||
*Dir["lib/**/*.rb"], *Dir["exe/**/*"], *Dir["sig/**/*"],
|
||||
]
|
||||
spec.bindir = "exe"
|
||||
spec.executables = ["vosk-transcriber"]
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_dependency "bytesize", "~> 0.1"
|
||||
spec.add_dependency "ffi", "~> 1.6"
|
||||
spec.add_dependency "fileutils", "~> 1.7"
|
||||
spec.add_dependency "httparty", "~> 0.21"
|
||||
spec.add_dependency "progressbar", "~> 1.13"
|
||||
spec.add_dependency "rubyzip", "~> 2.4"
|
||||
spec.add_dependency "srt", "~> 0.1.5"
|
||||
|
||||
# For more information and examples about making a new gem, check out our
|
||||
# guide at: https://bundler.io/guides/creating_gem.html
|
||||
spec.metadata["rubygems_mfa_required"] = "true"
|
||||
end
|
||||
|
||||
+17
-17
@@ -46,9 +46,6 @@ void LanguageModelEstimator::AddCounts(const std::vector<int32> &sentence) {
|
||||
void LanguageModelEstimator::IncrementCount(const std::vector<int32> &history,
|
||||
int32 next_phone) {
|
||||
int32 lm_state_index = FindOrCreateLmStateIndexForHistory(history);
|
||||
if (lm_states_[lm_state_index].tot_count == 0) {
|
||||
num_active_lm_states_++;
|
||||
}
|
||||
lm_states_[lm_state_index].AddCount(next_phone, 1);
|
||||
}
|
||||
|
||||
@@ -106,6 +103,7 @@ int32 LanguageModelEstimator::FindOrCreateLmStateIndexForHistory(
|
||||
int32 backoff_lm_state = FindOrCreateLmStateIndexForHistory(backoff_hist);
|
||||
lm_states_[ans].backoff_lmstate_index = backoff_lm_state;
|
||||
}
|
||||
num_active_lm_states_++;
|
||||
return ans;
|
||||
}
|
||||
|
||||
@@ -156,12 +154,13 @@ int32 LanguageModelEstimator::FindInitialFstState() const {
|
||||
|
||||
void LanguageModelEstimator::OutputToFst(
|
||||
int32 num_states,
|
||||
fst::StdVectorFst *fst) const {
|
||||
fst::StdVectorFst *out_fst) const {
|
||||
KALDI_ASSERT(num_states == num_active_lm_states_);
|
||||
fst->DeleteStates();
|
||||
fst::StdVectorFst fst;
|
||||
|
||||
for (int32 i = 0; i < num_states; i++)
|
||||
fst->AddState();
|
||||
fst->SetStart(FindInitialFstState());
|
||||
fst.AddState();
|
||||
fst.SetStart(FindInitialFstState());
|
||||
|
||||
int64 tot_count = 0;
|
||||
double tot_logprob = 0.0;
|
||||
@@ -184,28 +183,29 @@ void LanguageModelEstimator::OutputToFst(
|
||||
tot_count += count;
|
||||
tot_logprob += logprob * count;
|
||||
if (phone == 0) { // Go to final state
|
||||
fst->SetFinal(lm_state.fst_state, fst::TropicalWeight(-logprob));
|
||||
fst.SetFinal(lm_state.fst_state, fst::TropicalWeight(-logprob));
|
||||
} else { // It becomes a transition.
|
||||
std::vector<int32> next_history(lm_state.history);
|
||||
next_history.push_back(phone);
|
||||
int32 dest_lm_state = FindNonzeroLmStateIndexForHistory(next_history),
|
||||
dest_fst_state = lm_states_[dest_lm_state].fst_state;
|
||||
KALDI_ASSERT(dest_fst_state != -1);
|
||||
fst->AddArc(lm_state.fst_state,
|
||||
fst.AddArc(lm_state.fst_state,
|
||||
fst::StdArc(phone, phone, fst::TropicalWeight(-logprob),
|
||||
dest_fst_state));
|
||||
}
|
||||
}
|
||||
if (lm_state.backoff_lmstate_index >= 0) {
|
||||
fst->AddArc(lm_state.fst_state, fst::StdArc(0, 0, fst::TropicalWeight(-log(1 - opts_.discount)), lm_states_[lm_state.backoff_lmstate_index].fst_state));
|
||||
fst.AddArc(lm_state.fst_state, fst::StdArc(0, 0, fst::TropicalWeight(-log(1 - opts_.discount)), lm_states_[lm_state.backoff_lmstate_index].fst_state));
|
||||
}
|
||||
}
|
||||
fst::Connect(fst);
|
||||
// Make sure that Connect does not delete any states.
|
||||
int32 num_states_connected = fst->NumStates();
|
||||
KALDI_ASSERT(num_states_connected == num_states);
|
||||
fst::DeterminizeOptions<fst::StdArc> opts;
|
||||
fst::Determinize(fst, out_fst, opts);
|
||||
fst::Connect(out_fst);
|
||||
// arc-sort. ilabel or olabel doesn't matter, it's an acceptor.
|
||||
fst::ArcSort(fst, fst::ILabelCompare<fst::StdArc>());
|
||||
KALDI_LOG << "Created language model with " << num_states
|
||||
<< " states and " << fst::NumArcs(*fst) << " arcs.";
|
||||
fst::ArcSort(out_fst, fst::ILabelCompare<fst::StdArc>());
|
||||
KALDI_LOG << "Created language model with " << out_fst->NumStates()
|
||||
<< " states and " << fst::NumArcs(*out_fst) << " arcs.";
|
||||
KALDI_LOG << "Originally language model with " << fst.NumStates()
|
||||
<< " states and " << fst::NumArcs(fst) << " arcs.";
|
||||
}
|
||||
|
||||
+16
-3
@@ -18,6 +18,8 @@
|
||||
#include "lat/sausages.h"
|
||||
#include "language_model.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace fst;
|
||||
using namespace kaldi::nnet3;
|
||||
|
||||
@@ -355,6 +357,7 @@ void Recognizer::UpdateGrammarFst(char const *grammar)
|
||||
}
|
||||
estimator.AddCounts(sentence);
|
||||
}
|
||||
delete g_fst_;
|
||||
g_fst_ = new StdVectorFst();
|
||||
estimator.Estimate(g_fst_);
|
||||
|
||||
@@ -461,13 +464,12 @@ bool Recognizer::GetSpkVector(Vector<BaseFloat> &out_xvector, int *num_spk_frame
|
||||
int num_frames = spk_feature_->NumFramesReady() - frame_offset_ * 3;
|
||||
Matrix<BaseFloat> mfcc(num_frames, spk_feature_->Dim());
|
||||
|
||||
// Not very efficient, would be nice to have faster search
|
||||
std::unordered_set<int32> nonsilence_set(nonsilence_frames.begin(), nonsilence_frames.end());
|
||||
int num_nonsilence_frames = 0;
|
||||
Vector<BaseFloat> feat(spk_feature_->Dim());
|
||||
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
if (std::find(nonsilence_frames.begin(),
|
||||
nonsilence_frames.end(), i / 3) == nonsilence_frames.end()) {
|
||||
if (nonsilence_set.find(i / 3) == nonsilence_set.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -698,6 +700,17 @@ const char *Recognizer::NbestResult(CompactLattice &clat)
|
||||
obj["alternatives"].append(entry);
|
||||
}
|
||||
|
||||
if (spk_model_) {
|
||||
Vector<BaseFloat> xvector;
|
||||
int num_spk_frames;
|
||||
if (GetSpkVector(xvector, &num_spk_frames)) {
|
||||
for (int i = 0; i < xvector.Dim(); i++) {
|
||||
obj["spk"].append(xvector(i));
|
||||
}
|
||||
obj["spk_frames"] = num_spk_frames;
|
||||
}
|
||||
}
|
||||
|
||||
return StoreReturn(obj.dump());
|
||||
}
|
||||
|
||||
|
||||
+119
-2
@@ -1,3 +1,120 @@
|
||||
A proper simple setup to train a Vosk model
|
||||
# Vosk API Training
|
||||
|
||||
More documentation later
|
||||
This directory contains scripts and tools for training speech recognition models using the Kaldi toolkit.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Directory Structure](#directory-structure)
|
||||
3. [Installation](#installation)
|
||||
4. [Training Process](#training-process)
|
||||
- [Data Preparation](#data-preparation)
|
||||
- [Dictionary Preparation](#dictionary-preparation)
|
||||
- [MFCC Feature Extraction](#mfcc-feature-extraction)
|
||||
- [Acoustic Model Training](#acoustic-model-training)
|
||||
- [TDNN Chain Model Training](#tdnn-chain-model-training)
|
||||
- [Decoding](#decoding)
|
||||
5. [Results](#results)
|
||||
6. [Contributing](#contributing)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository provides tools for training custom speech recognition models using Kaldi. It supports acoustic model training, language model creation, and decoding pipelines.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── cmd.sh # Command configuration for training and decoding
|
||||
├── conf/
|
||||
│ ├── mfcc.conf # Configuration for MFCC feature extraction
|
||||
│ └── online_cmvn.conf # Online Cepstral Mean Variance Normalization (currently empty)
|
||||
├── local/
|
||||
│ ├── chain/
|
||||
│ │ ├── run_ivector_common.sh # Script for i-vector extraction during chain model training
|
||||
│ │ └── run_tdnn.sh # Script for training a TDNN model
|
||||
│ ├── data_prep.sh # Data preparation script for creating Kaldi data directories
|
||||
│ ├── download_and_untar.sh # Script for downloading and extracting datasets
|
||||
│ ├── download_lm.sh # Downloads language models
|
||||
│ ├── prepare_dict.sh # Prepares the pronunciation dictionary
|
||||
│ └── score.sh # Scoring script for evaluation
|
||||
├── path.sh # Script for setting Kaldi paths
|
||||
├── RESULTS # Script for printing the best WER results
|
||||
├── RESULTS.txt # Contains WER results from decoding
|
||||
├── run.sh # Main script for the entire training pipeline
|
||||
├── steps -> ../../wsj/s5/steps/ # Link to Kaldi’s WSJ steps for acoustic model training
|
||||
└── utils -> ../../wsj/s5/utils/ # Link to Kaldi’s utility scripts
|
||||
```
|
||||
|
||||
### Key Files:
|
||||
- **cmd.sh**: Defines commands for running training and decoding tasks.
|
||||
- **path.sh**: Sets up paths for Kaldi binaries and scripts.
|
||||
- **run.sh**: Main entry point for the training pipeline, running tasks in stages.
|
||||
- **RESULTS**: Displays Word Error Rate (WER) for the trained models.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- [Kaldi](https://github.com/kaldi-asr/kaldi): Kaldi toolkit must be installed and configured.
|
||||
- Required tools: `ffmpeg`, `sox`, `sctk` for data preparation and scoring.
|
||||
|
||||
### Steps
|
||||
1. Clone the Vosk API repository.
|
||||
2. Install Kaldi and ensure the `KALDI_ROOT` is correctly set in `path.sh`.
|
||||
3. Set environment variables using `cmd.sh` and `path.sh`.
|
||||
|
||||
## Training Process
|
||||
|
||||
### Data Preparation
|
||||
Run the data preparation stage in `run.sh`:
|
||||
```bash
|
||||
bash run.sh --stage 0 --stop_stage 0
|
||||
```
|
||||
This stage downloads and prepares the LibriSpeech dataset.
|
||||
|
||||
### Dictionary Preparation
|
||||
Prepare the pronunciation dictionary with:
|
||||
```bash
|
||||
bash run.sh --stage 1 --stop_stage 1
|
||||
```
|
||||
This step generates the necessary files for Kaldi's `prepare_lang.sh` script.
|
||||
|
||||
### MFCC Feature Extraction
|
||||
Run the MFCC extraction process:
|
||||
```bash
|
||||
bash run.sh --stage 2 --stop_stage 2
|
||||
```
|
||||
This step extracts Mel-frequency cepstral coefficients (MFCC) features and computes Cepstral Mean Variance Normalization (CMVN).
|
||||
|
||||
### Acoustic Model Training
|
||||
Train monophone, LDA+MLLT, and SAT models:
|
||||
```bash
|
||||
bash run.sh --stage 3 --stop_stage 3
|
||||
```
|
||||
This stage trains GMM-based models and aligns the data for TDNN training.
|
||||
|
||||
### TDNN Chain Model Training
|
||||
Train a Time-Delay Neural Network (TDNN) chain model:
|
||||
```bash
|
||||
bash run.sh --stage 4 --stop_stage 4
|
||||
```
|
||||
The chain model uses i-vectors for speaker adaptation.
|
||||
|
||||
### Decoding
|
||||
After training, decode the test data:
|
||||
```bash
|
||||
bash run.sh --stage 5 --stop_stage 5
|
||||
```
|
||||
This step decodes using the trained model and evaluates the Word Error Rate (WER).
|
||||
|
||||
## Results
|
||||
|
||||
WER can be evaluated by running:
|
||||
```bash
|
||||
bash RESULTS
|
||||
```
|
||||
Example of `RESULTS.txt`:
|
||||
```plaintext
|
||||
%WER 14.10 [ 2839 / 20138, 214 ins, 487 del, 2138 sub ] exp/chain/tdnn/decode_test/wer_11_0.0
|
||||
%WER 12.67 [ 2552 / 20138, 215 ins, 406 del, 1931 sub ] exp/chain/tdnn/decode_test_rescore/wer_11_0.0
|
||||
```
|
||||
@@ -44,6 +44,7 @@ RUN cd /opt \
|
||||
&& cd /opt/kaldi/src \
|
||||
&& sed -i "s:TARGET_ARCH=\"\`uname -m\`\":TARGET_ARCH=$(echo $CROSS_TRIPLE|cut -d - -f 1):g" configure \
|
||||
&& sed -i "s: -O1 : -O3 :g" makefiles/linux_openblas_arm.mk \
|
||||
&& sed -i "s: -O1 : -O3 :g" makefiles/linux_openblas_arm.mk \
|
||||
&& ./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no \
|
||||
&& make -j 10 online2 rnnlm \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
ARG DOCKCROSS_IMAGE=alphacep/dockcross-linux-armv7
|
||||
FROM ${DOCKCROSS_IMAGE}
|
||||
|
||||
LABEL description="A docker image for building portable Python linux binary wheels and Kaldi on other architectures"
|
||||
LABEL maintainer="contact@alphacephei.com"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
wget \
|
||||
libffi-dev \
|
||||
libpcre3-dev \
|
||||
zlib1g-dev \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
cmake \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
python3-setuptools \
|
||||
python3-cffi \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG OPENBLAS_ARGS=
|
||||
RUN cd /opt \
|
||||
&& git clone -b vosk --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/tools \
|
||||
&& git clone -b v0.3.20 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& git clone -b v3.2.1 --single-branch https://github.com/alphacep/clapack \
|
||||
&& echo ${OPENBLAS_ARGS} \
|
||||
&& make -C OpenBLAS ONLY_CBLAS=1 ${OPENBLAS_ARGS} HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 all \
|
||||
&& make -C OpenBLAS ${OPENBLAS_ARGS} HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 PREFIX=$(pwd)/OpenBLAS/install install \
|
||||
&& mkdir -p clapack/BUILD && cd clapack/BUILD && cmake .. \
|
||||
&& make -j 10 -C F2CLIBS \
|
||||
&& make -j 10 -C BLAS \
|
||||
&& make -j 10 -C SRC \
|
||||
&& find . -name "*.a" | xargs cp -t ../../OpenBLAS/install/lib \
|
||||
&& cd /opt/kaldi/tools \
|
||||
&& git clone --single-branch https://github.com/alphacep/openfst openfst \
|
||||
&& cd openfst \
|
||||
&& autoreconf -i \
|
||||
&& CFLAGS="-g -O3" ./configure --prefix=/opt/kaldi/tools/openfst --enable-static --enable-shared --enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin --host=${CROSS_TRIPLE} --build=x86-linux-gnu \
|
||||
&& make -j 10 && make install \
|
||||
&& cd /opt/kaldi/src \
|
||||
&& sed -i "s:TARGET_ARCH=\"\`uname -m\`\":TARGET_ARCH=$(echo $CROSS_TRIPLE|cut -d - -f 1):g" configure \
|
||||
&& sed -i "s: -O1 : -O3 :g" makefiles/linux_openblas_arm.mk \
|
||||
&& sed -i "s:-DHAVE_EXECINFO_H=1::g" makefiles/linux_openblas_arm.mk \
|
||||
&& ./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no \
|
||||
&& make -j 10 online2 rnnlm \
|
||||
&& find /opt/kaldi -name "*.o" -exec rm {} \;
|
||||
@@ -0,0 +1,70 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
g++ \
|
||||
bzip2 \
|
||||
unzip \
|
||||
make \
|
||||
wget \
|
||||
git \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
python3-setuptools \
|
||||
python3-cffi \
|
||||
zlib1g-dev \
|
||||
patch \
|
||||
cmake \
|
||||
xz-utils \
|
||||
automake \
|
||||
autoconf \
|
||||
libtool \
|
||||
pkg-config \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN cd /opt && \
|
||||
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20240820/llvm-mingw-20240820-msvcrt-ubuntu-20.04-x86_64.tar.xz \
|
||||
&& tar xf llvm-mingw-20240820-msvcrt-ubuntu-20.04-x86_64.tar.xz \
|
||||
&& mv llvm-mingw-20240820-msvcrt-ubuntu-20.04-x86_64 llvm-mingw
|
||||
|
||||
ENV PATH="$PATH:/opt/llvm-mingw/bin"
|
||||
|
||||
RUN mkdir /opt/kaldi \
|
||||
&& git clone https://github.com/alphacep/openfst \
|
||||
&& cd openfst \
|
||||
&& autoreconf -i \
|
||||
&& CXX=aarch64-w64-mingw32-g++ CXXFLAGS="-O3 -ftree-vectorize -DFST_NO_DYNAMIC_LINKING" \
|
||||
./configure --prefix=/opt/kaldi/local \
|
||||
--enable-shared --enable-static --with-pic --disable-bin \
|
||||
--enable-lookahead-fsts --enable-ngram-fsts --host=aarch64-w64-mingw32 \
|
||||
&& make -j $(nproc) \
|
||||
&& make install
|
||||
|
||||
RUN cd /opt/kaldi \
|
||||
&& git clone -b v0.3.20 --single-branch https://github.com/xianyi/OpenBLAS \
|
||||
&& cd OpenBLAS \
|
||||
&& make HOSTCC=gcc CC=aarch64-w64-mingw32-gcc ONLY_CBLAS=1 USE_LOCKING=1 USE_THREAD=0 USE_OPENMP=0 DYNAMIC_ARCH=0 TARGET=ARMV8 ARCH=arm64 BINARY=64 -j $(nproc) \
|
||||
&& make PREFIX=/opt/kaldi/local install
|
||||
|
||||
RUN cd /opt/kaldi \
|
||||
&& git clone -b v3.2.1 --single-branch https://github.com/alphacep/clapack \
|
||||
&& mkdir clapack/BUILD \
|
||||
&& cd clapack/BUILD \
|
||||
&& cmake -DCMAKE_C_COMPILER_TARGET=aarch64-w64-mingw32 -DCMAKE_C_COMPILER=aarch64-w64-mingw32-gcc -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_CROSSCOMPILING=True .. \
|
||||
&& make -C F2CLIBS/libf2c \
|
||||
&& make -C BLAS \
|
||||
&& make -C SRC \
|
||||
&& find . -name *.a -exec cp {} /opt/kaldi/local/lib \;
|
||||
|
||||
RUN cd /opt/kaldi \
|
||||
&& git clone -b vosk-android --single-branch https://github.com/alphacep/kaldi \
|
||||
&& cd kaldi/src \
|
||||
&& CXX=aarch64-w64-mingw32-g++ CXXFLAGS="-O3 -ftree-vectorize -DFST_NO_DYNAMIC_LINKING" ./configure --shared --mingw=yes --use-cuda=no \
|
||||
--mathlib=OPENBLAS_CLAPACK \
|
||||
--host=aarch64-w64-mingw32 --openblas-clapack-root=/opt/kaldi/local \
|
||||
--fst-root=/opt/kaldi/local --fst-version=1.8.0 \
|
||||
&& make depend -j \
|
||||
&& make LLVM_BUILD=1 -j $(nproc) online2 rnnlm
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
docker build --build-arg="DOCKCROSS_IMAGE=dockcross/linux-armv7l-musl" --build-arg="OPENBLAS_ARGS=TARGET=ARMV7" --file Dockerfile.dockcross-musl --tag alphacep/kaldi-dockcross-armv7-musl:latest .
|
||||
docker run --rm -v /home/shmyrev/travis/vosk-api/:/io alphacep/kaldi-dockcross-armv7-musl /io/travis/build-wheels-dockcross.sh
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
docker build --file Dockerfile.winaarch64 --tag alphacep/kaldi-winaarch64:latest .
|
||||
docker run --rm -v `realpath ..`:/io alphacep/kaldi-winaarch64 /io/travis/build-wheels-winaarch64.sh
|
||||
@@ -14,6 +14,11 @@ case $CROSS_TRIPLE in
|
||||
export VOSK_MACHINE=armv7l
|
||||
export VOSK_ARCHITECTURE=32bit
|
||||
;;
|
||||
*armv7l-linux-musleabihf*)
|
||||
export VOSK_MACHINE=armv7l
|
||||
export VOSK_ARCHITECTURE=32bit
|
||||
export VOSK_VARIANT="-musl"
|
||||
;;
|
||||
*i686-*)
|
||||
export VOSK_MACHINE=x86
|
||||
export VOSK_ARCHITECTURE=32bit
|
||||
@@ -29,9 +34,9 @@ case $CROSS_TRIPLE in
|
||||
esac
|
||||
|
||||
# Copy library to output folder
|
||||
mkdir -p /io/wheelhouse/vosk-linux-$VOSK_MACHINE
|
||||
cp /opt/vosk-api/src/*.so /opt/vosk-api/src/vosk_api.h /io/wheelhouse/vosk-linux-$VOSK_MACHINE
|
||||
mkdir -p /io/wheelhouse/vosk-linux-${VOSK_MACHINE}${VOSK_VARIANT}
|
||||
cp /opt/vosk-api/src/*.so /opt/vosk-api/src/vosk_api.h /io/wheelhouse/vosk-linux-$VOSK_MACHINE${VOSK_VARIANT}
|
||||
|
||||
# Build wheel
|
||||
python3 -m pip install requests tqdm srt websockets wheel
|
||||
python3 -m pip install requests tqdm srt websockets wheel --break-system-packages
|
||||
python3 -m pip wheel /opt/vosk-api/python --no-deps -w /io/wheelhouse
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
|
||||
# Build libvosk
|
||||
cd /opt
|
||||
git clone https://github.com/alphacep/vosk-api
|
||||
cd vosk-api/src
|
||||
EXTRA_LDFLAGS=-Wl,--out-implib,libvosk.lib CXX=aarch64-w64-mingw32-g++ EXT=dll KALDI_ROOT=/opt/kaldi/kaldi OPENFST_ROOT=/opt/kaldi/local OPENBLAS_ROOT=/opt/kaldi/local make -j $(nproc)
|
||||
|
||||
# Copy dlls to output folder
|
||||
mkdir -p /io/wheelhouse/vosk-winaarch64
|
||||
cp /opt/vosk-api/src/*.{dll,lib} /opt/vosk-api/src/vosk_api.h /io/wheelhouse/vosk-winaarch64
|
||||
|
||||
# Build wheel and put to the output folder
|
||||
export VOSK_SOURCE=/opt/vosk-api
|
||||
export VOSK_SYSTEM=Windows
|
||||
export VOSK_ARCHITECTURE=64bit
|
||||
python3 -m pip -v wheel /opt/vosk-api/python --no-deps -w /io/wheelhouse
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vosk-js",
|
||||
"version": "0.3.50",
|
||||
"version": "0.3.75",
|
||||
"description": "Node binding for continuous voice recoginition through vosk-api.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user