Files
36Wisdom/cmd/gentss/main.go
T
2026-08-14 13:45:02 +08:00

108 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
// gentss:离线生成 TTS 朗读音频(Edge TTS 神经网络语音)并回填各 audio 字段。
// 用法:
//
// go run ./cmd/gentss --list # 打印待生成清单
// go run ./cmd/gentss --strategy=1 # 只生成第 1 计(8 节点剧本已导入)
// go run ./cmd/gentss --only=script|content|scene|option|teach # 只生成指定内容源
// go run ./cmd/gentss --force # 覆盖已生成 mp3 重新合成
//
// 幂等:mp3 文件已存在且字段已回填 → 跳过(--force 覆盖);文件在但字段空 → 只补回填。
// 配置在 config.yml 的 tts 段;产物落 ui-src/static/generated/audio/URL /static/generated/audio/...(与 PNG 素材同挂载)。
import (
"flag"
"fmt"
"sync"
"time"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
var staticDir = "ui-src/static/generated" // 打包入客户端的音频目录
type genFlags struct {
listOnly bool
only string // script | content | scene | option | teach
strategy int // 0 = 全部计策
force bool
}
type ttsConfig struct {
voice string
rate string
version string
concurrency int
timeout int
retries int
}
func main() {
fl := parseFlags()
cfg := loadTTSConfig()
if fl.listOnly {
printInventory(fl)
return
}
client := &ttsClient{
voice: cfg.voice,
rate: cfg.rate,
version: cfg.version,
timeout: time.Duration(cfg.timeout) * time.Second,
retries: cfg.retries,
}
tasks := enumerateTasks(fl)
if len(tasks) == 0 {
fmt.Println("[gentss] 无待生成任务")
return
}
sem := make(chan struct{}, cfg.concurrency)
var wg sync.WaitGroup
for _, t := range tasks {
t.force = fl.force
wg.Add(1)
sem <- struct{}{}
go func(t task) {
defer wg.Done()
defer func() { <-sem }()
if err := runTask(client, t); err != nil {
glog.Errorf(ctx, "[gentss] %s 失败: %v", taskDesc(t), err)
}
}(t)
}
wg.Wait()
fmt.Println("[gentss] 完成")
}
func parseFlags() genFlags {
var fl genFlags
flag.BoolVar(&fl.listOnly, "list", false, "只打印待生成清单")
flag.StringVar(&fl.only, "only", "", "script | content | scene | option | teach,默认全部")
flag.IntVar(&fl.strategy, "strategy", 0, "只处理指定计策(strategy_id),0=全部")
flag.BoolVar(&fl.force, "force", false, "覆盖已生成音频重新合成")
flag.Parse()
return fl
}
func loadTTSConfig() ttsConfig {
return ttsConfig{
voice: g.Cfg().MustGet(ctx, "tts.voice", "zh-CN-XiaoxiaoNeural").String(),
rate: g.Cfg().MustGet(ctx, "tts.rate", "+0%").String(),
version: g.Cfg().MustGet(ctx, "tts.version", "1-143.0.3650.75").String(),
concurrency: g.Cfg().MustGet(ctx, "tts.concurrency", 2).Int(),
timeout: g.Cfg().MustGet(ctx, "tts.timeout", 60).Int(),
retries: g.Cfg().MustGet(ctx, "tts.retries", 3).Int(),
}
}
func taskDesc(t task) string {
if t.kind == "script" {
return fmt.Sprintf("node %d", t.nodeID)
}
return t.relPath
}