docs: update README with detailed desktop features and setup requirements

This commit is contained in:
2026-07-17 14:41:10 +03:00
parent 55fbd357b0
commit 54fb100d3c
35 changed files with 3469 additions and 162 deletions
+100 -57
View File
@@ -3,12 +3,12 @@ package config
import (
"errors"
"flag"
"strings"
"time"
)
const (
CurrentConfigVersion = 1
DefaultModelsDir = "models"
DefaultVADThreshold = 0.5
DefaultOverlayFontFamily = "Sans"
@@ -36,21 +36,18 @@ type Settings struct {
ModelShutdownTimeout time.Duration
}
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 gate below which recognition stays idle")
flag.Float64Var(&settings.VADThreshold, "vad-threshold", DefaultVADThreshold, "speech detection sensitivity from 0 to 1; higher rejects more non-speech")
flag.BoolVar(&settings.ModelAutoRestart, "model-auto-restart", true, "restart the isolated model worker after a crash or timeout")
flag.DurationVar(&settings.ModelTimeout, "model-timeout", DefaultModelTimeout, "maximum model startup or processing time; zero disables hang detection")
flag.DurationVar(&settings.ModelShutdownTimeout, "model-shutdown-timeout", DefaultModelShutdownTimeout, "time allowed for model flush and cleanup before force-killing the worker")
flag.Parse()
settings.Mode = Mode(mode)
return settings
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
@@ -81,44 +78,70 @@ type OverlaySettings struct {
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 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 gate below which recognition stays idle")
flags.Float64Var(&desktop.VADThreshold, "vad-threshold", DefaultVADThreshold, "speech detection sensitivity from 0 to 1; higher rejects more non-speech")
flags.BoolVar(&desktop.ModelAutoRestart, "model-auto-restart", true, "restart the isolated model worker after a crash or timeout")
flags.DurationVar(&desktop.ModelTimeout, "model-timeout", DefaultModelTimeout, "maximum model startup or processing time; zero disables hang detection")
flags.DurationVar(&desktop.ModelShutdownTimeout, "model-shutdown-timeout", DefaultModelShutdownTimeout, "time allowed for model flush and cleanup before force-killing the worker")
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.StringVar(&desktop.Overlay.FontFamily, "overlay-font-family", DefaultOverlayFontFamily, "caption font family")
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, "fixed caption width in logical pixels before the monitor safety cap")
flags.StringVar(&desktop.Overlay.Monitor, "overlay-monitor", "auto", "monitor selection: auto, numeric index, or connector name")
flags.DurationVar(&desktop.Overlay.FinalTimeout, "overlay-final-timeout", MinimumOverlayFinalLifetime, "completed-caption lifetime; zero keeps lines until the six-line cap removes them")
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
}
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")
@@ -148,32 +171,52 @@ func (s DesktopSettings) Validate() error {
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 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.Overlay.Opacity < 0 || s.Overlay.Opacity > 1 {
if s.Opacity < 0 || s.Opacity > 1 {
return errors.New("--overlay-opacity must be between 0 and 1")
}
if s.Overlay.FontSize <= 0 {
if s.FontSize <= 0 {
return errors.New("--overlay-font-size must be positive")
}
if strings.TrimSpace(s.Overlay.FontFamily) == "" {
if strings.TrimSpace(s.FontFamily) == "" {
return errors.New("--overlay-font-family cannot be empty")
}
if s.Overlay.BottomMargin < 0 {
if s.BottomMargin < 0 {
return errors.New("--overlay-bottom-margin cannot be negative")
}
if s.Overlay.MaxWidth <= 0 {
if s.MaxWidth <= 0 {
return errors.New("--overlay-max-width must be positive")
}
if s.Overlay.FinalTimeout < 0 {
if s.FinalTimeout < 0 {
return errors.New("--overlay-final-timeout cannot be negative")
}
if s.Overlay.FinalTimeout > 0 && s.Overlay.FinalTimeout < MinimumOverlayFinalLifetime {
if s.FinalTimeout > 0 && s.FinalTimeout < MinimumOverlayFinalLifetime {
return errors.New("--overlay-final-timeout must be zero or at least 3s")
}
if s.Overlay.Monitor == "" {
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
}