From 221e93a5d7267798553daa9ab1ff9ade694a1e68 Mon Sep 17 00:00:00 2001 From: ciricc Date: Sun, 14 Sep 2025 02:31:10 +0300 Subject: [PATCH] refactor(go bindings): fix backward compatibility for the interfaces --- bindings/go/pkg/whisper/context.go | 26 +++-- bindings/go/pkg/whisper/context_test.go | 23 ++-- bindings/go/pkg/whisper/interface.go | 133 +++++++++--------------- bindings/go/pkg/whisper/model.go | 67 ++---------- bindings/go/pkg/whisper/params_wrap.go | 118 +++++++++++++-------- 5 files changed, 156 insertions(+), 211 deletions(-) diff --git a/bindings/go/pkg/whisper/context.go b/bindings/go/pkg/whisper/context.go index a18bb95ed..6f162877f 100644 --- a/bindings/go/pkg/whisper/context.go +++ b/bindings/go/pkg/whisper/context.go @@ -12,19 +12,17 @@ import ( ) type context struct { - n int - model *model - st WhisperState - params *parameters - Parameters + n int + model *model + st WhisperState + *Parameters } -func newContext(model *model, params *parameters) (Context, error) { +func NewContext(model *model, params *Parameters) (*context, error) { c := new(context) c.model = model - c.params = params - c.Parameters = c.params + c.Parameters = params // allocate isolated state per context ctx, err := model.whisperContext().unsafeContext() @@ -68,8 +66,8 @@ func (context *context) Close() error { } // Params returns a high-level parameters wrapper -func (context *context) Params() Parameters { - return context.params +func (context *context) Params() *Parameters { + return context.Parameters } // ResetTimings resets the model performance timing counters. @@ -87,7 +85,7 @@ func (context *context) PrintTimings() { // SystemInfo returns the system information func (context *context) SystemInfo() string { return fmt.Sprintf("system_info: n_threads = %d / %d | %s\n", - context.params.Threads(), + context.Parameters.Threads(), runtime.NumCPU(), whisper.Whisper_print_system_info(), ) @@ -129,10 +127,10 @@ func (context *context) Process( // If the callback is defined then we force on single_segment mode if callNewSegment != nil { - context.params.SetSingleSegment(true) + context.Parameters.SetSingleSegment(true) } - lowLevelParams, err := context.params.unsafeParams() + lowLevelParams, err := context.Parameters.unsafeParams() if err != nil { return err } @@ -242,7 +240,7 @@ func (context *context) SetLanguage(lang string) error { return ErrModelNotMultilingual } - return context.params.SetLanguage(lang) + return context.Parameters.SetLanguage(lang) } // Deprecated: Use Model.IsLANG() instead - token checking is model-specific. diff --git a/bindings/go/pkg/whisper/context_test.go b/bindings/go/pkg/whisper/context_test.go index 1cda693d9..70f941b29 100644 --- a/bindings/go/pkg/whisper/context_test.go +++ b/bindings/go/pkg/whisper/context_test.go @@ -325,11 +325,11 @@ func TestContext_VAD_And_Diarization_Params_DoNotPanic(t *testing.T) { assert.Equal(uint16(1), dec.NumChans) data := buf.AsFloat32Buffer().Data - model, err := whisper.New(ModelPath) + model, err := whisper.NewModel(ModelPath) assert.NoError(err) defer func() { _ = model.Close() }() - ctx, err := model.NewContext() + ctx, err := whisper.NewContext(model, nil) assert.NoError(err) defer func() { _ = ctx.Close() }() @@ -358,12 +358,11 @@ func TestDiarization_TwoSpeakers_Boundaries(t *testing.T) { require.NoError(t, err) data := buf.AsFloat32Buffer().Data - model, err := whisper.New(ModelTinydiarizePath) + model, err := whisper.NewModel(ModelTinydiarizePath) require.NoError(t, err) defer func() { _ = model.Close() }() - // diarize ON with beam search and tighter segmentation - ctxOn, err := model.NewContextWithParams(whisper.SAMPLING_GREEDY, func(p whisper.Parameters) { + params, err := whisper.NewParameters(model, whisper.SAMPLING_GREEDY, func(p *whisper.Parameters) { p.SetDiarize(true) p.SetVAD(false) p.SetSplitOnWord(true) @@ -372,6 +371,10 @@ func TestDiarization_TwoSpeakers_Boundaries(t *testing.T) { p.SetTokenTimestamps(true) }) require.NoError(t, err) + + // diarize ON with beam search and tighter segmentation + ctxOn, err := whisper.NewContext(model, params) + require.NoError(t, err) defer func() { _ = ctxOn.Close() }() require.NoError(t, ctxOn.Process(data, nil, nil, nil)) @@ -389,15 +392,7 @@ func TestDiarization_TwoSpeakers_Boundaries(t *testing.T) { require.Greater(t, turnsOn, 0, "expected speaker turn boundaries with diarization enabled") // diarize OFF baseline with same segmentation and beam - ctxOff, err := model.NewContextWithParams(whisper.SAMPLING_BEAM_SEARCH, func(p whisper.Parameters) { - p.SetBeamSize(3) - p.SetDiarize(false) - p.SetVAD(false) - p.SetSplitOnWord(true) - p.SetMaxSegmentLength(40) - p.SetMaxTokensPerSegment(64) - p.SetTokenTimestamps(true) - }) + ctxOff, err := whisper.NewContext(model, params) require.NoError(t, err) defer func() { _ = ctxOff.Close() }() diff --git a/bindings/go/pkg/whisper/interface.go b/bindings/go/pkg/whisper/interface.go index 56974769c..9a0cecfb8 100644 --- a/bindings/go/pkg/whisper/interface.go +++ b/bindings/go/pkg/whisper/interface.go @@ -46,34 +46,19 @@ type TokenIdentifier interface { IsText(Token) (bool, error) } -type ParamsConfigure func(Parameters) +type ParamsConfigure func(*Parameters) // Model is the interface to a whisper model. Create a new model with the // function whisper.New(string) +// Deprecated: Use NewModel implementation struct instead of relying on this interface type Model interface { io.Closer // Return a new speech-to-text context. // It may return an error is the model is not loaded or closed + // Deprecated: Use NewContext implementation struct instead of relying on this interface NewContext() (Context, error) - // Return a new parameters wrapper - // sampling is the sampling strategy to use - // configure is the function to configure the parameters - // It may return an error is the model is not loaded or closed - NewParams( - sampling SamplingStrategy, - configure ParamsConfigure, - ) (Parameters, error) - - // Return a new speech-to-text context configured via the provided function - // and sampling strategy. The context is backed by an isolated whisper_state. - // It may return an error is the model is not loaded or closed - NewContextWithParams( - sampling SamplingStrategy, - configure ParamsConfigure, - ) (Context, error) - // Return true if the model is multilingual. // It returns false if the model is not loaded or closed IsMultilingual() bool @@ -87,73 +72,65 @@ type Model interface { // Reset model performance timing counters ResetTimings() - - // WhisperContext returns the memory-safe whisper context wrapper of the raw whisper context - // You may need to use this to get the raw whisper context - // Ot check that the model's context is not closed - WhisperContext() WhisperContext - - // Token identifier - TokenIdentifier() TokenIdentifier } -// Parameters configures decode / processing behavior -type Parameters interface { - SetTranslate(bool) - SetSplitOnWord(bool) - SetThreads(uint) - SetOffset(time.Duration) - SetDuration(time.Duration) - SetTokenThreshold(float32) - SetTokenSumThreshold(float32) - SetMaxSegmentLength(uint) - SetTokenTimestamps(bool) - SetMaxTokensPerSegment(uint) - SetAudioCtx(uint) - SetMaxContext(n int) - SetBeamSize(n int) - SetEntropyThold(t float32) - SetInitialPrompt(prompt string) +// // Parameters configures decode / processing behavior +// type Parameters interface { +// SetTranslate(bool) +// SetSplitOnWord(bool) +// SetThreads(uint) +// SetOffset(time.Duration) +// SetDuration(time.Duration) +// SetTokenThreshold(float32) +// SetTokenSumThreshold(float32) +// SetMaxSegmentLength(uint) +// SetTokenTimestamps(bool) +// SetMaxTokensPerSegment(uint) +// SetAudioCtx(uint) +// SetMaxContext(n int) +// SetBeamSize(n int) +// SetEntropyThold(t float32) +// SetInitialPrompt(prompt string) - SetNoContext(bool) - SetPrintSpecial(bool) - SetPrintProgress(bool) - SetPrintRealtime(bool) - SetPrintTimestamps(bool) +// SetNoContext(bool) +// SetPrintSpecial(bool) +// SetPrintProgress(bool) +// SetPrintRealtime(bool) +// SetPrintTimestamps(bool) - // Enable extra debug info (e.g., dump log_mel) - SetDebugMode(bool) - // Diarization (tinydiarize) - SetDiarize(bool) +// // Enable extra debug info (e.g., dump log_mel) +// SetDebugMode(bool) +// // Diarization (tinydiarize) +// SetDiarize(bool) - // Voice Activity Detection (VAD) - SetVAD(bool) - SetVADModelPath(string) - SetVADThreshold(float32) - SetVADMinSpeechMs(int) - SetVADMinSilenceMs(int) - SetVADMaxSpeechSec(float32) - SetVADSpeechPadMs(int) - SetVADSamplesOverlap(float32) +// // Voice Activity Detection (VAD) +// SetVAD(bool) +// SetVADModelPath(string) +// SetVADThreshold(float32) +// SetVADMinSpeechMs(int) +// SetVADMinSilenceMs(int) +// SetVADMaxSpeechSec(float32) +// SetVADSpeechPadMs(int) +// SetVADSamplesOverlap(float32) - // Set the temperature - SetTemperature(t float32) +// // Set the temperature +// SetTemperature(t float32) - // Set the fallback temperature incrementation - // Pass -1.0 to disable this feature - SetTemperatureFallback(t float32) +// // Set the fallback temperature incrementation +// // Pass -1.0 to disable this feature +// SetTemperatureFallback(t float32) - // Set the language - // If the model is not multilingual, this will return an error - SetLanguage(string) error +// // Set the language +// // If the model is not multilingual, this will return an error +// SetLanguage(string) error - // Set single segment mode - SetSingleSegment(bool) +// // Set single segment mode +// SetSingleSegment(bool) - // Getter methods - Language() string - Threads() int -} +// // Getter methods +// Language() string +// Threads() int +// } // Context is the speech recognition context. type Context interface { @@ -217,9 +194,6 @@ type Context interface { // Deprecated: Use Params().Language() instead Language() string - // Return the model that the context is backed by - Model() Model - // Deprecated: Use Model().IsMultilingual() instead IsMultilingual() bool @@ -269,9 +243,6 @@ type Context interface { // SystemInfo returns the system information SystemInfo() string - - // Params returns a high-level parameters wrapper - preferred method - Params() Parameters } // Segment is the text result of a speech recognition. diff --git a/bindings/go/pkg/whisper/model.go b/bindings/go/pkg/whisper/model.go index 988ae1522..d9aabdaf7 100644 --- a/bindings/go/pkg/whisper/model.go +++ b/bindings/go/pkg/whisper/model.go @@ -17,7 +17,15 @@ type model struct { // Make sure model adheres to the interface var _ Model = (*model)(nil) +// Deprecated: Use NewModel instead func New(path string) (Model, error) { + return NewModel(path) +} + +// NewModel creates a new model without initializing the context +func NewModel( + path string, +) (*model, error) { model := new(model) if _, err := os.Stat(path); err != nil { return nil, err @@ -92,73 +100,18 @@ func (model *model) Languages() []string { // Each context is backed by an isolated whisper_state for safe concurrent processing. func (model *model) NewContext() (Context, error) { // Create new context with default params - params, err := model.newParams(SAMPLING_GREEDY, nil) + params, err := NewParameters(model, SAMPLING_GREEDY, nil) if err != nil { return nil, err } // Return new context (now state-backed) - return newContext( + return NewContext( model, params, ) } -func (model *model) NewParams( - sampling SamplingStrategy, - configure ParamsConfigure, -) (Parameters, error) { - return model.newParams(sampling, nil) -} - -// NewContextWithParams creates a new speech-to-text context and allows -// callers to customize the decoding parameters before the state is used. -// The resulting Context is backed by an isolated whisper_state for safe -// concurrent processing. -func (model *model) NewContextWithParams( - sampling SamplingStrategy, - configure ParamsConfigure, -) (Context, error) { - params, err := model.newParams(sampling, configure) - if err != nil { - return nil, err - } - - return newContext( - model, - params, - ) -} - -func defaultParamsConfigure(params Parameters) { - params.SetTranslate(false) - params.SetPrintSpecial(false) - params.SetPrintProgress(false) - params.SetPrintRealtime(false) - params.SetPrintTimestamps(false) -} - -func (m *model) newParams( - sampling SamplingStrategy, - configure ParamsConfigure, -) (*parameters, error) { - ctx, err := m.ctx.unsafeContext() - if err != nil { - return nil, ErrModelClosed - } - - p := ctx.Whisper_full_default_params(whisper.SamplingStrategy(sampling)) - safeParams := newParameters(&p) - - defaultParamsConfigure(safeParams) - - if configure != nil { - configure(safeParams) - } - - return safeParams, nil -} - // PrintTimings prints the model performance timings to stdout. func (model *model) PrintTimings() { ctx, err := model.ctx.unsafeContext() diff --git a/bindings/go/pkg/whisper/params_wrap.go b/bindings/go/pkg/whisper/params_wrap.go index ad772d88b..05a1f04bc 100644 --- a/bindings/go/pkg/whisper/params_wrap.go +++ b/bindings/go/pkg/whisper/params_wrap.go @@ -1,62 +1,92 @@ package whisper import ( + "runtime" "time" // Bindings whisper "github.com/ggerganov/whisper.cpp/bindings/go" ) -// parameters is a high-level wrapper that implements the Parameters interface +// Parameters is a high-level wrapper that implements the Parameters interface // and delegates to the underlying low-level whisper.Params. -type parameters struct { +type Parameters struct { p *whisper.Params } -func newParameters(whisperParams *whisper.Params) *parameters { - return ¶meters{ - p: whisperParams, - } +func defaultParamsConfigure(params *Parameters) { + params.SetTranslate(false) + params.SetPrintSpecial(false) + params.SetPrintProgress(false) + params.SetPrintRealtime(false) + params.SetPrintTimestamps(false) + // Default behavior backward compatibility + params.SetThreads(uint(runtime.NumCPU())) + params.SetNoContext(true) } -func (w *parameters) SetTranslate(v bool) { w.p.SetTranslate(v) } -func (w *parameters) SetSplitOnWord(v bool) { w.p.SetSplitOnWord(v) } -func (w *parameters) SetThreads(v uint) { w.p.SetThreads(int(v)) } -func (w *parameters) SetOffset(d time.Duration) { w.p.SetOffset(int(d.Milliseconds())) } -func (w *parameters) SetDuration(d time.Duration) { w.p.SetDuration(int(d.Milliseconds())) } -func (w *parameters) SetTokenThreshold(t float32) { w.p.SetTokenThreshold(t) } -func (w *parameters) SetTokenSumThreshold(t float32) { w.p.SetTokenSumThreshold(t) } -func (w *parameters) SetMaxSegmentLength(n uint) { w.p.SetMaxSegmentLength(int(n)) } -func (w *parameters) SetTokenTimestamps(b bool) { w.p.SetTokenTimestamps(b) } -func (w *parameters) SetMaxTokensPerSegment(n uint) { w.p.SetMaxTokensPerSegment(int(n)) } -func (w *parameters) SetAudioCtx(n uint) { w.p.SetAudioCtx(int(n)) } -func (w *parameters) SetMaxContext(n int) { w.p.SetMaxContext(n) } -func (w *parameters) SetBeamSize(n int) { w.p.SetBeamSize(n) } -func (w *parameters) SetEntropyThold(t float32) { w.p.SetEntropyThold(t) } -func (w *parameters) SetInitialPrompt(prompt string) { w.p.SetInitialPrompt(prompt) } -func (w *parameters) SetTemperature(t float32) { w.p.SetTemperature(t) } -func (w *parameters) SetTemperatureFallback(t float32) { w.p.SetTemperatureFallback(t) } -func (w *parameters) SetNoContext(v bool) { w.p.SetNoContext(v) } -func (w *parameters) SetPrintSpecial(v bool) { w.p.SetPrintSpecial(v) } -func (w *parameters) SetPrintProgress(v bool) { w.p.SetPrintProgress(v) } -func (w *parameters) SetPrintRealtime(v bool) { w.p.SetPrintRealtime(v) } -func (w *parameters) SetPrintTimestamps(v bool) { w.p.SetPrintTimestamps(v) } -func (w *parameters) SetDebugMode(v bool) { w.p.SetDebugMode(v) } +func NewParameters( + model *model, + sampling SamplingStrategy, + configure ParamsConfigure, +) (*Parameters, error) { + ctx, err := model.ctx.unsafeContext() + if err != nil { + return nil, ErrModelClosed + } + + p := ctx.Whisper_full_default_params(whisper.SamplingStrategy(sampling)) + safeParams := &Parameters{ + p: &p, + } + + defaultParamsConfigure(safeParams) + + if configure != nil { + configure(safeParams) + } + + return safeParams, nil +} + +func (w *Parameters) SetTranslate(v bool) { w.p.SetTranslate(v) } +func (w *Parameters) SetSplitOnWord(v bool) { w.p.SetSplitOnWord(v) } +func (w *Parameters) SetThreads(v uint) { w.p.SetThreads(int(v)) } +func (w *Parameters) SetOffset(d time.Duration) { w.p.SetOffset(int(d.Milliseconds())) } +func (w *Parameters) SetDuration(d time.Duration) { w.p.SetDuration(int(d.Milliseconds())) } +func (w *Parameters) SetTokenThreshold(t float32) { w.p.SetTokenThreshold(t) } +func (w *Parameters) SetTokenSumThreshold(t float32) { w.p.SetTokenSumThreshold(t) } +func (w *Parameters) SetMaxSegmentLength(n uint) { w.p.SetMaxSegmentLength(int(n)) } +func (w *Parameters) SetTokenTimestamps(b bool) { w.p.SetTokenTimestamps(b) } +func (w *Parameters) SetMaxTokensPerSegment(n uint) { w.p.SetMaxTokensPerSegment(int(n)) } +func (w *Parameters) SetAudioCtx(n uint) { w.p.SetAudioCtx(int(n)) } +func (w *Parameters) SetMaxContext(n int) { w.p.SetMaxContext(n) } +func (w *Parameters) SetBeamSize(n int) { w.p.SetBeamSize(n) } +func (w *Parameters) SetEntropyThold(t float32) { w.p.SetEntropyThold(t) } +func (w *Parameters) SetInitialPrompt(prompt string) { w.p.SetInitialPrompt(prompt) } +func (w *Parameters) SetTemperature(t float32) { w.p.SetTemperature(t) } +func (w *Parameters) SetTemperatureFallback(t float32) { w.p.SetTemperatureFallback(t) } +func (w *Parameters) SetNoContext(v bool) { w.p.SetNoContext(v) } +func (w *Parameters) SetPrintSpecial(v bool) { w.p.SetPrintSpecial(v) } +func (w *Parameters) SetPrintProgress(v bool) { w.p.SetPrintProgress(v) } +func (w *Parameters) SetPrintRealtime(v bool) { w.p.SetPrintRealtime(v) } +func (w *Parameters) SetPrintTimestamps(v bool) { w.p.SetPrintTimestamps(v) } +func (w *Parameters) SetDebugMode(v bool) { w.p.SetDebugMode(v) } // Diarization (tinydiarize) -func (w *parameters) SetDiarize(v bool) { w.p.SetDiarize(v) } +func (w *Parameters) SetDiarize(v bool) { w.p.SetDiarize(v) } // Voice Activity Detection (VAD) -func (w *parameters) SetVAD(v bool) { w.p.SetVAD(v) } -func (w *parameters) SetVADModelPath(p string) { w.p.SetVADModelPath(p) } -func (w *parameters) SetVADThreshold(t float32) { w.p.SetVADThreshold(t) } -func (w *parameters) SetVADMinSpeechMs(ms int) { w.p.SetVADMinSpeechMs(ms) } -func (w *parameters) SetVADMinSilenceMs(ms int) { w.p.SetVADMinSilenceMs(ms) } -func (w *parameters) SetVADMaxSpeechSec(s float32) { w.p.SetVADMaxSpeechSec(s) } -func (w *parameters) SetVADSpeechPadMs(ms int) { w.p.SetVADSpeechPadMs(ms) } -func (w *parameters) SetVADSamplesOverlap(sec float32) { w.p.SetVADSamplesOverlap(sec) } +func (w *Parameters) SetVAD(v bool) { w.p.SetVAD(v) } +func (w *Parameters) SetVADModelPath(p string) { w.p.SetVADModelPath(p) } +func (w *Parameters) SetVADThreshold(t float32) { w.p.SetVADThreshold(t) } +func (w *Parameters) SetVADMinSpeechMs(ms int) { w.p.SetVADMinSpeechMs(ms) } +func (w *Parameters) SetVADMinSilenceMs(ms int) { w.p.SetVADMinSilenceMs(ms) } +func (w *Parameters) SetVADMaxSpeechSec(s float32) { w.p.SetVADMaxSpeechSec(s) } +func (w *Parameters) SetVADSpeechPadMs(ms int) { w.p.SetVADSpeechPadMs(ms) } +func (w *Parameters) SetVADSamplesOverlap(sec float32) { w.p.SetVADSamplesOverlap(sec) } -func (w *parameters) SetLanguage(lang string) error { +func (w *Parameters) SetLanguage(lang string) error { if lang == "auto" { return w.p.SetLanguage(-1) } @@ -67,12 +97,12 @@ func (w *parameters) SetLanguage(lang string) error { return w.p.SetLanguage(id) } -func (w *parameters) SetSingleSegment(v bool) { +func (w *Parameters) SetSingleSegment(v bool) { w.p.SetSingleSegment(v) } // Getter methods for Parameters interface -func (w *parameters) Language() string { +func (w *Parameters) Language() string { id := w.p.Language() if id == -1 { return "auto" @@ -81,12 +111,10 @@ func (w *parameters) Language() string { return whisper.Whisper_lang_str(id) } -func (w *parameters) Threads() int { +func (w *Parameters) Threads() int { return w.p.Threads() } -func (w *parameters) unsafeParams() (*whisper.Params, error) { +func (w *Parameters) unsafeParams() (*whisper.Params, error) { return w.p, nil } - -var _ Parameters = ¶meters{}