package speech import ( "context" "fmt" "os" "path/filepath" ) // LocalVoiceFileStore persists voice sample files on the local filesystem. // All files for a sample share a common prefix: {storageDir}/{sampleID}.{ext} type LocalVoiceFileStore struct { storageDir string } func NewLocalVoiceFileStore(storageDir string) (*LocalVoiceFileStore, error) { if err := os.MkdirAll(storageDir, 0750); err != nil { return nil, fmt.Errorf("create voice storage dir: %w", err) } return &LocalVoiceFileStore{storageDir: storageDir}, nil } func (s *LocalVoiceFileStore) SaveRaw(_ context.Context, sampleID string, data []byte) (string, error) { path := filepath.Join(s.storageDir, sampleID+".ogg") if err := os.WriteFile(path, data, 0600); err != nil { return "", fmt.Errorf("save raw audio: %w", err) } return path, nil } func (s *LocalVoiceFileStore) SaveConverted(_ context.Context, sampleID string, data []byte) (string, error) { path := filepath.Join(s.storageDir, sampleID+".wav") if err := os.WriteFile(path, data, 0600); err != nil { return "", fmt.Errorf("save converted audio: %w", err) } return path, nil } func (s *LocalVoiceFileStore) SaveVTT(_ context.Context, sampleID string, content string) (string, error) { path := filepath.Join(s.storageDir, sampleID+".vtt") if err := os.WriteFile(path, []byte(content), 0600); err != nil { return "", fmt.Errorf("save vtt: %w", err) } return path, nil } // Cleanup removes all files associated with a sample ID (.ogg, .wav, .vtt). // Missing files are silently ignored. func (s *LocalVoiceFileStore) Cleanup(_ context.Context, sampleID string) error { for _, ext := range []string{".ogg", ".wav", ".vtt"} { path := filepath.Join(s.storageDir, sampleID+ext) if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return fmt.Errorf("cleanup %s: %w", ext, err) } } return nil }