114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package volcano
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// requestBody 是真正发到上游 v3 端点的 JSON 顶层结构。
|
|
type requestBody struct {
|
|
User ttsUser `json:"user"`
|
|
Namespace string `json:"namespace"`
|
|
ReqParams ttsReqParams `json:"req_params"`
|
|
}
|
|
|
|
type ttsUser struct {
|
|
UID string `json:"uid"`
|
|
}
|
|
|
|
type ttsReqParams struct {
|
|
Text string `json:"text"`
|
|
Speaker string `json:"speaker"`
|
|
Model string `json:"model,omitempty"`
|
|
AudioParams ttsAudioParams `json:"audio_params"`
|
|
Additions string `json:"additions,omitempty"` // 注意:字符串
|
|
}
|
|
|
|
type ttsAudioParams struct {
|
|
Format string `json:"format"`
|
|
SampleRate int `json:"sample_rate"`
|
|
BitRate int `json:"bit_rate,omitempty"`
|
|
SpeechRate int `json:"speech_rate"`
|
|
LoudnessRate int `json:"loudness_rate,omitempty"`
|
|
EnableSubtitle bool `json:"enable_subtitle,omitempty"`
|
|
EnableTimestamp bool `json:"enable_timestamp,omitempty"`
|
|
}
|
|
|
|
// buildRequest 把 Options 序列化为上游请求体 JSON。
|
|
func buildRequest(opts Options) ([]byte, error) {
|
|
if opts.Text == "" {
|
|
return nil, fmt.Errorf("volcano: text is required")
|
|
}
|
|
if opts.Speaker == "" {
|
|
return nil, fmt.Errorf("volcano: speaker is required")
|
|
}
|
|
if opts.ResourceID == "" {
|
|
return nil, fmt.Errorf("volcano: resource id is required")
|
|
}
|
|
if opts.APIKey == "" {
|
|
return nil, fmt.Errorf("volcano: api key is required")
|
|
}
|
|
|
|
body := requestBody{
|
|
User: ttsUser{UID: opts.UID},
|
|
Namespace: "UnidirectionalTTS",
|
|
ReqParams: ttsReqParams{
|
|
Text: opts.Text,
|
|
Speaker: opts.Speaker,
|
|
Model: opts.Model,
|
|
AudioParams: ttsAudioParams{
|
|
Format: opts.Format,
|
|
SampleRate: opts.SampleRate,
|
|
BitRate: opts.BitRate,
|
|
SpeechRate: opts.SpeechRate,
|
|
LoudnessRate: opts.LoudnessRate,
|
|
EnableSubtitle: opts.EnableSubtitle,
|
|
EnableTimestamp: opts.EnableTimestamp,
|
|
},
|
|
},
|
|
}
|
|
|
|
if opts.Additions != nil && !opts.Additions.IsZero() {
|
|
// 文档明确 additions 字段为 JSON 字符串。
|
|
raw, err := json.Marshal(opts.Additions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal additions: %w", err)
|
|
}
|
|
body.ReqParams.Additions = string(raw)
|
|
}
|
|
|
|
raw, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal request: %w", err)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// convertSpeedToSpeechRate 把 OpenAI 风格的 speed(倍率)转换为 speech_rate(百分比)。
|
|
// speech_rate 范围 [-50, 100],对应 0.5x ~ 2.0x。
|
|
func convertSpeedToSpeechRate(speed float64) int {
|
|
if speed <= 0 {
|
|
speed = 1.0
|
|
}
|
|
rate := int((speed - 1.0) * 100)
|
|
if rate < -50 {
|
|
rate = -50
|
|
}
|
|
if rate > 100 {
|
|
rate = 100
|
|
}
|
|
return rate
|
|
}
|
|
|
|
// resolveUpstreamFormat 决定上游实际请求的 format。
|
|
// - 客户端要求 wav -> 上游走 pcm,我们本地拼 header
|
|
// - 其他 -> 直接用 clientFormat
|
|
//
|
|
// sampleRate 在 wav 走 pcm 的情况下也按原样传给上游(影响 PCM 的实际采样率)。
|
|
func resolveUpstreamFormat(clientFormat string) string {
|
|
if clientFormat == "wav" {
|
|
return "pcm"
|
|
}
|
|
return clientFormat
|
|
}
|