This commit is contained in:
Connor
2026-01-12 13:17:11 +08:00
parent 95851f8e69
commit 9600fc542c
132 changed files with 35734 additions and 5 deletions

136
api/handlers/ai_config.go Normal file
View File

@@ -0,0 +1,136 @@
package handlers
import (
"strconv"
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AIConfigHandler struct {
aiService *services.AIService
log *logger.Logger
}
func NewAIConfigHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger) *AIConfigHandler {
return &AIConfigHandler{
aiService: services.NewAIService(db, log),
log: log,
}
}
func (h *AIConfigHandler) CreateConfig(c *gin.Context) {
var req services.CreateAIConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
config, err := h.aiService.CreateConfig(&req)
if err != nil {
response.InternalError(c, "创建失败")
return
}
response.Created(c, config)
}
func (h *AIConfigHandler) GetConfig(c *gin.Context) {
configID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的配置ID")
return
}
config, err := h.aiService.GetConfig(uint(configID))
if err != nil {
if err.Error() == "config not found" {
response.NotFound(c, "配置不存在")
return
}
response.InternalError(c, "获取失败")
return
}
response.Success(c, config)
}
func (h *AIConfigHandler) ListConfigs(c *gin.Context) {
serviceType := c.Query("service_type")
configs, err := h.aiService.ListConfigs(serviceType)
if err != nil {
response.InternalError(c, "获取列表失败")
return
}
response.Success(c, configs)
}
func (h *AIConfigHandler) UpdateConfig(c *gin.Context) {
configID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的配置ID")
return
}
var req services.UpdateAIConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
config, err := h.aiService.UpdateConfig(uint(configID), &req)
if err != nil {
if err.Error() == "config not found" {
response.NotFound(c, "配置不存在")
return
}
response.InternalError(c, "更新失败")
return
}
response.Success(c, config)
}
func (h *AIConfigHandler) DeleteConfig(c *gin.Context) {
configID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的配置ID")
return
}
if err := h.aiService.DeleteConfig(uint(configID)); err != nil {
if err.Error() == "config not found" {
response.NotFound(c, "配置不存在")
return
}
response.InternalError(c, "删除失败")
return
}
response.Success(c, gin.H{"message": "删除成功"})
}
func (h *AIConfigHandler) TestConnection(c *gin.Context) {
var req services.TestConnectionRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.aiService.TestConnection(&req); err != nil {
response.BadRequest(c, "连接测试失败: "+err.Error())
return
}
response.Success(c, gin.H{"message": "连接测试成功"})
}

220
api/handlers/asset.go Normal file
View File

@@ -0,0 +1,220 @@
package handlers
import (
"strconv"
"strings"
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/domain/models"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AssetHandler struct {
assetService *services.AssetService
log *logger.Logger
}
func NewAssetHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger) *AssetHandler {
return &AssetHandler{
assetService: services.NewAssetService(db, log),
log: log,
}
}
func (h *AssetHandler) CreateAsset(c *gin.Context) {
var req services.CreateAssetRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
asset, err := h.assetService.CreateAsset(&req)
if err != nil {
h.log.Errorw("Failed to create asset", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, asset)
}
func (h *AssetHandler) UpdateAsset(c *gin.Context) {
assetID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
var req services.UpdateAssetRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
asset, err := h.assetService.UpdateAsset(uint(assetID), &req)
if err != nil {
h.log.Errorw("Failed to update asset", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, asset)
}
func (h *AssetHandler) GetAsset(c *gin.Context) {
assetID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
asset, err := h.assetService.GetAsset(uint(assetID))
if err != nil {
response.NotFound(c, "素材不存在")
return
}
response.Success(c, asset)
}
func (h *AssetHandler) ListAssets(c *gin.Context) {
var dramaID *string
if dramaIDStr := c.Query("drama_id"); dramaIDStr != "" {
dramaID = &dramaIDStr
}
var episodeID *uint
if episodeIDStr := c.Query("episode_id"); episodeIDStr != "" {
if id, err := strconv.ParseUint(episodeIDStr, 10, 32); err == nil {
uid := uint(id)
episodeID = &uid
}
}
var storyboardID *uint
if storyboardIDStr := c.Query("storyboard_id"); storyboardIDStr != "" {
if id, err := strconv.ParseUint(storyboardIDStr, 10, 32); err == nil {
uid := uint(id)
storyboardID = &uid
}
}
var assetType *models.AssetType
if typeStr := c.Query("type"); typeStr != "" {
t := models.AssetType(typeStr)
assetType = &t
}
var isFavorite *bool
if favoriteStr := c.Query("is_favorite"); favoriteStr != "" {
if favoriteStr == "true" {
fav := true
isFavorite = &fav
} else if favoriteStr == "false" {
fav := false
isFavorite = &fav
}
}
var tagIDs []uint
if tagIDsStr := c.Query("tag_ids"); tagIDsStr != "" {
for _, idStr := range strings.Split(tagIDsStr, ",") {
if id, err := strconv.ParseUint(strings.TrimSpace(idStr), 10, 32); err == nil {
tagIDs = append(tagIDs, uint(id))
}
}
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
req := &services.ListAssetsRequest{
DramaID: dramaID,
EpisodeID: episodeID,
StoryboardID: storyboardID,
Type: assetType,
Category: c.Query("category"),
TagIDs: tagIDs,
IsFavorite: isFavorite,
Search: c.Query("search"),
Page: page,
PageSize: pageSize,
}
assets, total, err := h.assetService.ListAssets(req)
if err != nil {
h.log.Errorw("Failed to list assets", "error", err)
response.InternalError(c, err.Error())
return
}
response.SuccessWithPagination(c, assets, total, page, pageSize)
}
func (h *AssetHandler) DeleteAsset(c *gin.Context) {
assetID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
if err := h.assetService.DeleteAsset(uint(assetID)); err != nil {
h.log.Errorw("Failed to delete asset", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, nil)
}
func (h *AssetHandler) ImportFromImageGen(c *gin.Context) {
imageGenID, err := strconv.ParseUint(c.Param("image_gen_id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
asset, err := h.assetService.ImportFromImageGen(uint(imageGenID))
if err != nil {
h.log.Errorw("Failed to import from image gen", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, asset)
}
func (h *AssetHandler) ImportFromVideoGen(c *gin.Context) {
videoGenID, err := strconv.ParseUint(c.Param("video_gen_id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
asset, err := h.assetService.ImportFromVideoGen(uint(videoGenID))
if err != nil {
h.log.Errorw("Failed to import from video gen", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, asset)
}

View File

@@ -0,0 +1,34 @@
package handlers
import (
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
)
// BatchGenerateCharacterImages 批量生成角色图片
func (h *CharacterLibraryHandler) BatchGenerateCharacterImages(c *gin.Context) {
var req struct {
CharacterIDs []string `json:"character_ids" binding:"required,min=1"`
Model string `json:"model"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 限制批量生成数量
if len(req.CharacterIDs) > 10 {
response.BadRequest(c, "单次最多生成10个角色")
return
}
// 异步批量生成
go h.libraryService.BatchGenerateCharacterImages(req.CharacterIDs, h.imageService, req.Model)
response.Success(c, gin.H{
"message": "批量生成任务已提交",
"count": len(req.CharacterIDs),
})
}

View File

@@ -0,0 +1,274 @@
package handlers
import (
"strconv"
services2 "github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type CharacterLibraryHandler struct {
libraryService *services2.CharacterLibraryService
imageService *services2.ImageGenerationService
log *logger.Logger
}
func NewCharacterLibraryHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger, transferService *services2.ResourceTransferService) *CharacterLibraryHandler {
return &CharacterLibraryHandler{
libraryService: services2.NewCharacterLibraryService(db, log),
imageService: services2.NewImageGenerationService(db, transferService, log),
log: log,
}
}
// ListLibraryItems 获取角色库列表
func (h *CharacterLibraryHandler) ListLibraryItems(c *gin.Context) {
var query services2.CharacterLibraryQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.BadRequest(c, err.Error())
return
}
if query.Page < 1 {
query.Page = 1
}
if query.PageSize < 1 || query.PageSize > 100 {
query.PageSize = 20
}
items, total, err := h.libraryService.ListLibraryItems(&query)
if err != nil {
h.log.Errorw("Failed to list library items", "error", err)
response.InternalError(c, "获取角色库失败")
return
}
response.SuccessWithPagination(c, items, total, query.Page, query.PageSize)
}
// CreateLibraryItem 添加到角色库
func (h *CharacterLibraryHandler) CreateLibraryItem(c *gin.Context) {
var req services2.CreateLibraryItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
item, err := h.libraryService.CreateLibraryItem(&req)
if err != nil {
h.log.Errorw("Failed to create library item", "error", err)
response.InternalError(c, "添加到角色库失败")
return
}
response.Created(c, item)
}
// GetLibraryItem 获取角色库项详情
func (h *CharacterLibraryHandler) GetLibraryItem(c *gin.Context) {
itemID := c.Param("id")
item, err := h.libraryService.GetLibraryItem(itemID)
if err != nil {
if err.Error() == "library item not found" {
response.NotFound(c, "角色库项不存在")
return
}
h.log.Errorw("Failed to get library item", "error", err)
response.InternalError(c, "获取失败")
return
}
response.Success(c, item)
}
// DeleteLibraryItem 删除角色库项
func (h *CharacterLibraryHandler) DeleteLibraryItem(c *gin.Context) {
itemID := c.Param("id")
if err := h.libraryService.DeleteLibraryItem(itemID); err != nil {
if err.Error() == "library item not found" {
response.NotFound(c, "角色库项不存在")
return
}
h.log.Errorw("Failed to delete library item", "error", err)
response.InternalError(c, "删除失败")
return
}
response.Success(c, gin.H{"message": "删除成功"})
}
// UploadCharacterImage 上传角色图片
func (h *CharacterLibraryHandler) UploadCharacterImage(c *gin.Context) {
characterID := c.Param("id")
// TODO: 处理文件上传
// 这里需要实现文件上传逻辑保存到OSS或本地
// 暂时使用简单的实现
var req struct {
ImageURL string `json:"image_url" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.libraryService.UploadCharacterImage(characterID, req.ImageURL); err != nil {
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权限")
return
}
h.log.Errorw("Failed to upload character image", "error", err)
response.InternalError(c, "上传失败")
return
}
response.Success(c, gin.H{"message": "上传成功"})
}
// ApplyLibraryItemToCharacter 从角色库应用形象
func (h *CharacterLibraryHandler) ApplyLibraryItemToCharacter(c *gin.Context) {
characterID := c.Param("id")
var req struct {
LibraryItemID string `json:"library_item_id" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.libraryService.ApplyLibraryItemToCharacter(characterID, req.LibraryItemID); err != nil {
if err.Error() == "library item not found" {
response.NotFound(c, "角色库项不存在")
return
}
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权限")
return
}
h.log.Errorw("Failed to apply library item", "error", err)
response.InternalError(c, "应用失败")
return
}
response.Success(c, gin.H{"message": "应用成功"})
}
// AddCharacterToLibrary 将角色添加到角色库
func (h *CharacterLibraryHandler) AddCharacterToLibrary(c *gin.Context) {
characterID := c.Param("id")
var req struct {
Category *string `json:"category"`
}
if err := c.ShouldBindJSON(&req); err != nil {
// 允许空body
req.Category = nil
}
item, err := h.libraryService.AddCharacterToLibrary(characterID, req.Category)
if err != nil {
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权限")
return
}
if err.Error() == "character has no image" {
response.BadRequest(c, "角色还没有形象图片")
return
}
h.log.Errorw("Failed to add character to library", "error", err)
response.InternalError(c, "添加失败")
return
}
response.Created(c, item)
}
// UpdateCharacter 更新角色信息
func (h *CharacterLibraryHandler) UpdateCharacter(c *gin.Context) {
characterID := c.Param("id")
var req struct {
Name *string `json:"name"`
Appearance *string `json:"appearance"`
Personality *string `json:"personality"`
Description *string `json:"description"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.libraryService.UpdateCharacter(characterID, &req); err != nil {
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权限")
return
}
h.log.Errorw("Failed to update character", "error", err)
response.InternalError(c, "更新失败")
return
}
response.Success(c, gin.H{"message": "更新成功"})
}
// DeleteCharacter 删除单个角色
func (h *CharacterLibraryHandler) DeleteCharacter(c *gin.Context) {
characterIDStr := c.Param("id")
characterID, err := strconv.ParseUint(characterIDStr, 10, 32)
if err != nil {
response.BadRequest(c, "无效的角色ID")
return
}
if err := h.libraryService.DeleteCharacter(uint(characterID)); err != nil {
h.log.Errorw("Failed to delete character", "error", err, "id", characterID)
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权删除此角色")
return
}
response.InternalError(c, "删除失败")
return
}
response.Success(c, gin.H{"message": "角色已删除"})
}

View File

@@ -0,0 +1,38 @@
package handlers
import (
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
)
// GenerateCharacterImage AI生成角色形象
func (h *CharacterLibraryHandler) GenerateCharacterImage(c *gin.Context) {
characterID := c.Param("id")
// 获取请求体中的model参数
var req struct {
Model string `json:"model"`
}
c.ShouldBindJSON(&req)
imageGen, err := h.libraryService.GenerateCharacterImage(characterID, h.imageService, req.Model)
if err != nil {
if err.Error() == "character not found" {
response.NotFound(c, "角色不存在")
return
}
if err.Error() == "unauthorized" {
response.Forbidden(c, "无权限")
return
}
h.log.Errorw("Failed to generate character image", "error", err)
response.InternalError(c, "生成失败")
return
}
response.Success(c, gin.H{
"message": "角色图片生成已启动",
"image_generation": imageGen,
})
}

310
api/handlers/drama.go Normal file
View File

@@ -0,0 +1,310 @@
package handlers
import (
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/domain/models"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type DramaHandler struct {
db *gorm.DB
dramaService *services.DramaService
videoMergeService *services.VideoMergeService
log *logger.Logger
}
func NewDramaHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger, transferService *services.ResourceTransferService) *DramaHandler {
return &DramaHandler{
db: db,
dramaService: services.NewDramaService(db, log),
videoMergeService: services.NewVideoMergeService(db, transferService, cfg.Storage.LocalPath, cfg.Storage.BaseURL, log),
log: log,
}
}
func (h *DramaHandler) CreateDrama(c *gin.Context) {
var req services.CreateDramaRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
drama, err := h.dramaService.CreateDrama(&req)
if err != nil {
response.InternalError(c, "创建失败")
return
}
response.Created(c, drama)
}
func (h *DramaHandler) GetDrama(c *gin.Context) {
dramaID := c.Param("id")
drama, err := h.dramaService.GetDrama(dramaID)
if err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "获取失败")
return
}
response.Success(c, drama)
}
func (h *DramaHandler) ListDramas(c *gin.Context) {
var query services.DramaListQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.BadRequest(c, err.Error())
return
}
if query.Page < 1 {
query.Page = 1
}
if query.PageSize < 1 || query.PageSize > 100 {
query.PageSize = 20
}
dramas, total, err := h.dramaService.ListDramas(&query)
if err != nil {
response.InternalError(c, "获取列表失败")
return
}
response.SuccessWithPagination(c, dramas, total, query.Page, query.PageSize)
}
func (h *DramaHandler) UpdateDrama(c *gin.Context) {
dramaID := c.Param("id")
var req services.UpdateDramaRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
drama, err := h.dramaService.UpdateDrama(dramaID, &req)
if err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "更新失败")
return
}
response.Success(c, drama)
}
func (h *DramaHandler) DeleteDrama(c *gin.Context) {
dramaID := c.Param("id")
if err := h.dramaService.DeleteDrama(dramaID); err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "删除失败")
return
}
response.Success(c, gin.H{"message": "删除成功"})
}
func (h *DramaHandler) GetDramaStats(c *gin.Context) {
stats, err := h.dramaService.GetDramaStats()
if err != nil {
response.InternalError(c, "获取统计失败")
return
}
response.Success(c, stats)
}
func (h *DramaHandler) SaveOutline(c *gin.Context) {
dramaID := c.Param("id")
var req services.SaveOutlineRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.dramaService.SaveOutline(dramaID, &req); err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "保存失败")
return
}
response.Success(c, gin.H{"message": "保存成功"})
}
func (h *DramaHandler) GetCharacters(c *gin.Context) {
dramaID := c.Param("id")
episodeID := c.Query("episode_id") // 可选:如果提供则只返回该章节的角色
var episodeIDPtr *string
if episodeID != "" {
episodeIDPtr = &episodeID
}
characters, err := h.dramaService.GetCharacters(dramaID, episodeIDPtr)
if err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
if err.Error() == "episode not found" {
response.NotFound(c, "章节不存在")
return
}
response.InternalError(c, "获取角色失败")
return
}
response.Success(c, characters)
}
func (h *DramaHandler) SaveCharacters(c *gin.Context) {
dramaID := c.Param("id")
var req services.SaveCharactersRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.dramaService.SaveCharacters(dramaID, &req); err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "保存失败")
return
}
response.Success(c, gin.H{"message": "保存成功"})
}
func (h *DramaHandler) SaveEpisodes(c *gin.Context) {
dramaID := c.Param("id")
var req services.SaveEpisodesRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.dramaService.SaveEpisodes(dramaID, &req); err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "保存失败")
return
}
response.Success(c, gin.H{"message": "保存成功"})
}
func (h *DramaHandler) SaveProgress(c *gin.Context) {
dramaID := c.Param("id")
var req services.SaveProgressRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.dramaService.SaveProgress(dramaID, &req); err != nil {
if err.Error() == "drama not found" {
response.NotFound(c, "剧本不存在")
return
}
response.InternalError(c, "保存失败")
return
}
response.Success(c, gin.H{"message": "保存成功"})
}
// FinalizeEpisode 完成集数制作(触发视频合成)
func (h *DramaHandler) FinalizeEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
if episodeID == "" {
response.BadRequest(c, "episode_id不能为空")
return
}
// 尝试读取时间线数据(可选)
var timelineData *services.FinalizeEpisodeRequest
if err := c.ShouldBindJSON(&timelineData); err != nil {
// 如果没有请求体或解析失败使用nil将使用默认场景顺序
h.log.Warnw("No timeline data provided, will use default scene order", "error", err)
timelineData = nil
} else if timelineData != nil {
h.log.Infow("Received timeline data", "clips_count", len(timelineData.Clips), "episode_id", episodeID)
}
// 触发视频合成任务
result, err := h.videoMergeService.FinalizeEpisode(episodeID, timelineData)
if err != nil {
h.log.Errorw("Failed to finalize episode", "error", err, "episode_id", episodeID)
response.InternalError(c, err.Error())
return
}
response.Success(c, result)
}
// DownloadEpisodeVideo 下载剧集视频
func (h *DramaHandler) DownloadEpisodeVideo(c *gin.Context) {
episodeID := c.Param("episode_id")
if episodeID == "" {
response.BadRequest(c, "episode_id不能为空")
return
}
// 查询episode
var episode models.Episode
if err := h.db.Preload("Drama").Where("id = ?", episodeID).First(&episode).Error; err != nil {
response.NotFound(c, "剧集不存在")
return
}
// 检查是否有视频
if episode.VideoURL == nil || *episode.VideoURL == "" {
response.BadRequest(c, "该剧集还没有生成视频")
return
}
// 返回视频URL让前端重定向下载
c.JSON(200, gin.H{
"video_url": *episode.VideoURL,
"title": episode.Title,
"episode_number": episode.EpisodeNum,
})
}

View File

@@ -0,0 +1,55 @@
package handlers
import (
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
)
// FramePromptHandler 处理帧提示词生成请求
type FramePromptHandler struct {
framePromptService *services.FramePromptService
log *logger.Logger
}
// NewFramePromptHandler 创建帧提示词处理器
func NewFramePromptHandler(framePromptService *services.FramePromptService, log *logger.Logger) *FramePromptHandler {
return &FramePromptHandler{
framePromptService: framePromptService,
log: log,
}
}
// GenerateFramePrompt 生成指定类型的帧提示词
// POST /api/v1/storyboards/:id/frame-prompt
func (h *FramePromptHandler) GenerateFramePrompt(c *gin.Context) {
storyboardID := c.Param("id")
var req struct {
FrameType string `json:"frame_type" binding:"required"` // first, key, last, panel, action
PanelCount int `json:"panel_count"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request body")
return
}
// 构建请求
serviceReq := services.GenerateFramePromptRequest{
StoryboardID: storyboardID,
FrameType: services.FrameType(req.FrameType),
PanelCount: req.PanelCount,
}
// 生成提示词
result, err := h.framePromptService.GenerateFramePrompt(serviceReq)
if err != nil {
h.log.Errorw("Failed to generate frame prompt", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, result)
}

View File

@@ -0,0 +1,30 @@
package handlers
import (
"github.com/drama-generator/backend/domain/models"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// GetStoryboardFramePrompts 查询镜头的所有帧提示词
// GET /api/v1/storyboards/:id/frame-prompts
func GetStoryboardFramePrompts(db *gorm.DB, log *logger.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
storyboardID := c.Param("id")
var framePrompts []models.FramePrompt
if err := db.Where("storyboard_id = ?", storyboardID).
Order("created_at DESC").
Find(&framePrompts).Error; err != nil {
log.Errorw("Failed to query frame prompts", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{
"frame_prompts": framePrompts,
})
}
}

View File

@@ -0,0 +1,182 @@
package handlers
import (
"strconv"
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ImageGenerationHandler struct {
imageService *services.ImageGenerationService
log *logger.Logger
}
func NewImageGenerationHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger, transferService *services.ResourceTransferService) *ImageGenerationHandler {
return &ImageGenerationHandler{
imageService: services.NewImageGenerationService(db, transferService, log),
log: log,
}
}
func (h *ImageGenerationHandler) GenerateImage(c *gin.Context) {
var req services.GenerateImageRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
imageGen, err := h.imageService.GenerateImage(&req)
if err != nil {
h.log.Errorw("Failed to generate image", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, imageGen)
}
func (h *ImageGenerationHandler) GenerateImagesForScene(c *gin.Context) {
sceneID := c.Param("scene_id")
images, err := h.imageService.GenerateImagesForScene(sceneID)
if err != nil {
h.log.Errorw("Failed to generate images for scene", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, images)
}
func (h *ImageGenerationHandler) GetBackgroundsForEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
backgrounds, err := h.imageService.GetScencesForEpisode(episodeID)
if err != nil {
h.log.Errorw("Failed to get backgrounds", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, backgrounds)
}
func (h *ImageGenerationHandler) ExtractBackgroundsForEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
// 同步执行场景提取
backgrounds, err := h.imageService.ExtractBackgroundsForEpisode(episodeID)
if err != nil {
h.log.Errorw("Failed to extract backgrounds", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, backgrounds)
}
func (h *ImageGenerationHandler) BatchGenerateForEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
images, err := h.imageService.BatchGenerateImagesForEpisode(episodeID)
if err != nil {
h.log.Errorw("Failed to batch generate images", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, images)
}
func (h *ImageGenerationHandler) GetImageGeneration(c *gin.Context) {
imageGenID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
imageGen, err := h.imageService.GetImageGeneration(uint(imageGenID))
if err != nil {
response.NotFound(c, "图片生成记录不存在")
return
}
response.Success(c, imageGen)
}
func (h *ImageGenerationHandler) ListImageGenerations(c *gin.Context) {
var sceneID *uint
if sceneIDStr := c.Query("scene_id"); sceneIDStr != "" {
id, err := strconv.ParseUint(sceneIDStr, 10, 32)
if err == nil {
uid := uint(id)
sceneID = &uid
}
}
var storyboardID *uint
if storyboardIDStr := c.Query("storyboard_id"); storyboardIDStr != "" {
id, err := strconv.ParseUint(storyboardIDStr, 10, 32)
if err == nil {
uid := uint(id)
storyboardID = &uid
}
}
frameType := c.Query("frame_type")
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var dramaIDUint *uint
if dramaIDStr := c.Query("drama_id"); dramaIDStr != "" {
did, _ := strconv.ParseUint(dramaIDStr, 10, 32)
didUint := uint(did)
dramaIDUint = &didUint
}
images, total, err := h.imageService.ListImageGenerations(dramaIDUint, sceneID, storyboardID, frameType, status, page, pageSize)
if err != nil {
h.log.Errorw("Failed to list images", "error", err)
response.InternalError(c, err.Error())
return
}
response.SuccessWithPagination(c, images, total, page, pageSize)
}
func (h *ImageGenerationHandler) DeleteImageGeneration(c *gin.Context) {
imageGenID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
if err := h.imageService.DeleteImageGeneration(uint(imageGenID)); err != nil {
h.log.Errorw("Failed to delete image", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, nil)
}

75
api/handlers/scene.go Normal file
View File

@@ -0,0 +1,75 @@
package handlers
import (
services2 "github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type SceneHandler struct {
sceneService *services2.StoryboardCompositionService
log *logger.Logger
}
func NewSceneHandler(db *gorm.DB, log *logger.Logger, imageGenService *services2.ImageGenerationService) *SceneHandler {
return &SceneHandler{
sceneService: services2.NewStoryboardCompositionService(db, log, imageGenService),
log: log,
}
}
func (h *SceneHandler) GetStoryboardsForEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
storyboards, err := h.sceneService.GetScenesForEpisode(episodeID)
if err != nil {
h.log.Errorw("Failed to get storyboards for episode", "error", err, "episode_id", episodeID)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{
"storyboards": storyboards,
"total": len(storyboards),
})
}
func (h *SceneHandler) UpdateScene(c *gin.Context) {
sceneID := c.Param("scene_id")
var req services2.UpdateSceneRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request")
return
}
if err := h.sceneService.UpdateScene(sceneID, &req); err != nil {
h.log.Errorw("Failed to update scene", "error", err, "scene_id", sceneID)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{"message": "Scene updated successfully"})
}
func (h *SceneHandler) GenerateSceneImage(c *gin.Context) {
var req services2.GenerateSceneImageRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request")
return
}
imageGen, err := h.sceneService.GenerateSceneImage(&req)
if err != nil {
h.log.Errorw("Failed to generate scene image", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{
"message": "Scene image generation started",
"image_generation": imageGen,
})
}

View File

@@ -0,0 +1,76 @@
package handlers
import (
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ScriptGenerationHandler struct {
scriptService *services.ScriptGenerationService
log *logger.Logger
}
func NewScriptGenerationHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger) *ScriptGenerationHandler {
return &ScriptGenerationHandler{
scriptService: services.NewScriptGenerationService(db, log),
log: log,
}
}
func (h *ScriptGenerationHandler) GenerateOutline(c *gin.Context) {
var req services.GenerateOutlineRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
result, err := h.scriptService.GenerateOutline(&req)
if err != nil {
h.log.Errorw("Failed to generate outline", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, result)
}
func (h *ScriptGenerationHandler) GenerateCharacters(c *gin.Context) {
var req services.GenerateCharactersRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 同步执行角色生成
characters, err := h.scriptService.GenerateCharacters(&req)
if err != nil {
h.log.Errorw("Failed to generate characters", "error", err)
response.InternalError(c, "生成角色失败")
return
}
response.Success(c, characters)
}
func (h *ScriptGenerationHandler) GenerateEpisodes(c *gin.Context) {
var req services.GenerateEpisodesRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
episodes, err := h.scriptService.GenerateEpisodes(&req)
if err != nil {
h.log.Errorw("Failed to generate episodes", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, episodes)
}

View File

@@ -0,0 +1,96 @@
package handlers
import (
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type StoryboardHandler struct {
storyboardService *services.StoryboardService
taskService *services.TaskService
log *logger.Logger
}
func NewStoryboardHandler(db *gorm.DB, cfg *config.Config, log *logger.Logger) *StoryboardHandler {
return &StoryboardHandler{
storyboardService: services.NewStoryboardService(db, log),
taskService: services.NewTaskService(db, log),
log: log,
}
}
// GenerateStoryboard 生成分镜头(异步)
func (h *StoryboardHandler) GenerateStoryboard(c *gin.Context) {
episodeID := c.Param("episode_id")
// 创建异步任务
task, err := h.taskService.CreateTask("storyboard_generation", episodeID)
if err != nil {
h.log.Errorw("Failed to create task", "error", err)
response.InternalError(c, err.Error())
return
}
// 启动后台goroutine处理
go h.processStoryboardGeneration(task.ID, episodeID)
// 立即返回任务ID
response.Success(c, gin.H{
"task_id": task.ID,
"status": "pending",
"message": "分镜生成任务已创建,正在后台处理...",
})
}
// processStoryboardGeneration 后台处理分镜生成
func (h *StoryboardHandler) processStoryboardGeneration(taskID, episodeID string) {
h.log.Infow("Starting storyboard generation", "task_id", taskID, "episode_id", episodeID)
// 更新任务状态为处理中
if err := h.taskService.UpdateTaskStatus(taskID, "processing", 10, "开始生成分镜..."); err != nil {
h.log.Errorw("Failed to update task status", "error", err)
}
// 调用实际的生成逻辑
result, err := h.storyboardService.GenerateStoryboard(episodeID)
if err != nil {
h.log.Errorw("Failed to generate storyboard", "error", err, "task_id", taskID)
if updateErr := h.taskService.UpdateTaskError(taskID, err); updateErr != nil {
h.log.Errorw("Failed to update task error", "error", updateErr)
}
return
}
// 更新任务结果
if err := h.taskService.UpdateTaskResult(taskID, result); err != nil {
h.log.Errorw("Failed to update task result", "error", err)
return
}
h.log.Infow("Storyboard generation completed", "task_id", taskID, "total", result.Total)
}
// UpdateStoryboard 更新分镜
func (h *StoryboardHandler) UpdateStoryboard(c *gin.Context) {
storyboardID := c.Param("id")
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request body")
return
}
err := h.storyboardService.UpdateStoryboard(storyboardID, req)
if err != nil {
h.log.Errorw("Failed to update storyboard", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{"message": "Storyboard updated successfully"})
}

57
api/handlers/task.go Normal file
View File

@@ -0,0 +1,57 @@
package handlers
import (
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type TaskHandler struct {
taskService *services.TaskService
log *logger.Logger
}
func NewTaskHandler(db *gorm.DB, log *logger.Logger) *TaskHandler {
return &TaskHandler{
taskService: services.NewTaskService(db, log),
log: log,
}
}
// GetTaskStatus 获取任务状态
func (h *TaskHandler) GetTaskStatus(c *gin.Context) {
taskID := c.Param("task_id")
task, err := h.taskService.GetTask(taskID)
if err != nil {
if err == gorm.ErrRecordNotFound {
response.NotFound(c, "任务不存在")
return
}
h.log.Errorw("Failed to get task", "error", err, "task_id", taskID)
response.InternalError(c, err.Error())
return
}
response.Success(c, task)
}
// GetResourceTasks 获取资源相关的所有任务
func (h *TaskHandler) GetResourceTasks(c *gin.Context) {
resourceID := c.Query("resource_id")
if resourceID == "" {
response.BadRequest(c, "缺少resource_id参数")
return
}
tasks, err := h.taskService.GetTasksByResource(resourceID)
if err != nil {
h.log.Errorw("Failed to get resource tasks", "error", err, "resource_id", resourceID)
response.InternalError(c, err.Error())
return
}
response.Success(c, tasks)
}

142
api/handlers/upload.go Normal file
View File

@@ -0,0 +1,142 @@
package handlers
import (
services2 "github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/config"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
)
type UploadHandler struct {
uploadService *services2.UploadService
characterLibraryService *services2.CharacterLibraryService
log *logger.Logger
}
func NewUploadHandler(cfg *config.Config, log *logger.Logger, characterLibraryService *services2.CharacterLibraryService) (*UploadHandler, error) {
uploadService, err := services2.NewUploadService(cfg, log)
if err != nil {
return nil, err
}
return &UploadHandler{
uploadService: uploadService,
characterLibraryService: characterLibraryService,
log: log,
}, nil
}
// UploadImage 上传图片
func (h *UploadHandler) UploadImage(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.BadRequest(c, "请选择文件")
return
}
defer file.Close()
// 检查文件类型
contentType := header.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/octet-stream"
}
// 验证是图片类型
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/jpg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
}
if !allowedTypes[contentType] {
response.BadRequest(c, "只支持图片格式 (jpg, png, gif, webp)")
return
}
// 检查文件大小 (10MB)
if header.Size > 10*1024*1024 {
response.BadRequest(c, "文件大小不能超过10MB")
return
}
// 上传到MinIO
fileURL, err := h.uploadService.UploadCharacterImage(file, header.Filename, contentType)
if err != nil {
h.log.Errorw("Failed to upload image", "error", err)
response.InternalError(c, "上传失败")
return
}
response.Success(c, gin.H{
"url": fileURL,
"filename": header.Filename,
"size": header.Size,
})
}
// UploadCharacterImage 上传角色图片带角色ID
func (h *UploadHandler) UploadCharacterImage(c *gin.Context) {
characterID := c.Param("id")
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.BadRequest(c, "请选择文件")
return
}
defer file.Close()
// 检查文件类型
contentType := header.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/octet-stream"
}
// 验证是图片类型
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/jpg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
}
if !allowedTypes[contentType] {
response.BadRequest(c, "只支持图片格式 (jpg, png, gif, webp)")
return
}
// 检查文件大小 (10MB)
if header.Size > 10*1024*1024 {
response.BadRequest(c, "文件大小不能超过10MB")
return
}
// 上传到MinIO
fileURL, err := h.uploadService.UploadCharacterImage(file, header.Filename, contentType)
if err != nil {
h.log.Errorw("Failed to upload character image", "error", err)
response.InternalError(c, "上传失败")
return
}
// 更新角色的image_url字段到数据库
err = h.characterLibraryService.UploadCharacterImage(characterID, fileURL)
if err != nil {
h.log.Errorw("Failed to update character image_url", "error", err, "character_id", characterID)
response.InternalError(c, "更新角色图片失败")
return
}
h.log.Infow("Character image uploaded and saved", "character_id", characterID, "url", fileURL)
response.Success(c, gin.H{
"url": fileURL,
"filename": header.Filename,
"size": header.Size,
})
}

View File

@@ -0,0 +1,149 @@
package handlers
import (
"strconv"
"github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/infrastructure/storage"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type VideoGenerationHandler struct {
videoService *services.VideoGenerationService
log *logger.Logger
}
func NewVideoGenerationHandler(db *gorm.DB, transferService *services.ResourceTransferService, localStorage *storage.LocalStorage, aiService *services.AIService, log *logger.Logger) *VideoGenerationHandler {
return &VideoGenerationHandler{
videoService: services.NewVideoGenerationService(db, transferService, localStorage, aiService, log),
log: log,
}
}
func (h *VideoGenerationHandler) GenerateVideo(c *gin.Context) {
var req services.GenerateVideoRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
videoGen, err := h.videoService.GenerateVideo(&req)
if err != nil {
h.log.Errorw("Failed to generate video", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, videoGen)
}
func (h *VideoGenerationHandler) GenerateVideoFromImage(c *gin.Context) {
imageGenID, err := strconv.ParseUint(c.Param("image_gen_id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的图片ID")
return
}
videoGen, err := h.videoService.GenerateVideoFromImage(uint(imageGenID))
if err != nil {
h.log.Errorw("Failed to generate video from image", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, videoGen)
}
func (h *VideoGenerationHandler) BatchGenerateForEpisode(c *gin.Context) {
episodeID := c.Param("episode_id")
videos, err := h.videoService.BatchGenerateVideosForEpisode(episodeID)
if err != nil {
h.log.Errorw("Failed to batch generate videos", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, videos)
}
func (h *VideoGenerationHandler) GetVideoGeneration(c *gin.Context) {
videoGenID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
videoGen, err := h.videoService.GetVideoGeneration(uint(videoGenID))
if err != nil {
response.NotFound(c, "视频生成记录不存在")
return
}
response.Success(c, videoGen)
}
func (h *VideoGenerationHandler) ListVideoGenerations(c *gin.Context) {
var storyboardID *uint
// 优先使用storyboard_id参数
if storyboardIDStr := c.Query("storyboard_id"); storyboardIDStr != "" {
id, err := strconv.ParseUint(storyboardIDStr, 10, 32)
if err == nil {
uid := uint(id)
storyboardID = &uid
}
}
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
var dramaIDUint *uint
if dramaIDStr := c.Query("drama_id"); dramaIDStr != "" {
did, _ := strconv.ParseUint(dramaIDStr, 10, 32)
didUint := uint(did)
dramaIDUint = &didUint
}
// 计算offset(page - 1) * pageSize
offset := (page - 1) * pageSize
videos, total, err := h.videoService.ListVideoGenerations(dramaIDUint, storyboardID, status, pageSize, offset)
if err != nil {
h.log.Errorw("Failed to list videos", "error", err)
response.InternalError(c, err.Error())
return
}
response.SuccessWithPagination(c, videos, total, page, pageSize)
}
func (h *VideoGenerationHandler) DeleteVideoGeneration(c *gin.Context) {
videoGenID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.BadRequest(c, "无效的ID")
return
}
if err := h.videoService.DeleteVideoGeneration(uint(videoGenID)); err != nil {
h.log.Errorw("Failed to delete video", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, nil)
}

104
api/handlers/video_merge.go Normal file
View File

@@ -0,0 +1,104 @@
package handlers
import (
"strconv"
services2 "github.com/drama-generator/backend/application/services"
"github.com/drama-generator/backend/pkg/logger"
"github.com/drama-generator/backend/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type VideoMergeHandler struct {
mergeService *services2.VideoMergeService
log *logger.Logger
}
func NewVideoMergeHandler(db *gorm.DB, transferService *services2.ResourceTransferService, storagePath, baseURL string, log *logger.Logger) *VideoMergeHandler {
return &VideoMergeHandler{
mergeService: services2.NewVideoMergeService(db, transferService, storagePath, baseURL, log),
log: log,
}
}
func (h *VideoMergeHandler) MergeVideos(c *gin.Context) {
var req services2.MergeVideoRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request")
return
}
merge, err := h.mergeService.MergeVideos(&req)
if err != nil {
h.log.Errorw("Failed to merge videos", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{
"message": "Video merge task created",
"merge": merge,
})
}
func (h *VideoMergeHandler) GetMerge(c *gin.Context) {
mergeIDStr := c.Param("merge_id")
mergeID, err := strconv.ParseUint(mergeIDStr, 10, 32)
if err != nil {
response.BadRequest(c, "Invalid merge ID")
return
}
merge, err := h.mergeService.GetMerge(uint(mergeID))
if err != nil {
h.log.Errorw("Failed to get merge", "error", err)
response.NotFound(c, "Merge not found")
return
}
response.Success(c, gin.H{"merge": merge})
}
func (h *VideoMergeHandler) ListMerges(c *gin.Context) {
episodeID := c.Query("episode_id")
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
var episodeIDPtr *string
if episodeID != "" {
episodeIDPtr = &episodeID
}
merges, total, err := h.mergeService.ListMerges(episodeIDPtr, status, page, pageSize)
if err != nil {
h.log.Errorw("Failed to list merges", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{
"merges": merges,
"total": total,
"page": page,
"page_size": pageSize,
})
}
func (h *VideoMergeHandler) DeleteMerge(c *gin.Context) {
mergeIDStr := c.Param("merge_id")
mergeID, err := strconv.ParseUint(mergeIDStr, 10, 32)
if err != nil {
response.BadRequest(c, "Invalid merge ID")
return
}
if err := h.mergeService.DeleteMerge(uint(mergeID)); err != nil {
h.log.Errorw("Failed to delete merge", "error", err)
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{"message": "Merge deleted successfully"})
}