129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package volcano
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
"github.com/volcano-tts/tts-api/adapter/provider"
|
||
|
|
"github.com/volcano-tts/tts-api/dto"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Provider 是火山 v3 TTS 的 provider.Provider 实现。
|
||
|
|
// 内部持有复用的 HTTPClient(keep-alive)。
|
||
|
|
type Provider struct {
|
||
|
|
client *HTTPClient
|
||
|
|
}
|
||
|
|
|
||
|
|
// New 构造火山 provider。在 init() 里注册,主干经 provider.Get("volcano") 取用。
|
||
|
|
func New() *Provider {
|
||
|
|
return &Provider{client: NewHTTPClient()}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Name 实现 provider.Provider。
|
||
|
|
func (p *Provider) Name() string { return "volcano" }
|
||
|
|
|
||
|
|
// Capabilities 实现 provider.Provider。
|
||
|
|
// 火山支持多音色、倍率语速、字符计费,并需要 resource_id 额外维度。
|
||
|
|
func (p *Provider) Capabilities() provider.Capabilities {
|
||
|
|
return provider.Capabilities{
|
||
|
|
Formats: []string{"mp3", "wav", "pcm", "ogg_opus", "aac", "flac"},
|
||
|
|
Speed: true,
|
||
|
|
Usage: true,
|
||
|
|
Voices: true,
|
||
|
|
ExtraScopes: true,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Synthesize 实现 provider.Provider:从归一化 Request 还原火山 Options 后执行合成。
|
||
|
|
// 行为与原 Synthesis(controller 直接调用)完全一致,仅入口形态变化。
|
||
|
|
func (p *Provider) Synthesize(ctx context.Context, req provider.Request, mtr provider.MetricsRecorder) (*dto.SynthesisResult, error) {
|
||
|
|
if mtr == nil {
|
||
|
|
mtr = provider.NoopMetrics()
|
||
|
|
}
|
||
|
|
opts := optionsFromRequest(req)
|
||
|
|
return synthesize(ctx, p.client, opts, req.Format, mtr)
|
||
|
|
}
|
||
|
|
|
||
|
|
// optionsFromRequest 把 provider.Request 还原成火山 Options。
|
||
|
|
// 厂商私有参数从 Credentials.Scope(resource_id)与 Request.Extra 取回,与 BuildRequest 对称。
|
||
|
|
func optionsFromRequest(req provider.Request) Options {
|
||
|
|
opts := Options{
|
||
|
|
APIKey: req.Credentials.APIKey,
|
||
|
|
ResourceID: req.Credentials.Scope["resource_id"],
|
||
|
|
Text: req.Text,
|
||
|
|
Speaker: req.VoiceKey,
|
||
|
|
Model: req.Model,
|
||
|
|
Format: req.Format,
|
||
|
|
SampleRate: req.SampleRate,
|
||
|
|
SpeechRate: convertSpeedToSpeechRate(req.Speed),
|
||
|
|
UID: "uid",
|
||
|
|
}
|
||
|
|
if v := req.Extra["uid"]; v != "" {
|
||
|
|
opts.UID = v
|
||
|
|
}
|
||
|
|
if v := req.Extra["bit_rate"]; v != "" {
|
||
|
|
if n, err := strconv.Atoi(v); err == nil {
|
||
|
|
opts.BitRate = n
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if v := req.Extra["loudness_rate"]; v != "" {
|
||
|
|
if n, err := strconv.Atoi(v); err == nil {
|
||
|
|
opts.LoudnessRate = n
|
||
|
|
}
|
||
|
|
}
|
||
|
|
opts.EnableSubtitle = req.Extra["enable_subtitle"] == "1"
|
||
|
|
opts.EnableTimestamp = req.Extra["enable_timestamp"] == "1"
|
||
|
|
if v := req.Extra["additions"]; v != "" {
|
||
|
|
var adds Additions
|
||
|
|
if err := json.Unmarshal([]byte(v), &adds); err == nil {
|
||
|
|
opts.Additions = &adds
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return opts
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildRequest 把火山 Options 映射成归一化 provider.Request。
|
||
|
|
// 阶段0使用:setting 继续构造 volcano.Options,再经此函数转成 provider.Request 交给主干,
|
||
|
|
// 从而让 controller 不再依赖具体实现。
|
||
|
|
// 阶段2(配置命名空间化)后,此函数被"直接构造 provider.Request"取代。
|
||
|
|
func BuildRequest(opts Options) provider.Request {
|
||
|
|
req := provider.Request{
|
||
|
|
Text: opts.Text,
|
||
|
|
VoiceKey: opts.Speaker,
|
||
|
|
Model: opts.Model,
|
||
|
|
SampleRate: opts.SampleRate,
|
||
|
|
Credentials: provider.Credentials{
|
||
|
|
APIKey: opts.APIKey,
|
||
|
|
Scope: map[string]string{"resource_id": opts.ResourceID},
|
||
|
|
},
|
||
|
|
Extra: map[string]string{},
|
||
|
|
}
|
||
|
|
if opts.UID != "" && opts.UID != "uid" {
|
||
|
|
req.Extra["uid"] = opts.UID
|
||
|
|
}
|
||
|
|
if opts.BitRate != 0 {
|
||
|
|
req.Extra["bit_rate"] = strconv.Itoa(opts.BitRate)
|
||
|
|
}
|
||
|
|
if opts.LoudnessRate != 0 {
|
||
|
|
req.Extra["loudness_rate"] = strconv.Itoa(opts.LoudnessRate)
|
||
|
|
}
|
||
|
|
if opts.EnableSubtitle {
|
||
|
|
req.Extra["enable_subtitle"] = "1"
|
||
|
|
}
|
||
|
|
if opts.EnableTimestamp {
|
||
|
|
req.Extra["enable_timestamp"] = "1"
|
||
|
|
}
|
||
|
|
if opts.Additions != nil && !opts.Additions.IsZero() {
|
||
|
|
if b, err := json.Marshal(opts.Additions); err == nil {
|
||
|
|
req.Extra["additions"] = string(b)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return req
|
||
|
|
}
|
||
|
|
|
||
|
|
// init 注册火山 provider。主干经 provider.Get("volcano") 取用,新增上游不改主干。
|
||
|
|
func init() {
|
||
|
|
provider.Register(New())
|
||
|
|
}
|