采集面:mediafetch 新增 inline_voice.go(音频白名单,与 documents.ExtAllowed 互不污染; 复用既有来源白名单 / 逐跳校验 / 边读边限 / 超时);四个适配器零改动。 消费面:channelbridge 在流水线最前端把「语音字节 → 文本」归一并写回 msg.Content; inbound.go 的丢空判据放宽为「无文本且无可用媒体」。 微信优先平台自带转写(Content 非空 ⇒ 不调 ASR,不重复付费)。 降级(主人定的两层):新增 voice/outcome.go 的 STTOutcome 识破静默档位; errors.go 分类鉴权失败/欠费/限流(CircuitWorthy);voice_breaker.go 临时熔断 + 退避(TTL 恢复); 无论是否熔断,消息面都落一行中文诚实提示(绝不静默、绝不留空消息)。 独立短超时:VOICE_STT_TIMEOUT_SEC(默认 20s,同步 .env.example)。 kook 不再把 CDN 地址写进 Content(地址仍在 Media[].URL)。 设计先行:design/06 §1.1/§1.2、design/08 §4.1、design/24 §1.3/§4.2、开源对照清单。 变异 5 条(静默档位不带 Cause / Transcribed 误判 / 熔断恒放行 / ChatText 退化 / 平台文字判据取反)。 诚实边界:实施时官方音频规格尚未抓到 ⇒ MaxVoiceBytes 自定 5 MiB(保守)、采样率仍 16 kHz; 官方规格随后已抓到(10 MB / 5 min;格式含 amr、无 silk),对齐留待下次改动。
85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
package voice
|
|
|
|
import "context"
|
|
|
|
// Provider 是语音降级链上的一级。它可实现零个、一个
|
|
// 或两个方向;返回 nil 表示该方向在此级上不可用,
|
|
// 链会移向下一级。
|
|
type Provider interface {
|
|
// Name 返回稳定的提供方标识。
|
|
Name() string
|
|
// STT 返回语音转文字实现,提供方无法转写时
|
|
// 返回 nil。
|
|
STT() STT
|
|
// TTS 返回文字转语音实现,提供方无法合成时
|
|
// 返回 nil。
|
|
TTS() TTS
|
|
}
|
|
|
|
// StaticProvider 是名字固定、STT/TTS 在构造时设定的
|
|
// Provider。下面各具体提供方都用它。
|
|
type StaticProvider struct {
|
|
name string
|
|
stt STT
|
|
tts TTS
|
|
}
|
|
|
|
// Name 返回提供方标识。
|
|
func (p *StaticProvider) Name() string { return p.name }
|
|
|
|
// STT 返回配置的 STT,可能为 nil。
|
|
func (p *StaticProvider) STT() STT { return p.stt }
|
|
|
|
// TTS 返回配置的 TTS,可能为 nil。
|
|
func (p *StaticProvider) TTS() TTS { return p.tts }
|
|
|
|
// NewProvider 用给定名字及其支持的可选方向构造一个
|
|
// Provider。
|
|
func NewProvider(name string, stt STT, tts TTS) *StaticProvider {
|
|
return &StaticProvider{name: name, stt: stt, tts: tts}
|
|
}
|
|
|
|
// CloudProvider 是链上的云端一级。它是一个适配点:
|
|
// 真实的 STT/TTS 实现经 NewProvider 与云端适配器接入。
|
|
func CloudProvider(stt STT, tts TTS) *StaticProvider {
|
|
return NewProvider("cloud", stt, tts)
|
|
}
|
|
|
|
// LocalProvider 是链上的本地模型一级。
|
|
func LocalProvider(stt STT, tts TTS) *StaticProvider {
|
|
return NewProvider("local", stt, tts)
|
|
}
|
|
|
|
// EdgeTTSProvider 是 edge-tts 一级(仅文字转语音)。
|
|
func EdgeTTSProvider(tts TTS) *StaticProvider {
|
|
return NewProvider("edge-tts", nil, tts)
|
|
}
|
|
|
|
// EspeakProvider 是 espeak 一级(仅文字转语音)。
|
|
func EspeakProvider(tts TTS) *StaticProvider {
|
|
return NewProvider("espeak", nil, tts)
|
|
}
|
|
|
|
// SilentProviderName 是末端静默档位(SilentProvider)的稳定标识。调用方靠它**识破
|
|
// 静默降级**:该档位总是「成功」(空文本 + 无错误),因此只看转写文本会把「没有可用
|
|
// 的识别后端」当成一次成功的空转写(切片 IM-ASR)。
|
|
const SilentProviderName = "silent"
|
|
|
|
// SilentProvider 是末端一级。它总是成功,返回空
|
|
// 转写与空音频,因此包含它的链永不报错。
|
|
func SilentProvider() *StaticProvider {
|
|
return NewProvider(SilentProviderName, silenceSTT{}, silenceTTS{})
|
|
}
|
|
|
|
// silenceSTT 是静默 STT 一级;它返回空转写。
|
|
type silenceSTT struct{}
|
|
|
|
func (silenceSTT) STT(_ context.Context, _ Audio) (string, error) { return "", nil }
|
|
|
|
// silenceTTS 是静默 TTS 一级;它返回空音频。
|
|
type silenceTTS struct{}
|
|
|
|
func (silenceTTS) TTS(_ context.Context, _ string) (Audio, error) {
|
|
return Audio{}, nil
|
|
}
|