2025-12-25 17:01:46 +08:00
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"easyaudioencode/internal/core/audioencode"
|
2025-12-31 11:29:58 +08:00
|
|
|
|
"easyaudioencode/pkg/ffmpeg"
|
2025-12-25 17:01:46 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"git.lnton.com/lnton/pkg/reason"
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"mime/multipart"
|
|
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 配置常量
|
|
|
|
|
|
const (
|
|
|
|
|
|
// 最大上传文件大小 100MB
|
|
|
|
|
|
maxUploadSize = 100 * 1024 * 1024
|
|
|
|
|
|
// 文件保存目录
|
|
|
|
|
|
uploadDir = "./uploads"
|
|
|
|
|
|
sourceDir = "source"
|
|
|
|
|
|
encodeDir = "encode"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
|
// 允许的文件扩展名
|
|
|
|
|
|
allowedExts = map[string]bool{
|
|
|
|
|
|
".mp3": true,
|
|
|
|
|
|
".wav": true,
|
|
|
|
|
|
}
|
|
|
|
|
|
// 允许的 MIME 类型
|
|
|
|
|
|
allowedMimeTypes = map[string]bool{
|
|
|
|
|
|
"audio/mpeg": true, // MP3
|
|
|
|
|
|
"audio/wav": true, // WAV
|
|
|
|
|
|
"audio/x-wav": true, // WAV 另一种常见 MIME
|
|
|
|
|
|
"audio/x-pn-wav": true,
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// uploadAudioHandler 处理音频文件上传
|
|
|
|
|
|
func (a AudioEncodeAPI) uploadAudioHandler(c *gin.Context, _ *struct{}) (any, error) {
|
|
|
|
|
|
if err := os.MkdirAll(filepath.Join(uploadDir, sourceDir), 0755); err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("创建上传目录失败: %v", err.Error()))
|
|
|
|
|
|
}
|
2025-12-31 11:29:58 +08:00
|
|
|
|
if err := os.MkdirAll(filepath.Join(uploadDir, encodeDir), 0755); err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("创建转码目录失败: %v", err.Error()))
|
|
|
|
|
|
}
|
2025-12-25 17:01:46 +08:00
|
|
|
|
// 限制请求体大小
|
|
|
|
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUploadSize)
|
|
|
|
|
|
if err := c.Request.ParseMultipartForm(maxUploadSize); err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("文件过大(最大 %dMB)或解析失败: %v", maxUploadSize/1024/1024, err))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取上传的文件
|
|
|
|
|
|
file, fileHeader, err := c.Request.FormFile("audio")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("获取文件失败: %v", err))
|
|
|
|
|
|
}
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 校验文件扩展名
|
|
|
|
|
|
fileExt := strings.ToLower(filepath.Ext(fileHeader.Filename))
|
|
|
|
|
|
sourceFileName := strings.TrimSuffix(fileHeader.Filename, fileExt)
|
|
|
|
|
|
if !allowedExts[fileExt] {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("不支持的文件类型,仅允许 MP3/WAV,当前扩展名: %s", fileExt))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 校验文件 MIME 类型
|
|
|
|
|
|
//mimeType, err := getFileMimeType(file)
|
|
|
|
|
|
//if err != nil {
|
|
|
|
|
|
// return nil, reason.ErrServer.SetMsg(fmt.Sprintf("获取文件 MIME 类型失败: %v", err))
|
|
|
|
|
|
//}
|
|
|
|
|
|
//if !allowedMimeTypes[mimeType] {
|
|
|
|
|
|
// return nil, reason.ErrServer.SetMsg(fmt.Sprintf("文件类型校验失败,实际 MIME: %s,仅允许 MP3/WAV", mimeType))
|
|
|
|
|
|
//}
|
|
|
|
|
|
uuidStr := uuid.New().String()
|
|
|
|
|
|
// 生成唯一文件名(避免覆盖)
|
|
|
|
|
|
fileName := fmt.Sprintf("%s%s", uuidStr, fileExt)
|
|
|
|
|
|
filePath := filepath.Join(uploadDir, sourceDir, fileName)
|
2025-12-31 11:29:58 +08:00
|
|
|
|
outputFile := filepath.Join(uploadDir, encodeDir, fmt.Sprintf("%s.g711a", uuidStr))
|
2025-12-25 17:01:46 +08:00
|
|
|
|
// 创建目标文件
|
|
|
|
|
|
dstFile, err := os.Create(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("创建文件失败: %v", err))
|
|
|
|
|
|
}
|
|
|
|
|
|
defer dstFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 复制文件内容
|
|
|
|
|
|
if _, err := io.Copy(dstFile, file); err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf("保存文件失败: %v", err.Error()))
|
|
|
|
|
|
}
|
2025-12-31 11:29:58 +08:00
|
|
|
|
|
2025-12-25 17:01:46 +08:00
|
|
|
|
in := &audioencode.AddAudioEncodeInput{
|
2025-12-31 11:29:58 +08:00
|
|
|
|
Name: sourceFileName,
|
|
|
|
|
|
FileName: fileName,
|
|
|
|
|
|
Size: fileHeader.Size,
|
|
|
|
|
|
SourceUrl: filePath,
|
|
|
|
|
|
EncodeUrl: outputFile,
|
|
|
|
|
|
Mode: fileExt,
|
|
|
|
|
|
EncodeStatus: audioencode.EncodeStatusPing,
|
2025-12-25 17:01:46 +08:00
|
|
|
|
}
|
2025-12-31 11:29:58 +08:00
|
|
|
|
duration, _, err := ffmpeg.GetAudioDuration(filePath)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
in.Duration = int64(duration.Seconds())
|
|
|
|
|
|
}
|
|
|
|
|
|
info, err := a.core.AddAudioEncode(c.Request.Context(), in)
|
2025-12-25 17:01:46 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add audioencode err [%s]`, err.Error()))
|
|
|
|
|
|
}
|
2025-12-31 11:29:58 +08:00
|
|
|
|
a.transcodeCore.StartAudioEncode(filePath, outputFile, info.ID)
|
2025-12-25 17:01:46 +08:00
|
|
|
|
return gin.H{"data": "上传成功!", "filePath": filePath, "filename": fileName, "size": fileHeader.Size}, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getFileMimeType 获取文件的真实 MIME 类型(通过读取文件前几个字节)
|
|
|
|
|
|
func getFileMimeType(file multipart.File) (string, error) {
|
|
|
|
|
|
// 读取文件前 512 字节用于检测 MIME 类型
|
|
|
|
|
|
buffer := make([]byte, 512)
|
|
|
|
|
|
n, err := file.Read(buffer)
|
|
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置文件指针到开头
|
|
|
|
|
|
if _, err := file.Seek(0, io.SeekStart); err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检测 MIME 类型
|
|
|
|
|
|
return http.DetectContentType(buffer[:n]), nil
|
|
|
|
|
|
}
|