初始化
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
// Package media 统一生成可长期稳定引用的 COS 媒体对象键和扩展名。
|
||||
package media
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const objectRoot = "juyou_ran"
|
||||
|
||||
var unsafeKeyPart = regexp.MustCompile(`[^a-z0-9-]+`)
|
||||
var safeExtension = regexp.MustCompile(`^\.[a-z0-9]+$`)
|
||||
|
||||
// objectKey 为所有媒体对象添加统一的业务根目录,避免不同业务直接污染存储桶根目录。
|
||||
func objectKey(format string, args ...any) string {
|
||||
return fmt.Sprintf(objectRoot+"/"+format, args...)
|
||||
}
|
||||
|
||||
// Slug 将业务名称转换为受控英文路径片段;展示名称保留在数据库中。
|
||||
func Slug(value, fallback string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
value = unsafeKeyPart.ReplaceAllString(value, "-")
|
||||
value = strings.Trim(value, "-")
|
||||
if value == "" {
|
||||
value = fallback
|
||||
}
|
||||
if len(value) > 48 {
|
||||
value = strings.Trim(value[:48], "-")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Extension 根据媒体类型或安全的原文件扩展名生成对象扩展名。
|
||||
func Extension(filename, contentType string) string {
|
||||
switch strings.ToLower(contentType) {
|
||||
case "image/jpeg":
|
||||
return ".jpg"
|
||||
case "image/png":
|
||||
return ".png"
|
||||
case "image/webp":
|
||||
return ".webp"
|
||||
case "image/gif":
|
||||
return ".gif"
|
||||
case "video/mp4":
|
||||
return ".mp4"
|
||||
case "video/webm":
|
||||
return ".webm"
|
||||
case "audio/mpeg":
|
||||
return ".mp3"
|
||||
case "audio/wav", "audio/x-wav":
|
||||
return ".wav"
|
||||
case "text/vtt":
|
||||
return ".vtt"
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
if len(ext) > 1 && len(ext) <= 8 && safeExtension.MatchString(ext) {
|
||||
return ext
|
||||
}
|
||||
return ".bin"
|
||||
}
|
||||
|
||||
// EpisodeSource 返回剧集原视频的对象键。
|
||||
func EpisodeSource(projectID uuid.UUID, episodeNo int, episodeID, assetID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/episodes/%03d-%s/source/%s-source%s", projectID, episodeNo, episodeID, assetID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// EpisodeSubtitle 返回剧集字幕的对象键。
|
||||
func EpisodeSubtitle(projectID uuid.UUID, episodeNo int, episodeID, assetID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/episodes/%03d-%s/subtitles/%s-subtitle%s", projectID, episodeNo, episodeID, assetID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// ProjectSource 返回无剧集项目原视频的对象键。
|
||||
func ProjectSource(projectID, assetID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/source/%s-source%s", projectID, assetID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// ProjectSubtitle 返回无剧集项目字幕的对象键。
|
||||
func ProjectSubtitle(projectID, assetID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/subtitles/%s-subtitle%s", projectID, assetID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// AssetReference 返回项目资产参考媒体的对象键。
|
||||
func AssetReference(projectID uuid.UUID, assetType string, assetID, mediaID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/assets/%s/%s/%s-reference%s", projectID, Slug(assetType, "custom"), assetID, mediaID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// ProductImageReference 返回独立图片生成工具参考图片的稳定对象键。
|
||||
func ProductImageReference(userID, mediaID uuid.UUID, filename, contentType string) string {
|
||||
return objectKey("product-images/users/%s/references/%s%s", userID, mediaID, Extension(filename, contentType))
|
||||
}
|
||||
|
||||
// AssetOutput 返回项目资产生成结果的对象键。
|
||||
func AssetOutput(projectID uuid.UUID, assetType string, assetID, taskID, mediaID uuid.UUID, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/assets/%s/%s/outputs/%s-%s-result%s", projectID, Slug(assetType, "custom"), assetID, taskID, mediaID, Extension("", contentTypeFor("image", contentType)))
|
||||
}
|
||||
|
||||
// StoryboardFrame 返回剧集分镜抽帧的对象键。
|
||||
func StoryboardFrame(projectID, episodeID, storyboardID, mediaID uuid.UUID, sequence int, timestampMS int64) string {
|
||||
return objectKey("video-redraw/projects/%s/episodes/%s/storyboards/%04d-%s/frames/%s-%010d.jpg", projectID, episodeID, sequence, storyboardID, mediaID, timestampMS)
|
||||
}
|
||||
|
||||
// StoryboardAnalysis 返回剧集分镜反推原始响应的对象键。
|
||||
func StoryboardAnalysis(projectID, episodeID, storyboardID, taskID, mediaID uuid.UUID, sequence int) string {
|
||||
return objectKey("video-redraw/projects/%s/episodes/%s/storyboards/%04d-%s/analysis/%s-%s-reverse.json", projectID, episodeID, sequence, storyboardID, taskID, mediaID)
|
||||
}
|
||||
|
||||
// TaskOutput 返回剧集分镜生成结果的对象键。
|
||||
func TaskOutput(projectID, episodeID uuid.UUID, sequence int, taskID, mediaID uuid.UUID, outputType, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/episodes/%s/storyboards/%04d/outputs/%s-%s-result%s", projectID, episodeID, sequence, taskID, mediaID, Extension("", contentTypeFor(outputType, contentType)))
|
||||
}
|
||||
|
||||
// ProjectStoryboardFrame 返回无剧集项目分镜抽帧的对象键。
|
||||
func ProjectStoryboardFrame(projectID, storyboardID, mediaID uuid.UUID, sequence int, timestampMS int64) string {
|
||||
return objectKey("video-redraw/projects/%s/storyboards/%04d-%s/frames/%s-%010d.jpg", projectID, sequence, storyboardID, mediaID, timestampMS)
|
||||
}
|
||||
|
||||
// ProjectStoryboardAnalysis 返回无剧集项目分镜反推原始响应的对象键。
|
||||
func ProjectStoryboardAnalysis(projectID, storyboardID, taskID, mediaID uuid.UUID, sequence int) string {
|
||||
return objectKey("video-redraw/projects/%s/storyboards/%04d-%s/analysis/%s-%s-reverse.json", projectID, sequence, storyboardID, taskID, mediaID)
|
||||
}
|
||||
|
||||
// ProjectTaskOutput 返回无剧集项目分镜生成结果的对象键。
|
||||
func ProjectTaskOutput(projectID uuid.UUID, sequence int, taskID, mediaID uuid.UUID, outputType, contentType string) string {
|
||||
return objectKey("video-redraw/projects/%s/storyboards/%04d/outputs/%s-%s-result%s", projectID, sequence, taskID, mediaID, Extension("", contentTypeFor(outputType, contentType)))
|
||||
}
|
||||
|
||||
// StyleImage 返回管理端风格图片的统一对象键。
|
||||
func StyleImage(mediaID uuid.UUID, extension string) string {
|
||||
return objectKey("styles/%s/%s%s", time.Now().Format("2006/01"), mediaID, extension)
|
||||
}
|
||||
|
||||
// UserAvatar 返回用户头像的统一对象键。
|
||||
func UserAvatar(userID, mediaID uuid.UUID, extension string) string {
|
||||
return objectKey("users/%s/avatar/%s%s", userID, mediaID, extension)
|
||||
}
|
||||
|
||||
// StandaloneTaskOutput 返回独立生成任务的结果对象键。
|
||||
func StandaloneTaskOutput(userID, taskID uuid.UUID, outputType, contentType string) string {
|
||||
return objectKey("users/%s/tasks/%s/output-1%s", userID, taskID, Extension("", contentTypeFor(outputType, contentType)))
|
||||
}
|
||||
|
||||
// contentTypeFor 返回已知内容类型,缺省时根据输出类型选择默认媒体类型。
|
||||
func contentTypeFor(outputType, contentType string) string {
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
if outputType == "image" {
|
||||
return "image/png"
|
||||
}
|
||||
return "video/mp4"
|
||||
}
|
||||
Reference in New Issue
Block a user