引包目录名调整

This commit is contained in:
2026-04-27 14:02:43 +08:00
parent 6ba2262a17
commit 11bf15e72b
48 changed files with 56 additions and 58 deletions

View File

@@ -1,195 +0,0 @@
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
var text = "欢迎使用红动未来数字人服务平台我们将为您提供最优质的AI数字人解决方案。"
type TTSCommonResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Text string `json:"text"`
Audio string `json:"audio"`
}
func main() {
// 获取当前工作目录
outputDir, err := os.Getwd()
if err != nil {
fmt.Printf("获取当前目录失败: %v\n", err)
os.Exit(1)
}
// 查找项目根目录(向上查找包含 go.mod 的目录)
outputDir = findProjectRoot(outputDir)
// 验证根目录是否正确(检查是否有 go.mod
if _, err := os.Stat(outputDir + "/go.mod"); err != nil {
fmt.Printf("未找到项目根目录,当前目录: %s\n", outputDir)
os.Exit(1)
}
fmt.Println("=================== TTS测试开始 ===================")
fmt.Printf("输出目录: %s\n", outputDir)
fmt.Printf("随机文本: %s\n", text)
fmt.Printf("请求URL: http://127.0.0.1:8000/tts\n")
// 创建带超时的 HTTP 客户端120秒超时
client := &http.Client{
Timeout: 120 * time.Second,
}
resp, err := client.Post("http://127.0.0.1:8000/tts", "application/json", bytes.NewBufferString(fmt.Sprintf(`"%s"`, text)))
if err != nil {
fmt.Printf("请求失败: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
// 打印响应头
fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type"))
fmt.Printf("Content-Length: %s\n", resp.Header.Get("Content-Length"))
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应失败: %v\n", err)
os.Exit(1)
}
fmt.Printf("状态码: %d, 响应大小: %d字节\n", resp.StatusCode, len(body))
// 打印响应内容的前200字节用于调试
if len(body) > 0 {
previewLen := minInt(200, len(body))
fmt.Printf("响应内容预览(前%d字节): ", previewLen)
if len(body) >= 4 && string(body[:4]) == "RIFF" {
// WAV文件头
fmt.Printf("WAV文件格式 (RIFF...)\n")
} else if len(body) >= 3 && string(body[:3]) == "ID3" {
// MP3 ID3标签
fmt.Printf("MP3 ID3格式\n")
} else if len(body) >= 2 && body[0] == 0xFF && (body[1]&0xE0) == 0xE0 {
// MP3帧同步
fmt.Printf("MP3帧格式\n")
} else {
// 可能是JSON或其他格式
fmt.Printf("%s\n", string(body[:previewLen]))
}
} else {
fmt.Printf("响应内容为空!\n")
os.Exit(1)
}
// 尝试解析JSON响应包含base64音频
var commonResp TTSCommonResponse
var audioData []byte
var ext string
if json.Unmarshal(body, &commonResp) == nil && commonResp.Audio != "" && commonResp.Audio != "base64_placeholder" {
fmt.Printf("检测到JSON响应code=%d, msg=%s\n", commonResp.Code, commonResp.Msg)
fmt.Printf("Audio字段长度: %d 字符\n", len(commonResp.Audio))
// 检查是否成功
if commonResp.Code != 0 {
fmt.Printf("TTS服务返回错误: %s\n", commonResp.Msg)
os.Exit(1)
}
// 解码base64音频数据
decoded, err := base64.StdEncoding.DecodeString(commonResp.Audio)
if err != nil {
fmt.Printf("base64解码失败: %v\n", err)
os.Exit(1)
}
if len(decoded) == 0 {
fmt.Printf("解码后数据为空!\n")
os.Exit(1)
}
audioData = decoded
fmt.Printf("解码后音频数据大小: %d 字节\n", len(audioData))
// 根据解码后的音频数据格式决定扩展名
if len(audioData) >= 4 && string(audioData[:4]) == "RIFF" {
ext = ".wav"
fmt.Printf("检测到WAV格式\n")
} else if len(audioData) >= 3 && string(audioData[:3]) == "ID3" || (len(audioData) >= 2 && audioData[0] == 0xFF && (audioData[1]&0xE0) == 0xE0) {
ext = ".mp3"
fmt.Printf("检测到MP3格式\n")
} else {
ext = ".wav" // 默认wav
fmt.Printf("未知格式,默认保存为 .wav\n")
}
} else {
// 直接是二进制音频数据
audioData = body
// 根据音频数据格式决定扩展名
if len(audioData) >= 4 && string(audioData[:4]) == "RIFF" {
ext = ".wav"
} else if len(audioData) >= 3 && string(audioData[:3]) == "ID3" || (len(audioData) >= 2 && audioData[0] == 0xFF && (audioData[1]&0xE0) == 0xE0) {
ext = ".mp3"
} else {
ext = ".wav" // 默认wav
}
}
// 保存音频文件
filename := fmt.Sprintf("%s/tts_output_%d%s", outputDir, time.Now().Unix(), ext)
if err = os.WriteFile(filename, audioData, 0644); err != nil {
fmt.Printf("写文件失败: %v\n", err)
os.Exit(1)
}
fmt.Printf("音频已保存: %s (%d字节)\n", filename, len(audioData))
fmt.Println("=================== TTS测试成功 ===================")
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
// findProjectRoot 查找项目根目录(包含 go.mod 的目录)
func findProjectRoot(startDir string) string {
dir := startDir
for {
// 检查当前目录是否有 go.mod
if _, err := os.Stat(dir + "/go.mod"); err == nil {
return dir
}
// 如果已经是根目录或无法继续向上查找,返回当前目录
parentDir := dir[:maxInt(0, len(dir)-len("/"+getLastPathSegment(dir)))]
if parentDir == dir || parentDir == "" {
return startDir
}
dir = parentDir
}
}
// getLastPathSegment 获取路径的最后一部分
func getLastPathSegment(path string) string {
if idx := strings.LastIndex(path, "/"); idx != -1 {
return path[idx+1:]
}
return path
}

View File

@@ -1,52 +0,0 @@
package consts
// Age 年龄段类型
type Age string
// 年龄段常量
const (
AgeChild Age = "child" // 儿童
AgeTeenager Age = "teenager" // 青少年
AgeYoung Age = "young" // 青年
AgeMiddle Age = "middle" // 中年
AgeSenior Age = "senior" // 老年
AgeUnlimited Age = "unlimited" // 不限
)
// GetAgeText 获取年龄段文本
func GetAgeText(age string) string {
switch age {
case string(AgeChild):
return "儿童"
case string(AgeTeenager):
return "青少年"
case string(AgeYoung):
return "青年"
case string(AgeMiddle):
return "中年"
case string(AgeSenior):
return "老年"
case string(AgeUnlimited):
return "不限"
default:
return "未知"
}
}
// GetAllAgeKeyValue 获取所有年龄段选项
func GetAllAgeKeyValue() []AgeKeyValue {
return []AgeKeyValue{
{Value: string(AgeChild), Label: "儿童"},
{Value: string(AgeTeenager), Label: "青少年"},
{Value: string(AgeYoung), Label: "青年"},
{Value: string(AgeMiddle), Label: "中年"},
{Value: string(AgeSenior), Label: "老年"},
{Value: string(AgeUnlimited), Label: "不限"},
}
}
// AgeKeyValue 年龄段键值对
type AgeKeyValue struct {
Value string `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,40 +0,0 @@
package consts
// AudioStatus 音频状态类型
type AudioStatus int
// 音频状态常量
const (
AudioStatusGenerating AudioStatus = 0 // 生成中
AudioStatusSuccess AudioStatus = 1 // 成功
AudioStatusFailed AudioStatus = 2 // 失败
)
// GetAudioStatusText 获取音频状态文本
func GetAudioStatusText(status int) string {
switch status {
case int(AudioStatusGenerating):
return "生成中"
case int(AudioStatusSuccess):
return "成功"
case int(AudioStatusFailed):
return "失败"
default:
return "未知"
}
}
// GetAllAudioStatusKeyValue 获取所有音频状态选项
func GetAllAudioStatusKeyValue() []AudioStatusKeyValue {
return []AudioStatusKeyValue{
{Value: int(AudioStatusGenerating), Label: "生成中"},
{Value: int(AudioStatusSuccess), Label: "成功"},
{Value: int(AudioStatusFailed), Label: "失败"},
}
}
// AudioStatusKeyValue 音频状态键值对
type AudioStatusKeyValue struct {
Value int `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,8 +0,0 @@
package consts
// MongoDB集合名称常量
const (
DigitalHumanCollection = "digital_human" // 数字人形象集合
AudioCollection = "audio" // 音频集合
VideoCollection = "video" // 视频集合
)

View File

@@ -1,11 +0,0 @@
package consts
// CustomVoiceStatus 自定义音色状态类型
type CustomVoiceStatus int
const (
CustomVoiceStatusGenerating CustomVoiceStatus = 0 // 生成中
CustomVoiceStatusSuccess CustomVoiceStatus = 1 // 成功
CustomVoiceStatusFailed CustomVoiceStatus = 2 // 失败
)

View File

@@ -1,55 +0,0 @@
package consts
// DigitalHumanStatus 数字人状态类型
type DigitalHumanStatus int
// 数字人状态常量
const (
DigitalHumanStatusInactive DigitalHumanStatus = 0 // 停用
DigitalHumanStatusActive DigitalHumanStatus = 1 // 启用
)
// GetDigitalHumanStatusText 获取数字人状态文本
func GetDigitalHumanStatusText(status int) string {
switch status {
case int(DigitalHumanStatusInactive):
return "停用"
case int(DigitalHumanStatusActive):
return "启用"
default:
return "未知"
}
}
// GetAllDigitalHumanStatusKeyValue 获取所有数字人状态选项
func GetAllDigitalHumanStatusKeyValue() []DigitalHumanStatusKeyValue {
return []DigitalHumanStatusKeyValue{
{Value: int(DigitalHumanStatusInactive), Label: "停用"},
{Value: int(DigitalHumanStatusActive), Label: "启用"},
}
}
// DigitalHumanStatusKeyValue 数字人状态键值对
type DigitalHumanStatusKeyValue struct {
Value int `json:"value"`
Label string `json:"label"`
}
// GetStatusText 获取状态文本(向后兼容)
func GetStatusText(status int) string {
return GetDigitalHumanStatusText(status)
}
// GetAllStatusKeyValue 获取所有状态选项(向后兼容)
func GetAllStatusKeyValue() []StatusKeyValue {
return []StatusKeyValue{
{Value: int(DigitalHumanStatusInactive), Label: "停用"},
{Value: int(DigitalHumanStatusActive), Label: "启用"},
}
}
// StatusKeyValue 状态键值对(向后兼容)
type StatusKeyValue struct {
Value int `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,40 +0,0 @@
package consts
// Gender 性别类型
type Gender string
// 性别常量
const (
GenderMale Gender = "male" // 男
GenderFemale Gender = "female" // 女
GenderOther Gender = "other" // 其他
)
// GetGenderText 获取性别文本
func GetGenderText(gender string) string {
switch gender {
case string(GenderMale):
return "男"
case string(GenderFemale):
return "女"
case string(GenderOther):
return "其他"
default:
return "未知"
}
}
// GetAllGenderKeyValue 获取所有性别选项
func GetAllGenderKeyValue() []GenderKeyValue {
return []GenderKeyValue{
{Value: string(GenderMale), Label: "男"},
{Value: string(GenderFemale), Label: "女"},
{Value: string(GenderOther), Label: "其他"},
}
}
// GenderKeyValue 性别键值对
type GenderKeyValue struct {
Value string `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,7 +0,0 @@
package public
const (
ModelNameCustomVoice = "qwen3-tts-customvoice" // 预设音频
ModelNameVoiceDesign = "qwen3-tts-voicedesign" // 设计音频
ModelNameBase = "qwen3-tts-base" // 克隆音频
)

View File

@@ -1,9 +0,0 @@
package public
const (
TableNameAudio = "digital_human_audio"
TableNameCustomVoice = "digital_human_custom_voice"
TableNameAsyncTaskRef = "digital_human_async_task_ref"
TableNameVideo = "digital_human_video"
TableNameDigitalHuman = "digital_human"
)

View File

@@ -1,51 +0,0 @@
package consts
// Resolution 视频分辨率
type Resolution string
const (
Resolution480P Resolution = "480p" // 标清
Resolution720P Resolution = "720p" // 高清
Resolution1080P Resolution = "1080p" // 全高清
Resolution2K Resolution = "2k" // 2K超清
Resolution4K Resolution = "4k" // 4K超高清
Resolution8K Resolution = "8k" // 8K超高清
)
// Text 获取分辨率文本描述
func (r Resolution) Text() string {
switch r {
case Resolution480P:
return "标清 (480p)"
case Resolution720P:
return "高清 (720p)"
case Resolution1080P:
return "全高清 (1080p)"
case Resolution2K:
return "2K超清 (1440p)"
case Resolution4K:
return "4K超高清 (2160p)"
case Resolution8K:
return "8K超高清 (4320p)"
default:
return string(r)
}
}
// ResolutionKeyValue 分辨率键值对(用于前端选项)
type ResolutionKeyValue struct {
Key string `json:"key"`
Value string `json:"value"`
}
// GetResolutionOptions 获取所有分辨率选项
func GetResolutionOptions() []ResolutionKeyValue {
return []ResolutionKeyValue{
{Key: string(Resolution480P), Value: Resolution480P.Text()},
{Key: string(Resolution720P), Value: Resolution720P.Text()},
{Key: string(Resolution1080P), Value: Resolution1080P.Text()},
{Key: string(Resolution2K), Value: Resolution2K.Text()},
{Key: string(Resolution4K), Value: Resolution4K.Text()},
{Key: string(Resolution8K), Value: Resolution8K.Text()},
}
}

View File

@@ -1,60 +0,0 @@
package consts
// Style 风格类型
type Style string
// 风格常量
const (
StyleBusiness Style = "business" // 商务
StyleCasual Style = "casual" // 休闲
StyleFormal Style = "formal" // 正式
StyleCreative Style = "creative" // 创意
StyleElegant Style = "elegant" // 优雅
StyleFriendly Style = "friendly" // 友好
StyleProfessional Style = "professional" // 专业
StyleUnlimited Style = "unlimited" // 不限
)
// GetStyleText 获取风格文本
func GetStyleText(style string) string {
switch style {
case string(StyleBusiness):
return "商务"
case string(StyleCasual):
return "休闲"
case string(StyleFormal):
return "正式"
case string(StyleCreative):
return "创意"
case string(StyleElegant):
return "优雅"
case string(StyleFriendly):
return "友好"
case string(StyleProfessional):
return "专业"
case string(StyleUnlimited):
return "不限"
default:
return "未知"
}
}
// GetAllStyleKeyValue 获取所有风格选项
func GetAllStyleKeyValue() []StyleKeyValue {
return []StyleKeyValue{
{Value: string(StyleBusiness), Label: "商务"},
{Value: string(StyleCasual), Label: "休闲"},
{Value: string(StyleFormal), Label: "正式"},
{Value: string(StyleCreative), Label: "创意"},
{Value: string(StyleElegant), Label: "优雅"},
{Value: string(StyleFriendly), Label: "友好"},
{Value: string(StyleProfessional), Label: "专业"},
{Value: string(StyleUnlimited), Label: "不限"},
}
}
// StyleKeyValue 风格键值对
type StyleKeyValue struct {
Value string `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,40 +0,0 @@
package consts
// VideoStatus 视频状态类型
type VideoStatus int
// 视频生成状态常量
const (
VideoStatusGenerating VideoStatus = 0 // 生成中
VideoStatusSuccess VideoStatus = 1 // 成功
VideoStatusFailed VideoStatus = 2 // 失败
)
// GetVideoStatusText 获取视频状态文本
func GetVideoStatusText(status int) string {
switch status {
case int(VideoStatusGenerating):
return "生成中"
case int(VideoStatusSuccess):
return "成功"
case int(VideoStatusFailed):
return "失败"
default:
return "未知"
}
}
// GetAllVideoStatusKeyValue 获取所有视频状态选项
func GetAllVideoStatusKeyValue() []VideoStatusKeyValue {
return []VideoStatusKeyValue{
{Value: int(VideoStatusGenerating), Label: "生成中"},
{Value: int(VideoStatusSuccess), Label: "成功"},
{Value: int(VideoStatusFailed), Label: "失败"},
}
}
// VideoStatusKeyValue 视频状态键值对
type VideoStatusKeyValue struct {
Value int `json:"value"`
Label string `json:"label"`
}

View File

@@ -1,30 +0,0 @@
package controller
import (
"context"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/service"
"github.com/gogf/gf/v2/util/gconv"
)
type asyncTask struct{}
// AsyncTask 异步任务同步控制器(供定时任务服务调用)
var AsyncTask = new(asyncTask)
// SyncAsyncTasks 扫描待处理任务并同步状态/转移结果
func (c *asyncTask) SyncAsyncTasks(ctx context.Context, req *dto.SyncAsyncTasksReq) (res *dto.SyncAsyncTasksRes, err error) {
// 从上下文获取用户信息gfdb Hook 会自动填充)
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.AsyncTask.Sync(ctx, req)
}

View File

@@ -1,113 +0,0 @@
package controller
import (
"context"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/service"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/util/gconv"
)
type audio struct{}
// Audio 音频控制器
var Audio = new(audio)
// CreateAudio 创建音频
func (c *audio) CreateAudio(ctx context.Context, req *dto.CreateAudioReq) (res *dto.CreateAudioRes, err error) {
// 从上下文获取用户信息gfdb Hook 会自动填充)
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.Audio.Create(ctx, req)
}
// ListAudio 获取音频列表
func (c *audio) ListAudio(ctx context.Context, req *dto.ListAudioReq) (res *dto.ListAudioRes, err error) {
// 从上下文获取用户信息
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.Audio.List(ctx, req)
}
// GetAudio 获取音频详情
func (c *audio) GetAudio(ctx context.Context, req *dto.GetAudioReq) (res *dto.GetAudioRes, err error) {
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.Audio.GetOne(ctx, req.ID)
}
// UpdateAudio 更新音频
func (c *audio) UpdateAudio(ctx context.Context, req *dto.UpdateAudioReq) (res *beans.ResponseEmpty, err error) {
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
err = service.Audio.Update(ctx, req)
return
}
// DeleteAudio 删除音频
func (c *audio) DeleteAudio(ctx context.Context, req *dto.DeleteAudioReq) (res *beans.ResponseEmpty, err error) {
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
err = service.Audio.Delete(ctx, req.ID)
return
}
// GenerateAudio 重新生成音频
func (c *audio) GenerateAudio(ctx context.Context, req *dto.GenerateAudioReq) (res *dto.GenerateAudioRes, err error) {
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.Audio.Generate(ctx, req)
}
// TTS 文本转语音
func (c *audio) TTS(ctx context.Context, req *dto.TTSReq) (res *dto.TTSRes, err error) {
return service.Audio.TTS(ctx, req)
}
// GetStatusOptions 获取状态选项
func (c *audio) GetStatusOptions(ctx context.Context, req *dto.GetAudioStatusOptionsReq) (res *dto.GetAudioStatusOptionsRes, err error) {
return service.Audio.GetStatusOptions(ctx, req)
}

View File

@@ -1,61 +0,0 @@
package controller
import (
"context"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/service"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/util/gconv"
)
type customVoice struct{}
// CustomVoice 自定义音色控制器
var CustomVoice = new(customVoice)
// CreateCustomVoice 创建自定义音色
func (c *customVoice) CreateCustomVoice(ctx context.Context, req *dto.CreateCustomVoiceReq) (res *dto.CreateCustomVoiceRes, err error) {
// 从上下文获取用户信息
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.CustomVoice.CreateCustomVoice(ctx, req)
}
// ListCustomVoices 获取自定义音色列表
func (c *customVoice) ListCustomVoices(ctx context.Context, req *dto.ListCustomVoiceReq) (res *dto.ListCustomVoiceRes, err error) {
// 从上下文获取用户信息
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
return service.CustomVoice.ListCustomVoices(ctx, req)
}
// DeleteCustomVoice 删除自定义音色
func (c *customVoice) DeleteCustomVoice(ctx context.Context, req *dto.DeleteCustomVoiceReq) (res *beans.ResponseEmpty, err error) {
// 从上下文获取用户信息
if ctx.Value("userId") == nil {
ctx = context.WithValue(ctx, "userId", gconv.String(1))
}
if ctx.Value("userName") == nil {
ctx = context.WithValue(ctx, "userName", "admin")
}
if ctx.Value("tenantId") == nil {
ctx = context.WithValue(ctx, "tenantId", uint64(1))
}
err = service.CustomVoice.DeleteCustomVoice(ctx, req)
return
}

View File

@@ -1,67 +0,0 @@
package controller
import (
"context"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/service"
"gitea.com/red-future/common/beans"
)
type digitalhuman struct{}
// DigitalHuman 数字人形象控制器
var DigitalHuman = new(digitalhuman)
// CreateDigitalHuman 创建数字人形象
func (c *digitalhuman) CreateDigitalHuman(ctx context.Context, req *dto.CreateDigitalHumanReq) (res *dto.CreateDigitalHumanRes, err error) {
return service.DigitalHuman.Create(ctx, req)
}
// ListDigitalHuman 获取数字人形象列表
func (c *digitalhuman) ListDigitalHuman(ctx context.Context, req *dto.ListDigitalHumanReq) (res *dto.ListDigitalHumanRes, err error) {
return service.DigitalHuman.List(ctx, req)
}
// GetDigitalHuman 获取数字人形象详情
func (c *digitalhuman) GetDigitalHuman(ctx context.Context, req *dto.GetDigitalHumanReq) (res *dto.GetDigitalHumanRes, err error) {
return service.DigitalHuman.GetOne(ctx, req.ID)
}
// UpdateDigitalHuman 更新数字人形象
func (c *digitalhuman) UpdateDigitalHuman(ctx context.Context, req *dto.UpdateDigitalHumanReq) (res *beans.ResponseEmpty, err error) {
err = service.DigitalHuman.Update(ctx, req)
return
}
// UpdateDigitalHumanStatus 更新数字人形象状态
func (c *digitalhuman) UpdateDigitalHumanStatus(ctx context.Context, req *dto.UpdateDigitalHumanStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.DigitalHuman.UpdateStatus(ctx, req.ID, req.Status)
return
}
// DeleteDigitalHuman 删除数字人形象
func (c *digitalhuman) DeleteDigitalHuman(ctx context.Context, req *dto.DeleteDigitalHumanReq) (res *beans.ResponseEmpty, err error) {
err = service.DigitalHuman.Delete(ctx, req.ID)
return
}
// GetDigitalHumanStatusOptions 获取数字人状态选项
func (c *digitalhuman) GetDigitalHumanStatusOptions(ctx context.Context, req *dto.GetDigitalHumanStatusOptionsReq) (res *dto.GetDigitalHumanStatusOptionsRes, err error) {
return service.DigitalHuman.GetStatusOptions(ctx, req)
}
// GetGenderOptions 获取性别选项
func (c *digitalhuman) GetGenderOptions(ctx context.Context, req *dto.GetGenderOptionsReq) (res *dto.GetGenderOptionsRes, err error) {
return service.DigitalHuman.GetGenderOptions(ctx, req)
}
// GetAgeOptions 获取年龄段选项
func (c *digitalhuman) GetAgeOptions(ctx context.Context, req *dto.GetAgeOptionsReq) (res *dto.GetAgeOptionsRes, err error) {
return service.DigitalHuman.GetAgeOptions(ctx, req)
}
// GetStyleOptions 获取风格选项
func (c *digitalhuman) GetStyleOptions(ctx context.Context, req *dto.GetStyleOptionsReq) (res *dto.GetStyleOptionsRes, err error) {
return service.DigitalHuman.GetStyleOptions(ctx, req)
}

View File

@@ -1,56 +0,0 @@
package controller
import (
"context"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/service"
"gitea.com/red-future/common/beans"
)
type video struct{}
// Video 视频控制器
var Video = new(video)
// CreateVideo 创建视频
func (c *video) CreateVideo(ctx context.Context, req *dto.CreateVideoReq) (res *dto.CreateVideoRes, err error) {
return service.Video.Create(ctx, req)
}
// ListVideo 获取视频列表
func (c *video) ListVideo(ctx context.Context, req *dto.ListVideoReq) (res *dto.ListVideoRes, err error) {
return service.Video.List(ctx, req)
}
// GetVideo 获取视频详情
func (c *video) GetVideo(ctx context.Context, req *dto.GetVideoReq) (res *dto.GetVideoRes, err error) {
return service.Video.GetOne(ctx, req.ID)
}
// UpdateVideo 更新视频
func (c *video) UpdateVideo(ctx context.Context, req *dto.UpdateVideoReq) (res *beans.ResponseEmpty, err error) {
err = service.Video.Update(ctx, req)
return
}
// DeleteVideo 删除视频
func (c *video) DeleteVideo(ctx context.Context, req *dto.DeleteVideoReq) (res *beans.ResponseEmpty, err error) {
err = service.Video.Delete(ctx, req.ID)
return
}
// GenerateVideo 生成视频
func (c *video) GenerateVideo(ctx context.Context, req *dto.GenerateVideoReq) (res *dto.GenerateVideoRes, err error) {
return service.Video.Generate(ctx, req)
}
// GetVideoStatusOptions 获取视频状态选项
func (c *video) GetVideoStatusOptions(ctx context.Context, req *dto.GetVideoStatusOptionsReq) (res *dto.GetVideoStatusOptionsRes, err error) {
return service.Video.GetStatusOptions(ctx, req)
}
// GetResolutionOptions 获取分辨率选项
func (c *video) GetResolutionOptions(ctx context.Context, req *dto.GetResolutionOptionsReq) (res *dto.GetResolutionOptionsRes, err error) {
return service.Video.GetResolutionOptions(ctx, req)
}

View File

@@ -1,69 +0,0 @@
package dao
import (
"context"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
)
var AsyncTaskRef = &asyncTaskRefDao{}
type asyncTaskRefDao struct{}
func (d *asyncTaskRefDao) Insert(ctx context.Context, ref *entity.AsyncTaskRef) (id int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAsyncTaskRef).Data(ref).Insert()
if err != nil {
return 0, err
}
return r.LastInsertId()
}
// ListPending 列出待处理任务绑定state=0/1
func (d *asyncTaskRefDao) ListPending(ctx context.Context, limit int) (list []*entity.AsyncTaskRef, err error) {
if limit <= 0 {
limit = 200
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAsyncTaskRef).
Where("deleted_at IS NULL").
// 业务侧只维护三态:生成中/成功/失败;绑定表仅用于“待同步列表”
WhereIn(entity.AsyncTaskRefCol.State, []int{0, 1}).
OrderAsc(entity.AsyncTaskRefCol.UpdatedAt).
Limit(limit).
All()
if err != nil {
return nil, err
}
err = r.Structs(&list)
return
}
func (d *asyncTaskRefDao) UpdateByTaskID(ctx context.Context, taskID string, data gdb.Map) (rows int64, err error) {
// 触发 gfdb 的 updateHook 自动填充 updater需要显式带 updater 字段
data[entity.AsyncTaskRefCol.Updater] = ""
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAsyncTaskRef).
Where(entity.AsyncTaskRefCol.TaskID, taskID).
Data(data).
Update()
if err != nil {
return 0, err
}
return r.RowsAffected()
}
func (d *asyncTaskRefDao) GetByTaskID(ctx context.Context, taskID string) (ref *entity.AsyncTaskRef, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAsyncTaskRef).
Where(entity.AsyncTaskRefCol.TaskID, taskID).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
err = r.Struct(&ref)
return
}

View File

@@ -1,128 +0,0 @@
package dao
import (
"context"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var Audio = &audio{}
type audio struct{}
// Insert 插入音频
func (d *audio) Insert(ctx context.Context, req *dto.CreateAudioReq) (id int64, err error) {
var res *entity.Audio
if err = gconv.Struct(req, &res); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).Data(&res).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新音频
func (d *audio) Update(ctx context.Context, id int64, updateData *entity.Audio) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).Where(entity.AudioCol.Id, id).Data(&updateData).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// UpdateStatus 更新音频状态
func (d *audio) UpdateStatus(ctx context.Context, id int64, status consts.AudioStatus, errorMsg string, audioURL string, duration int, externalID string) (rows int64, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).Where(entity.AudioCol.Id, id)
updateData := gdb.Map{
entity.AudioCol.Status: int(status),
}
if errorMsg != "" {
updateData[entity.AudioCol.ErrorMsg] = errorMsg
}
if audioURL != "" {
updateData[entity.AudioCol.AudioURL] = audioURL
}
if duration > 0 {
updateData[entity.AudioCol.Duration] = duration
}
if externalID != "" {
updateData[entity.AudioCol.ExternalID] = externalID
}
r, err := model.Data(&updateData).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除音频
func (d *audio) Delete(ctx context.Context, id int64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).Where(entity.AudioCol.Id, id).Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个音频
func (d *audio) GetOne(ctx context.Context, id int64) (audio *entity.Audio, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).Where(entity.AudioCol.Id, id).One()
if err != nil {
return
}
err = r.Struct(&audio)
return
}
// List 获取音频列表
func (d *audio) List(ctx context.Context, req *dto.ListAudioReq) (res []*entity.Audio, total int, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameAudio).OmitEmpty()
// 构建查询过滤条件
if req.Status != consts.AudioStatusGenerating && req.Status != consts.AudioStatusSuccess && req.Status != consts.AudioStatusFailed {
// 不添加状态过滤
} else {
model = model.Where(entity.AudioCol.Status+" = ?", req.Status)
}
if !g.IsEmpty(req.Keyword) {
like := "%" + req.Keyword + "%"
model = model.Where(
"("+entity.AudioCol.Name+
" LIKE ? OR "+entity.AudioCol.Description+
" LIKE ? OR "+entity.AudioCol.ScriptText+
" LIKE ?)",
like, like, like,
)
}
model = model.OrderDesc(entity.AudioCol.CreatedAt)
if req.Page != nil {
if req.Page.PageNum <= 0 {
req.Page.PageNum = 1
}
if req.Page.PageSize <= 0 {
req.Page.PageSize = 10
}
model = model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}

View File

@@ -1,135 +0,0 @@
package dao
import (
"context"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
)
// CustomVoice 自定义音色数据访问层
var CustomVoice = &customVoice{}
type customVoice struct{}
// Insert 插入自定义音色
func (d *customVoice) Insert(ctx context.Context, req *dto.CreateCustomVoiceReq) (id int64, err error) {
var result *entity.CustomVoice
if err = gconv.Struct(req, &result); err != nil {
return
}
// 初始状态:生成中
result.Status = 0
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).Data(&result).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
func (d *customVoice) UpdateReferenceAudio(ctx context.Context, id int64, referenceAudio []byte) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).
Where(entity.CustomVoiceCol.Id, id).
Data(gdb.Map{
entity.CustomVoiceCol.ReferenceAudio: referenceAudio,
}).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
func (d *customVoice) UpdateDescription(ctx context.Context, id int64, description string) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).
Where(entity.CustomVoiceCol.Id, id).
Data(gdb.Map{
entity.CustomVoiceCol.Description: description,
}).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// UpdateStatus 更新自定义音色状态/结果
func (d *customVoice) UpdateStatus(ctx context.Context, id int64, status int, errorMsg string, ossFile string) (rows int64, err error) {
data := gdb.Map{
entity.CustomVoiceCol.Status: status,
}
if errorMsg != "" {
data[entity.CustomVoiceCol.ErrorMsg] = errorMsg
}
if ossFile != "" {
data[entity.CustomVoiceCol.OssFile] = ossFile
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).
Where(entity.CustomVoiceCol.Id, id).
Data(data).
Update()
if err != nil {
return 0, err
}
return r.RowsAffected()
}
// Delete 删除自定义音色
func (d *customVoice) Delete(ctx context.Context, id int64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).Where(entity.CustomVoiceCol.Id, id).Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个自定义音色
func (d *customVoice) GetOne(ctx context.Context, id int64) (customVoice *entity.CustomVoice, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice).Where(entity.CustomVoiceCol.Id, id).One()
if err != nil {
return
}
err = r.Struct(&customVoice)
return
}
// List 获取自定义音色列表
func (d *customVoice) List(ctx context.Context, req *dto.ListCustomVoiceReq) (res []*entity.CustomVoice, total int, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameCustomVoice)
// 处理分页
if req.Page == nil {
req.Page = &beans.Page{PageNum: 1, PageSize: 20}
}
r, total, err := model.OrderDesc(entity.CustomVoiceCol.CreatedAt).Page(int(req.Page.PageNum), int(req.Page.PageSize)).AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// GetCustomVoiceItem 转换为 DTO 列表项
func (d *customVoice) GetCustomVoiceItem(entity *entity.CustomVoice) *dto.CustomVoiceItem {
item := &dto.CustomVoiceItem{
ID: gconv.String(entity.Id),
Name: entity.Name,
Description: entity.Description,
Status: entity.Status,
ErrorMsg: entity.ErrorMsg,
OssFile: entity.OssFile,
}
if entity.CreatedAt != nil {
item.CreatedAt = entity.CreatedAt
}
if entity.UpdatedAt != nil {
item.UpdatedAt = entity.UpdatedAt
}
return item
}

View File

@@ -1,125 +0,0 @@
package dao
import (
"context"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
// DigitalHuman 数字人形象数据
var DigitalHuman = &digitalHuman{}
type digitalHuman struct{}
// Insert 插入数字人形象
func (d *digitalHuman) Insert(ctx context.Context, req *dto.CreateDigitalHumanReq) (ids []any, err error) {
var result entity.DigitalHuman
if err = gconv.Struct(req, &result); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Data(&result).Insert()
if err != nil {
return
}
lastInsertId, err := r.LastInsertId()
if err != nil {
return
}
ids = []any{lastInsertId}
return
}
// Update 更新数字人形象
func (d *digitalHuman) Update(ctx context.Context, id int64, updateData *entity.DigitalHuman) (err error) {
_, err = gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Data(updateData).Where("id = ?", id).Update()
return
}
// UpdateStatus 更新数字人形象状态
func (d *digitalHuman) UpdateStatus(ctx context.Context, id int64, status consts.DigitalHumanStatus) (rows int64, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Where("id = ?", id)
r, err := model.Data(g.Map{"status": status}).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除数字人形象
func (d *digitalHuman) Delete(ctx context.Context, id int64) (err error) {
_, err = gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Where("id = ?", id).Delete()
return
}
// GetOne 获取单个数字人形象
func (d *digitalHuman) GetOne(ctx context.Context, id int64) (digitalHuman *entity.DigitalHuman, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Where("id = ?", id).One()
if err != nil {
return
}
err = r.Struct(&digitalHuman)
return
}
// List 获取数字人形象列表
func (d *digitalHuman) List(ctx context.Context, req *dto.ListDigitalHumanReq) (res []entity.DigitalHuman, total int64, err error) {
model := d.buildListFilter(ctx, req)
var totalCount int
totalCount, err = model.Count()
if err != nil {
return
}
total = gconv.Int64(totalCount)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, err := model.OrderDesc("id").All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *digitalHuman) buildListFilter(ctx context.Context, req *dto.ListDigitalHumanReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).OmitEmpty()
// 状态字段允许查询所有状态值包括0停用所以需要特别处理
if req.Status != consts.DigitalHumanStatusInactive && req.Status != consts.DigitalHumanStatusActive {
// 如果状态不是有效值之一,则不添加状态过滤条件
} else {
model = model.Where("status = ?", req.Status)
}
if !g.IsEmpty(req.Gender) {
model = model.Where("gender = ?", req.Gender)
}
if !g.IsEmpty(req.Style) {
model = model.Where("style = ?", req.Style)
}
if !g.IsEmpty(req.Keyword) {
model = model.Where("name LIKE ? OR description LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
return model
}
// Count 计数
func (d *digitalHuman) Count(ctx context.Context, req *dto.CreateDigitalHumanReq) (count int64, err error) {
var totalCount int
totalCount, err = gfdb.DB(ctx).Model(ctx, public.TableNameDigitalHuman).Where("name = ?", req.Name).Count()
if err != nil {
return
}
count = gconv.Int64(totalCount)
return
}

View File

@@ -1,131 +0,0 @@
package dao
import (
"context"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
// Video 视频数据
var Video = &video{}
type video struct{}
// Insert 插入视频
func (d *video) Insert(ctx context.Context, req *dto.CreateVideoReq) (ids []any, err error) {
var result entity.Video
if err = gconv.Struct(req, &result); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameVideo).Data(&result).Insert()
if err != nil {
return
}
lastInsertId, err := r.LastInsertId()
if err != nil {
return
}
ids = []any{lastInsertId}
return
}
// Update 更新视频
func (d *video) Update(ctx context.Context, id int64, updateData *entity.Video) (err error) {
_, err = gfdb.DB(ctx).Model(ctx, public.TableNameVideo).Data(updateData).Where("id = ?", id).Update()
return
}
// Delete 删除视频
func (d *video) Delete(ctx context.Context, id int64) (err error) {
_, err = gfdb.DB(ctx).Model(ctx, public.TableNameVideo).Where("id = ?", id).Delete()
return
}
// GetOne 获取单个视频
func (d *video) GetOne(ctx context.Context, id int64) (video *entity.Video, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameVideo).Where("id = ?", id).One()
if err != nil {
return
}
err = r.Struct(&video)
return
}
// List 获取视频列表
func (d *video) List(ctx context.Context, req *dto.ListVideoReq) (res []entity.Video, total int64, err error) {
model := d.buildListFilter(ctx, req)
var totalCount int
totalCount, err = model.Count()
if err != nil {
return
}
total = gconv.Int64(totalCount)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, err := model.OrderDesc("id").All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *video) buildListFilter(ctx context.Context, req *dto.ListVideoReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, public.TableNameVideo).OmitEmpty()
// 状态字段允许查询所有状态值包括0生成中所以需要特别处理
if req.Status != consts.VideoStatusGenerating && req.Status != consts.VideoStatusSuccess && req.Status != consts.VideoStatusFailed {
// 如果状态不是有效值之一,则不添加状态过滤条件
} else {
model = model.Where("status = ?", req.Status)
}
if !g.IsEmpty(req.DigitalHumanID) {
model = model.Where("digitalHumanId = ?", req.DigitalHumanID)
}
if !g.IsEmpty(req.Keyword) {
model = model.Where("name LIKE ? OR description LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
return model
}
// UpdateStatus 更新视频状态
func (d *video) UpdateStatus(ctx context.Context, id int64, status consts.VideoStatus, errorMsg string, videoURL string, duration int, thumbnailURL string, externalTaskID string) (rows int64, err error) {
model := gfdb.DB(ctx).Model(ctx, public.TableNameVideo).Where("id = ?", id)
updateData := gdb.Map{
"status": status,
}
if errorMsg != "" {
updateData["errorMsg"] = errorMsg
}
if videoURL != "" {
updateData["videoUrl"] = videoURL
}
if duration > 0 {
updateData["duration"] = duration
}
if thumbnailURL != "" {
updateData["thumbnailUrl"] = thumbnailURL
}
if externalTaskID != "" {
updateData["externalTaskId"] = externalTaskID
}
r, err := model.Data(updateData).Update()
if err != nil {
return
}
return r.RowsAffected()
}

View File

@@ -1,151 +0,0 @@
package dto
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// CreateAudioReq 创建音频请求
type CreateAudioReq struct {
g.Meta `path:"/createAudio" method:"post" tags:"音频管理" summary:"创建音频" dc:"创建新的音频"`
// 基础信息
Name string `json:"name" v:"required" dc:"音频名称"`
Description string `json:"description" dc:"音频描述"`
ScriptText string `json:"scriptText" v:"required" dc:"话术文本"`
// 音色配置
Voice string `json:"voice" dc:"音色serena/vivian/uncle_fu/ryan/aiden/ono_anna/sohee/eric/dylan默认serena"`
VoiceType string `json:"voiceType" dc:"音色类型preset/custom预设/自定义默认preset"`
CustomVoice string `json:"customVoice" dc:"自定义音色ID用于声音克隆voiceType=custom时必填"`
}
// CreateAudioRes 创建音频响应
type CreateAudioRes struct {
Id int64 `json:"id" dc:"音频ID"`
}
// ListAudioReq 获取音频列表请求
type ListAudioReq struct {
g.Meta `path:"/listAudios" method:"get" tags:"音频管理" summary:"获取音频列表" dc:"分页查询音频列表,支持多条件筛选"`
*beans.Page
Status consts.AudioStatus `json:"status" dc:"状态0生成中/1成功/2失败"`
Keyword string `json:"keyword" dc:"关键词搜索"`
}
// ListAudioRes 获取音频列表响应
type ListAudioRes struct {
List []*AudioListItem `json:"list" dc:"音频列表"`
Total int64 `json:"total" dc:"总数"`
}
// AudioListItem 音频列表项
type AudioListItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ScriptText string `json:"scriptText"`
AudioURL string `json:"audioUrl"`
Status consts.AudioStatus `json:"status"`
ErrorMsg string `json:"errorMsg"`
Duration int `json:"duration"`
ExternalID string `json:"externalId"`
Voice string `json:"voice"`
VoiceType string `json:"voiceType"`
CustomVoice string `json:"customVoice"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// GetAudioReq 获取音频详情请求
type GetAudioReq struct {
g.Meta `path:"/getAudio" method:"get" tags:"音频管理" summary:"获取音频详情" dc:"获取音频详情"`
ID int64 `json:"id" v:"required" dc:"音频ID"`
}
// GetAudioRes 获取音频详情响应
type GetAudioRes struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ScriptText string `json:"scriptText"`
AudioURL string `json:"audioUrl"`
Status consts.AudioStatus `json:"status"`
ErrorMsg string `json:"errorMsg"`
Duration int `json:"duration"`
ExternalID string `json:"externalId"`
Voice string `json:"voice"`
VoiceType string `json:"voiceType"`
CustomVoice string `json:"customVoice"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// UpdateAudioReq 更新音频请求
type UpdateAudioReq struct {
g.Meta `path:"/updateAudio" method:"put" tags:"音频管理" summary:"更新音频" dc:"更新音频信息"`
ID int64 `json:"id" v:"required" dc:"音频ID"`
// 基础信息
Name string `json:"name" dc:"音频名称"`
Description string `json:"description" dc:"音频描述"`
// 音色配置
Voice string `json:"voice" dc:"音色"`
VoiceType string `json:"voiceType" dc:"音色类型"`
CustomVoice string `json:"customVoice" dc:"自定义音色ID"`
}
// DeleteAudioReq 删除音频请求
type DeleteAudioReq struct {
g.Meta `path:"/deleteAudio" method:"delete" tags:"音频管理" summary:"删除音频" dc:"删除音频"`
ID int64 `json:"id" v:"required" dc:"音频ID"`
}
// GenerateAudioReq 生成音频请求
type GenerateAudioReq struct {
g.Meta `path:"/generateAudio" method:"post" tags:"音频管理" summary:"生成音频" dc:"根据话术文本生成音频"`
ID int64 `json:"id" v:"required" dc:"音频ID"`
}
// GenerateAudioRes 生成音频响应
type GenerateAudioRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
}
// TTSReq 文本转语音请求
type TTSReq struct {
g.Meta `path:"/tts" method:"post" tags:"音频管理" summary:"文本转语音" dc:"将文本转换为语音直接返回MP3二进制数据"`
Text string `json:"text" v:"required" dc:"要转换的文本内容"`
Voice string `json:"voice" dc:"音色默认default"`
Speed int `json:"speed" dc:"语速0.5-2.0默认1.0"`
}
// TTSRes 文本转语音响应返回二进制MP3数据
type TTSRes struct {
g.Meta `mime:"audio/mpeg"`
Data []byte `json:"-" dc:"MP3音频二进制数据"`
}
// GetAudioStatusOptionsReq 获取音频状态选项请求
type GetAudioStatusOptionsReq struct {
g.Meta `path:"/getAudioStatusOptions" method:"get" tags:"音频管理" summary:"获取音频状态选项" dc:"获取所有音频状态的选项列表"`
}
// GetAudioStatusOptionsRes 获取音频状态选项响应
type GetAudioStatusOptionsRes struct {
Options []consts.AudioStatusKeyValue `json:"options" dc:"音频状态选项列表"`
}
// Qwen3TTSRequest Qwen3-TTS 请求结构
type Qwen3TTSRequest struct {
Text string `json:"text"`
Speaker string `json:"speaker"` // 预设音色名或克隆音色ID
VoiceID string `json:"voice_id,omitempty"` // 克隆音色ID可选
}
// Qwen3TTSResponse Qwen3-TTS 响应结构
type Qwen3TTSResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Audio string `json:"audio"` // base64编码的音频数据
}

View File

@@ -1,67 +0,0 @@
package dto
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// CreateCustomVoiceReq 创建自定义音色请求
type CreateCustomVoiceReq struct {
g.Meta `path:"/createCustomVoice" method:"post" tags:"自定义音色" summary:"创建自定义音色" dc:"上传参考音频创建自定义音色"`
VoiceType string `json:"voiceType" v:"required" dc:"音色类型 design/clone设计/克隆)"`
Name string `json:"name" v:"required" dc:"音色名称"`
Description string `json:"description" dc:"音色描述"`
Text string `json:"text" dc:"参考文本"`
// 参考音频
ReferenceAudio []byte `json:"referenceAudio" dc:"参考音频数据base64编码的WAV文件voiceType=clone 时必填)"`
}
// CreateCustomVoiceRes 创建自定义音色响应
type CreateCustomVoiceRes struct {
VoiceID string `json:"voiceId" dc:"音色ID"`
}
// ListCustomVoiceReq 获取自定义音色列表请求
type ListCustomVoiceReq struct {
g.Meta `path:"/listCustomVoices" method:"get" tags:"自定义音色" summary:"获取自定义音色列表" dc:"分页查询自定义音色列表"`
*beans.Page
}
// ListCustomVoiceRes 获取自定义音色列表响应
type ListCustomVoiceRes struct {
List []*CustomVoiceItem `json:"list" dc:"自定义音色列表"`
Total int64 `json:"total" dc:"总数"`
}
// CustomVoiceItem 自定义音色列表项
type CustomVoiceItem struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status int `json:"status" dc:"状态0生成中/1成功/2失败"`
ErrorMsg string `json:"errorMsg" dc:"错误信息"`
OssFile string `json:"ossFile" dc:"结果文件OSS地址"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// DeleteCustomVoiceReq 删除自定义音色请求
type DeleteCustomVoiceReq struct {
g.Meta `path:"/deleteCustomVoice" method:"delete" tags:"自定义音色" summary:"删除自定义音色" dc:"删除自定义音色"`
VoiceID string `json:"voiceId" v:"required" dc:"音色ID"`
}
// Qwen3VoiceCloneRequest Qwen3-TTS 音色克隆请求
type Qwen3VoiceCloneRequest struct {
Name string `json:"name"` // 音色名称
Audio string `json:"audio"` // base64编码的参考音频
Text string `json:"text"` // 参考文本(可选)
}
// Qwen3VoiceCloneResponse Qwen3-TTS 音色克隆响应
type Qwen3VoiceCloneResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
VoiceID string `json:"voice_id"` // 克隆后的音色ID
}

View File

@@ -1,158 +0,0 @@
package dto
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// CreateDigitalHumanReq 创建数字人形象请求
type CreateDigitalHumanReq struct {
g.Meta `path:"/createDigitalHuman" method:"post" tags:"数字人形象管理" summary:"创建数字人形象" dc:"创建新的数字人形象"`
// 基础信息
Name string `json:"name" v:"required" dc:"数字人名称"`
Description string `json:"description" dc:"数字人描述"`
ImageURL string `json:"imageUrl" dc:"形象图片URL"`
VideoURL string `json:"videoUrl" dc:"形象视频URL"`
Status consts.DigitalHumanStatus `json:"status" dc:"状态1启用/0停用" d:"1"`
Tags []string `json:"tags" dc:"标签"`
Gender consts.Gender `json:"gender" dc:"性别"`
Age consts.Age `json:"age" dc:"年龄段"`
Style consts.Style `json:"style" dc:"风格:商务/休闲/正式等"`
ExternalID string `json:"externalId" dc:"外部系统ID"`
Metadata []map[string]interface{} `json:"metadata" dc:"动态元数据"`
}
// CreateDigitalHumanRes 创建数字人形象响应
type CreateDigitalHumanRes struct {
Id int64 `json:"id" dc:"数字人形象ID"`
}
// ListDigitalHumanReq 获取数字人形象列表请求
type ListDigitalHumanReq struct {
g.Meta `path:"/listDigitalHumans" method:"get" tags:"数字人形象管理" summary:"获取数字人形象列表" dc:"分页查询数字人形象列表,支持多条件筛选"`
*beans.Page
Status consts.DigitalHumanStatus `json:"status" dc:"状态"`
Gender consts.Gender `json:"gender" dc:"性别"`
Style consts.Style `json:"style" dc:"风格"`
Keyword string `json:"keyword" dc:"关键词搜索"`
}
// ListDigitalHumanRes 获取数字人形象列表响应
type ListDigitalHumanRes struct {
List []*DigitalHumanListItem `json:"list" dc:"数字人形象列表"`
Total int64 `json:"total" dc:"总数"`
}
// DigitalHumanListItem 数字人形象列表项
type DigitalHumanListItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ImageURL string `json:"imageUrl"`
VideoURL string `json:"videoUrl"`
Status consts.DigitalHumanStatus `json:"status"`
Tags []string `json:"tags"`
Gender consts.Gender `json:"gender"`
Age consts.Age `json:"age"`
Style consts.Style `json:"style"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// GetDigitalHumanReq 获取数字人形象详情请求
type GetDigitalHumanReq struct {
g.Meta `path:"/getDigitalHuman" method:"get" tags:"数字人形象管理" summary:"获取数字人形象详情" dc:"获取数字人形象详情"`
ID int64 `json:"id" v:"required" dc:"数字人形象ID"`
}
// GetDigitalHumanRes 获取数字人形象详情响应
type GetDigitalHumanRes struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ImageURL string `json:"imageUrl"`
VideoURL string `json:"videoUrl"`
Status consts.DigitalHumanStatus `json:"status"`
Tags []string `json:"tags"`
Gender consts.Gender `json:"gender"`
Age consts.Age `json:"age"`
Style consts.Style `json:"style"`
ExternalID string `json:"externalId"`
Metadata []map[string]interface{} `json:"metadata"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// UpdateDigitalHumanReq 更新数字人形象请求
type UpdateDigitalHumanReq struct {
g.Meta `path:"/updateDigitalHuman" method:"put" tags:"数字人形象管理" summary:"更新数字人形象" dc:"更新数字人形象信息"`
ID int64 `json:"id" v:"required" dc:"数字人形象ID"`
// 基础信息
Name string `json:"name" dc:"数字人名称"`
Description string `json:"description" dc:"数字人描述"`
ImageURL string `json:"imageUrl" dc:"形象图片URL"`
VideoURL string `json:"videoUrl" dc:"形象视频URL"`
Status consts.DigitalHumanStatus `json:"status" dc:"状态1启用/0停用"`
Tags []string `json:"tags" dc:"标签"`
Gender consts.Gender `json:"gender" dc:"性别"`
Age consts.Age `json:"age" dc:"年龄段"`
Style consts.Style `json:"style" dc:"风格:商务/休闲/正式等"`
ExternalID string `json:"externalId" dc:"外部系统ID"`
Metadata []map[string]interface{} `json:"metadata" dc:"动态元数据"`
}
// UpdateDigitalHumanStatusReq 更新数字人形象状态请求
type UpdateDigitalHumanStatusReq struct {
g.Meta `path:"/updateDigitalHumanStatus" method:"put" tags:"数字人形象管理" summary:"更新数字人形象状态" dc:"更新数字人形象状态"`
ID int64 `json:"id" v:"required" dc:"数字人形象ID"`
Status consts.DigitalHumanStatus `json:"status" v:"required|in:1,0" dc:"状态1启用/0停用"`
}
// DeleteDigitalHumanReq 删除数字人形象请求
type DeleteDigitalHumanReq struct {
g.Meta `path:"/deleteDigitalHuman" method:"delete" tags:"数字人形象管理" summary:"删除数字人形象" dc:"删除数字人形象"`
ID int64 `json:"id" v:"required" dc:"数字人形象ID"`
}
// GetDigitalHumanStatusOptionsReq 获取数字人状态选项请求
type GetDigitalHumanStatusOptionsReq struct {
g.Meta `path:"/getDigitalHumanStatusOptions" method:"get" tags:"数字人形象管理" summary:"获取数字人状态选项" dc:"获取所有数字人状态的选项列表"`
}
// GetDigitalHumanStatusOptionsRes 获取数字人状态选项响应
type GetDigitalHumanStatusOptionsRes struct {
Options []consts.StatusKeyValue `json:"options" dc:"状态选项列表"`
}
// GetGenderOptionsReq 获取性别选项请求
type GetGenderOptionsReq struct {
g.Meta `path:"/getGenderOptions" method:"get" tags:"数字人形象管理" summary:"获取性别选项" dc:"获取所有性别选项列表"`
}
// GetGenderOptionsRes 获取性别选项响应
type GetGenderOptionsRes struct {
Options []consts.GenderKeyValue `json:"options" dc:"性别选项列表"`
}
// GetAgeOptionsReq 获取年龄段选项请求
type GetAgeOptionsReq struct {
g.Meta `path:"/getAgeOptions" method:"get" tags:"数字人形象管理" summary:"获取年龄段选项" dc:"获取所有年龄段选项列表"`
}
// GetAgeOptionsRes 获取年龄段选项响应
type GetAgeOptionsRes struct {
Options []consts.AgeKeyValue `json:"options" dc:"年龄段选项列表"`
}
// GetStyleOptionsReq 获取风格选项请求
type GetStyleOptionsReq struct {
g.Meta `path:"/getStyleOptions" method:"get" tags:"数字人形象管理" summary:"获取风格选项" dc:"获取所有风格选项列表"`
}
// GetStyleOptionsRes 获取风格选项响应
type GetStyleOptionsRes struct {
Options []consts.StyleKeyValue `json:"options" dc:"风格选项列表"`
}

View File

@@ -1,131 +0,0 @@
package dto
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// CreateVideoReq 创建视频请求
type CreateVideoReq struct {
g.Meta `path:"/createVideo" method:"post" tags:"视频管理" summary:"创建视频" dc:"创建新的视频任务"`
// 基础信息
Name string `json:"name" v:"required" dc:"视频名称"`
Description string `json:"description" dc:"视频描述"`
DigitalHumanID int64 `json:"digitalHumanId" v:"required" dc:"数字人形象ID"`
AudioID int64 `json:"audioId" v:"required" dc:"音频ID"`
Resolution consts.Resolution `json:"resolution" dc:"分辨率480p/720p/1080p/2k/4k/8k"`
}
// CreateVideoRes 创建视频响应
type CreateVideoRes struct {
Id int64 `json:"id" dc:"视频ID"`
}
// ListVideoReq 获取视频列表请求
type ListVideoReq struct {
g.Meta `path:"/listVideos" method:"get" tags:"视频管理" summary:"获取视频列表" dc:"分页查询视频列表,支持多条件筛选"`
*beans.Page
Status consts.VideoStatus `json:"status" dc:"状态0生成中/1成功/2失败"`
DigitalHumanID int64 `json:"digitalHumanId" dc:"数字人形象ID"`
Keyword string `json:"keyword" dc:"关键词搜索"`
}
// ListVideoRes 获取视频列表响应
type ListVideoRes struct {
List []*VideoListItem `json:"list" dc:"视频列表"`
Total int64 `json:"total" dc:"总数"`
}
// VideoListItem 视频列表项
type VideoListItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DigitalHumanID int64 `json:"digitalHumanId"`
DigitalHumanName string `json:"digitalHumanName"`
AudioID int64 `json:"audioId"`
AudioURL string `json:"audioUrl"`
VideoURL string `json:"videoUrl"`
Status consts.VideoStatus `json:"status"`
ErrorMsg string `json:"errorMsg"`
Duration int `json:"duration"`
Resolution consts.Resolution `json:"resolution"`
ThumbnailURL string `json:"thumbnailUrl"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// GetVideoReq 获取视频详情请求
type GetVideoReq struct {
g.Meta `path:"/getVideo" method:"get" tags:"视频管理" summary:"获取视频详情" dc:"获取视频详情"`
ID int64 `json:"id" v:"required" dc:"视频ID"`
}
// GetVideoRes 获取视频详情响应
type GetVideoRes struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DigitalHumanID int64 `json:"digitalHumanId"`
DigitalHumanName string `json:"digitalHumanName"`
AudioID int64 `json:"audioId"`
AudioURL string `json:"audioUrl"`
VideoURL string `json:"videoUrl"`
Status consts.VideoStatus `json:"status"`
ErrorMsg string `json:"errorMsg"`
Duration int `json:"duration"`
Resolution consts.Resolution `json:"resolution"`
ThumbnailURL string `json:"thumbnailUrl"`
ExternalTaskID string `json:"externalTaskId"`
CreatedAt *gtime.Time `json:"createdAt"`
UpdatedAt *gtime.Time `json:"updatedAt"`
}
// UpdateVideoReq 更新视频请求
type UpdateVideoReq struct {
g.Meta `path:"/updateVideo" method:"put" tags:"视频管理" summary:"更新视频" dc:"更新视频信息"`
ID int64 `json:"id" v:"required" dc:"视频ID"`
// 基础信息
Name string `json:"name" dc:"视频名称"`
Description string `json:"description" dc:"视频描述"`
}
// DeleteVideoReq 删除视频请求
type DeleteVideoReq struct {
g.Meta `path:"/deleteVideo" method:"delete" tags:"视频管理" summary:"删除视频" dc:"删除视频"`
ID int64 `json:"id" v:"required" dc:"视频ID"`
}
// GenerateVideoReq 生成视频请求
type GenerateVideoReq struct {
g.Meta `path:"/generateVideo" method:"post" tags:"视频管理" summary:"生成视频" dc:"选择数字人,选择已生成的音频,调用数字人形象与音频合成形成视频"`
ID int64 `json:"id" v:"required" dc:"视频ID"`
}
// GenerateVideoRes 生成视频响应
type GenerateVideoRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
}
// GetVideoStatusOptionsReq 获取视频状态选项请求
type GetVideoStatusOptionsReq struct {
g.Meta `path:"/getVideoStatusOptions" method:"get" tags:"视频管理" summary:"获取视频状态选项" dc:"获取所有视频状态的选项列表"`
}
// GetVideoStatusOptionsRes 获取视频状态选项响应
type GetVideoStatusOptionsRes struct {
Options []consts.VideoStatusKeyValue `json:"options" dc:"视频状态选项列表"`
}
// GetResolutionOptionsReq 获取分辨率选项请求
type GetResolutionOptionsReq struct {
g.Meta `path:"/getResolutionOptions" method:"get" tags:"视频管理" summary:"获取分辨率选项" dc:"获取所有分辨率的选项列表"`
}
// GetResolutionOptionsRes 获取分辨率选项响应
type GetResolutionOptionsRes struct {
Options []consts.ResolutionKeyValue `json:"options" dc:"分辨率选项列表"`
}

View File

@@ -1,38 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
type asyncTaskRefCol struct {
beans.SQLBaseCol
TaskID string
State string
TableName string
BizID string
OssFile string
ErrorMsg string
}
var AsyncTaskRefCol = asyncTaskRefCol{
SQLBaseCol: beans.DefSQLBaseCol,
TaskID: "task_id",
State: "state",
TableName: "table_name",
BizID: "biz_id",
OssFile: "oss_file",
ErrorMsg: "error_msg",
}
// AsyncTaskRef 异步任务绑定记录(中间件 task_id 绑定业务表)
// - state: 保存中间件返回的任务状态0排队中/1执行中/2成功/3失败/4已下载
type AsyncTaskRef struct {
beans.SQLBaseDO `orm:",inline"`
TaskID string `orm:"task_id" json:"taskId"`
State int `orm:"state" json:"state"`
TableName string `orm:"table_name" json:"tableName"`
BizID int64 `orm:"biz_id" json:"bizId,string"`
OssFile string `orm:"oss_file" json:"ossFile"`
ErrorMsg string `orm:"error_msg" json:"errorMsg"`
}

View File

@@ -1,55 +0,0 @@
package entity
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
)
type audioCol struct {
beans.SQLBaseCol
Name string
Description string
ScriptText string
AudioURL string
Status string
ErrorMsg string
Duration string
ExternalID string
Voice string
VoiceType string
CustomVoice string
}
var AudioCol = audioCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Description: "description",
ScriptText: "script_text",
AudioURL: "audio_url",
Status: "status",
ErrorMsg: "error_msg",
Duration: "duration",
ExternalID: "external_id",
Voice: "voice",
VoiceType: "voice_type",
CustomVoice: "custom_voice",
}
// Audio 音频实体
type Audio struct {
beans.SQLBaseDO `orm:",inline"`
// 基础信息
Name string `orm:"name" json:"name"` // 音频名称
Description string `orm:"description" json:"description"` // 音频描述
ScriptText string `orm:"script_text" json:"scriptText"` // 话术文本
AudioURL string `orm:"audio_url" json:"audioUrl"` // 音频文件URL
Status consts.AudioStatus `orm:"status" json:"status"` // 状态0生成中/1成功/2失败
ErrorMsg string `orm:"error_msg" json:"errorMsg"` // 错误信息
Duration int `orm:"duration" json:"duration"` // 音频时长(秒)
ExternalID string `orm:"external_id" json:"externalId"` // 外部音频ID
// 音色相关
Voice string `orm:"voice" json:"voice"` // 音色serena/vivian/uncle_fu/ryan/aiden/ono_anna/sohee/eric/dylan
VoiceType string `orm:"voice_type" json:"voiceType"` // 音色类型preset/custom预设/克隆)
CustomVoice string `orm:"custom_voice" json:"customVoice"` // 自定义音色ID用于声音克隆
}

View File

@@ -1,38 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
type customVoiceCol struct {
beans.SQLBaseCol
Name string
Description string
Status string
ErrorMsg string
OssFile string
ReferenceAudio string
}
var CustomVoiceCol = customVoiceCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Description: "description",
Status: "status",
ErrorMsg: "error_msg",
OssFile: "oss_file",
ReferenceAudio: "reference_audio",
}
// CustomVoice 自定义音色实体
type CustomVoice struct {
beans.SQLBaseDO `orm:",inline"`
// 基础信息
Name string `orm:"name" json:"name"` // 音色名称
Description string `orm:"description" json:"description"` // 音色描述
Text string `orm:"text" json:"text"` // 参考文本
Status int `orm:"status" json:"status"` // 状态0生成中/1成功/2失败
ErrorMsg string `orm:"error_msg" json:"errorMsg"` // 错误信息
OssFile string `orm:"oss_file" json:"ossFile"` // 结果文件URL如参考音频/特征文件等)
ReferenceAudio []byte `orm:"reference_audio" json:"referenceAudio"` // 参考音频数据(二进制)
}

View File

@@ -1,57 +0,0 @@
package entity
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
)
type digitalHumanCol struct {
beans.SQLBaseCol
Name string
Description string
AvatarURL string
VideoURL string
Voice string
Status string
Tags string
Gender string
Age string
Style string
ExternalID string
Metadata string
}
var DigitalHumanCol = digitalHumanCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Description: "description",
AvatarURL: "avatar_url",
VideoURL: "video_url",
Voice: "voice",
Status: "status",
Tags: "tags",
Gender: "gender",
Age: "age",
Style: "style",
ExternalID: "external_id",
Metadata: "metadata",
}
// DigitalHuman 数字人形象实体
type DigitalHuman struct {
beans.SQLBaseDO `orm:",inline"`
// 基础信息
Name string `orm:"name" json:"name"` // 数字人名称
Description string `orm:"description" json:"description"` // 数字人描述
AvatarURL string `orm:"avatar_url" json:"imageUrl"` // 形象图片URL
VideoURL string `orm:"video_url" json:"videoUrl"` // 形象视频URL
Voice string `orm:"voice" json:"voice"` // 默认音色
Status consts.DigitalHumanStatus `orm:"status" json:"status"` // 状态1启用/0停用
Tags []string `orm:"tags" json:"tags"` // 标签
Gender consts.Gender `orm:"gender" json:"gender"` // 性别
Age consts.Age `orm:"age" json:"age"` // 年龄段
Style consts.Style `orm:"style" json:"style"` // 风格:商务/休闲/正式等
ExternalID string `orm:"external_id" json:"externalId"` // 外部系统ID
Metadata []map[string]interface{} `orm:"metadata" json:"metadata"` // 动态元数据
}

View File

@@ -1,60 +0,0 @@
package entity
import (
"digital-human/digitalhuman/consts"
"gitea.com/red-future/common/beans"
)
type videoCol struct {
beans.SQLBaseCol
Name string
Description string
AudioID string
ScriptText string
VideoURL string
Status string
ErrorMsg string
Duration string
ThumbnailURL string
ExternalID string
DigitalHumanID string
DigitalHumanName string
Resolution string
}
var VideoCol = videoCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Description: "description",
AudioID: "audio_id",
ScriptText: "script_text",
VideoURL: "video_url",
Status: "status",
ErrorMsg: "error_msg",
Duration: "duration",
ThumbnailURL: "thumbnail_url",
ExternalID: "external_id",
DigitalHumanID: "digital_human_id",
DigitalHumanName: "digital_human_name",
Resolution: "resolution",
}
// Video 视频实体
type Video struct {
beans.SQLBaseDO `orm:",inline"`
// 基础信息
Name string `orm:"name" json:"name"` // 视频名称
Description string `orm:"description" json:"description"` // 视频描述
DigitalHumanID int64 `orm:"digital_human_id" json:"digitalHumanId"` // 数字人形象ID
AudioID int64 `orm:"audio_id" json:"audioId"` // 音频ID
ScriptText string `orm:"script_text" json:"scriptText"` // 话术文本
VideoURL string `orm:"video_url" json:"videoUrl"` // 合成视频URL
Status consts.VideoStatus `orm:"status" json:"status"` // 状态0生成中/1成功/2失败
ErrorMsg string `orm:"error_msg" json:"errorMsg"` // 错误信息
Duration int `orm:"duration" json:"duration"` // 视频时长(秒)
ThumbnailURL string `orm:"thumbnail_url" json:"thumbnailUrl"` // 缩略图URL
ExternalID string `orm:"external_id" json:"externalId"` // 外部任务ID
DigitalHumanName string `orm:"digital_human_name" json:"digitalHumanName"` // 数字人名称(冗余字段)
Resolution consts.Resolution `orm:"resolution" json:"resolution"` // 分辨率
}

View File

@@ -1,195 +0,0 @@
package service
import (
"context"
"fmt"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/dao"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type asyncTaskService struct{}
// AsyncTask 异步任务同步服务(供定时任务/业务轮询调用)
var AsyncTask = new(asyncTaskService)
// Sync
// 1) 扫描 digital_human_async_task_ref 中 state=0/1 的记录(业务“生成中”)
// 2) 组装 task_id 批量请求 model-asynch /task/get-task-batch
// 3) 中间件状态映射到业务状态业务只维护三态0生成中/1成功/2失败
// - 中间件 0/1/3能查到 task_id -> 业务 0生成中
// - 中间件 2/4成功/已下载) -> 业务 1成功
// - 中间件 查不到 task_id返回列表缺失 -> 业务 2失败
//
// 4) 绑定表仅用于“待同步列表”,因此:
// - 对中间件 0/1/3 不额外写库(减少查询/更新开销)
// - 对成功(2/4)与缺失(task_id 查不到)才更新绑定表
func (s *asyncTaskService) Sync(ctx context.Context, req *dto.SyncAsyncTasksReq) (res *dto.SyncAsyncTasksRes, err error) {
limit := 200
if req != nil && req.Limit > 0 {
limit = req.Limit
}
refs, err := dao.AsyncTaskRef.ListPending(ctx, limit)
if err != nil {
return nil, err
}
taskIDs := make([]string, 0, len(refs))
refMap := make(map[string]*entity.AsyncTaskRef, len(refs))
for _, r := range refs {
if r == nil || r.TaskID == "" {
continue
}
taskIDs = append(taskIDs, r.TaskID)
refMap[r.TaskID] = r
}
out := &dto.SyncAsyncTasksRes{
Total: len(taskIDs),
List: make([]dto.SyncAsyncTasksItem, 0, len(taskIDs)),
}
if len(taskIDs) == 0 {
return out, nil
}
items, err := getModelAsynchTaskBatch(ctx, taskIDs)
if err != nil {
return nil, err
}
seen := make(map[string]struct{}, len(items))
handled := 0
for _, it := range items {
r := refMap[it.TaskID]
if r == nil {
continue
}
seen[it.TaskID] = struct{}{}
switch it.State {
case 0, 1, 3:
// 排队中/执行中/失败(可能重试):业务侧仍视为生成中,不更新绑定表,减少更新开销
case 2, 4:
// 成功/已下载:业务侧写入 oss_file 并标记成功
if it.OssFile == "" {
errMsg := "中间件返回空oss地址"
_ = s.updateBizFailed(ctx, r, errMsg)
_, _ = dao.AsyncTaskRef.UpdateByTaskID(ctx, it.TaskID, gdb.Map{
entity.AsyncTaskRefCol.State: it.State,
entity.AsyncTaskRefCol.OssFile: "",
entity.AsyncTaskRefCol.ErrorMsg: errMsg,
})
out.List = append(out.List, dto.SyncAsyncTasksItem{
TaskID: it.TaskID,
State: it.State,
TableName: r.TableName,
BizID: fmt.Sprintf("%d", r.BizID),
OssFile: "",
ErrorMsg: errMsg,
})
continue
}
if err := s.updateBizSuccess(ctx, r, it.OssFile); err != nil {
errMsg := fmt.Sprintf("生成音频失败: %v", err)
_ = s.updateBizFailed(ctx, r, errMsg)
_, _ = dao.AsyncTaskRef.UpdateByTaskID(ctx, it.TaskID, gdb.Map{
entity.AsyncTaskRefCol.State: it.State,
entity.AsyncTaskRefCol.OssFile: it.OssFile,
entity.AsyncTaskRefCol.ErrorMsg: errMsg,
})
out.List = append(out.List, dto.SyncAsyncTasksItem{
TaskID: it.TaskID,
State: it.State,
TableName: r.TableName,
BizID: fmt.Sprintf("%d", r.BizID),
OssFile: it.OssFile,
ErrorMsg: errMsg,
})
continue
}
handled++
_, _ = dao.AsyncTaskRef.UpdateByTaskID(ctx, it.TaskID, gdb.Map{
entity.AsyncTaskRefCol.State: it.State,
entity.AsyncTaskRefCol.OssFile: it.OssFile,
entity.AsyncTaskRefCol.ErrorMsg: "",
})
default:
// 其他状态:不处理
}
out.List = append(out.List, dto.SyncAsyncTasksItem{
TaskID: it.TaskID,
State: it.State,
TableName: r.TableName,
BizID: fmt.Sprintf("%d", r.BizID),
OssFile: it.OssFile,
ErrorMsg: "",
})
}
// 处理“查不到 task_id”的情况
// 中间件对失败重试耗尽的任务会硬删除,批量接口不会返回该 task_id。
// 业务侧把这种情况视为失败终态,并软删除绑定记录,避免重复轮询。
for _, taskID := range taskIDs {
if _, ok := seen[taskID]; ok {
continue
}
r := refMap[taskID]
if r == nil {
continue
}
msg := "模型任务不存在已失败"
_ = s.updateBizFailed(ctx, r, msg)
_, _ = dao.AsyncTaskRef.UpdateByTaskID(ctx, taskID, gdb.Map{
entity.AsyncTaskRefCol.State: 3,
entity.AsyncTaskRefCol.ErrorMsg: msg,
"deleted_at": gtime.Now(),
})
out.List = append(out.List, dto.SyncAsyncTasksItem{
TaskID: taskID,
State: 3,
TableName: r.TableName,
BizID: fmt.Sprintf("%d", r.BizID),
OssFile: "",
ErrorMsg: msg,
})
}
out.Handled = handled
g.Log().Infof(ctx, "[AsyncTask.Sync] total=%d handled=%d", out.Total, out.Handled)
return out, nil
}
// updateBizSuccess 更新业务侧状态为成功
func (s *asyncTaskService) updateBizSuccess(ctx context.Context, ref *entity.AsyncTaskRef, ossFile string) error {
switch ref.TableName {
case public.TableNameAudio:
_, err := dao.Audio.UpdateStatus(ctx, ref.BizID, consts.AudioStatusSuccess, "", ossFile, 0, "")
return err
case public.TableNameCustomVoice:
_, err := dao.CustomVoice.UpdateStatus(ctx, ref.BizID, 1, "", ossFile)
return err
default:
return fmt.Errorf("未知 table_name=%s", ref.TableName)
}
}
// updateBizFailed 更新业务侧状态为失败
func (s *asyncTaskService) updateBizFailed(ctx context.Context, ref *entity.AsyncTaskRef, msg string) error {
switch ref.TableName {
case public.TableNameAudio:
_, err := dao.Audio.UpdateStatus(ctx, ref.BizID, consts.AudioStatusFailed, msg, "", 0, "")
return err
case public.TableNameCustomVoice:
_, err := dao.CustomVoice.UpdateStatus(ctx, ref.BizID, 2, msg, "")
return err
default:
return nil
}
}

View File

@@ -1,268 +0,0 @@
package service
import (
"context"
"encoding/base64"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/dao"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
type audio struct{}
// Audio 音频服务
var Audio = new(audio)
// UploadFileResponse OSS 文件上传响应结构
type UploadFileResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
FileURL string `json:"fileURL" dc:"上传地址"`
FileSize int `json:"fileSize" dc:"文件大小"`
FileName string `json:"fileName" dc:"文件名称"`
FileFormat string `json:"fileFormat" dc:"文件格式"`
FileAddressPrefix string `json:"fileAddressPrefix"`
} `json:"data"`
}
// Create 创建音频
func (s *audio) Create(ctx context.Context, req *dto.CreateAudioReq) (res *dto.CreateAudioRes, err error) {
// 设置默认音色
if req.Voice == "" {
req.Voice = "Serena" // 默认音色
}
if req.VoiceType == "" {
req.VoiceType = "Preset" // 默认预设音色
}
// 如果是自定义音色,验证音色是否存在
if req.VoiceType == "custom" && req.CustomVoice != "" {
customVoiceID := gconv.Int64(req.CustomVoice)
_, err := dao.CustomVoice.GetOne(ctx, customVoiceID)
if err != nil {
return nil, gerror.Wrapf(err, "自定义音色不存在: %s", req.CustomVoice)
}
}
// 插入数据库(初始状态为生成中)
audioID, err := dao.Audio.Insert(ctx, req)
if err != nil {
return nil, err
}
// 通过 model-asynch 创建异步任务(由中间件执行模型调用与产物落 OSS
// 约定:
// - custom克隆音色 -> base 模型(需要参考音频/参考文本) 否则 -> customvoice 模型
var taskID string
if req.VoiceType == "custom" {
customVoiceID := gconv.Int64(req.CustomVoice)
// 1. 先获取自定义音色详情
cv, err := dao.CustomVoice.GetOne(ctx, customVoiceID)
if err != nil {
_, _ = dao.Audio.UpdateStatus(ctx, audioID, consts.AudioStatusFailed, "获取自定义音色失败: "+err.Error(), "", 0, "")
return nil, err
}
// 2. 调用模型生成音频
refAudioBase64 := base64.StdEncoding.EncodeToString(cv.ReferenceAudio)
xVectorOnlyMode := false
if cv.Text == "" {
xVectorOnlyMode = true
}
taskID, err = TTS.CreateBaseTask(asyncCtx(ctx), req.ScriptText, "Auto", cv.Text, cv.OssFile, refAudioBase64, xVectorOnlyMode, 1.0)
} else {
// 1. 调用模型生成音频
taskID, err = TTS.CreateCustomVoiceTask(asyncCtx(ctx), req.ScriptText, req.Voice, "Auto", "", 1.0)
}
if err != nil {
_, _ = dao.Audio.UpdateStatus(ctx, audioID, consts.AudioStatusFailed, "创建异步任务失败: "+err.Error(), "", 0, "")
return nil, err
}
_, _ = dao.AsyncTaskRef.Insert(ctx, &entity.AsyncTaskRef{
TaskID: taskID,
State: 0,
TableName: public.TableNameAudio,
BizID: audioID,
})
res = &dto.CreateAudioRes{
Id: audioID,
}
return
}
// List 获取音频列表
func (s *audio) List(ctx context.Context, req *dto.ListAudioReq) (res *dto.ListAudioRes, err error) {
audioList, total, err := dao.Audio.List(ctx, req)
if err != nil {
return nil, err
}
res = &dto.ListAudioRes{
Total: int64(total),
List: make([]*dto.AudioListItem, 0, len(audioList)),
}
for _, audio := range audioList {
res.List = append(res.List, &dto.AudioListItem{
ID: audio.Id,
Name: audio.Name,
Description: audio.Description,
ScriptText: audio.ScriptText,
AudioURL: audio.AudioURL,
Status: audio.Status,
ErrorMsg: audio.ErrorMsg,
Duration: audio.Duration,
ExternalID: audio.ExternalID,
Voice: audio.Voice,
VoiceType: audio.VoiceType,
CustomVoice: audio.CustomVoice,
CreatedAt: audio.CreatedAt,
UpdatedAt: audio.UpdatedAt,
})
}
return res, nil
}
// GetOne 获取单个音频
func (s *audio) GetOne(ctx context.Context, id int64) (*dto.GetAudioRes, error) {
audioOne, err := dao.Audio.GetOne(ctx, id)
if err != nil {
return nil, err
}
return &dto.GetAudioRes{
ID: audioOne.Id,
Name: audioOne.Name,
Description: audioOne.Description,
ScriptText: audioOne.ScriptText,
AudioURL: audioOne.AudioURL,
Status: audioOne.Status,
ErrorMsg: audioOne.ErrorMsg,
Duration: audioOne.Duration,
ExternalID: audioOne.ExternalID,
Voice: audioOne.Voice,
VoiceType: audioOne.VoiceType,
CustomVoice: audioOne.CustomVoice,
CreatedAt: audioOne.CreatedAt,
UpdatedAt: audioOne.UpdatedAt,
}, nil
}
// Update 更新音频
func (s *audio) Update(ctx context.Context, req *dto.UpdateAudioReq) (err error) {
// 先获取原始音频信息
audioOne, err := dao.Audio.GetOne(ctx, req.ID)
if err != nil {
return gerror.Wrap(err, "获取原始音频信息失败")
}
// 修改字段
if !g.IsEmpty(req.Name) {
audioOne.Name = req.Name
}
if !g.IsEmpty(req.Description) {
audioOne.Description = req.Description
}
if !g.IsEmpty(req.Voice) {
audioOne.Voice = req.Voice
}
if !g.IsEmpty(req.VoiceType) {
audioOne.VoiceType = req.VoiceType
}
if !g.IsEmpty(req.CustomVoice) {
audioOne.CustomVoice = req.CustomVoice
}
_, err = dao.Audio.Update(ctx, req.ID, audioOne)
return err
}
// Delete 删除音频
func (s *audio) Delete(ctx context.Context, id int64) error {
_, err := dao.Audio.Delete(ctx, id)
return err
}
// Generate 重新生成音频
func (s *audio) Generate(ctx context.Context, req *dto.GenerateAudioReq) (res *dto.GenerateAudioRes, err error) {
// 获取音频信息
audioOne, err := dao.Audio.GetOne(ctx, req.ID)
if err != nil {
return nil, gerror.Wrap(err, "获取音频信息失败")
}
// 重置状态为生成中
_, err = dao.Audio.UpdateStatus(ctx, req.ID, consts.AudioStatusGenerating, "", "", 0, "")
if err != nil {
return nil, err
}
// 构建请求
createReq := &dto.CreateAudioReq{
Name: audioOne.Name,
Description: audioOne.Description,
ScriptText: audioOne.ScriptText,
Voice: audioOne.Voice,
VoiceType: audioOne.VoiceType,
CustomVoice: audioOne.CustomVoice,
}
// 异步重新生成音频
var taskID string
if createReq.VoiceType == "custom" {
customVoiceID := gconv.Int64(createReq.CustomVoice)
cv, err := dao.CustomVoice.GetOne(ctx, customVoiceID)
if err != nil {
_, _ = dao.Audio.UpdateStatus(ctx, req.ID, consts.AudioStatusFailed, "获取自定义音色失败: "+err.Error(), "", 0, "")
return nil, err
}
refAudioBase64 := ""
if cv != nil && len(cv.ReferenceAudio) > 0 {
refAudioBase64 = base64.StdEncoding.EncodeToString(cv.ReferenceAudio)
}
refText := ""
if cv != nil {
refText = cv.Text
}
xVectorOnlyMode := false
if refText == "" {
xVectorOnlyMode = true
}
taskID, err = TTS.CreateBaseTask(asyncCtx(ctx), createReq.ScriptText, "Auto", refText, cv.OssFile, refAudioBase64, xVectorOnlyMode, 1.0)
} else {
taskID, err = TTS.CreateCustomVoiceTask(asyncCtx(ctx), createReq.ScriptText, createReq.Voice, "Auto", "", 1.0)
}
if err != nil {
_, _ = dao.Audio.UpdateStatus(ctx, req.ID, consts.AudioStatusFailed, "创建异步任务失败: "+err.Error(), "", 0, "")
return nil, err
}
_, _ = dao.AsyncTaskRef.Insert(ctx, &entity.AsyncTaskRef{
TaskID: taskID,
State: 0,
TableName: public.TableNameAudio,
BizID: req.ID,
})
res = &dto.GenerateAudioRes{
TaskID: gconv.String(req.ID),
}
return
}
// GetStatusOptions 获取状态选项
func (s *audio) GetStatusOptions(ctx context.Context, req *dto.GetAudioStatusOptionsReq) (res *dto.GetAudioStatusOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetAudioStatusOptionsRes)
res.Options = consts.GetAllAudioStatusKeyValue()
return res, nil
}
// TTS 文本转语音(使用 Qwen3-TTS
func (s *audio) TTS(ctx context.Context, req *dto.TTSReq) (res *dto.TTSRes, err error) {
_ = ctx
_ = req
return nil, gerror.New("该接口已迁移为异步:请使用 CreateAudio 创建异步任务并通过轮询/批量领取获取结果")
}

View File

@@ -1,106 +0,0 @@
package service
import (
"context"
"encoding/base64"
"digital-human/digitalhuman/consts/public"
"digital-human/digitalhuman/dao"
"digital-human/digitalhuman/model/dto"
"digital-human/digitalhuman/model/entity"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
type customVoice struct{}
// CustomVoice 自定义音色服务
var CustomVoice = new(customVoice)
// CreateCustomVoice 创建自定义音色
func (s *customVoice) CreateCustomVoice(ctx context.Context, req *dto.CreateCustomVoiceReq) (res *dto.CreateCustomVoiceRes, err error) {
g.Log().Infof(ctx, "创建自定义音色: name=%s, voiceType=%s", req.Name, req.VoiceType)
// 插入数据库(状态:生成中)
voiceID, err := dao.CustomVoice.Insert(ctx, req)
if err != nil {
return nil, err
}
switch req.VoiceType {
case "design":
// 设计音频:按模型约定只传 text + instruct
taskID, err := TTS.CreateVoiceDesignTask(asyncCtx(ctx), req.Text, req.Description, "", 0)
if err != nil {
_, _ = dao.CustomVoice.UpdateStatus(ctx, voiceID, 2, "创建异步任务失败: "+err.Error(), "")
return nil, err
}
_, _ = dao.AsyncTaskRef.Insert(ctx, &entity.AsyncTaskRef{
TaskID: taskID,
State: 0,
TableName: public.TableNameCustomVoice,
BizID: voiceID,
})
res = &dto.CreateCustomVoiceRes{VoiceID: gconv.String(voiceID)}
g.Log().Infof(ctx, "自定义音色创建成功: voiceId=%d taskId=%s", voiceID, taskID)
case "clone":
// TODO : 克隆音色:使用语音转文字暂预留,后续找模型对应处理
refAudioBase64 := base64.StdEncoding.EncodeToString(req.ReferenceAudio)
taskID, err := TTS.SpeechToText(asyncCtx(ctx), refAudioBase64)
if err != nil {
_, _ = dao.CustomVoice.UpdateStatus(ctx, voiceID, 2, "创建异步任务失败: "+err.Error(), "")
return nil, err
}
_, _ = dao.AsyncTaskRef.Insert(ctx, &entity.AsyncTaskRef{
TaskID: taskID,
State: 0,
TableName: public.TableNameCustomVoice,
BizID: voiceID,
})
res = &dto.CreateCustomVoiceRes{VoiceID: gconv.String(voiceID)}
g.Log().Infof(ctx, "克隆音色成功: voiceId=%d taskId=%s", voiceID, taskID)
default:
return nil, gerror.New("不支持的音色类型")
}
return
}
// ListCustomVoices 获取自定义音色列表
func (s *customVoice) ListCustomVoices(ctx context.Context, req *dto.ListCustomVoiceReq) (res *dto.ListCustomVoiceRes, err error) {
customVoices, total, err := dao.CustomVoice.List(ctx, req)
if err != nil {
return nil, err
}
res = &dto.ListCustomVoiceRes{
Total: int64(total),
List: make([]*dto.CustomVoiceItem, 0, len(customVoices)),
}
for _, cv := range customVoices {
res.List = append(res.List, dao.CustomVoice.GetCustomVoiceItem(cv))
}
return
}
// DeleteCustomVoice 删除自定义音色
func (s *customVoice) DeleteCustomVoice(ctx context.Context, req *dto.DeleteCustomVoiceReq) (err error) {
// 验证音色是否存在
voiceID := gconv.Int64(req.VoiceID)
_, err = dao.CustomVoice.GetOne(ctx, voiceID)
if err != nil {
return gerror.Wrapf(err, "音色不存在: %s", req.VoiceID)
}
// 删除音色
_, err = dao.CustomVoice.Delete(ctx, voiceID)
if err != nil {
return gerror.Wrapf(err, "删除音色失败: %s", req.VoiceID)
}
g.Log().Infof(ctx, "自定义音色删除成功: voiceId=%s", req.VoiceID)
return nil
}

View File

@@ -1,179 +0,0 @@
package service
import (
"context"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/dao"
"digital-human/digitalhuman/model/dto"
"encoding/json"
"errors"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type digitalHuman struct{}
// DigitalHuman 数字人形象服务
var DigitalHuman = new(digitalHuman)
// Create 创建数字人形象
func (s *digitalHuman) Create(ctx context.Context, req *dto.CreateDigitalHumanReq) (res *dto.CreateDigitalHumanRes, err error) {
count, err := dao.DigitalHuman.Count(ctx, req)
if err != nil {
return nil, err
}
if count > 0 {
return nil, errors.New("数字人形象名称已存在")
}
// 插入数据库
ids, err := dao.DigitalHuman.Insert(ctx, req)
if err != nil {
return nil, err
}
// PostgreSQL 使用 int64
id := ids[0].(int64)
res = &dto.CreateDigitalHumanRes{
Id: id,
}
return
}
// List 获取数字人形象列表
func (s *digitalHuman) List(ctx context.Context, req *dto.ListDigitalHumanReq) (res *dto.ListDigitalHumanRes, error error) {
digitalHumanList, total, err := dao.DigitalHuman.List(ctx, req)
if err != nil {
return
}
res = &dto.ListDigitalHumanRes{
Total: total,
}
b, err := json.Marshal(digitalHumanList)
if err != nil {
return
}
err = json.Unmarshal(b, &res.List)
return
}
// GetOne 获取单个数字人形象
func (s *digitalHuman) GetOne(ctx context.Context, id int64) (*dto.GetDigitalHumanRes, error) {
digitalHumanOne, err := dao.DigitalHuman.GetOne(ctx, id)
if err != nil {
return nil, err
}
var createdAt, updatedAt *gtime.Time
if digitalHumanOne.CreatedAt != nil {
createdAt = digitalHumanOne.CreatedAt
}
if digitalHumanOne.UpdatedAt != nil {
updatedAt = digitalHumanOne.UpdatedAt
}
return &dto.GetDigitalHumanRes{
ID: digitalHumanOne.Id,
Name: digitalHumanOne.Name,
Description: digitalHumanOne.Description,
ImageURL: digitalHumanOne.AvatarURL,
VideoURL: digitalHumanOne.VideoURL,
Status: digitalHumanOne.Status,
Tags: digitalHumanOne.Tags,
Gender: digitalHumanOne.Gender,
Age: digitalHumanOne.Age,
Style: digitalHumanOne.Style,
ExternalID: digitalHumanOne.ExternalID,
Metadata: digitalHumanOne.Metadata,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}, nil
}
// Update 更新数字人形象
func (s *digitalHuman) Update(ctx context.Context, req *dto.UpdateDigitalHumanReq) error {
// 先获取原始数字人形象信息
digitalHumanOne, err := dao.DigitalHuman.GetOne(ctx, req.ID)
if err != nil {
return gerror.Wrap(err, "获取原始数字人形象信息失败")
}
// 修改字段
if !g.IsEmpty(req.Name) {
digitalHumanOne.Name = req.Name
}
if !g.IsEmpty(req.Description) {
digitalHumanOne.Description = req.Description
}
if !g.IsEmpty(req.ImageURL) {
digitalHumanOne.AvatarURL = req.ImageURL
}
if !g.IsEmpty(req.VideoURL) {
digitalHumanOne.VideoURL = req.VideoURL
}
digitalHumanOne.Status = req.Status
if req.Tags != nil {
digitalHumanOne.Tags = req.Tags
}
if !g.IsEmpty(req.Gender) {
digitalHumanOne.Gender = req.Gender
}
if !g.IsEmpty(req.Age) {
digitalHumanOne.Age = req.Age
}
if !g.IsEmpty(req.Style) {
digitalHumanOne.Style = req.Style
}
if !g.IsEmpty(req.ExternalID) {
digitalHumanOne.ExternalID = req.ExternalID
}
if req.Metadata != nil {
digitalHumanOne.Metadata = req.Metadata
}
return dao.DigitalHuman.Update(ctx, req.ID, digitalHumanOne)
}
// UpdateStatus 更新数字人形象状态
func (s *digitalHuman) UpdateStatus(ctx context.Context, id int64, status consts.DigitalHumanStatus) error {
_, err := dao.DigitalHuman.UpdateStatus(ctx, id, status)
return err
}
// Delete 删除数字人形象
func (s *digitalHuman) Delete(ctx context.Context, id int64) error {
return dao.DigitalHuman.Delete(ctx, id)
}
// GetStatusOptions 获取状态选项
func (s *digitalHuman) GetStatusOptions(ctx context.Context, req *dto.GetDigitalHumanStatusOptionsReq) (res *dto.GetDigitalHumanStatusOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetDigitalHumanStatusOptionsRes)
res.Options = consts.GetAllStatusKeyValue()
return res, nil
}
// GetGenderOptions 获取性别选项
func (s *digitalHuman) GetGenderOptions(ctx context.Context, req *dto.GetGenderOptionsReq) (res *dto.GetGenderOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetGenderOptionsRes)
res.Options = consts.GetAllGenderKeyValue()
return res, nil
}
// GetAgeOptions 获取年龄段选项
func (s *digitalHuman) GetAgeOptions(ctx context.Context, req *dto.GetAgeOptionsReq) (res *dto.GetAgeOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetAgeOptionsRes)
res.Options = consts.GetAllAgeKeyValue()
return res, nil
}
// GetStyleOptions 获取风格选项
func (s *digitalHuman) GetStyleOptions(ctx context.Context, req *dto.GetStyleOptionsReq) (res *dto.GetStyleOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetStyleOptionsRes)
res.Options = consts.GetAllStyleKeyValue()
return res, nil
}

View File

@@ -1,219 +0,0 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
stdhttp "net/http"
"os"
"path/filepath"
"sync"
"time"
commonHttp "gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
var commonHttpTransportMu sync.Mutex
// asyncCtx 异步上下文处理
func asyncCtx(ctx context.Context) context.Context {
asyncCtx := context.WithoutCancel(ctx)
if r := g.RequestFromCtx(ctx); r != nil {
if token := r.Header.Get("Authorization"); token != "" {
asyncCtx = context.WithValue(asyncCtx, "token", token)
}
}
if user, uErr := utils.GetUserInfo(ctx); uErr == nil && user != nil {
asyncCtx = context.WithValue(asyncCtx, "user", user)
}
return asyncCtx
}
// setCommonHttpResponseHeaderTimeout 调整公共 HTTP 客户端响应头超时,避免长时推理被 30s 默认值打断。
func setCommonHttpResponseHeaderTimeout(d time.Duration) {
if d <= 0 {
return
}
commonHttpTransportMu.Lock()
defer commonHttpTransportMu.Unlock()
if tr, ok := commonHttp.Httpclient.Transport.(*stdhttp.Transport); ok && tr != nil {
if tr.ResponseHeaderTimeout < d {
tr.ResponseHeaderTimeout = d
}
}
}
// forwardHeaders 透传调用链路中必须的头信息,优先使用异步上下文里固化的 token。
func forwardHeaders(ctx context.Context) map[string]string {
headers := make(map[string]string)
if token, ok := ctx.Value("token").(string); ok && token != "" {
headers["Authorization"] = token
}
if r := g.RequestFromCtx(ctx); r != nil {
if headers["Authorization"] == "" {
if token := r.Header.Get("Authorization"); token != "" {
headers["Authorization"] = token
}
}
if userInfo := r.Header.Get("X-User-Info"); userInfo != "" {
headers["X-User-Info"] = userInfo
}
}
return headers
}
// commonPostJSON 使用 common/http 的底层客户端直连 JSON 接口,适配非统一响应包装结构。
func commonPostJSON(ctx context.Context, url string, headers map[string]string, req any, resp any) error {
client := commonHttp.Httpclient.Clone().ContentJson()
if deadline, ok := ctx.Deadline(); ok {
if d := time.Until(deadline); d > 0 {
client.SetTimeout(d)
}
}
if len(headers) > 0 {
client.SetHeaderMap(headers)
}
r, err := client.DoRequest(ctx, stdhttp.MethodPost, url, req)
if err != nil {
return err
}
defer r.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
return gerror.Wrap(err, "读取响应失败")
}
if r.StatusCode != stdhttp.StatusOK {
return gerror.Newf("HTTP状态码异常: %d, body: %s", r.StatusCode, string(body))
}
if err := json.Unmarshal(body, resp); err != nil {
return gerror.Wrapf(err, "解析响应失败, body: %s", string(body))
}
return nil
}
func commonPostMultipartFile(ctx context.Context, url string, headers map[string]string, form map[string]string, fileField string, filePath string, resp any) error {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for k, v := range form {
if v == "" {
continue
}
if err := writer.WriteField(k, v); err != nil {
return gerror.Wrapf(err, "写入表单字段失败: %s", k)
}
}
f, err := os.Open(filePath)
if err != nil {
return gerror.Wrapf(err, "打开文件失败: %s", filePath)
}
defer f.Close()
part, err := writer.CreateFormFile(fileField, filepath.Base(filePath))
if err != nil {
return gerror.Wrapf(err, "创建表单文件失败: %s", fileField)
}
if _, err := io.Copy(part, f); err != nil {
return gerror.Wrap(err, "写入文件内容失败")
}
contentType := writer.FormDataContentType()
if err := writer.Close(); err != nil {
return gerror.Wrap(err, "关闭表单写入器失败")
}
client := commonHttp.Httpclient.Clone()
if deadline, ok := ctx.Deadline(); ok {
if d := time.Until(deadline); d > 0 {
client.SetTimeout(d)
}
}
if headers == nil {
headers = make(map[string]string)
}
headers["Content-Type"] = contentType
client.SetHeaderMap(headers)
r, err := client.DoRequest(ctx, stdhttp.MethodPost, url, body.Bytes())
if err != nil {
return err
}
defer r.Close()
raw, err := io.ReadAll(r.Body)
if err != nil {
return gerror.Wrap(err, "读取响应失败")
}
if r.StatusCode != stdhttp.StatusOK {
return gerror.Newf("HTTP状态码异常: %d, body: %s", r.StatusCode, string(raw))
}
if err := json.Unmarshal(raw, resp); err != nil {
return gerror.Wrapf(err, "解析响应失败, body: %s", string(raw))
}
return nil
}
// -------------------------- model-asynch 调用封装 --------------------------
const modelAsynchServiceName = "model-asynch"
type modelAsynchCreateTaskReq struct {
ModelName string `json:"modelName"`
InputRef string `json:"inputRef,omitempty"`
RequestPayload any `json:"requestPayload"`
}
type modelAsynchCreateTaskRes struct {
TaskID string `json:"taskId"`
}
// createModelAsynchTask 调用 model-asynch 创建任务
// 注意:路由以 GoFrame 默认输出为准(通常为 /task/create-task
func createModelAsynchTask(ctx context.Context, modelName string, payload any, inputRef string) (taskID string, err error) {
taskUrl := g.Cfg().MustGet(ctx, "model-asynch.addr", "127.0.0.1:8080")
headers := forwardHeaders(ctx)
req := &modelAsynchCreateTaskReq{
ModelName: modelName,
InputRef: inputRef,
RequestPayload: payload,
}
var res modelAsynchCreateTaskRes
if err := commonHttp.Post(ctx, fmt.Sprintf("%s/task/createTask", taskUrl), headers, &res, req); err != nil {
return "", err
}
return res.TaskID, nil
}
type modelAsynchBatchReq struct {
TaskIDs []string `json:"taskIds"`
}
type modelAsynchBatchItem struct {
TaskID string `json:"taskId"`
State int `json:"state"`
OssFile string `json:"ossFile"`
}
type modelAsynchBatchRes struct {
List []modelAsynchBatchItem `json:"list"`
}
// getModelAsynchTaskBatch 批量查询任务(成功 2->4 的逻辑由中间件内部处理)
func getModelAsynchTaskBatch(ctx context.Context, taskIDs []string) (items []modelAsynchBatchItem, err error) {
taskUrl := g.Cfg().MustGet(ctx, "model-asynch.addr", "127.0.0.1:8080")
headers := forwardHeaders(ctx)
req := &modelAsynchBatchReq{TaskIDs: taskIDs}
var res modelAsynchBatchRes
if err := commonHttp.Post(ctx, fmt.Sprintf("%s/task/getTaskBatch", taskUrl), headers, &res, req); err != nil {
return nil, err
}
return res.List, nil
}

View File

@@ -1,117 +0,0 @@
package service
import (
"context"
"encoding/base64"
"digital-human/digitalhuman/consts/public"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
type tts struct{}
// TTS 统一的模型异步调用封装(通过 model-asynch 中间件)
var TTS = new(tts)
// CreateVoiceDesignTask 设计音频任务VoiceDesign
func (s *tts) CreateVoiceDesignTask(
ctx context.Context,
text string,
instruct string,
language string, // 空则 Auto
speed float64, // <=0 则 1.0
) (taskID string, err error) {
if language == "" {
language = "Auto"
}
if speed <= 0 {
speed = 1.0
}
payload := map[string]any{
"text": text,
"language": language,
"instruct": instruct,
"speed": speed,
"response_format": "wav",
}
g.Log().Info(ctx, "[CreateVoiceDesignTask] %v", payload)
return createModelAsynchTask(ctx, public.ModelNameVoiceDesign, payload, "")
}
// CreateCustomVoiceTask 预设音色CustomVoice任务
// - speaker: 预设说话人(如 Vivian/Serena/Ryan/...
// - instruct: 可选,情绪/风格控制
func (s *tts) CreateCustomVoiceTask(
ctx context.Context,
text string,
speaker string,
language string, // 例如 "Chinese"/"English"/"Auto",空则默认 "Auto"
instruct string, // 可空
speed float64, // 0.5~2.0<=0 则默认 1.0
) (taskID string, err error) {
if language == "" {
language = "Auto"
}
if speed <= 0 {
speed = 1.0
}
payload := map[string]any{
"text": text,
"language": language,
"speaker": speaker,
"instruct": instruct,
"speed": speed,
"response_format": "wav", // 建议统一用 wav
}
g.Log().Info(ctx, "[CreateCustomVoiceTask] %v", payload)
return createModelAsynchTask(ctx, public.ModelNameCustomVoice, payload, "")
}
// CreateBaseTask 声音克隆Base / clone任务
// 说明ref_audio_url 与 ref_audio_base64 二选一
func (s *tts) CreateBaseTask(
ctx context.Context,
text string,
language string, // 例如 "Chinese"/"English"/"Auto",空则默认 "Auto"
refText string, // 当 xVectorOnlyMode=false 时必填
refAudioURL string, // 可空
refAudioBase64 string, // 可空(不带 data: 前缀也可以)
xVectorOnlyMode bool, // true=不需要 refText但质量可能下降
speed float64, // 0.5~2.0<=0 则默认 1.0
) (taskID string, err error) {
if language == "" {
language = "Auto"
}
if speed <= 0 {
speed = 1.0
}
payload := map[string]any{
"text": text,
"language": language,
"ref_text": refText,
"ref_audio_url": refAudioURL,
"ref_audio_base64": refAudioBase64,
"x_vector_only_mode": xVectorOnlyMode,
"speed": speed,
"response_format": "wav",
}
g.Log().Info(ctx, "[CreateBaseTask] %v", payload)
return createModelAsynchTask(ctx, public.ModelNameBase, payload, "")
}
// SpeechToText 语音转文本(预留)
// audioBase64base64 编码的音频数据WAV/MP3等
func (s *tts) SpeechToText(ctx context.Context, audioBase64 string) (text string, err error) {
_ = ctx
if audioBase64 == "" {
return "", gerror.New("audioBase64 不能为空")
}
// 简单校验 base64 合法性
if _, err := base64.StdEncoding.DecodeString(audioBase64); err != nil {
return "", gerror.Wrap(err, "audioBase64 非法")
}
return "", gerror.New("SpeechToText 暂未实现:后续接入语音识别模型后补齐")
}

View File

@@ -1,218 +0,0 @@
package service
import (
"context"
"digital-human/digitalhuman/consts"
"digital-human/digitalhuman/dao"
"digital-human/digitalhuman/model/dto"
"encoding/json"
"errors"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type video struct{}
// Video 视频服务
var Video = new(video)
// Create 创建视频
func (s *video) Create(ctx context.Context, req *dto.CreateVideoReq) (res *dto.CreateVideoRes, err error) {
// 验证数字人形象是否存在且启用
digitalHumanOne, err := dao.DigitalHuman.GetOne(ctx, req.DigitalHumanID)
if err != nil {
return nil, gerror.Wrap(err, "数字人形象不存在")
}
if digitalHumanOne.Status != consts.DigitalHumanStatusActive {
return nil, errors.New("数字人形象未启用")
}
// 验证音频是否存在且已生成成功
audioOne, err := dao.Audio.GetOne(ctx, req.AudioID)
if err != nil {
return nil, gerror.Wrap(err, "音频不存在")
}
if audioOne.Status != consts.AudioStatusSuccess {
return nil, errors.New("音频未生成成功,无法合成视频")
}
// 创建视频记录(初始状态为生成中)
ids, err := dao.Video.Insert(ctx, req)
if err != nil {
return nil, err
}
// 保存视频IDPostgreSQL 使用 int64
videoID := ids[0].(int64)
// 异步生成视频
go s.generateVideo(ctx, req.DigitalHumanID, digitalHumanOne.Name, req.AudioID, audioOne.AudioURL, audioOne.Duration, req.Resolution, videoID)
res = &dto.CreateVideoRes{
Id: videoID,
}
return
}
// generateVideo 生成视频(异步处理)
func (s *video) generateVideo(ctx context.Context, digitalHumanID int64, digitalHumanName string, audioID int64, audioURL string, duration int, resolution consts.Resolution, videoID int64) {
// 更新视频状态设置音频URL和时长
_, _ = dao.Video.UpdateStatus(ctx, videoID, consts.VideoStatusGenerating, "", audioURL, duration, "", "")
// 调用数字人形象与音频合成服务
videoURL, thumbnailURL, externalTaskID, err := s.synthesizeVideo(ctx, digitalHumanID, audioURL, resolution)
if err != nil {
// 视频合成失败
_, _ = dao.Video.UpdateStatus(ctx, videoID, consts.VideoStatusFailed, "视频合成失败: "+err.Error(), "", 0, "", "")
return
}
// 更新视频生成状态为成功
_, _ = dao.Video.UpdateStatus(ctx, videoID, consts.VideoStatusSuccess, "", videoURL, duration, thumbnailURL, externalTaskID)
}
// synthesizeVideo 合成视频(模拟)
func (s *video) synthesizeVideo(ctx context.Context, digitalHumanID int64, audioURL string, resolution consts.Resolution) (videoURL string, thumbnailURL string, externalTaskID string, err error) {
// TODO: 调用真实的数字人视频合成服务API
// 这里模拟返回
g.Log().Info(ctx, "合成视频数字人ID:", digitalHumanID, "音频URL:", audioURL, "分辨率:", resolution)
// 模拟外部任务ID使用雪花算法或UUID
externalTaskID = gconv.String(digitalHumanID) + "-" + gconv.String(gtime.Timestamp())
// 模拟视频URL实际应该从视频合成服务获取
videoURL = "https://example.com/video/" + externalTaskID + ".mp4"
// 模拟缩略图URL
thumbnailURL = "https://example.com/video/" + externalTaskID + "_thumb.jpg"
return videoURL, thumbnailURL, externalTaskID, nil
}
// List 获取视频列表
func (s *video) List(ctx context.Context, req *dto.ListVideoReq) (res *dto.ListVideoRes, error error) {
videoList, total, err := dao.Video.List(ctx, req)
if err != nil {
return
}
res = &dto.ListVideoRes{
Total: total,
}
b, err := json.Marshal(videoList)
if err != nil {
return
}
err = json.Unmarshal(b, &res.List)
return
}
// GetOne 获取单个视频
func (s *video) GetOne(ctx context.Context, id int64) (*dto.GetVideoRes, error) {
videoOne, err := dao.Video.GetOne(ctx, id)
if err != nil {
return nil, err
}
var createdAt, updatedAt *gtime.Time
if videoOne.CreatedAt != nil {
createdAt = videoOne.CreatedAt
}
if videoOne.UpdatedAt != nil {
updatedAt = videoOne.UpdatedAt
}
return &dto.GetVideoRes{
ID: videoOne.Id,
Name: videoOne.Name,
Description: videoOne.Description,
DigitalHumanID: videoOne.DigitalHumanID,
DigitalHumanName: videoOne.DigitalHumanName,
AudioID: videoOne.AudioID,
AudioURL: "",
VideoURL: videoOne.VideoURL,
Status: videoOne.Status,
ErrorMsg: videoOne.ErrorMsg,
Duration: videoOne.Duration,
Resolution: videoOne.Resolution,
ThumbnailURL: videoOne.ThumbnailURL,
ExternalTaskID: videoOne.ExternalID,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}, nil
}
// Update 更新视频
func (s *video) Update(ctx context.Context, req *dto.UpdateVideoReq) error {
// 先获取原始视频信息
videoOne, err := dao.Video.GetOne(ctx, req.ID)
if err != nil {
return gerror.Wrap(err, "获取原始视频信息失败")
}
// 修改字段
if !g.IsEmpty(req.Name) {
videoOne.Name = req.Name
}
if !g.IsEmpty(req.Description) {
videoOne.Description = req.Description
}
return dao.Video.Update(ctx, req.ID, videoOne)
}
// Delete 删除视频
func (s *video) Delete(ctx context.Context, id int64) error {
return dao.Video.Delete(ctx, id)
}
// Generate 重新生成视频
func (s *video) Generate(ctx context.Context, req *dto.GenerateVideoReq) (res *dto.GenerateVideoRes, err error) {
// 获取视频信息
videoOne, err := dao.Video.GetOne(ctx, req.ID)
if err != nil {
return nil, gerror.Wrap(err, "获取视频信息失败")
}
// 验证音频是否仍然有效(已生成成功)
if videoOne.AudioID != 0 {
audioOne, err := dao.Audio.GetOne(ctx, videoOne.AudioID)
if err != nil {
return nil, gerror.Wrap(err, "获取音频信息失败")
}
if audioOne.Status != consts.AudioStatusSuccess {
return nil, errors.New("音频未生成成功,无法合成视频")
}
}
// 重置状态为生成中
_, err = dao.Video.UpdateStatus(ctx, req.ID, consts.VideoStatusGenerating, "", "", 0, "", "")
if err != nil {
return nil, err
}
// 异步重新生成视频
go s.generateVideo(ctx, videoOne.DigitalHumanID, videoOne.DigitalHumanName, videoOne.AudioID, "", videoOne.Duration, videoOne.Resolution, req.ID)
res = &dto.GenerateVideoRes{
TaskID: gconv.String(req.ID),
}
return
}
// GetStatusOptions 获取状态选项
func (s *video) GetStatusOptions(ctx context.Context, req *dto.GetVideoStatusOptionsReq) (res *dto.GetVideoStatusOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetVideoStatusOptionsRes)
res.Options = consts.GetAllVideoStatusKeyValue()
return res, nil
}
// GetResolutionOptions 获取分辨率选项
func (s *video) GetResolutionOptions(ctx context.Context, req *dto.GetResolutionOptionsReq) (res *dto.GetResolutionOptionsRes, err error) {
_ = ctx
_ = req
res = new(dto.GetResolutionOptionsRes)
res.Options = consts.GetResolutionOptions()
return res, nil
}