代码初始化
This commit is contained in:
106
controller/audio/audio_extract_controller.go
Normal file
106
controller/audio/audio_extract_controller.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
common "media/controller/common"
|
||||
dto "media/model/dto/audio"
|
||||
service "media/service/asr"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type audio struct{}
|
||||
|
||||
var AudioExtract = new(audio)
|
||||
|
||||
// safeResult 对外输出的识别结果(隐藏内部路径)
|
||||
type safeResult struct {
|
||||
Text string `json:"text"`
|
||||
Model string `json:"model"`
|
||||
Language string `json:"language"`
|
||||
AudioSize int64 `json:"audioSize"`
|
||||
AudioDuration string `json:"audioDuration"`
|
||||
Scenes *dto.SceneSummaryDTO `json:"scenes,omitempty"`
|
||||
}
|
||||
|
||||
// safeItem 对外输出的单视频结果
|
||||
type safeItem struct {
|
||||
FileName string `json:"fileName"`
|
||||
Result *safeResult `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// TranscribeHandler 语音转文字+分镜分析
|
||||
// 支持两种入参方式:
|
||||
// 1. JSON body: {"video_urls":[...], "model":"medium", "language":"zh", "threshold":0.3}
|
||||
// 2. 文件上传: files 参数(兼容单/多文件)
|
||||
func (c *audio) TranscribeHandler(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.TranscribeReq
|
||||
if json.Unmarshal(body, &req) == nil && len(req.VideoURLs) > 0 {
|
||||
// 填充默认值
|
||||
if req.Model == "" {
|
||||
req.Model = g.Cfg().MustGet(ctx, "whisper.model", "medium").String()
|
||||
}
|
||||
if req.Language == "" {
|
||||
req.Language = g.Cfg().MustGet(ctx, "whisper.language", "zh").String()
|
||||
}
|
||||
if req.Threshold <= 0 {
|
||||
req.Threshold = 0.3
|
||||
}
|
||||
|
||||
res, svcErr := service.VideoTranscribe.TranscribeWithURLs(ctx, &req)
|
||||
if svcErr != nil {
|
||||
r.Response.WriteJson(g.Map{"code": 500, "message": svcErr.Error()})
|
||||
return
|
||||
}
|
||||
r.Response.WriteJson(g.Map{"code": 200, "message": "success", "data": toSafeItems(res.Results)})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 文件上传模式
|
||||
savePaths, err := common.SaveUploadedFiles(r)
|
||||
if err != nil || len(savePaths) == 0 {
|
||||
r.Response.WriteJson(g.Map{"code": 400, "message": "请上传视频文件( multipart )或提供 video_urls( JSON )"})
|
||||
return
|
||||
}
|
||||
|
||||
results := service.VideoTranscribe.TranscribeUpload(ctx, savePaths,
|
||||
r.Get("model", g.Cfg().MustGet(ctx, "whisper.model", "medium").String()).String(),
|
||||
r.Get("language", g.Cfg().MustGet(ctx, "whisper.language", "zh").String()).String(),
|
||||
r.Get("threshold", 0.3).Float64())
|
||||
|
||||
r.Response.WriteJson(g.Map{"code": 200, "message": "success", "data": toSafeItems(results)})
|
||||
}
|
||||
|
||||
// toSafeItems 将结果转为安全的响应格式(移除 audioPath 等内部路径)
|
||||
func toSafeItems(results []dto.TranscribeItem) []safeItem {
|
||||
var items []safeItem
|
||||
for _, item := range results {
|
||||
si := safeItem{FileName: item.FileName, Error: item.Error}
|
||||
if item.Result != nil {
|
||||
if r, ok := item.Result.(*dto.TranscribeResult); ok {
|
||||
si.Result = &safeResult{
|
||||
Text: r.Text,
|
||||
Model: r.Model,
|
||||
Language: r.Language,
|
||||
AudioSize: r.AudioSize,
|
||||
AudioDuration: r.AudioDuration,
|
||||
Scenes: r.Scenes,
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, si)
|
||||
}
|
||||
return items
|
||||
}
|
||||
68
controller/common/upload.go
Normal file
68
controller/common/upload.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
var allowedExts = map[string]bool{
|
||||
".mp4": true, ".avi": true, ".mov": true, ".mkv": true,
|
||||
".flv": true, ".wmv": true, ".webm": true, ".m4v": true,
|
||||
".ts": true, ".mpeg": true, ".mpg": true,
|
||||
}
|
||||
|
||||
// SaveUploadedFiles 保存上传的视频文件,返回本地路径列表
|
||||
func SaveUploadedFiles(r *ghttp.Request) ([]string, error) {
|
||||
ctx := r.Context()
|
||||
tempDir := getTempDir(ctx)
|
||||
os.MkdirAll(tempDir, 0755)
|
||||
|
||||
files := r.GetUploadFiles("files")
|
||||
if len(files) == 0 {
|
||||
if f := r.GetUploadFile("file"); f != nil {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
|
||||
var saved []string
|
||||
for _, f := range files {
|
||||
ext := filepath.Ext(f.Filename)
|
||||
if !allowedExts[ext] {
|
||||
continue
|
||||
}
|
||||
savePath := filepath.Join(tempDir, fmt.Sprintf("%d_%s", time.Now().UnixMilli(), f.Filename))
|
||||
src, err := f.Open()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dst, err := os.Create(savePath)
|
||||
if err != nil {
|
||||
src.Close()
|
||||
continue
|
||||
}
|
||||
io.Copy(dst, src)
|
||||
src.Close()
|
||||
dst.Close()
|
||||
saved = append(saved, savePath)
|
||||
}
|
||||
return saved, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
156
controller/video/concat_controller.go
Normal file
156
controller/video/concat_controller.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
common "media/controller/common"
|
||||
dto "media/model/dto/video"
|
||||
service "media/service/video"
|
||||
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
// 文件上传模式
|
||||
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
|
||||
}
|
||||
|
||||
svcRes, svcErr := service.Concat.Concat(ctx, &service.ConcatReq{
|
||||
VideoPaths: savePaths,
|
||||
Method: r.Get("method", "auto").String(),
|
||||
})
|
||||
service.CleanupConcat(savePaths)
|
||||
if svcErr != nil {
|
||||
r.Response.WriteJson(g.Map{"code": 500, "message": "视频拼接失败: " + svcErr.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.ServeFile(svcRes.OutputPath)
|
||||
go func(path string) {
|
||||
time.Sleep(5 * time.Second)
|
||||
os.Remove(path)
|
||||
}(svcRes.OutputPath)
|
||||
}
|
||||
|
||||
func downloadFromURL(ctx context.Context, rawURL, tempDir string) (string, error) {
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
segments := strings.Split(parsedURL.Path, "/")
|
||||
fileName := segments[len(segments)-1]
|
||||
if fileName == "" {
|
||||
fileName = fmt.Sprintf("video_%d.mp4", time.Now().UnixMilli())
|
||||
}
|
||||
savePath := filepath.Join(tempDir, fmt.Sprintf("%d_%s", time.Now().UnixMilli(), fileName))
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Minute}
|
||||
resp, err := client.Get(rawURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
out, err := os.Create(savePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
os.Remove(savePath)
|
||||
}
|
||||
return savePath, err
|
||||
}
|
||||
|
||||
func cleanupConcat(paths []string) {
|
||||
for _, p := range paths {
|
||||
os.Remove(p)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user