package tts import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" "go.uber.org/zap" ) // OpenAIService 基于 OpenAI TTS API 的语音合成实现。 type OpenAIService struct { apiKey string model string voice string speed float64 endpoint string timeout time.Duration logger *zap.SugaredLogger client *http.Client } // NewOpenAIService 创建 OpenAI TTS 服务。 // model、voice、endpoint 由 config 层保证非空。 func NewOpenAIService(apiKey, model, voice, endpoint string, speed float64, timeoutSec, httpClientTimeoutSec int, logger *zap.SugaredLogger) *OpenAIService { if speed <= 0 { speed = 1.0 } timeout := time.Duration(timeoutSec) * time.Second if timeout <= 0 { timeout = 5 * time.Second } httpClientTimeout := time.Duration(httpClientTimeoutSec) * time.Second if httpClientTimeout <= 0 { httpClientTimeout = 30 * time.Second } return &OpenAIService{ apiKey: apiKey, model: model, voice: voice, speed: speed, endpoint: endpoint, timeout: timeout, logger: logger, client: &http.Client{Timeout: httpClientTimeout}, } } // ttsRequest OpenAI TTS API 请求。 type ttsRequest struct { Model string `json:"model"` Input string `json:"input"` Voice string `json:"voice"` ResponseFormat string `json:"response_format"` Speed float64 `json:"speed"` } // SynthesizeStream 实现 tts.Service。从 textStream 读取句子,逐句调用 OpenAI TTS API。 func (o *OpenAIService) SynthesizeStream(ctx context.Context, textStream <-chan string, opts Options) (<-chan Chunk, error) { voice := opts.Voice if voice == "" { voice = o.voice } speed := opts.Speed if speed <= 0 { speed = o.speed } ch := make(chan Chunk, 4) go func() { defer close(ch) for text := range textStream { if text == "" { continue } audio, err := o.synthesize(ctx, text, voice, speed) if err != nil { o.logger.Warnw("tts: synthesize failed", "error", err, "text", text) // 静默跳过,不中断整个流 continue } select { case ch <- Chunk{Audio: audio, IsLast: true, Final: false}: case <-ctx.Done(): return } } // textStream 关闭,发送 Final 标记 select { case ch <- Chunk{Audio: nil, IsLast: false, Final: true}: case <-ctx.Done(): } }() return ch, nil } // synthesize 调用 OpenAI TTS API 合成单个句子。 func (o *OpenAIService) synthesize(ctx context.Context, text, voice string, speed float64) ([]byte, error) { // 单句超时 ctx, cancel := context.WithTimeout(ctx, o.timeout) defer cancel() body := ttsRequest{ Model: o.model, Input: text, Voice: voice, ResponseFormat: "mp3", Speed: speed, } payload, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("tts: marshal request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, o.endpoint+"/audio/speech", bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("tts: create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+o.apiKey) resp, err := o.client.Do(req) if err != nil { return nil, fmt.Errorf("tts: send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { errBody, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("tts: api error (status %d): %s", resp.StatusCode, string(errBody)) } // 读取整个 MP3 响应 audio, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("tts: read response: %w", err) } return audio, nil }