package config import ( "fmt" "os" "path/filepath" ) type VAD struct { Enabled bool `yaml:"enabled"` Model string `yaml:"model"` Threshold float64 `yaml:"threshold"` MinSpeechMs int `yaml:"min_speech_duration_ms"` MinSilenceMs int `yaml:"min_silence_duration_ms"` MaxSpeechSec float64 `yaml:"max_speech_duration_s"` SpeechPadMs int `yaml:"speech_pad_ms"` SamplesOverlap float64 `yaml:"samples_overlap"` } func DefaultVAD() VAD { return VAD{ Threshold: 0.5, MinSpeechMs: 250, MinSilenceMs: 100, MaxSpeechSec: 0, SpeechPadMs: 30, SamplesOverlap: 0.1, } } func (v VAD) WithDefaults() VAD { d := DefaultVAD() if v.Threshold <= 0 { v.Threshold = d.Threshold } if v.MinSpeechMs <= 0 { v.MinSpeechMs = d.MinSpeechMs } if v.MinSilenceMs <= 0 { v.MinSilenceMs = d.MinSilenceMs } if v.SpeechPadMs <= 0 { v.SpeechPadMs = d.SpeechPadMs } if v.SamplesOverlap <= 0 { v.SamplesOverlap = d.SamplesOverlap } return v } func (v VAD) ResolveModelPath(modelsDir string) string { if v.Model == "" { return "" } if filepath.IsAbs(v.Model) { return v.Model } if modelsDir == "" { return v.Model } direct := filepath.Join(modelsDir, v.Model) if _, err := os.Stat(direct); err == nil { return direct } // VAD weights often live under models_dir/vad/ (not listed in /spr/models). base := filepath.Base(v.Model) inVAD := filepath.Join(modelsDir, "vad", base) if _, err := os.Stat(inVAD); err == nil { return inVAD } return direct } func (v VAD) Validate() error { if !v.Enabled { return nil } if v.Model == "" { return fmt.Errorf("vad.model is required when vad.enabled is true") } if _, err := os.Stat(v.Model); err != nil { return fmt.Errorf("vad model %q: %w", v.Model, err) } if v.Threshold < 0 || v.Threshold > 1 { return fmt.Errorf("vad.threshold must be between 0 and 1") } return nil }