// Package config defines Captioneer's command-line configuration. package config import ( "errors" "strings" "time" ) const ( CurrentConfigVersion = 1 DefaultModelsDir = "models" DefaultVADThreshold = 0.5 DefaultOverlayFontFamily = "Sans" MinimumOverlayFinalLifetime = 3 * time.Second DefaultModelTimeout = 30 * time.Second DefaultModelShutdownTimeout = 2 * time.Second ) type Mode string const ( ModeFixed Mode = "fixed" ModeVAD Mode = "vad" ) type Settings struct { Mode Mode ChunkDuration time.Duration ModelsDir string Threads int PreviewThresholdDB float64 VADThreshold float64 ModelAutoRestart bool ModelTimeout time.Duration ModelShutdownTimeout time.Duration } func DefaultSettings() Settings { return Settings{ Mode: ModeVAD, ChunkDuration: time.Second, ModelsDir: DefaultModelsDir, Threads: 2, PreviewThresholdDB: -45, VADThreshold: DefaultVADThreshold, ModelAutoRestart: true, ModelTimeout: DefaultModelTimeout, ModelShutdownTimeout: DefaultModelShutdownTimeout, } } type OutputMode string const ( OutputTerminal OutputMode = "terminal" OutputOverlay OutputMode = "overlay" OutputBoth OutputMode = "both" ) type OverlayBackend string const ( BackendAuto OverlayBackend = "auto" BackendWayland OverlayBackend = "wayland" BackendX11 OverlayBackend = "x11" ) type OverlaySettings struct { Backend OverlayBackend Opacity float64 FontSize int FontFamily string BottomMargin int MaxWidth int Monitor string FinalTimeout time.Duration ClickThrough bool } func DefaultOverlaySettings() OverlaySettings { return OverlaySettings{ Backend: BackendAuto, Opacity: 0.90, FontSize: 28, FontFamily: DefaultOverlayFontFamily, BottomMargin: 100, MaxWidth: 1200, Monitor: "auto", FinalTimeout: MinimumOverlayFinalLifetime, ClickThrough: true, } } type OutputSettings struct { Overlay bool History bool StdoutCaptions bool StderrDiagnostics bool } type GUISettings struct { StartCaptions bool MainWindowAlwaysOnTop bool } type TranscriptLogSettings struct { Enabled bool Directory string } type AppConfig struct { Version int Settings Settings Overlay OverlaySettings Outputs OutputSettings GUI GUISettings TranscriptLog TranscriptLogSettings } func DefaultAppConfig() AppConfig { return AppConfig{ Version: CurrentConfigVersion, Settings: DefaultSettings(), Overlay: DefaultOverlaySettings(), Outputs: OutputSettings{ Overlay: true, History: true, StdoutCaptions: false, StderrDiagnostics: true, }, GUI: GUISettings{StartCaptions: true}, TranscriptLog: TranscriptLogSettings{ Directory: DefaultTranscriptDirectory(), }, } } type DesktopSettings struct { Settings Output OutputMode Overlay OverlaySettings } func (s Settings) Validate() error { if s.Mode != ModeFixed && s.Mode != ModeVAD { return errors.New("--mode is required and must be either fixed or vad") } if s.ChunkDuration <= 0 { return errors.New("--chunk-duration must be positive") } if s.Threads <= 0 { return errors.New("--threads must be positive") } if s.VADThreshold < 0 || s.VADThreshold > 1 { return errors.New("--vad-threshold must be between 0 and 1") } if s.ModelTimeout < 0 { return errors.New("--model-timeout cannot be negative") } if s.ModelShutdownTimeout <= 0 { return errors.New("--model-shutdown-timeout must be positive") } return nil } func (s DesktopSettings) Validate() error { if err := s.Settings.Validate(); err != nil { return err } if s.Output != OutputTerminal && s.Output != OutputOverlay && s.Output != OutputBoth { return errors.New("--output is required and must be terminal, overlay, or both") } return s.Overlay.Validate() } func (s OverlaySettings) Validate() error { if s.Backend != BackendAuto && s.Backend != BackendWayland && s.Backend != BackendX11 { return errors.New("--overlay-backend must be auto, wayland, or x11") } if s.Opacity < 0 || s.Opacity > 1 { return errors.New("--overlay-opacity must be between 0 and 1") } if s.FontSize <= 0 { return errors.New("--overlay-font-size must be positive") } if strings.TrimSpace(s.FontFamily) == "" { return errors.New("--overlay-font-family cannot be empty") } if s.BottomMargin < 0 { return errors.New("--overlay-bottom-margin cannot be negative") } if s.MaxWidth <= 0 { return errors.New("--overlay-max-width must be positive") } if s.FinalTimeout < 0 { return errors.New("--overlay-final-timeout cannot be negative") } if s.FinalTimeout > 0 && s.FinalTimeout < MinimumOverlayFinalLifetime { return errors.New("--overlay-final-timeout must be zero or at least 3s") } if s.Monitor == "" { return errors.New("--overlay-monitor cannot be empty") } return nil } func (c AppConfig) Validate() error { if c.Version != CurrentConfigVersion { return errors.New("unsupported configuration version") } if err := c.Settings.Validate(); err != nil { return err } if err := c.Overlay.Validate(); err != nil { return err } if c.TranscriptLog.Enabled && strings.TrimSpace(c.TranscriptLog.Directory) == "" { return errors.New("caption log directory cannot be empty when logging is enabled") } return nil }