根据业务需求 实现golang 结合ffmpeg 实现h265转码h264格式并上传阿里云oss
开发环境 gin+gorm+ubuntu20.04 go版本1.20 ffmpeg为6.1
ffmpeg 安装可参考Ubuntu20.04安装ffmpeg(解决h265转码h264而安装)-CSDN博客
type VideoResponse struct {
Size int64 `json:"size"`
Path string `json:"path"`
FullPath string `json:"full_path"`
Name string `json:"name"`
Type string `json:"type"`
}
const paths = "static/uploadfile/"
type Video struct {
api.Api
}
setOnlinePath 设置上传路径
//func setOnlinePath(c *gin.Context) string {
// shopUuid := c.GetHeader(mzl_shop.ShopUuidKey)
// onlinePath := config.ApplicationConfig.Mode + "/" + "shopUuid_" + shopUuid + "/"
// return onlinePath
//}
// UploadVideo 上传视频
// @Summary 上传视频
// @Description 获取JSON
// @Tags 公共接口
// @Accept multipart/form-data
// @Param file formData file true "file"
// @Param fileName formData file true "fileName"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/public/UploadVideo [post]
// @Security Bearer
func (e Video) UploadVideo(c *gin.Context) {
e.MakeContext(c)
if e.GetShopUuid() == "" {
e.Error(200, errors.New(""), "商户号不能为空")
return
}
// 获取文件
files, err := c.FormFile("file")
if err != nil {
e.Error(200, errors.New(""), "视频文件不能为空")
return
}
fileName := c.PostForm("fileName")
if fileName == "" {
guid := uuid.New().String()
fileName = guid + utils.GetExt(files.Filename)
}
err = utils.IsNotExistMkDir(path)
if err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
}
singleFile := paths + fileName
//保存文件
_ = c.SaveUploadedFile(files, singleFile)
fileType, _ := utils.GetType(singleFile)
fileResponse := VideoResponse{
Size: pkg.GetFileSize(singleFile),
Path: singleFile,
//FullPath: urlPerfix + singleFile,
FullPath: singleFile,
Name: fileName,
Type: fileType,
}
resp, err := e.GetOssoOperate(c, fileResponse)
if err != nil {
e.Error(200, errors.New(""), "视频上传失败")
return
}
e.OK(resp, "视频上传成功")
}
// video 操作
func (e Video) GetOssoOperate(c *gin.Context, fileResponse VideoResponse) (resp dto.UploadCallbackResp, err error) {
rq := dto.UploadCallbackReq{}
_ = c.ShouldBind(&rq)
err = e.MakeContext(c).Errors
if err != nil {
// 处理错误
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
//一定要绝对路径 不然找不到文件
filePrefix := "/www/wwwroot/xxxxx/uploadfile" //输入
fileOutPrefix := "/www/wwwroot/xxxxx/uploadfile/out_file" //输出
filePath := "/" + fileResponse.Name
fileName := filePrefix + filePath
codec := checkVideoCodec(fileName)
switch codec {
case "h265":
tempFilePath := fileOutPrefix + filePath
err = convertVideoCodec(fileName, tempFilePath, "libx264")
if err != nil {
log.Println("输出失败:%s", err)
return
}
// 替换原文件(并删除输出文件)
err = os.Rename(tempFilePath, fileName)
if err != nil {
log.Println("替换原文件失败:%s", err)
return
}
}
// 上传到对象存储
oxsStore := file_store.OXS{
Endpoint: config.OssConfig.Endpoint,
AccessKeyID: config.OssConfig.AccessKeyId,
AccessKeySecret: config.OssConfig.AccessKeySecret,
BucketName: config.OssConfig.BucketName,
}
var oxs = oxsStore.Setup(file_store.AliYunOSS)
ossFileName := config.ApplicationConfig.Mode + "/shopUuid_" + e.GetShopUuid() + filePath
err = oxs.UpLoad(ossFileName, fileName)
if err != nil {
fmt.Println("err::", err)
}
// 删除本地文件
err = os.Remove(fileResponse.Path)
if err != nil {
log.Println("删除文件 Error:", err)
return
}
// 敏感过滤
req := dto2.SafetyReq{}
rq.MimeType = fileResponse.Type
//oss文件地址
rq.Filename = "xxxxxx/" + ossFileName
resp.Data = rq
greenSer := green.GreenService{
Endpoint: config.GreenConfig.Endpoint,
Region: config.GreenConfig.Region,
AccessKeyId: config.GreenConfig.AccessKeyId,
AccessKeySecret: config.GreenConfig.AccessKeySecret,
}
if strings.Contains(fileResponse.Type, "video/mp4") {
req.Type = dto2.GreenTypeVideo
req.Content = rq.Filename
req.Level = 70
req.Num = 5
req.Offset = 2
} else {
e.Logger.Error("MimeType:" + rq.MimeType)
e.OK(resp, "回调成功")
return
}
res, err := greenSer.InitHandle(&req)
if err != nil {
e.Logger.Error(err)
return
}
if res == nil {
e.Logger.Error("res is nil")
e.OK(resp, "回调成功")
return
}
// 存在敏感
if res.Confidence == 1 {
// 删除资源
err = ossObj().DeleteObject(ossFileName)
if err != nil {
// 处理错误
e.Logger.Error(err)
}
// 设置返回
resp.Msg = res.Msg
resp.Code = res.Confidence
e.OK(resp, resp.Msg)
return
}
return
}
调用用ffmpeg命令判断视频格式&转码为h264封装得操作方法
// 检查视频编码格式
func checkVideoCodec(filePath string) string {
_, stderr, err := executeFFmpegCommand("-i", filePath, "-hide_banner", "-f", "null", "-")
if err != nil {
fmt.Printf("Error executing command: %v\n", err)
fmt.Println("ffmpeg output:", stderr)
return ""
}
if strings.Contains(stderr, "hevc") || strings.Contains(stderr, "h265") || strings.Contains(stderr, "mpeg") || strings.Contains(stderr, "av1") || strings.Contains(stderr, "Copy") {
return "h265"
} else if strings.Contains(stderr, "avc1") || strings.Contains(stderr, "h264") {
return "h264"
}
return "unknown"
}
// 转换视频编码格式
func convertVideoCodec(filePath, outputPath, codec string) error {
// 调整转码参数,例如使用多线程和更快的预设
args := []string{"-i", filePath, "-vcodec", codec, "-preset", "ultrafast", "-threads", "0", outputPath} //快 但是视频会变大
//args := []string{"-i", filePath, "-vcodec", codec, outputPath} //慢 但是时间久
//args := []string{"-i", filePath, "-vcodec", codec, "-preset", "veryfast", "-threads", "0", outputPath}
_, stderr, err := executeFFmpegCommand(args...)
if err != nil {
fmt.Printf("Command finished with error: %v\n", err)
fmt.Printf("ffmpeg error output: %s\n", stderr)
return err
}
return nil
}
// 执行ffmpeg命令并返回输出
func executeFFmpegCommand(args ...string) (string, string, error) {
cmd := exec.Command("ffmpeg", args...)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
return out.String(), stderr.String(), err
}
apifox 传入file和filename