109 lines
4.2 KiB
Go
109 lines
4.2 KiB
Go
// 本文件负责按创作项目类型筛选当前剧集可下载的视频,并生成 ZIP 文件清单。
|
|
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
"juhe-factory/api/internal/videoarchive"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// episodeVideoArchiveRow 描述生成视频查询结果及其在分镜中的业务状态。
|
|
type episodeVideoArchiveRow struct {
|
|
ObjectKey string
|
|
MimeType string
|
|
SequenceNo int
|
|
Active bool
|
|
}
|
|
|
|
// EpisodeVideoArchive 查询当前剧集视频:短剧创作仅包含主视频和备选视频,视频转绘包含主视频和历史视频。
|
|
func (s *Creative) EpisodeVideoArchive(userID, projectID, episodeID uuid.UUID) (videoarchive.Manifest, error) {
|
|
// 校验项目归属并获取项目类型,当前剧集视频 ZIP 仅对短剧创作和视频转绘开放。
|
|
var project struct {
|
|
ProjectType string
|
|
}
|
|
if err := s.DB.Table("creative_projects").Select("project_type").
|
|
Where("id=? AND user_id=? AND deleted_at IS NULL", projectID, userID).
|
|
Take(&project).Error; err != nil {
|
|
return videoarchive.Manifest{}, err
|
|
}
|
|
if project.ProjectType != "premium_drama" && project.ProjectType != "video_redraw" {
|
|
return videoarchive.Manifest{}, gorm.ErrRecordNotFound
|
|
}
|
|
var episode struct {
|
|
ProjectName string
|
|
EpisodeName string
|
|
}
|
|
if err := s.DB.Table("project_episodes episode").
|
|
Select("project.name AS project_name,episode.name AS episode_name").
|
|
Joins("JOIN creative_projects project ON project.id=episode.project_id AND project.deleted_at IS NULL").
|
|
Where("episode.id=? AND episode.project_id=? AND episode.deleted_at IS NULL", episodeID, projectID).
|
|
Take(&episode).Error; err != nil {
|
|
return videoarchive.Manifest{}, err
|
|
}
|
|
|
|
rows := make([]episodeVideoArchiveRow, 0)
|
|
query := s.DB.Table("generation_outputs output").
|
|
Select(`media.object_key,media.mime_type,storyboard.sequence_no,
|
|
storyboard.active_output_id=output.id AS active`).
|
|
Joins("JOIN generation_tasks task ON task.id=output.task_id AND task.task_type='video_generation'").
|
|
Joins("JOIN episode_storyboards storyboard ON storyboard.id=task.storyboard_id AND storyboard.deleted_at IS NULL").
|
|
Joins("JOIN media_assets media ON media.id=output.media_asset_id AND media.deleted_at IS NULL").
|
|
Where("storyboard.episode_id=?", episodeID)
|
|
if project.ProjectType == "premium_drama" {
|
|
query = query.Where("storyboard.active_output_id=output.id OR coalesce(output.metadata->>'candidate','false')='true'")
|
|
}
|
|
if err := query.Order("storyboard.sequence_no ASC,(storyboard.active_output_id=output.id) DESC,output.created_at DESC").Find(&rows).Error; err != nil {
|
|
return videoarchive.Manifest{}, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return videoarchive.Manifest{}, errors.New("当前剧集暂无可下载视频")
|
|
}
|
|
|
|
entries := make([]videoarchive.Entry, 0, len(rows))
|
|
versions := make(map[int]int)
|
|
for _, row := range rows {
|
|
label := "主视频"
|
|
if !row.Active {
|
|
versions[row.SequenceNo]++
|
|
if project.ProjectType == "premium_drama" {
|
|
label = fmt.Sprintf("备选视频%d", versions[row.SequenceNo])
|
|
} else {
|
|
label = fmt.Sprintf("历史视频%d", versions[row.SequenceNo])
|
|
}
|
|
}
|
|
name := fmt.Sprintf("分镜%03d-%s%s", row.SequenceNo, label, videoArchiveExtension(row.ObjectKey, row.MimeType))
|
|
entries = append(entries, videoarchive.Entry{ObjectKey: row.ObjectKey, Name: videoarchive.SafeFilename(name, "视频.mp4")})
|
|
}
|
|
archiveName := videoarchive.SafeFilename(episode.ProjectName+episode.EpisodeName, "剧集视频") + ".zip"
|
|
return videoarchive.Manifest{Name: archiveName, Entries: entries}, nil
|
|
}
|
|
|
|
// videoArchiveExtension 根据已保存的媒体类型和对象键生成稳定的视频扩展名。
|
|
func videoArchiveExtension(objectKey, mimeType string) string {
|
|
switch strings.ToLower(strings.TrimSpace(strings.Split(mimeType, ";")[0])) {
|
|
case "video/webm":
|
|
return ".webm"
|
|
case "video/quicktime":
|
|
return ".mov"
|
|
case "video/x-m4v":
|
|
return ".m4v"
|
|
case "video/x-matroska":
|
|
return ".mkv"
|
|
case "video/x-msvideo":
|
|
return ".avi"
|
|
case "video/mp4":
|
|
return ".mp4"
|
|
}
|
|
extension := strings.ToLower(path.Ext(objectKey))
|
|
if map[string]bool{".mp4": true, ".webm": true, ".mov": true, ".m4v": true, ".mkv": true, ".avi": true}[extension] {
|
|
return extension
|
|
}
|
|
return ".mp4"
|
|
}
|