package config import ( "errors" "os" "path/filepath" "strconv" "strings" "github.com/BurntSushi/toml" ) type Config struct { Hotkey string `toml:"hotkey"` FfmpegInput string `toml:"ffmpeg_input"` WhisperModel string `toml:"whisper_model"` WhisperLang string `toml:"whisper_lang"` WhisperDevice string `toml:"whisper_device"` WhisperExtraArgs string `toml:"whisper_extra_args"` RecordTimeoutSec int `toml:"record_timeout_sec"` WhisperTimeoutSec int `toml:"whisper_timeout_sec"` SegmentSec int `toml:"segment_sec"` Streaming bool `toml:"streaming"` InjectionBackend string `toml:"injection_backend"` } func DefaultPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".config", "lel", "config.toml") } func Defaults() Config { return Config{ Hotkey: "Cmd+m", FfmpegInput: "pulse:default", WhisperModel: "base", WhisperLang: "en", WhisperDevice: "cpu", WhisperExtraArgs: "", RecordTimeoutSec: 120, WhisperTimeoutSec: 300, SegmentSec: 5, Streaming: false, InjectionBackend: "clipboard", } } func Load(path string) (Config, error) { cfg := Defaults() if path == "" { path = DefaultPath() } if _, err := os.Stat(path); err == nil { if _, err := toml.DecodeFile(path, &cfg); err != nil { return cfg, err } } applyEnv(&cfg) if strings.TrimSpace(cfg.Hotkey) == "" { return cfg, errors.New("hotkey cannot be empty") } if cfg.RecordTimeoutSec <= 0 { return cfg, errors.New("record_timeout_sec must be > 0") } if cfg.WhisperTimeoutSec <= 0 { return cfg, errors.New("whisper_timeout_sec must be > 0") } return cfg, nil } func applyEnv(cfg *Config) { if v := os.Getenv("WHISPER_MODEL"); v != "" { cfg.WhisperModel = v } if v := os.Getenv("WHISPER_LANG"); v != "" { cfg.WhisperLang = v } if v := os.Getenv("WHISPER_DEVICE"); v != "" { cfg.WhisperDevice = v } if v := os.Getenv("WHISPER_EXTRA_ARGS"); v != "" { cfg.WhisperExtraArgs = v } if v := os.Getenv("WHISPER_FFMPEG_IN"); v != "" { cfg.FfmpegInput = v } if v := os.Getenv("WHISPER_STREAM"); v != "" { cfg.Streaming = parseBool(v) } if v := os.Getenv("WHISPER_SEGMENT_SEC"); v != "" { if n, err := strconv.Atoi(v); err == nil { cfg.SegmentSec = n } } if v := os.Getenv("WHISPER_TIMEOUT_SEC"); v != "" { if n, err := strconv.Atoi(v); err == nil { cfg.WhisperTimeoutSec = n } } if v := os.Getenv("LEL_RECORD_TIMEOUT_SEC"); v != "" { if n, err := strconv.Atoi(v); err == nil { cfg.RecordTimeoutSec = n } } if v := os.Getenv("LEL_HOTKEY"); v != "" { cfg.Hotkey = v } if v := os.Getenv("LEL_INJECTION_BACKEND"); v != "" { cfg.InjectionBackend = v } } func parseBool(v string) bool { v = strings.ToLower(strings.TrimSpace(v)) return v == "1" || v == "true" || v == "yes" || v == "on" }