代码初始化
This commit is contained in:
@@ -2,7 +2,6 @@ package video
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -18,99 +17,98 @@ import (
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type video struct{}
|
||||
|
||||
var Concat = new(video)
|
||||
|
||||
// ConcatVideosHandler 视频拼接
|
||||
// 支持两种入参方式:
|
||||
// 1. JSON body: {"video_urls":[...], "method":"auto"}
|
||||
// 2. 文件上传: files 参数(至少2个视频)
|
||||
func (c *video) ConcatVideosHandler(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
|
||||
|
||||
// 优先尝试 JSON body(URL 列表模式)
|
||||
body := r.GetBody()
|
||||
if len(body) > 0 && body[0] == '{' {
|
||||
var req dto.ConcatReq
|
||||
if json.Unmarshal(body, &req) == nil && len(req.VideoURLs) >= 2 {
|
||||
if req.Method == "" {
|
||||
req.Method = "auto"
|
||||
}
|
||||
|
||||
tempDir := g.Cfg().MustGet(ctx, "ffmpeg.temp_dir", "resource/temp").String()
|
||||
if !filepath.IsAbs(tempDir) {
|
||||
absDir, _ := filepath.Abs(tempDir)
|
||||
tempDir = absDir
|
||||
}
|
||||
os.MkdirAll(tempDir, 0755)
|
||||
|
||||
var savePaths []string
|
||||
for _, videoURL := range req.VideoURLs {
|
||||
savePath, dlErr := downloadFromURL(ctx, videoURL, tempDir)
|
||||
if dlErr != nil {
|
||||
continue
|
||||
}
|
||||
savePaths = append(savePaths, savePath)
|
||||
}
|
||||
if len(savePaths) < 2 {
|
||||
cleanupConcat(savePaths)
|
||||
r.Response.WriteJson(g.Map{"code": 400, "message": "成功下载的视频不足2个"})
|
||||
return
|
||||
}
|
||||
|
||||
svcRes, svcErr := service.Concat.Concat(ctx, &service.ConcatReq{
|
||||
VideoPaths: savePaths,
|
||||
Method: req.Method,
|
||||
})
|
||||
cleanupConcat(savePaths)
|
||||
if svcErr != nil {
|
||||
r.Response.WriteJson(g.Map{"code": 500, "message": "视频拼接失败: " + svcErr.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(g.Map{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": g.Map{
|
||||
"outputPath": svcRes.OutputPath,
|
||||
"fileSize": svcRes.FileSize,
|
||||
"duration": svcRes.Duration,
|
||||
"durationStr": svcRes.DurationStr,
|
||||
"methodUsed": svcRes.MethodUsed,
|
||||
"inputFiles": svcRes.InputFiles,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
// Concat 视频拼接(URL模式) POST /video/concat
|
||||
func (c *video) Concat(ctx context.Context, req *dto.ConcatReq) (res *dto.ConcatRes, err error) {
|
||||
ctx = withUser(ctx)
|
||||
if req.Method == "" {
|
||||
req.Method = "auto"
|
||||
}
|
||||
|
||||
// 文件上传模式
|
||||
savePaths, err := common.SaveUploadedFiles(r)
|
||||
if err != nil || len(savePaths) < 2 {
|
||||
r.Response.WriteJson(g.Map{"code": 400, "message": fmt.Sprintf("至少需要2个视频,当前%d个", len(savePaths))})
|
||||
return
|
||||
savePaths, err := downloadVideos(ctx, req.VideoURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanupConcat(savePaths)
|
||||
|
||||
svcRes, svcErr := service.Concat.Concat(ctx, &service.ConcatReq{
|
||||
svcRes, err := service.Concat.Concat(ctx, &service.ConcatReq{
|
||||
VideoPaths: savePaths,
|
||||
Method: r.Get("method", "auto").String(),
|
||||
Method: req.Method,
|
||||
Upload: req.Upload,
|
||||
})
|
||||
service.CleanupConcat(savePaths)
|
||||
if svcErr != nil {
|
||||
r.Response.WriteJson(g.Map{"code": 500, "message": "视频拼接失败: " + svcErr.Error()})
|
||||
return
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toDTORes(svcRes), nil
|
||||
}
|
||||
|
||||
// ConcatUpload 视频拼接(文件上传模式) POST /video/concat/upload
|
||||
func (c *video) ConcatUpload(ctx context.Context, req *dto.ConcatUploadReq) (res *dto.ConcatRes, err error) {
|
||||
ctx = withUser(ctx)
|
||||
savePaths, err := common.SaveUploadedFilesFromCtx(ctx)
|
||||
if err != nil || len(savePaths) < 2 {
|
||||
return nil, fmt.Errorf("至少需要2个视频,当前%d个", len(savePaths))
|
||||
}
|
||||
defer service.CleanupConcat(savePaths)
|
||||
|
||||
if req.Method == "" {
|
||||
req.Method = "auto"
|
||||
}
|
||||
|
||||
r.Response.ServeFile(svcRes.OutputPath)
|
||||
go func(path string) {
|
||||
time.Sleep(5 * time.Second)
|
||||
os.Remove(path)
|
||||
}(svcRes.OutputPath)
|
||||
svcRes, err := service.Concat.Concat(ctx, &service.ConcatReq{
|
||||
VideoPaths: savePaths,
|
||||
Method: req.Method,
|
||||
Upload: req.Upload,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toDTORes(svcRes), nil
|
||||
}
|
||||
|
||||
// withUser 为 context 注入默认用户(无认证基础设施时使用)
|
||||
func withUser(ctx context.Context) context.Context {
|
||||
if ctx.Value("user") == nil {
|
||||
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// toDTORes 将 Service 内部响应类型转换为 DTO 响应类型
|
||||
func toDTORes(svcRes *service.ConcatRes) *dto.ConcatRes {
|
||||
return &dto.ConcatRes{
|
||||
OutputPath: svcRes.OutputPath,
|
||||
FileSize: svcRes.FileSize,
|
||||
Duration: svcRes.Duration,
|
||||
DurationStr: svcRes.DurationStr,
|
||||
MethodUsed: svcRes.MethodUsed,
|
||||
InputFiles: svcRes.InputFiles,
|
||||
FileURL: svcRes.FileURL,
|
||||
}
|
||||
}
|
||||
|
||||
// downloadVideos 下载视频URL列表
|
||||
func downloadVideos(ctx context.Context, videoURLs []string) ([]string, error) {
|
||||
tempDir := getTempDir(ctx)
|
||||
os.MkdirAll(tempDir, 0755)
|
||||
|
||||
var savePaths []string
|
||||
for _, videoURL := range videoURLs {
|
||||
savePath, dlErr := downloadFromURL(ctx, videoURL, tempDir)
|
||||
if dlErr != nil {
|
||||
continue
|
||||
}
|
||||
savePaths = append(savePaths, savePath)
|
||||
}
|
||||
if len(savePaths) < 2 {
|
||||
return savePaths, fmt.Errorf("成功下载的视频不足2个")
|
||||
}
|
||||
return savePaths, nil
|
||||
}
|
||||
|
||||
func downloadFromURL(ctx context.Context, rawURL, tempDir string) (string, error) {
|
||||
@@ -154,3 +152,15 @@ func cleanupConcat(paths []string) {
|
||||
os.Remove(p)
|
||||
}
|
||||
}
|
||||
|
||||
func getTempDir(ctx context.Context) string {
|
||||
tempDir := g.Cfg().MustGet(ctx, "ffmpeg.temp_dir", "resource/temp").String()
|
||||
if tempDir == "" {
|
||||
tempDir = "resource/temp"
|
||||
}
|
||||
if !filepath.IsAbs(tempDir) {
|
||||
absDir, _ := filepath.Abs(tempDir)
|
||||
tempDir = absDir
|
||||
}
|
||||
return tempDir
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user