Compare commits

..

3 Commits

Author SHA1 Message Date
Nickolay Shmyrev d497c3a0a5 Move to 3-value endpointer config 2023-12-13 04:29:21 +01:00
Nickolay Shmyrev f37ee638a4 Add endpointer delays parameter 2023-11-27 15:02:55 +01:00
Nickolay Shmyrev e0b29bb17f Implement more endpointer modes 2023-11-10 01:58:31 +01:00
92 changed files with 283 additions and 2722 deletions
-1
View File
@@ -10,7 +10,6 @@ add_library(vosk
src/recognizer.cc
src/spk_model.cc
src/vosk_api.cc
src/postprocessor.cc
)
find_package(kaldi REQUIRED)
+4 -4
View File
@@ -4,13 +4,13 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.34.0'
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.24.0'
}
}
allprojects {
version = '0.3.75'
version = '0.3.47'
}
subprojects {
@@ -24,7 +24,7 @@ subprojects {
}
mavenPublishing {
publishToMavenCentral()
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01, false)
signAllPublications()
}
+7 -12
View File
@@ -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_TOOLCHAIN_PATH/bin:$PATH
PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${OS_NAME}-x86_64/bin:$PATH
OPENFST_VERSION=1.8.0
for arch in armeabi-v7a arm64-v8a x86_64 x86; do
@@ -45,7 +45,6 @@ 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
@@ -55,8 +54,6 @@ 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
@@ -66,7 +63,6 @@ case $arch in
CC=x86_64-linux-android21-clang
CXX=x86_64-linux-android21-clang++
ARCHFLAGS=""
PAGESIZE_LDFLAGS=""
;;
x86)
BLAS_ARCH=ATOM
@@ -76,7 +72,6 @@ case $arch in
CC=i686-linux-android21-clang
CXX=i686-linux-android21-clang++
ARCHFLAGS=""
PAGESIZE_LDFLAGS=""
;;
esac
@@ -84,16 +79,16 @@ mkdir -p $WORKDIR/local/lib
# openblas first
cd $WORKDIR
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
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
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_TOOLCHAIN_PATH/bin/$AR \
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 \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_CROSSCOMPILING=True ..
make -j 8 -C F2CLIBS/libf2c
@@ -123,7 +118,7 @@ CXX=$CXX AR=$AR RANLIB=$RANLIB CXXFLAGS="$ARCHFLAGS -O3 -DFST_NO_DYNAMIC_LINKING
--fst-root=${WORKDIR}/local --fst-version=${OPENFST_VERSION}
make -j 8 depend
cd $WORKDIR/kaldi/src
make -j 8 online2 rnnlm
make -j 8 online2 lm rnnlm
# Vosk-api
cd $WORKDIR
@@ -134,7 +129,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 ${PAGESIZE_LDFLAGS}"
EXTRA_LDFLAGS="-llog -static-libstdc++ -Wl,-soname,libvosk.so"
cp $WORKDIR/vosk/libvosk.so $WORKDIR/../../src/main/jniLibs/$arch/libvosk.so
done
+4 -4
View File
@@ -4,14 +4,14 @@ def pomDescription = "Vosk speech recognition library for Android"
android {
namespace 'org.vosk'
compileSdkVersion 36
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 36
targetSdkVersion 33
versionCode 10
versionName = version
archivesBaseName = archiveName
ndkVersion = "28.2.13676358"
ndkVersion = "25.2.9519653"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -25,7 +25,7 @@ task buildVosk(type: Exec) {
}
dependencies {
api 'net.java.dev.jna:jna:5.18.1@aar'
api 'net.java.dev.jna:jna:5.13.0@aar'
}
//preBuild.dependsOn buildVosk
@@ -56,18 +56,8 @@ 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,34 +236,6 @@ 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.
@@ -1,21 +0,0 @@
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,51 +66,6 @@ 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.
@@ -182,19 +137,6 @@ 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
*/
+2 -2
View File
@@ -4,10 +4,10 @@ def pomDescription = "Small English model for Android"
android {
namespace "org.vosk"
compileSdkVersion 36
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 36
targetSdkVersion 33
versionCode 10
versionName = version
archivesBaseName = archiveName
-3
View File
@@ -28,9 +28,6 @@ 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 -2
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>VoskDemo</RootNamespace>
</PropertyGroup>
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vosk" Version="0.3.75" />
<PackageReference Include="Vosk" Version="0.3.45" />
</ItemGroup>
</Project>
-17
View File
@@ -1,17 +0,0 @@
<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>
+3 -3
View File
@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Vosk</id>
<version>0.3.75</version>
<version>0.3.45</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="net8.0"/>
<group targetFramework=".NETStandard2.0"/>
</dependencies>
</metadata>
<files>
<file src="**" exclude="bin/**;obj/**;build.sh;src/*.cs;*.nupkg;**/.keep-me" />
<file src="**" exclude="src/*.cs;build.sh;**/.keep-me;*.nupkg" />
</files>
</package>
+2 -2
View File
@@ -1,2 +1,2 @@
rm -rf bin lib obj
/home/shmyrev/local/dotnet/dotnet pack Vosk.csproj -p:NuspecFile=Vosk.nuspec -o .
mcs -out:lib/netstandard2.0/Vosk.dll -target:library src/*.cs
nuget pack
+2 -7
View File
@@ -65,12 +65,6 @@ 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);
@@ -113,6 +107,7 @@ 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);
}
}
}
-15
View File
@@ -1,12 +1,5 @@
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;
@@ -98,14 +91,6 @@ 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
View File
@@ -1,99 +0,0 @@
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)
}
-5
View File
@@ -1,5 +0,0 @@
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.
-54
View File
@@ -1,54 +0,0 @@
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()
}
-2
View File
@@ -16,8 +16,6 @@ func main() {
flag.StringVar(&filename, "f", "", "file to transcribe")
flag.Parse()
vosk.GPUInit()
model, err := vosk.NewModel("model")
if err != nil {
log.Fatal(err)
+1 -12
View File
@@ -1,8 +1,7 @@
package vosk
// #cgo CPPFLAGS: -I ${SRCDIR}/../src
// #cgo !windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
// #cgo windows LDFLAGS: -L ${SRCDIR}/../src -lvosk -lpthread
// #cgo LDFLAGS: -L ${SRCDIR}/../src -lvosk -ldl -lpthread
// #include <stdlib.h>
// #include <vosk_api.h>
import "C"
@@ -125,16 +124,6 @@ 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)
+1 -1
View File
@@ -11,5 +11,5 @@ repositories {
}
dependencies {
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.75'
implementation group: 'com.alphacephei', name: 'vosk', version: '0.3.45'
}
+2 -2
View File
@@ -16,7 +16,7 @@ repositories {
archivesBaseName = 'vosk'
group = 'com.alphacephei'
version = '0.3.75'
version = '0.3.45'
mavenPublish {
group = 'com.alphacephei'
@@ -25,7 +25,7 @@ mavenPublish {
}
dependencies {
api group: 'net.java.dev.jna', name: 'jna', version: '5.18.1'
api group: 'net.java.dev.jna', name: 'jna', version: '5.13.0'
testImplementation 'junit:junit:4.13'
}
@@ -82,18 +82,8 @@ 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,34 +236,6 @@ 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,10 +15,8 @@ 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 {
@@ -97,24 +95,9 @@ 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"));
}
}
+16 -30
View File
@@ -1,6 +1,5 @@
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.config.JvmTarget
/*
* Copyright 2020 Alpha Cephei Inc. & Doomsdayrs
@@ -19,15 +18,15 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
*/
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("multiplatform") version "1.8.10"
id("com.android.library")
`maven-publish`
id("org.jetbrains.dokka") version "1.9.20"
kotlin("plugin.serialization") version "2.0.0"
id("org.jetbrains.dokka") version "1.7.20"
kotlin("plugin.serialization") version "1.8.10"
}
group = "com.alphacephei"
version = "0.3.75"
version = "0.4.0-alpha0"
repositories {
google()
@@ -68,11 +67,9 @@ fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.native(
kotlin {
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
compilations.all {
kotlinOptions.jvmTarget = JvmTarget.JVM_11.description
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
environment("MODEL", "VOSK_MODEL")
@@ -83,7 +80,7 @@ kotlin {
}
}
androidTarget {
android {
publishAllLibraryVariants()
}
@@ -105,16 +102,6 @@ kotlin {
}
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate {
withJvm()
withAndroidTarget()
if (enableNative)
withNative()
}
publishing {
publications {
withType<MavenPublication> {
@@ -143,13 +130,13 @@ kotlin {
}
}
val jna_version = "5.14.0"
val coroutines_version = "1.7.3"
val jna_version = "5.13.0"
val coroutines_version = "1.6.4"
sourceSets {
val commonMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
}
}
@@ -174,7 +161,7 @@ kotlin {
api("net.java.dev.jna:jna:$jna_version@aar")
}
}
val androidUnitTest by getting {
val androidTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
}
@@ -183,16 +170,15 @@ kotlin {
}
android {
namespace = "com.alphacephei.library"
compileSdk = 34
compileSdk = 33
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 24
targetSdk = 34
targetSdk = 33
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
multipleVariants {
+1 -1
View File
@@ -23,7 +23,7 @@ pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android") {
useModule("com.android.tools.build:gradle:8.3.0")
useModule("com.android.tools.build:gradle:7.3.0")
}
}
}
@@ -1,27 +0,0 @@
/*
* 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,7 +151,6 @@ expect class Recognizer : Freeable {
* }],
* </pre>
*
* C equivalent = vosk_recognizer_set_words
* @param words - boolean value
*/
fun setOutputWordTimes(words: Boolean)
@@ -169,23 +168,6 @@ 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
*
@@ -1,32 +0,0 @@
/*
* 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?) : Exception
expect open class IOException(message: String? = null) : Exception
+8 -24
View File
@@ -42,13 +42,14 @@ internal object LibVosk {
@Throws(IOException::class)
private fun unpackDll(targetDir: File, lib: String) {
Vosk::class.java.getResourceAsStream("/win32-x86-64/$lib.dll")!!.use {
Files.copy(
it,
File(targetDir, "$lib.dll").toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
val source: InputStream =
Vosk::class.java.getResourceAsStream("/win32-x86-64/$lib.dll")!!
Files.copy(
source,
File(targetDir, "$lib.dll").toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
init {
@@ -56,7 +57,6 @@ internal object LibVosk {
Platform.isAndroid() -> {
Native.register(LibVosk::class.java, "vosk")
}
Platform.isWindows() -> {
// We have to unpack dependencies
try {
@@ -79,7 +79,6 @@ internal object LibVosk {
Native.register(LibVosk::class.java, "libvosk");
}
}
else -> {
Native.register(LibVosk::class.java, "vosk");
}
@@ -195,19 +194,4 @@ 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,27 +327,4 @@ 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)
}
}
@@ -1,51 +0,0 @@
/*
* 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/
+7 -19
View File
@@ -69,18 +69,16 @@ 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');
} else if (os.platform() == 'linux' && os.arch() == 'arm64') {
soname = path.join(__dirname, 'lib', 'linux-arm64', 'libvosk.so');
soname = path.join(__dirname, "lib", "osx-universal", "libvosk.dylib")
} 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, {
@@ -130,9 +128,6 @@ class Model {
* @type {unknown}
*/
this.handle = libvosk.vosk_model_new(modelPath);
if (!this.handle) {
throw new Error('Failed to create a model.');
}
}
/**
@@ -166,9 +161,6 @@ class SpeakerModel {
* @type {unknown}
*/
this.handle = libvosk.vosk_spk_model_new(modelPath);
if (!this.handle) {
throw new Error('Failed to create a speaker model.');
}
}
/**
@@ -243,10 +235,6 @@ 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
View File
@@ -1,6 +1,6 @@
{
"name": "vosk",
"version": "0.3.75",
"version": "0.3.45",
"description": "Node binding for continuous offline voice recoginition with Vosk library.",
"repository": {
"type": "git",
+1 -1
View File
@@ -41,7 +41,7 @@ if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE
print("Audio file must be WAV format mono PCM.")
sys.exit(1)
rec.SetEndpointerDelays(0.5, 0.3, 10.0)
rec.SetEndpointerDelays(300, 500, 2000)
while True:
data = wf.readframes(4000)
+40 -39
View File
@@ -1,39 +1,40 @@
#!/usr/bin/env python3
import json
import gradio as gr
from vosk import KaldiRecognizer, Model
model = Model(lang="en-us")
def transcribe(stream, new_chunk):
sample_rate, audio_data = new_chunk
audio_data = audio_data.tobytes()
if stream is None:
rec = KaldiRecognizer(model, sample_rate)
result = []
else:
rec, result = stream
if rec.AcceptWaveform(audio_data):
text_result = json.loads(rec.Result())["text"]
if text_result != "":
result.append(text_result)
partial_result = ""
else:
partial_result = json.loads(rec.PartialResult())["partial"] + " "
return (rec, result), "\n".join(result) + "\n" + partial_result
gr.Interface(
fn=transcribe,
inputs=[
"state", gr.Audio(sources=["microphone"], type="numpy", streaming=True),
],
outputs=[
"state", "text",
],
live=True).launch(share=True)
#!/usr/bin/env python3
import json
import gradio as gr
from vosk import KaldiRecognizer, Model
model = Model(lang="en-us")
def transcribe(data, state):
sample_rate, audio_data = data
audio_data = (audio_data >> 16).astype("int16").tobytes()
if state is None:
rec = KaldiRecognizer(model, sample_rate)
result = []
else:
rec, result = state
if rec.AcceptWaveform(audio_data):
text_result = json.loads(rec.Result())["text"]
if text_result != "":
result.append(text_result)
partial_result = ""
else:
partial_result = json.loads(rec.PartialResult())["partial"] + " "
return "\n".join(result) + "\n" + partial_result, (rec, result)
gr.Interface(
fn=transcribe,
inputs=[
gr.Audio(source="microphone", type="numpy", streaming=True),
"state"
],
outputs=[
"textbox",
"state"
],
live=True).launch(share=True)
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env python3
import wave
import sys
from vosk import Processor
proc = Processor("ru_itn_tagger.fst", "ru_itn_verbalizer.fst")
print (proc.process("у нас десять яблок"))
print (proc.process("у нас десять яблок и десять миллилитров воды точка"))
print (proc.process("мы пришли в восемь часов пять минут"))
+1 -1
View File
@@ -45,7 +45,7 @@ with open("README.md", "rb") as fh:
setuptools.setup(
name="vosk",
version="0.3.75",
version="0.3.46",
author="Alpha Cephei Inc",
author_email="contact@alphacephei.com",
description="Offline open source speech recognition API based on Kaldi and Vosk",
+1 -1
View File
@@ -28,7 +28,7 @@ def recognize(line):
def main():
p = Pool(8)
texts = p.map(recognize, open(sys.argv[1], encoding="utf-8").readlines())
texts = p.map(recognize, open(sys.argv[1], encoding="uft-8").readlines())
print ("\n".join(texts))
main()
+11 -19
View File
@@ -144,9 +144,15 @@ class SpkModel:
class EndpointerMode(enum.Enum):
DEFAULT = 0
SHORT = 1
LONG = 2
VERY_LONG = 3
SINGLE_WORD = 1
VERY_SHORT = 2
SHORT = 3
STANDARD = 4
STANDARD_5 = 5
STANDARD_180 = 6
LONG = 7
VERY_LONG = 8
VERY_LONG_180 = 9
class KaldiRecognizer:
@@ -183,8 +189,8 @@ class KaldiRecognizer:
def SetEndpointerMode(self, mode):
_c.vosk_recognizer_set_endpointer_mode(self._handle, mode.value)
def SetEndpointerDelays(self, t_start_max, t_end, t_max):
_c.vosk_recognizer_set_endpointer_delays(self._handle, t_start_max, t_end, t_max)
def SetEndpointerDelays(self, sct, nit, t):
_c.vosk_recognizer_set_endpointer_delays(self._handle, sct, nit, t)
def SetSpkModel(self, spk_model):
_c.vosk_recognizer_set_spk_model(self._handle, spk_model._handle)
@@ -287,17 +293,3 @@ class BatchRecognizer:
def GetPendingChunks(self):
return _c.vosk_batch_recognizer_get_pending_chunks(self._handle)
class Processor:
def __init__(self, *args):
self._handle = _c.vosk_text_processor_new(args[0].encode('utf-8'), args[1].encode('utf-8'))
if self._handle == _ffi.NULL:
raise Exception("Failed to create processor")
def __del__(self):
_c.vosk_text_processor_free(self._handle)
def process(self, text):
return _ffi.string(_c.vosk_text_processor_itn(self._handle, text.encode('utf-8'))).decode('utf-8')
+2 -2
View File
@@ -15,8 +15,8 @@ parser.add_argument(
"--model", "-m", type=str,
help="model path")
parser.add_argument(
"--server", "-s", type=str,
help="use server for recognition. For example ws://localhost:2700")
"--server", "-s", const="ws://localhost:2700", action="store_const",
help="use server for recognition")
parser.add_argument(
"--list-models", default=False, action="store_true",
help="list available models")
+1 -1
View File
@@ -99,7 +99,7 @@ class Transcriber:
monologues = {"schemaVersion":"2.0", "monologues":[], "text":[]}
for part in result:
if part["text"] != "":
monologues["text"] += [part["text"]]
monologues["text"] += part["text"]
for _, res in enumerate(result):
if not "result" in res:
continue
-12
View File
@@ -1,12 +0,0 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
# rspec failure tracking
.rspec_status
.idea
-3
View File
@@ -1,3 +0,0 @@
--format documentation
--color
--require spec_helper
-53
View File
@@ -1,53 +0,0 @@
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
-18
View File
@@ -1,18 +0,0 @@
# 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"
-109
View File
@@ -1,109 +0,0 @@
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
View File
@@ -1,138 +0,0 @@
# 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
-50
View File
@@ -1,50 +0,0 @@
# 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]
-11
View File
@@ -1,11 +0,0 @@
#!/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__)
-8
View File
@@ -1,8 +0,0 @@
#!/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
-1
View File
@@ -1 +0,0 @@
../../python/example/test.wav
-37
View File
@@ -1,37 +0,0 @@
#!/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
-17
View File
@@ -1,17 +0,0 @@
#!/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
-34
View File
@@ -1,34 +0,0 @@
#!/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
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "vosk"
# TODO
+3 -343
View File
@@ -1,345 +1,5 @@
# 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
class Vosk
def self.hi
puts "Hello world!"
end
end
-180
View File
@@ -1,180 +0,0 @@
# 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
-58
View File
@@ -1,58 +0,0 @@
# 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
-5
View File
@@ -1,5 +0,0 @@
# frozen_string_literal: true
module Vosk
VERSION = "0.3.45"
end
-4
View File
@@ -1,4 +0,0 @@
module Vosk
VERSION: String
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
end
-18
View File
@@ -1,18 +0,0 @@
# 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
-173
View File
@@ -1,173 +0,0 @@
# 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
-182
View File
@@ -1,182 +0,0 @@
# 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
+11 -40
View File
@@ -1,41 +1,12 @@
# 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"
Gem::Specification.new do |s|
s.name = "vosk"
s.version = "0.3.45"
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"
end
+4 -5
View File
@@ -23,18 +23,17 @@ VOSK_SOURCES= \
language_model.cc \
model.cc \
spk_model.cc \
vosk_api.cc \
postprocessor.cc
vosk_api.cc
VOSK_HEADERS= \
recognizer.h \
language_model.h \
model.h \
spk_model.h \
vosk_api.h \
postprocessor.h
vosk_api.h
CFLAGS=-g -O3 -std=c++17 -Wno-deprecated-declarations -fPIC -DFST_NO_DYNAMIC_LINKING -I. -I$(KALDI_ROOT)/src -I$(OPENFST_ROOT)/include $(EXTRA_CFLAGS)
CFLAGS=-g -O3 -std=c++17 -Wno-deprecated-declarations -fPIC -DFST_NO_DYNAMIC_LINKING \
-I. -I$(KALDI_ROOT)/src -I$(OPENFST_ROOT)/include $(EXTRA_CFLAGS)
LDFLAGS=
+17 -17
View File
@@ -46,6 +46,9 @@ 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);
}
@@ -103,7 +106,6 @@ 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;
}
@@ -154,13 +156,12 @@ int32 LanguageModelEstimator::FindInitialFstState() const {
void LanguageModelEstimator::OutputToFst(
int32 num_states,
fst::StdVectorFst *out_fst) const {
fst::StdVectorFst *fst) const {
KALDI_ASSERT(num_states == num_active_lm_states_);
fst::StdVectorFst fst;
fst->DeleteStates();
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;
@@ -183,29 +184,28 @@ 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::DeterminizeOptions<fst::StdArc> opts;
fst::Determinize(fst, out_fst, opts);
fst::Connect(out_fst);
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);
// arc-sort. ilabel or olabel doesn't matter, it's an acceptor.
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.";
fst::ArcSort(fst, fst::ILabelCompare<fst::StdArc>());
KALDI_LOG << "Created language model with " << num_states
<< " states and " << fst::NumArcs(*fst) << " arcs.";
}
-66
View File
@@ -1,66 +0,0 @@
// Copyright (c) 2022 Zhendong Peng (pzd17@tsinghua.org.cn)
//
// 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.
#include "postprocessor.h"
using fst::TokenType;
Processor::Processor(const std::string& tagger_path,
const std::string& verbalizer_path) {
tagger_.reset(StdVectorFst::Read(tagger_path));
verbalizer_.reset(StdVectorFst::Read(verbalizer_path));
compiler_ = std::make_shared<StringCompiler<StdArc>>(TokenType::BYTE);
printer_ = std::make_shared<StringPrinter<StdArc>>(TokenType::BYTE);
}
std::string Processor::ShortestPath(const StdVectorFst& lattice) {
StdVectorFst shortest_path;
fst::ShortestPath(lattice, &shortest_path, 1, true);
std::string output;
printer_->operator()(shortest_path, &output);
return output;
}
std::string Processor::Compose(const std::string& input,
const StdVectorFst* fst) {
StdVectorFst input_fst;
compiler_->operator()(input, &input_fst);
StdVectorFst lattice;
fst::Compose(input_fst, *fst, &lattice);
return ShortestPath(lattice);
}
std::string Processor::Tag(const std::string& input) {
if (input.empty()) {
return "";
}
return Compose(input, tagger_.get());
}
std::string Processor::Verbalize(const std::string& input) {
if (input.empty()) {
return "";
}
std::string output = Compose(input, verbalizer_.get());
output.erase(std::remove(output.begin(), output.end(), '\0'), output.end());
return output;
}
std::string Processor::Normalize(const std::string& input) {
return Verbalize(Tag(input));
}
-45
View File
@@ -1,45 +0,0 @@
// Copyright (c) 2022 Zhendong Peng (pzd17@tsinghua.org.cn)
//
// 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.
#ifndef PROCESSOR_WETEXT_PROCESSOR_H_
#define PROCESSOR_WETEXT_PROCESSOR_H_
#include <memory>
#include <string>
#include "fst/fstlib.h"
using fst::StdArc;
using fst::StdVectorFst;
using fst::StringCompiler;
using fst::StringPrinter;
class Processor {
public:
Processor(const std::string& tagger_path, const std::string& verbalizer_path);
std::string Tag(const std::string& input);
std::string Verbalize(const std::string& input);
std::string Normalize(const std::string& input);
private:
std::string ShortestPath(const StdVectorFst& lattice);
std::string Compose(const std::string& input, const StdVectorFst* fst);
std::shared_ptr<StdVectorFst> tagger_ = nullptr;
std::shared_ptr<StdVectorFst> verbalizer_ = nullptr;
std::shared_ptr<StringCompiler<StdArc>> compiler_ = nullptr;
std::shared_ptr<StringPrinter<StdArc>> printer_ = nullptr;
};
#endif // PROCESSOR_WETEXT_PROCESSOR_H_
+95 -34
View File
@@ -18,8 +18,6 @@
#include "lat/sausages.h"
#include "language_model.h"
#include <unordered_set>
using namespace fst;
using namespace kaldi::nnet3;
@@ -224,36 +222,110 @@ void Recognizer::SetNLSML(bool nlsml)
void Recognizer::SetEndpointerMode(int mode)
{
float rule1, rule2, rule3, rule4, rule5;
if (mode == 0) {
endpoint_config_ = model_->endpoint_config_;
return;
}
float scale = 1.0;
switch(mode) {
case 1:
scale = 0.75;
case 1: // SINGLE_WORD
rule2 = 0.1;
rule3 = 0.15;
rule4 = 0.2;
rule1 = 4.0;
rule5 = 7.0;
break;
case 2:
scale = 1.50;
case 2: // VERY_SHORT
rule2 = 0.3;
rule3 = 0.3;
rule4 = 0.6;
rule1 = 3.0;
rule5 = 5.0;
break;
case 3:
scale = 4.0;
case 3: // SHORT
rule2 = 0.3;
rule3 = 0.4;
rule4 = 0.6;
rule1 = 3.0;
rule5 = 5.0;
break;
case 4: // STANDARD
rule2 = 0.8;
rule3 = 0.9;
rule4 = 1.0;
rule1 = 3.0;
rule5 = 7.0;
break;
case 5: // STANDARD_5
rule2 = 0.8;
rule3 = 0.9;
rule4 = 1.5;
rule1 = 5.0;
rule5 = 7.0;
break;
case 6: // STANDARD_180
rule2 = 0.8;
rule3 = 0.9;
rule4 = 1.5;
rule1 = 3.0;
rule5 = 180.0;
break;
case 7: // LONG
rule2 = 1.0;
rule3 = 1.2;
rule4 = 2.0;
rule1 = 4.0;
rule5 = 10.0;
break;
case 8: // VERY_LONG
rule2 = 2.0;
rule3 = 2.5;
rule4 = 3.0;
rule1 = 4.0;
rule5 = 15.0;
break;
case 9: // VERY_LONG_180
rule2 = 2.0;
rule3 = 2.5;
rule4 = 3.0;
rule1 = 4.0;
rule5 = 180.0;
break;
default: // STANDARD
rule2 = 0.8;
rule3 = 0.9;
rule4 = 1.0;
rule1 = 5.0;
rule5 = 7.0;
break;
}
KALDI_LOG << "Updating endpointer scale " << scale;
KALDI_LOG << "Updating endpointer timeouts to " << rule1 << "," << rule2 << "," << rule3 << "," << rule4 << "," << rule5;
endpoint_config_ = model_->endpoint_config_;
endpoint_config_.rule2.min_trailing_silence *= scale;
endpoint_config_.rule3.min_trailing_silence *= scale;
endpoint_config_.rule4.min_trailing_silence *= scale;
endpoint_config_.rule1.min_trailing_silence = rule1;
endpoint_config_.rule2.min_trailing_silence = rule2;
endpoint_config_.rule3.min_trailing_silence = rule3;
endpoint_config_.rule4.min_trailing_silence = rule4;
endpoint_config_.rule5.min_utterance_length = rule5;
}
void Recognizer::SetEndpointerDelays(float t_start_max, float t_end, float t_max)
void Recognizer::SetEndpointerDelays(int sct, int nit, int t)
{
float rule1, rule2, rule3, rule4, rule5;
rule1 = t_start_max;
rule2 = t_end;
rule3 = t_end + 0.5;
rule4 = t_end + 1.0;
rule5 = t_max;
rule2 = sct / 1000.0;
if (sct < 500) {
rule3 = sct / 1000.0 * 1.5;
rule4 = sct / 1000.0 * 2;
} else {
rule3 = sct / 1000.0 + 0.5;
rule4 = sct / 1000.0 + 1.0;
}
rule1 = nit / 1000.0;
rule5 = t / 1000.0;
KALDI_LOG << "Updating endpointer delays " << rule1 << "," << rule2 << "," << rule3 << "," << rule4 << "," << rule5;
KALDI_LOG << "Updating endpointer timeouts to " << rule1 << "," << rule2 << "," << rule3 << "," << rule4 << "," << rule5;
endpoint_config_ = model_->endpoint_config_;
endpoint_config_.rule1.min_trailing_silence = rule1;
endpoint_config_.rule2.min_trailing_silence = rule2;
@@ -277,7 +349,7 @@ void Recognizer::SetSpkModel(SpkModel *spk_model)
void Recognizer::SetGrm(char const *grammar)
{
if (state_ == RECOGNIZER_RUNNING) {
KALDI_ERR << "Can't add grammar to already running recognizer";
KALDI_ERR << "Can't add speaker model to already running recognizer";
return;
}
@@ -357,7 +429,6 @@ void Recognizer::UpdateGrammarFst(char const *grammar)
}
estimator.AddCounts(sentence);
}
delete g_fst_;
g_fst_ = new StdVectorFst();
estimator.Estimate(g_fst_);
@@ -464,12 +535,13 @@ 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());
std::unordered_set<int32> nonsilence_set(nonsilence_frames.begin(), nonsilence_frames.end());
// Not very efficient, would be nice to have faster search
int num_nonsilence_frames = 0;
Vector<BaseFloat> feat(spk_feature_->Dim());
for (int i = 0; i < num_frames; ++i) {
if (nonsilence_set.find(i / 3) == nonsilence_set.end()) {
if (std::find(nonsilence_frames.begin(),
nonsilence_frames.end(), i / 3) == nonsilence_frames.end()) {
continue;
}
@@ -700,17 +772,6 @@ 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());
}
+1 -1
View File
@@ -53,7 +53,7 @@ class Recognizer {
void SetPartialWords(bool partial_words);
void SetNLSML(bool nlsml);
void SetEndpointerMode(int mode);
void SetEndpointerDelays(float t_start_max, float t_end, float t_max);
void SetEndpointerDelays(int sct, int nit, int t);
bool AcceptWaveform(const char *data, int len);
bool AcceptWaveform(const short *sdata, int len);
bool AcceptWaveform(const float *fdata, int len);
+2 -28
View File
@@ -17,7 +17,6 @@
#include "recognizer.h"
#include "model.h"
#include "spk_model.h"
#include "postprocessor.h"
#if HAVE_CUDA
#include "cudamatrix/cu-device.h"
@@ -138,12 +137,12 @@ void vosk_recognizer_set_endpointer_mode(VoskRecognizer *recognizer, VoskEndpoin
((Recognizer *)recognizer)->SetEndpointerMode(mode);
}
void vosk_recognizer_set_endpointer_delays(VoskRecognizer *recognizer, float t_start_max, float t_end, float t_max)
void vosk_recognizer_set_endpointer_delays(VoskRecognizer *recognizer, int sct, int nit, int t)
{
if (recognizer == nullptr) {
return;
}
((Recognizer *)recognizer)->SetEndpointerDelays(t_start_max, t_end, t_max);
((Recognizer *)recognizer)->SetEndpointerDelays(sct, nit, t);
}
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length)
@@ -305,28 +304,3 @@ int vosk_batch_recognizer_get_pending_chunks(VoskBatchRecognizer *recognizer)
return 0;
#endif
}
VoskTextProcessor *vosk_text_processor_new(const char *tagger, const char *verbalizer)
{
try {
return (VoskTextProcessor *)new Processor(tagger, verbalizer);
} catch (...) {
return nullptr;
}
}
void vosk_text_processor_free(VoskTextProcessor *processor)
{
delete ((Processor *)processor);
}
char *vosk_text_processor_itn(VoskTextProcessor *processor, const char *input)
{
Processor *wprocessor = (Processor *)processor;
std::string sinput(input);
std::string tagged_text = wprocessor->Tag(sinput);
std::string normalized_text = wprocessor->Verbalize(tagged_text);
return strdup(normalized_text.c_str());
}
+16 -20
View File
@@ -39,8 +39,6 @@ typedef struct VoskSpkModel VoskSpkModel;
* speaker information and so on */
typedef struct VoskRecognizer VoskRecognizer;
/** Inverse text normalization */
typedef struct VoskTextProcessor VoskTextProcessor;
/**
* Batch model object
@@ -219,11 +217,17 @@ void vosk_recognizer_set_partial_words(VoskRecognizer *recognizer, int partial_w
*/
void vosk_recognizer_set_nlsml(VoskRecognizer *recognizer, int nlsml);
typedef enum VoskEpMode {
VOSK_EP_ANSWER_DEFAULT = 0,
VOSK_EP_ANSWER_SHORT = 1,
VOSK_EP_ANSWER_LONG = 2,
VOSK_EP_ANSWER_VERY_LONG = 3,
typedef enum VoskEndpointerMode {
VOSK_EP_DEFAULT = 0,
VOSK_EP_SINGLE_WORD = 1,
VOSK_EP_VERY_SHORT = 2,
VOSK_EP_SHORT = 3,
VOSK_EP_STANDARD = 4,
VOSK_EP_STANDARD_5 = 5,
VOSK_EP_STANDARD_180 = 6,
VOSK_EP_LONG = 7,
VOSK_EP_VERY_LONG = 8,
VOSK_EP_VERY_LONG_180 = 9
} VoskEndpointerMode;
/**
@@ -236,11 +240,12 @@ void vosk_recognizer_set_endpointer_mode(VoskRecognizer *recognizer, VoskEndpoi
/**
* 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)
* @param sct speech complete timeout
* @param sint speech incomplete timeout
* @param nit no input timeout
* @param t recognition timeout
**/
void vosk_recognizer_set_endpointer_delays(VoskRecognizer *recognizer, float t_start_max, float t_end, float t_max);
void vosk_recognizer_set_endpointer_delays(VoskRecognizer *recognizer, int sct, int nit, int t);
/** Accept voice data
*
@@ -378,15 +383,6 @@ void vosk_batch_recognizer_pop(VoskBatchRecognizer *recognizer);
/** Get amount of pending chunks for more intelligent waiting */
int vosk_batch_recognizer_get_pending_chunks(VoskBatchRecognizer *recognizer);
/** Create text processor */
VoskTextProcessor *vosk_text_processor_new(const char *tagger, const char *verbalizer);
/** Release text processor */
void vosk_text_processor_free(VoskTextProcessor *processor);
/** Convert string */
char *vosk_text_processor_itn(VoskTextProcessor *processor, const char *input);
#ifdef __cplusplus
}
#endif
+2 -119
View File
@@ -1,120 +1,3 @@
# Vosk API Training
A proper simple setup to train a Vosk model
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 Kaldis WSJ steps for acoustic model training
└── utils -> ../../wsj/s5/utils/ # Link to Kaldis 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
```
More documentation later
+1 -2
View File
@@ -44,7 +44,6 @@ 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 \
&& make -j 10 online2 lm rnnlm \
&& find /opt/kaldi -name "*.o" -exec rm {} \;
+1 -1
View File
@@ -35,5 +35,5 @@ RUN cd /opt \
&& 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 \
&& ./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no \
&& make -j 10 online2 rnnlm \
&& make -j 10 online2 lm rnnlm \
&& find /opt/kaldi -name "*.o" -exec rm {} \;
-50
View File
@@ -1,50 +0,0 @@
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 {} \;
+1 -1
View File
@@ -30,5 +30,5 @@ RUN cd /opt \
&& ./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no \
&& sed -i 's:-msse -msse2:-msse -msse2:g' kaldi.mk \
&& sed -i 's: -O1 : -O3 :g' kaldi.mk \
&& make -j $(nproc) online2 rnnlm \
&& make -j $(nproc) online2 lm rnnlm \
&& find /opt/kaldi -name "*.o" -exec rm {} \;
+1 -1
View File
@@ -27,5 +27,5 @@ RUN cd /opt \
&& ./configure --mathlib=MKL --shared --use-cuda=no \
&& sed -i 's:-msse -msse2:-msse -msse2 -mavx -mavx2:g' kaldi.mk \
&& sed -i 's: -O1 : -O3 :g' kaldi.mk \
&& make -j $(nproc) online2 rnnlm \
&& make -j $(nproc) online2 lm rnnlm \
&& find /opt/kaldi -name "*.o" -exec rm {} \;
+1 -1
View File
@@ -62,4 +62,4 @@ RUN cd /opt/kaldi \
--host=x86_64-w64-mingw32 --openblas-clapack-root=/opt/kaldi/local \
--fst-root=/opt/kaldi/local --fst-version=1.8.0 \
&& make depend -j \
&& make -j $(nproc) online2 rnnlm
&& make -j $(nproc) online2 lm rnnlm
+1 -1
View File
@@ -61,4 +61,4 @@ RUN cd /opt/kaldi \
--host=i686-w64-mingw32 --openblas-clapack-root=/opt/kaldi/local \
--fst-root=/opt/kaldi/local --fst-version=1.8.0 \
&& make depend -j \
&& make -j $(nproc) online2 rnnlm
&& make -j $(nproc) online2 lm rnnlm
-70
View File
@@ -1,70 +0,0 @@
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
-7
View File
@@ -1,7 +0,0 @@
#!/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
-5
View File
@@ -1,5 +0,0 @@
#!/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
+3 -8
View File
@@ -14,11 +14,6 @@ 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
@@ -34,9 +29,9 @@ case $CROSS_TRIPLE in
esac
# Copy library to output folder
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}
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
# Build wheel
python3 -m pip install requests tqdm srt websockets wheel --break-system-packages
python3 -m pip install requests tqdm srt websockets wheel
python3 -m pip wheel /opt/vosk-api/python --no-deps -w /io/wheelhouse
-18
View File
@@ -1,18 +0,0 @@
#!/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
View File
@@ -1,6 +1,6 @@
{
"name": "vosk-js",
"version": "0.3.75",
"version": "0.3.45",
"description": "Node binding for continuous voice recoginition through vosk-api.",
"repository": {
"type": "git",