Skip to content

Commit eb84f09

Browse files
authored
Merge pull request #54 from mutablelogic/stream
Stream
2 parents 9a7843c + a4e1f09 commit eb84f09

File tree

14 files changed

+328
-25
lines changed

14 files changed

+328
-25
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ generate: mkdir go-tidy
3939

4040
# Make server
4141
server: mkdir generate go-tidy libwhisper libggml
42-
@echo "Building whisper-server"
43-
@PKG_CONFIG_PATH=${ROOT_PATH}/${BUILD_DIR} ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/whisper-server ./cmd/server
42+
@echo "Building whisper"
43+
@PKG_CONFIG_PATH=${ROOT_PATH}/${BUILD_DIR} ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/whisper ./cmd/whisper
4444

4545
# Make cli
4646
cli: mkdir generate go-tidy

cmd/server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// Packages
1414
context "github.com/mutablelogic/go-server/pkg/context"
1515
httpserver "github.com/mutablelogic/go-server/pkg/httpserver"
16-
whisper "github.com/mutablelogic/go-whisper/pkg/whisper"
16+
whisper "github.com/mutablelogic/go-whisper"
1717
api "github.com/mutablelogic/go-whisper/pkg/whisper/api"
1818
version "github.com/mutablelogic/go-whisper/pkg/whisper/version"
1919
)

cmd/whisper/download.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
// Packages
7+
"github.com/djthorpe/go-tablewriter"
8+
)
9+
10+
type DownloadCmd struct {
11+
Model string `arg:"" help:"Model to download"`
12+
}
13+
14+
func (cmd *DownloadCmd) Run(ctx *Globals) error {
15+
model, err := ctx.service.DownloadModel(ctx.ctx, cmd.Model, func(curBytes, totalBytes uint64) {
16+
log.Printf("Downloaded %d of %d bytes", curBytes, totalBytes)
17+
})
18+
if err != nil {
19+
return err
20+
}
21+
return ctx.writer.Write(model, tablewriter.OptHeader())
22+
}

cmd/whisper/main.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"syscall"
8+
9+
// Packages
10+
kong "github.com/alecthomas/kong"
11+
tablewriter "github.com/djthorpe/go-tablewriter"
12+
ctx "github.com/mutablelogic/go-server/pkg/context"
13+
whisper "github.com/mutablelogic/go-whisper"
14+
)
15+
16+
type Globals struct {
17+
NoGPU bool `name:"nogpu" help:"Disable GPU acceleration"`
18+
Debug bool `name:"debug" help:"Enable debug output"`
19+
Dir string `name:"dir" help:"Path to model store, uses ${WHISPER_DIR} " default:"${WHISPER_DIR}"`
20+
21+
// Writer, service and context
22+
writer *tablewriter.Writer
23+
service *whisper.Whisper
24+
ctx context.Context
25+
}
26+
27+
type CLI struct {
28+
Globals
29+
Models ModelsCmd `cmd:"models" help:"List models"`
30+
Download DownloadCmd `cmd:"download" help:"Download a model"`
31+
Server ServerCmd `cmd:"models" help:"Run the whisper server"`
32+
}
33+
34+
func main() {
35+
// The name of the executable
36+
name, err := os.Executable()
37+
if err != nil {
38+
panic(err)
39+
} else {
40+
name = filepath.Base(name)
41+
}
42+
43+
// Create a cli parser
44+
cli := CLI{}
45+
cmd := kong.Parse(&cli,
46+
kong.Name(name),
47+
kong.Description("speech transcription and translation service"),
48+
kong.UsageOnError(),
49+
kong.ConfigureHelp(kong.HelpOptions{Compact: true}),
50+
kong.Vars{
51+
"WHISPER_DIR": dirEnvOrDefault(name),
52+
},
53+
)
54+
55+
// Create a whisper server - set options
56+
opts := []whisper.Opt{}
57+
if cli.Globals.Debug {
58+
opts = append(opts, whisper.OptDebug())
59+
}
60+
if cli.Globals.NoGPU {
61+
opts = append(opts, whisper.OptNoGPU())
62+
}
63+
64+
// Create directory if it doesn't exist
65+
if err := os.MkdirAll(cli.Globals.Dir, 0755); err != nil {
66+
cmd.FatalIfErrorf(err)
67+
return
68+
}
69+
70+
// Create a whisper server - create
71+
service, err := whisper.New(cli.Globals.Dir, opts...)
72+
if err != nil {
73+
cmd.FatalIfErrorf(err)
74+
return
75+
} else {
76+
cli.Globals.service = service
77+
}
78+
defer service.Close()
79+
80+
// Create a tablewriter object with text output
81+
writer := tablewriter.New(os.Stdout, tablewriter.OptOutputText())
82+
cli.Globals.writer = writer
83+
84+
// Create a context
85+
cli.Globals.ctx = ctx.ContextForSignal(os.Interrupt, syscall.SIGQUIT)
86+
87+
// Run the command
88+
if err := cmd.Run(&cli.Globals); err != nil {
89+
cmd.FatalIfErrorf(err)
90+
}
91+
}
92+
93+
func dirEnvOrDefault(name string) string {
94+
if dir := os.Getenv("WHISPER_DIR"); dir != "" {
95+
return dir
96+
}
97+
if dir, err := os.UserCacheDir(); err == nil {
98+
return filepath.Join(dir, name)
99+
}
100+
return os.TempDir()
101+
}

cmd/whisper/models.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package main
2+
3+
import (
4+
// Packages
5+
"github.com/djthorpe/go-tablewriter"
6+
)
7+
8+
type ModelsCmd struct{}
9+
10+
func (*ModelsCmd) Run(ctx *Globals) error {
11+
return ctx.writer.Write(ctx.service.ListModels(), tablewriter.OptHeader())
12+
}

cmd/whisper/server.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
7+
// Packages
8+
"github.com/mutablelogic/go-server/pkg/httpserver"
9+
"github.com/mutablelogic/go-whisper/pkg/whisper/api"
10+
)
11+
12+
type ServerCmd struct {
13+
Endpoint string `name:"endpoint" help:"Endpoint for the server" default:"/api/v1"`
14+
Listen string `name:"listen" help:"Listen address for the server" default:"localhost:8080"`
15+
}
16+
17+
func (cmd *ServerCmd) Run(ctx *Globals) error {
18+
// Create a mux for serving requests, then register the endpoints with the mux
19+
mux := http.NewServeMux()
20+
21+
// Register the endpoints
22+
api.RegisterEndpoints(cmd.Endpoint, mux, ctx.service)
23+
24+
// Create a new HTTP server
25+
log.Println("List address", cmd.Listen)
26+
server, err := httpserver.Config{
27+
Listen: cmd.Listen,
28+
Router: mux,
29+
}.New()
30+
if err != nil {
31+
return err
32+
}
33+
34+
// Run the server until CTRL+C
35+
log.Println("Press CTRL+C to exit")
36+
return server.Run(ctx.ctx)
37+
}
File renamed without changes.

pkg/whisper/api/logging.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package api
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"sync/atomic"
7+
"time"
8+
)
9+
10+
var (
11+
req int32
12+
)
13+
14+
func wrapLogging(fn http.HandlerFunc) http.HandlerFunc {
15+
return func(w http.ResponseWriter, r *http.Request) {
16+
req := nextReq()
17+
delta := time.Now()
18+
log.Printf("R%d %s %s", req, r.Method, r.URL)
19+
fn(w, r)
20+
log.Printf("R%d Took %v", req, time.Since(delta).Truncate(time.Millisecond))
21+
}
22+
}
23+
24+
func nextReq() int32 {
25+
return atomic.AddInt32(&req, 1)
26+
}

pkg/whisper/api/models.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
// Packages
1212
"github.com/mutablelogic/go-server/pkg/httprequest"
1313
"github.com/mutablelogic/go-server/pkg/httpresponse"
14-
"github.com/mutablelogic/go-whisper/pkg/whisper"
14+
"github.com/mutablelogic/go-whisper"
1515
"github.com/mutablelogic/go-whisper/pkg/whisper/schema"
1616
)
1717

pkg/whisper/api/register.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
// Packages
88
"github.com/mutablelogic/go-server/pkg/httpresponse"
9-
"github.com/mutablelogic/go-whisper/pkg/whisper"
9+
"github.com/mutablelogic/go-whisper"
1010
)
1111

1212
/////////////////////////////////////////////////////////////////////////////
@@ -15,7 +15,7 @@ import (
1515
func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper) {
1616
// Health: GET /v1/health
1717
// returns an empty OK response
18-
mux.HandleFunc(joinPath(base, "health"), func(w http.ResponseWriter, r *http.Request) {
18+
mux.HandleFunc(joinPath(base, "health"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
1919
defer r.Body.Close()
2020

2121
switch r.Method {
@@ -24,14 +24,14 @@ func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper
2424
default:
2525
httpresponse.Error(w, http.StatusMethodNotAllowed)
2626
}
27-
})
27+
}))
2828

2929
// List Models: GET /v1/models
3030
// returns available models
3131
// Download Model: POST /v1/models?stream={bool}
3232
// downloads a model from the server
3333
// if stream is true then progress is streamed back to the client
34-
mux.HandleFunc(joinPath(base, "models"), func(w http.ResponseWriter, r *http.Request) {
34+
mux.HandleFunc(joinPath(base, "models"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
3535
defer r.Body.Close()
3636

3737
switch r.Method {
@@ -42,13 +42,13 @@ func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper
4242
default:
4343
httpresponse.Error(w, http.StatusMethodNotAllowed)
4444
}
45-
})
45+
}))
4646

4747
// Get: GET /v1/models/{id}
4848
// returns an existing model
4949
// Delete: DELETE /v1/models/{id}
5050
// deletes an existing model
51-
mux.HandleFunc(joinPath(base, "models/{id}"), func(w http.ResponseWriter, r *http.Request) {
51+
mux.HandleFunc(joinPath(base, "models/{id}"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
5252
defer r.Body.Close()
5353

5454
id := r.PathValue("id")
@@ -60,12 +60,12 @@ func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper
6060
default:
6161
httpresponse.Error(w, http.StatusMethodNotAllowed)
6262
}
63-
})
63+
}))
6464

6565
// Translate: POST /v1/audio/translations
6666
// Translates audio into english or another language - language parameter should be set to the
6767
// destination language of the audio. Will default to english if not set.
68-
mux.HandleFunc(joinPath(base, "audio/translations"), func(w http.ResponseWriter, r *http.Request) {
68+
mux.HandleFunc(joinPath(base, "audio/translations"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
6969
defer r.Body.Close()
7070

7171
switch r.Method {
@@ -74,12 +74,12 @@ func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper
7474
default:
7575
httpresponse.Error(w, http.StatusMethodNotAllowed)
7676
}
77-
})
77+
}))
7878

7979
// Transcribe: POST /v1/audio/transcriptions
8080
// Transcribes audio into the input language - language parameter should be set to the source
8181
// language of the audio
82-
mux.HandleFunc(joinPath(base, "audio/transcriptions"), func(w http.ResponseWriter, r *http.Request) {
82+
mux.HandleFunc(joinPath(base, "audio/transcriptions"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
8383
defer r.Body.Close()
8484

8585
switch r.Method {
@@ -102,7 +102,21 @@ func RegisterEndpoints(base string, mux *http.ServeMux, whisper *whisper.Whisper
102102
default:
103103
httpresponse.Error(w, http.StatusMethodNotAllowed)
104104
}
105-
})
105+
}))
106+
107+
// Transcribe: POST /v1/audio/transcriptions/{model-id}
108+
// Transcribes streamed media into the input language
109+
mux.HandleFunc(joinPath(base, "audio/transcriptions/{model}"), wrapLogging(func(w http.ResponseWriter, r *http.Request) {
110+
defer r.Body.Close()
111+
112+
model := r.PathValue("model")
113+
switch r.Method {
114+
case http.MethodPost:
115+
TranscribeStream(r.Context(), whisper, w, r, model)
116+
default:
117+
httpresponse.Error(w, http.StatusMethodNotAllowed)
118+
}
119+
}))
106120
}
107121

108122
/////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)