2026-07-16 12:12:01 +03:00
|
|
|
// Package config defines Captioneer's command-line configuration.
|
|
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"flag"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const DefaultModelsDir = "models"
|
|
|
|
|
|
|
|
|
|
type Mode string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ModeFixed Mode = "fixed"
|
|
|
|
|
ModeVAD Mode = "vad"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Settings struct {
|
|
|
|
|
Mode Mode
|
|
|
|
|
ChunkDuration time.Duration
|
|
|
|
|
ModelsDir string
|
|
|
|
|
Threads int
|
|
|
|
|
PreviewThresholdDB float64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Parse() Settings {
|
|
|
|
|
var settings Settings
|
|
|
|
|
mode := ""
|
|
|
|
|
flag.StringVar(&mode, "mode", "", "caption mode: vad or fixed (required)")
|
|
|
|
|
flag.DurationVar(&settings.ChunkDuration, "chunk-duration", time.Second, "fixed chunk size or VAD preview refresh interval")
|
|
|
|
|
flag.StringVar(&settings.ModelsDir, "models-dir", DefaultModelsDir, "directory containing downloaded Sherpa models")
|
|
|
|
|
flag.IntVar(&settings.Threads, "threads", 2, "CPU threads used by recognition and VAD")
|
|
|
|
|
flag.Float64Var(&settings.PreviewThresholdDB, "preview-threshold-dbfs", -45, "RMS dBFS threshold used to begin VAD previews")
|
|
|
|
|
flag.Parse()
|
|
|
|
|
settings.Mode = Mode(mode)
|
|
|
|
|
return settings
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-16 17:24:50 +03:00
|
|
|
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
|
|
|
|
|
BottomMargin int
|
|
|
|
|
MaxWidth int
|
|
|
|
|
Monitor string
|
|
|
|
|
FinalTimeout time.Duration
|
|
|
|
|
ClickThrough bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DesktopSettings struct {
|
|
|
|
|
Settings
|
|
|
|
|
Output OutputMode
|
|
|
|
|
Overlay OverlaySettings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ParseDesktop() DesktopSettings {
|
|
|
|
|
flags := flag.CommandLine
|
|
|
|
|
var desktop DesktopSettings
|
|
|
|
|
var mode string
|
|
|
|
|
var output string
|
|
|
|
|
var backend string
|
|
|
|
|
flags.StringVar(&mode, "mode", "", "caption mode: vad or fixed (required)")
|
|
|
|
|
flags.DurationVar(&desktop.ChunkDuration, "chunk-duration", time.Second, "fixed chunk size or VAD preview refresh interval")
|
|
|
|
|
flags.StringVar(&desktop.ModelsDir, "models-dir", DefaultModelsDir, "directory containing downloaded Sherpa models")
|
|
|
|
|
flags.IntVar(&desktop.Threads, "threads", 2, "CPU threads used by recognition and VAD")
|
|
|
|
|
flags.Float64Var(&desktop.PreviewThresholdDB, "preview-threshold-dbfs", -45, "RMS dBFS threshold used to begin VAD previews")
|
|
|
|
|
flags.StringVar(&output, "output", "", "caption output: terminal, overlay, or both (required)")
|
|
|
|
|
flags.StringVar(&backend, "overlay-backend", "auto", "display backend: auto, wayland, or x11")
|
|
|
|
|
flags.Float64Var(&desktop.Overlay.Opacity, "overlay-opacity", 0.90, "caption background opacity from 0 to 1")
|
|
|
|
|
flags.IntVar(&desktop.Overlay.FontSize, "overlay-font-size", 28, "caption font size in logical pixels")
|
|
|
|
|
flags.IntVar(&desktop.Overlay.BottomMargin, "overlay-bottom-margin", 100, "distance from the monitor bottom in logical pixels")
|
|
|
|
|
flags.IntVar(&desktop.Overlay.MaxWidth, "overlay-max-width", 1200, "maximum caption width in logical pixels")
|
|
|
|
|
flags.StringVar(&desktop.Overlay.Monitor, "overlay-monitor", "auto", "monitor selection: auto, numeric index, or connector name")
|
|
|
|
|
flags.DurationVar(&desktop.Overlay.FinalTimeout, "overlay-final-timeout", 4*time.Second, "how long a final caption remains; zero waits for replacement")
|
|
|
|
|
flags.BoolVar(&desktop.Overlay.ClickThrough, "overlay-click-through", true, "pass pointer input through the caption window")
|
|
|
|
|
flag.Parse()
|
|
|
|
|
desktop.Mode = Mode(mode)
|
|
|
|
|
desktop.Output = OutputMode(output)
|
|
|
|
|
desktop.Overlay.Backend = OverlayBackend(backend)
|
|
|
|
|
return desktop
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-16 12:12:01 +03:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-07-16 17:24:50 +03:00
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.Backend != BackendAuto && s.Overlay.Backend != BackendWayland && s.Overlay.Backend != BackendX11 {
|
|
|
|
|
return errors.New("--overlay-backend must be auto, wayland, or x11")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.Opacity < 0 || s.Overlay.Opacity > 1 {
|
|
|
|
|
return errors.New("--overlay-opacity must be between 0 and 1")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.FontSize <= 0 {
|
|
|
|
|
return errors.New("--overlay-font-size must be positive")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.BottomMargin < 0 {
|
|
|
|
|
return errors.New("--overlay-bottom-margin cannot be negative")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.MaxWidth <= 0 {
|
|
|
|
|
return errors.New("--overlay-max-width must be positive")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.FinalTimeout < 0 {
|
|
|
|
|
return errors.New("--overlay-final-timeout cannot be negative")
|
|
|
|
|
}
|
|
|
|
|
if s.Overlay.Monitor == "" {
|
|
|
|
|
return errors.New("--overlay-monitor cannot be empty")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|