258 lines
6.7 KiB
Go
258 lines
6.7 KiB
Go
// 视频重绘接口处理器,负责重绘工作台、分析任务和源媒体上传请求。
|
|
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
mediakey "juhe-factory/api/internal/media"
|
|
"juhe-factory/api/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func (h *Creative) GetRedrawWorkbench(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := h.service.GetRedrawWorkbench(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": data})
|
|
}
|
|
|
|
func (h *Creative) GetRedrawEpisodeWorkbench(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
episodeID, err := service.ParseUUID(c.Param("episode_id"), "剧集")
|
|
if err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
data, err := h.service.GetRedrawEpisodeWorkbench(currentWebUser(c).ID, projectID, episodeID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": data})
|
|
}
|
|
|
|
func (h *Creative) QueueRedrawEpisodeScript(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
episodeID, err := service.ParseUUID(c.Param("episode_id"), "剧集")
|
|
if err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
task, err := h.service.QueueRedrawEpisodeScript(currentWebUser(c).ID, projectID, episodeID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusAccepted, gin.H{"data": task})
|
|
}
|
|
|
|
// SaveRedrawEpisodeScript 保存用户修改后的剧集反推剧本正文。
|
|
func (h *Creative) SaveRedrawEpisodeScript(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
episodeID, err := service.ParseUUID(c.Param("episode_id"), "剧集")
|
|
if err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
var body struct {
|
|
Content string `json:"content"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
if err := h.service.SaveRedrawEpisodeScript(currentWebUser(c).ID, projectID, episodeID, body.Content); err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Creative) UpdateRedrawAnalysisSettings(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var body struct {
|
|
AudioSource string `json:"audio_source"`
|
|
SourceLanguage string `json:"source_language"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
if err := h.service.UpdateRedrawAnalysisSettings(currentWebUser(c).ID, projectID, body.AudioSource, body.SourceLanguage); err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Creative) QueueRedrawAnalysis(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
task, err := h.service.QueueRedrawAnalysis(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusAccepted, gin.H{"data": task})
|
|
}
|
|
|
|
func (h *Creative) ReanalyzeRedraw(c *gin.Context) {
|
|
if !h.objectStorageAvailable(c) {
|
|
return
|
|
}
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
objectKeys, err := h.service.ResetRedrawAnalysis(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
if !h.deleteStoredObjects(c, objectKeys) {
|
|
return
|
|
}
|
|
task, err := h.service.QueueRedrawAnalysis(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusAccepted, gin.H{"data": task})
|
|
}
|
|
|
|
func (h *Creative) QueueRedrawStoryboardAnalysis(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
storyboardID, err := service.ParseUUID(c.Param("storyboard_id"), "分镜")
|
|
if err != nil {
|
|
h.bad(c, err)
|
|
return
|
|
}
|
|
task, err := h.service.QueueRedrawStoryboardAnalysis(currentWebUser(c).ID, projectID, storyboardID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusAccepted, gin.H{"data": task})
|
|
}
|
|
|
|
func (h *Creative) QueueRedrawScript(c *gin.Context) {
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
task, err := h.service.QueueRedrawScript(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusAccepted, gin.H{"data": task})
|
|
}
|
|
|
|
func (h *Creative) UploadRedrawSource(c *gin.Context) {
|
|
h.uploadRedrawMedia(c, "source")
|
|
}
|
|
|
|
func (h *Creative) UploadRedrawSubtitle(c *gin.Context) {
|
|
h.uploadRedrawMedia(c, "subtitle")
|
|
}
|
|
|
|
func (h *Creative) uploadRedrawMedia(c *gin.Context, kind string) {
|
|
if h.cos == nil {
|
|
fail(c, http.StatusServiceUnavailable, "cos_not_configured", "COS 对象存储未配置")
|
|
return
|
|
}
|
|
projectID, ok := h.projectID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
target, err := h.service.PrepareRedrawMediaUpload(currentWebUser(c).ID, projectID, kind)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
project := target.Project
|
|
file, header, err := c.Request.FormFile("file")
|
|
if err != nil {
|
|
h.bad(c, errors.New("请选择文件"))
|
|
return
|
|
}
|
|
defer file.Close()
|
|
contentType := cleanContentType(header.Header.Get("Content-Type"))
|
|
maxBytes := int64(50 * 1024 * 1024)
|
|
ext := strings.ToLower(filepath.Ext(header.Filename))
|
|
allowedVideoTypes := map[string]bool{"video/mp4": true, "video/webm": true, "video/quicktime": true, "video/x-m4v": true}
|
|
allowed := allowedVideoTypes[contentType] && map[string]bool{".mp4": true, ".webm": true, ".mov": true, ".m4v": true}[ext]
|
|
if kind == "subtitle" {
|
|
maxBytes = 2 * 1024 * 1024
|
|
allowed = ext == ".srt" || ext == ".vtt" || ext == ".txt"
|
|
if contentType == "" {
|
|
contentType = "text/plain"
|
|
}
|
|
}
|
|
if !allowed || header.Size <= 0 || header.Size > maxBytes {
|
|
h.bad(c, fmt.Errorf("%s文件格式或大小不符合要求", map[bool]string{true: "字幕", false: "视频"}[kind == "subtitle"]))
|
|
return
|
|
}
|
|
oldMediaID := target.OldMediaID
|
|
if kind == "source" && project.SourceVideoAssetID != nil {
|
|
objectKeys, err := h.service.ResetRedrawAnalysis(currentWebUser(c).ID, projectID)
|
|
if err != nil {
|
|
h.respondError(c, err)
|
|
return
|
|
}
|
|
if !h.deleteStoredObjects(c, objectKeys) {
|
|
return
|
|
}
|
|
}
|
|
mediaID := uuid.New()
|
|
key := mediakey.ProjectSource(projectID, mediaID, header.Filename, contentType)
|
|
displayName := project.Name + "原视频"
|
|
if kind == "subtitle" {
|
|
key = mediakey.ProjectSubtitle(projectID, mediaID, header.Filename, contentType)
|
|
displayName = project.Name + "字幕"
|
|
}
|
|
asset, err := h.storeUpload(c, mediaID, currentWebUser(c).ID, file, header, key, displayName, contentType, nil)
|
|
if err != nil {
|
|
h.internal(c, err)
|
|
return
|
|
}
|
|
if err := h.service.PersistRedrawMediaUpload(projectID, kind, project.RedrawStatus, asset, oldMediaID); err != nil {
|
|
_ = h.cos.Delete(c.Request.Context(), asset.ObjectKey)
|
|
h.internal(c, err)
|
|
return
|
|
}
|
|
if target.OldObjectKey != "" && !h.deleteStoredObjects(c, []string{target.OldObjectKey}) {
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"data": asset})
|
|
}
|