package audio import ( "context" "fmt" "os" "os/exec" "path/filepath" "strings" "time" ) type Recorder struct { Input string } type RecordResult struct { WavPath string TempDir string } func (r Recorder) Start(ctx context.Context) (*exec.Cmd, *RecordResult, error) { tmpdir, err := os.MkdirTemp("", "lel-") if err != nil { return nil, nil, err } wav := filepath.Join(tmpdir, "mic.wav") args := []string{"-hide_banner", "-loglevel", "error"} args = append(args, ffmpegInputArgs(r.Input)...) args = append(args, "-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", wav) cmd := exec.CommandContext(ctx, "ffmpeg", args...) if err := cmd.Start(); err != nil { _ = os.RemoveAll(tmpdir) return nil, nil, err } return cmd, &RecordResult{WavPath: wav, TempDir: tmpdir}, nil } func WaitWithTimeout(cmd *exec.Cmd, timeout time.Duration) error { done := make(chan error, 1) go func() { done <- cmd.Wait() }() select { case err := <-done: return err case <-time.After(timeout): if cmd.Process != nil { _ = cmd.Process.Kill() } return fmt.Errorf("process timeout after %s", timeout) } } func ffmpegInputArgs(spec string) []string { if spec == "" { spec = "pulse:default" } kind := spec name := "default" if idx := strings.Index(spec, ":"); idx != -1 { kind = spec[:idx] name = spec[idx+1:] } return []string{"-f", kind, "-i", name} }