This commit is contained in:
soraefir 2023-06-27 09:05:25 +02:00
commit 5eedba33b9
Signed by: sora
GPG Key ID: A362EA0491E2EEA0
5 changed files with 207 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/model
voskcli

1
README.md Normal file
View File

@ -0,0 +1 @@
#VoskCLI

14
go.mod Normal file
View File

@ -0,0 +1,14 @@
module git.helcel.net/helcel/voskcli
go 1.20
require (
github.com/alphacep/vosk-api/go v0.3.46-0.20230429023501-12f29a3415e4
github.com/m7shapan/njson v1.0.8
)
require (
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
)

13
go.sum Normal file
View File

@ -0,0 +1,13 @@
github.com/alphacep/vosk-api/go v0.3.46-0.20230429023501-12f29a3415e4 h1:MGwc3N+btl1ayMYPbe5VyK0vifwwmgsUbDxZd9auO2Y=
github.com/alphacep/vosk-api/go v0.3.46-0.20230429023501-12f29a3415e4/go.mod h1:9X8IJsHnFk/b1xyvjlZifo+ZL5VTAx3LW+JQce/eRcA=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/m7shapan/njson v1.0.8 h1:5FNWjy8WhZ53Ev4ricYPtZfAv7o3/7wAKL+zhXxknvg=
github.com/m7shapan/njson v1.0.8/go.mod h1:h3P0qmwsv8+s+cE07cXuTgtyKCrBIAEUger0hA71Fy0=
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

177
voskcli.go Normal file
View File

@ -0,0 +1,177 @@
package main
import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
njson "github.com/m7shapan/njson"
vosk "github.com/alphacep/vosk-api/go"
)
func usage() {
fmt.Println(`Usage: voskcli [FILE...]
--mic=NAME Specify the audio device (use 'arecord -L' to find mic name).
--model=PATH Path to the model to use`)
}
func fatal(a ...any) {
fmt.Fprintln(os.Stderr, "voskcli:", fmt.Sprint(a...))
os.Exit(1)
}
func warn(a ...any) {
fmt.Fprintln(os.Stderr, "voskcli: WARNING:", fmt.Sprint(a...))
}
func print(a ...any) {
fmt.Fprintln(os.Stdout, fmt.Sprint(a...))
}
func parseResults(json string) string {
type ResultJson struct {
Text string `njson:"text"`
Words []string `njson:"result.#.word"`
Confs []float64 `njson:"result.#.conf"`
Starts []float64 `njson:"result.#.start"`
Ends []float64 `njson:"result.#.end"`
Confidence float64 `njson:"confidence"` // only with alternatives
}
var s struct {
Alternatives []ResultJson `njson:"alternatives"`
// copy paste of ResultJson
Text string `njson:"text"`
Words []string `njson:"result.#.word"`
Confs []float64 `njson:"result.#.conf"`
Starts []float64 `njson:"result.#.start"`
Ends []float64 `njson:"result.#.end"`
Confidence float64 `njson:"confidence"` // only with alternatives
ParText string `njson:"partial"`
ParWords []string `njson:"partial_result.#.word"`
ParConfs []float64 `njson:"partial_result.#.conf"`
ParStarts []float64 `njson:"partial_result.#.start"`
ParEnds []float64 `njson:"partial_result.#.end"`
}
err := njson.Unmarshal([]byte(json), &s)
if err != nil {
panic(err)
}
print(json);
return s.Text
}
func getMic(mic string) string {
if mic == "" {
mic = "default"
}
return mic
}
func record(mic string) (io.Reader, error) {
cmd := exec.Command("arecord", "-q", "-fS16_LE", "-c1", "-r16000", "-D", mic)
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
return stdout, nil
}
type Action struct {
Tags []string
Text string
}
func main() {
var opts struct {
Mic string
Model string
Verbose bool
}
var model *vosk.VoskModel
{
m := os.Getenv("VOSK_MODEL")
if m == "" {
fatal("you need to install the a vosk model and set $VOSK_MODEL")
}
if opts.Verbose {
fmt.Fprintln(os.Stderr, "Model: " + m)
}
var err error
model, err = vosk.NewModel(m)
if err != nil {
fatal(err)
}
}
defer model.Free()
var r *vosk.VoskRecognizer
{
var err error
r, err = vosk.NewRecognizer(model, float64(16000))
if err != nil {
panic(err)
}
r.SetWords(1)
r.SetMaxAlternatives(3)//3 ? 10 ?
}
defer r.Free()
var mic string
var audio io.Reader
mic = getMic(opts.Mic)
if opts.Verbose {
fmt.Fprintln(os.Stderr, "Microphone: " + mic)
}
var err error
audio, err = record(mic)
if err != nil {
fatal(err)
}
terminate := make(chan os.Signal, 1)
signal.Notify(terminate, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
for {
select {
case <-terminate: return
default:
}
chunk := make([]byte, 4096)
_, err := io.ReadFull(audio, chunk)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
panic(err)
}
r, err := record(mic)
if err != nil {
warn(err)
}
audio = r
continue
}
var res string
if (r.AcceptWaveform(chunk) == 1) {
res = r.FinalResult()
parseResults(res);
} else {
res = r.PartialResult() // r.Result()
parseResults(res);
}
}
}