// Command record-asr runs an HTTP transcription service backed by // SenseVoiceSmall (int8) and a silero VAD, served by the sherpa-onnx Go // bindings. Models are auto-downloaded on first run. All runtime knobs come // from ASR_* environment variables; an access password can be set via // ASR_PASSWORD. package main import ( "context" "fmt" "log" "os" "os/signal" "syscall" "git.misaka.ren/m1saka/record-asr/internal/audio" "git.misaka.ren/m1saka/record-asr/internal/config" "git.misaka.ren/m1saka/record-asr/internal/models" "git.misaka.ren/m1saka/record-asr/internal/server" "git.misaka.ren/m1saka/record-asr/internal/store" "git.misaka.ren/m1saka/record-asr/internal/transcribe" ) func main() { cfg, err := config.Load() if err != nil { fmt.Fprintf(os.Stderr, "config: %v\n", err) os.Exit(1) } log.Printf("config: %s", cfg) // Configure the audio decoder (ffmpeg path + per-file decode timeout) // before anything tries to transcribe. audio.Configure(cfg.FFmpegPath, cfg.DecodeTimeout) // Root context cancelled on SIGINT/SIGTERM for a clean shutdown. The model // manager and HTTP server both respect it. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // Models first — they auto-download and load before we serve traffic. mgr, err := models.New(ctx, cfg) if err != nil { fmt.Fprintf(os.Stderr, "model init: %v\n", err) os.Exit(1) } defer mgr.Close() // Persisted task store + the transcription pipeline. st, err := store.Open(cfg.DataDir) if err != nil { fmt.Fprintf(os.Stderr, "store: %v\n", err) os.Exit(1) } pipe := transcribe.New(mgr) srv := server.New(cfg, st, pipe, mgr) if err := srv.Run(ctx); err != nil { // http.Server already logged ErrServerClosed path. fmt.Fprintf(os.Stderr, "server: %v\n", err) os.Exit(1) } log.Printf("shutdown complete") }