commit 5eedba33b96dcd3826010cbd12e404cbfb544383 Author: soraefir Date: Tue Jun 27 09:05:25 2023 +0200 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eda398e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/model +voskcli diff --git a/README.md b/README.md new file mode 100644 index 0000000..6532189 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +#VoskCLI \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3bb6485 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..755d892 --- /dev/null +++ b/go.sum @@ -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= diff --git a/voskcli.go b/voskcli.go new file mode 100644 index 0000000..48a82d2 --- /dev/null +++ b/voskcli.go @@ -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); + } + } + + +}