引包目录名调整
This commit is contained in:
218
digital-human/service/video_service.go
Normal file
218
digital-human/service/video_service.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"ai-agent/digital-human/consts"
|
||||
"ai-agent/digital-human/dao"
|
||||
"ai-agent/digital-human/model/dto"
|
||||
"context"
|
||||
"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
|
||||
}
|
||||
|
||||
// 保存视频ID(PostgreSQL 使用 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
|
||||
}
|
||||
Reference in New Issue
Block a user