视频剪辑和处理

This commit is contained in:
lmk
2026-06-22 09:18:45 +08:00
parent 9bc48060dc
commit 7d4ac82334
34 changed files with 3319 additions and 839 deletions
+43
View File
@@ -0,0 +1,43 @@
package video
import "github.com/gogf/gf/v2/frame/g"
// ---------- 场景检测+视频分割(异步)----------
// SceneSplitReq 场景检测+视频分割请求
type SceneSplitReq struct {
g.Meta `path:"/scene-split" method:"post" tags:"场景分割" summary:"场景检测并分割视频(异步)" dc:"下载视频,基于PySceneDetect检测场景变化并分割成多个视频片段,同时提取音频,全部上传到MinIO后回调通知"`
VideoURL string `json:"videoUrl" v:"required#视频URL不能为空" dc:"视频URL地址"`
Threshold float64 `json:"threshold" dc:"场景检测阈值(默认27),越小越敏感、场景切割越多" d:"27.0"`
CallbackURL string `json:"callbackUrl" dc:"回调地址,处理完成后POST结果到该地址(可选)"`
}
// CreateSceneSplitTaskRes 创建场景分割任务响应
type CreateSceneSplitTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
}
// GetSceneSplitTaskReq 查询场景分割任务请求
type GetSceneSplitTaskReq struct {
g.Meta `path:"/scene-split/task/{taskId}" method:"get" tags:"场景分割" summary:"查询场景分割任务结果" dc:"根据taskId查询异步场景分割任务结果"`
TaskID string `json:"taskId" dc:"任务ID"`
}
// SegmentEntry 单个分片信息(按时间线排列)
type SegmentEntry struct {
Timeline string `json:"timeline" dc:"时间线标识,如 0.0-7.2"`
URL string `json:"url" dc:"分片MinIO地址"`
}
// GetSceneSplitTaskRes 查询场景分割任务响应
type GetSceneSplitTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
Status string `json:"status" dc:"状态: pending/running/success/failed"`
AudioURL string `json:"audioUrl,omitempty" dc:"提取的音频MinIO地址"`
Segments []SegmentEntry `json:"segments,omitempty" dc:"按时间线排列的视频分片列表"`
SceneCount int `json:"sceneCount" dc:"检测到的场景数/分片数"`
AudioDuration float64 `json:"audioDuration,omitempty" dc:"音频时长(秒)"`
VideoDuration float64 `json:"videoDuration,omitempty" dc:"视频总时长(秒)"`
ErrorMessage string `json:"errorMessage,omitempty" dc:"错误信息"`
CreatedAt int64 `json:"createdAt" dc:"创建时间戳"`
}
+9
View File
@@ -0,0 +1,9 @@
package video
// TranscodeRes 视频转码响应
type TranscodeRes struct {
OutputPath string `json:"outputPath" dc:"输出文件完整路径"`
FileSize int64 `json:"fileSize" dc:"文件大小(字节)"`
FileName string `json:"fileName" dc:"输出文件名"`
DurationStr string `json:"durationStr" dc:"视频时长"`
}
+39
View File
@@ -0,0 +1,39 @@
package video
import "github.com/gogf/gf/v2/frame/g"
// ---------- 视频拼接+混音(异步) ----------
// VideoAudioMergeAsyncReq 视频拼接+混音异步请求
type VideoAudioMergeAsyncReq struct {
g.Meta `path:"/merge/async" method:"post" tags:"视频拼接+混音" summary:"视频拼接并混音(异步)" dc:"将多个视频拼接后混入多个音频(按顺序拼接),立即返回taskId,完成后通过callback_url通知结果"`
VideoURLs []string `json:"video_urls" v:"required#视频URL列表不能为空" dc:"视频URL列表(按此顺序拼接)"`
AudioURLs []string `json:"audio_urls" v:"required#音频URL列表不能为空" dc:"音频URL列表(按此顺序拼接)"`
Upload bool `json:"upload" dc:"是否上传到MinIO" d:"false"`
CallbackURL string `json:"callback_url" v:"required#回调地址不能为空" dc:"回调地址,处理完成后POST结果到该地址"`
}
// CreateMergeTaskRes 创建异步拼接+混音任务响应
type CreateMergeTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
}
// GetMergeTaskReq 查询异步拼接+混音任务请求
type GetMergeTaskReq struct {
g.Meta `path:"/merge/task/{taskId}" method:"get" tags:"视频拼接+混音" summary:"查询拼接混音任务结果" dc:"根据taskId查询异步拼接+混音任务的结果"`
TaskID string `json:"taskId" dc:"任务ID"`
}
// GetMergeTaskRes 查询异步拼接+混音任务响应
type GetMergeTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
Status string `json:"status" dc:"状态: pending/running/success/failed"`
FileURL string `json:"fileURL,omitempty" dc:"MinIO文件访问路径"`
FileSize int64 `json:"fileSize,omitempty" dc:"文件大小(字节)"`
FileName string `json:"fileName,omitempty" dc:"文件名"`
FileFormat string `json:"fileFormat,omitempty" dc:"文件格式"`
FileAddressPrefix string `json:"fileAddressPrefix,omitempty" dc:"MinIO地址前缀"`
DurationStr string `json:"durationStr,omitempty" dc:"合并后视频时长"`
ErrorMessage string `json:"errorMessage,omitempty" dc:"错误信息"`
CreatedAt int64 `json:"createdAt" dc:"创建时间戳"`
}
+78
View File
@@ -0,0 +1,78 @@
package video
import "github.com/gogf/gf/v2/frame/g"
// ---------- 字幕元素定义 ----------
// CaptionElement 单个字幕/图片元素
type CaptionElement struct {
Type string `json:"type" v:"required#元素类型必填" dc:"元素类型:text/image"`
Text string `json:"text" dc:"文字内容(type=text时必填)"`
ImageURL string `json:"imageUrl" dc:"图片URL(type=image时必填)"`
StartTime float64 `json:"startTime" d:"0" dc:"开始时间(秒,默认0)"`
Duration float64 `json:"duration" d:"0" dc:"持续时长(秒,0或空=一直显示到视频结束)"`
X string `json:"x" d:"center" dc:"水平位置:left/center/right或像素值"`
Y string `json:"y" d:"center" dc:"垂直位置:top/center/bottom或像素值"`
FontSize int `json:"fontSize" d:"36" dc:"字号(type=text有效)"`
FontColor string `json:"fontColor" d:"#FFFFFF" dc:"字体颜色"`
BgColor string `json:"bgColor" dc:"背景色,如#FFFF00,空=透明"`
BgOpacity float64 `json:"bgOpacity" d:"1.0" dc:"背景透明度0-1"`
Width int `json:"width" dc:"元素宽度(type=image有效)"`
Height int `json:"height" dc:"元素高度(type=image有效)"`
Animation string `json:"animation" dc:"动画效果:fadeIn/slideUp/slideLeft/scaleIn"`
TrackIndex int `json:"trackIndex" d:"1" dc:"轨道层级(数字越大越靠前)"`
}
// SubtitleSegment 外部传入的字幕时间线片段
type SubtitleSegment struct {
Start float64 `json:"start" v:"required#开始时间必填" dc:"开始时间(秒)"`
End float64 `json:"end" v:"required#结束时间必填" dc:"结束时间(秒)"`
Text string `json:"text" v:"required#字幕文本必填" dc:"字幕文本内容"`
}
// SubtitleStyle 字幕样式配置
type SubtitleStyle struct {
FontSize int `json:"fontSize" d:"28" dc:"字号(默认28)"`
FontColor string `json:"fontColor" d:"#FFFFFF" dc:"字体颜色(默认白色)"`
BgColor *string `json:"bgColor" dc:"背景色,空字符串=透明(默认#000000)。传空字符串 '' 可去掉背景色"`
BgOpacity *float64 `json:"bgOpacity" dc:"背景透明度0-1(默认0.6)。传0可使背景完全透明"`
X string `json:"x" d:"center" dc:"水平位置:left/center/right或像素值(默认center)"`
Y string `json:"y" d:"bottom" dc:"垂直位置:top/center/bottom或像素值(默认bottom)"`
}
// ---------- 创建字幕任务 ----------
// CreateCaptionTaskReq 创建字幕叠加任务请求
type CreateCaptionTaskReq struct {
g.Meta `path:"/" method:"post" tags:"字幕叠加" summary:"创建字幕叠加任务(异步)" dc:"创建使用HyperFrames渲染的视频字幕叠加任务,返回taskId"`
VideoURLs []string `json:"video_urls" v:"required#视频URL列表不能为空" dc:"背景视频URL列表"`
AudioURL string `json:"audio_url" dc:"背景音频URL(可选)"`
Subtitles []SubtitleSegment `json:"subtitles" dc:"字幕时间线列表(可选),每段包含起始时间和文本"`
SubtitleStyle *SubtitleStyle `json:"subtitle_style" dc:"字幕样式配置(可选),不传则使用默认值"`
Elements []CaptionElement `json:"elements" dc:"字幕/图片元素列表(可选)"`
CallbackURL string `json:"callback_url" dc:"任务完成后的回调地址(可选)"`
}
// CreateCaptionTaskRes 创建字幕任务响应
type CreateCaptionTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
}
// ---------- 查询字幕任务 ----------
// GetCaptionTaskReq 查询字幕任务请求
type GetCaptionTaskReq struct {
g.Meta `path:"/{taskId}" method:"get" tags:"字幕叠加" summary:"查询字幕任务结果" dc:"根据taskId查询字幕任务详情"`
TaskID string `json:"taskId" dc:"任务ID"`
}
// GetCaptionTaskRes 查询字幕任务响应
type GetCaptionTaskRes struct {
TaskID string `json:"taskId" dc:"任务ID"`
Status string `json:"status" dc:"任务状态"`
FileURL string `json:"fileUrl,omitempty" dc:"输出文件URL"`
FileSize int64 `json:"fileSize,omitempty" dc:"输出文件大小"`
FileName string `json:"fileName,omitempty" dc:"输出文件名"`
DurationStr string `json:"durationStr,omitempty" dc:"视频时长"`
ErrorMessage string `json:"errorMessage,omitempty" dc:"错误信息"`
}
+48
View File
@@ -0,0 +1,48 @@
package video
import "gitea.redpowerfuture.com/red-future/common/beans"
// SceneSplitTask 场景检测+视频分割异步任务实体
type SceneSplitTask struct {
beans.SQLBaseDO `orm:",inherit"`
TaskID string `orm:"task_id" json:"taskId" description:"任务唯一标识"`
VideoURL string `orm:"video_url" json:"videoUrl" description:"原始视频URL"`
Status string `orm:"status" json:"status" description:"任务状态:pending/running/success/failed"`
SegmentURLs string `orm:"segment_urls" json:"segmentURLs" description:"分片列表JSON数组,如 [{\"timeline\":\"0.0-5.2\",\"url\":\"...\"}]"`
AudioURL string `orm:"audio_url" json:"audioUrl" description:"提取的音频URL"`
SceneCount int `orm:"scene_count" json:"sceneCount" description:"场景数/分片数"`
AudioDuration float64 `orm:"audio_duration" json:"audioDuration" description:"音频时长(秒)"`
VideoDuration float64 `orm:"video_duration" json:"videoDuration" description:"视频总时长(秒)"`
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"回调地址"`
}
// SceneSplitTaskCol 字段定义
type SceneSplitTaskCol struct {
beans.SQLBaseCol
TaskID string
VideoURL string
Status string
SegmentURLs string
AudioURL string
SceneCount string
AudioDuration string
VideoDuration string
ErrorMessage string
CallbackURL string
}
// SceneSplitTaskCols 字段常量
var SceneSplitTaskCols = SceneSplitTaskCol{
SQLBaseCol: beans.DefSQLBaseCol,
TaskID: "task_id",
VideoURL: "video_url",
Status: "status",
SegmentURLs: "segment_urls",
AudioURL: "audio_url",
SceneCount: "scene_count",
AudioDuration: "audio_duration",
VideoDuration: "video_duration",
ErrorMessage: "error_message",
CallbackURL: "callback_url",
}
@@ -0,0 +1,54 @@
package video
import "gitea.redpowerfuture.com/red-future/common/beans"
// VideoAudioMergeTask 视频拼接+混音异步任务实体
type VideoAudioMergeTask struct {
beans.SQLBaseDO `orm:",inherit"`
TaskID string `orm:"task_id" json:"taskId" description:"任务唯一标识"`
VideoURLs string `orm:"video_urls" json:"videoUrls" description:"视频URL列表(JSON数组)"`
AudioURLs string `orm:"audio_urls" json:"audioUrls" description:"音频URL列表(JSON数组)"`
Status string `orm:"status" json:"status" description:"任务状态:pending/running/success/failed"`
FileURL string `orm:"file_url" json:"fileUrl" description:"MinIO文件访问路径"`
FileSize int64 `orm:"file_size" json:"fileSize" description:"文件大小(字节)"`
FileName string `orm:"file_name" json:"fileName" description:"文件名"`
FileFormat string `orm:"file_format" json:"fileFormat" description:"文件格式"`
FileAddressPrefix string `orm:"file_address_prefix" json:"fileAddressPrefix" description:"MinIO地址前缀"`
DurationStr string `orm:"duration_str" json:"durationStr" description:"合并后视频时长"`
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"回调地址"`
}
// VideoAudioMergeTaskCol 字段定义
type VideoAudioMergeTaskCol struct {
beans.SQLBaseCol
TaskID string
VideoURLs string
AudioURLs string
Status string
FileURL string
FileSize string
FileName string
FileFormat string
FileAddressPrefix string
DurationStr string
ErrorMessage string
CallbackURL string
}
// VideoAudioMergeTaskCols 字段常量
var VideoAudioMergeTaskCols = VideoAudioMergeTaskCol{
SQLBaseCol: beans.DefSQLBaseCol,
TaskID: "task_id",
VideoURLs: "video_urls",
AudioURLs: "audio_urls",
Status: "status",
FileURL: "file_url",
FileSize: "file_size",
FileName: "file_name",
FileFormat: "file_format",
FileAddressPrefix: "file_address_prefix",
DurationStr: "duration_str",
ErrorMessage: "error_message",
CallbackURL: "callback_url",
}
+59
View File
@@ -0,0 +1,59 @@
package video
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// VideoCaptionTask 字幕叠加任务实体
type VideoCaptionTask struct {
beans.SQLBaseDO `orm:",inherit"`
TaskID string `orm:"task_id" json:"taskId" description:"任务唯一标识"`
VideoURLs string `orm:"video_urls" json:"videoUrls" description:"视频URL列表JSON"`
AudioURL string `orm:"audio_url" json:"audioUrl" description:"背景音乐URL"`
Elements string `orm:"elements" json:"elements" description:"字幕元素列表JSON"`
Width int `orm:"width" json:"width" description:"视频宽度"`
Height int `orm:"height" json:"height" description:"视频高度"`
Status string `orm:"status" json:"status" description:"任务状态"`
FileURL string `orm:"file_url" json:"fileUrl" description:"输出文件URL"`
FileSize int64 `orm:"file_size" json:"fileSize" description:"输出文件大小"`
FileName string `orm:"file_name" json:"fileName" description:"输出文件名"`
DurationStr string `orm:"duration_str" json:"durationStr" description:"视频时长"`
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"回调地址"`
}
// VideoCaptionTaskCol 数据库字段名常量
type VideoCaptionTaskCol struct {
beans.SQLBaseCol
TaskID string
VideoURLs string
AudioURL string
Elements string
Width string
Height string
Status string
FileURL string
FileSize string
FileName string
DurationStr string
ErrorMessage string
CallbackURL string
}
// VideoCaptionTaskCols 字段名常量实例
var VideoCaptionTaskCols = VideoCaptionTaskCol{
SQLBaseCol: beans.DefSQLBaseCol,
TaskID: "task_id",
VideoURLs: "video_urls",
AudioURL: "audio_url",
Elements: "elements",
Width: "width",
Height: "height",
Status: "status",
FileURL: "file_url",
FileSize: "file_size",
FileName: "file_name",
DurationStr: "duration_str",
ErrorMessage: "error_message",
CallbackURL: "callback_url",
}