golang 结合ffmpeg 实现视频转码h265转码h264格式 并上传阿里云oss

根据业务需求 实现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

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个示例代码,使用 `gin` 框架实现分片上传阿里云OSS功能: ```go package main import ( "fmt" "io" "log" "net/http" "os" "path/filepath" "strconv" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/gin-gonic/gin" ) const ( MaxUploadSize = 5 * 1024 * 1024 * 1024 // 5 GB ChunkSize = 10 * 1024 * 1024 // 10 MB ) func main() { r := gin.Default() r.POST("/upload", UploadHandler) if err := r.Run(":8080"); err != nil { log.Fatal(err) } } func UploadHandler(c *gin.Context) { file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } defer file.Close() fileSize, err := strconv.Atoi(c.Request.FormValue("size")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } fileName := header.Filename filePath := filepath.Join(os.TempDir(), fileName) dst, err := os.Create(filePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer dst.Close() if _, err := io.Copy(dst, file); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if int64(fileSize) > MaxUploadSize { c.JSON(http.StatusBadRequest, gin.H{"error": "file size exceeds the maximum limit"}) return } client, err := oss.New("oss-cn-hangzhou.aliyuncs.com", "<access_key>", "<access_secret>") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } bucket, err := client.Bucket("<bucket_name>") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } chunkSize := ChunkSize totalPartsNum := (fileSize + chunkSize - 1) / chunkSize objectName := fileName // Initiate the multipart upload imur, err := bucket.InitiateMultipartUpload(objectName) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var uploadedParts []oss.UploadPart var partNumber int // Upload each part for i := 0; i < totalPartsNum; i++ { partSize := chunkSize if i == totalPartsNum-1 { partSize = fileSize - i*chunkSize } partNumber = i + 1 // Open the file and read the bytes file, err := os.Open(filePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer file.Close() // Seek to the start of the part offset := int64(i * chunkSize) if _, err := file.Seek(offset, io.SeekStart); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Read the part into memory partBuffer := make([]byte, partSize) if _, err := file.Read(partBuffer); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Upload the part to OSS uploadPart, err := bucket.UploadPart(imur, partBuffer, partNumber) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } uploadedParts = append(uploadedParts, uploadPart) } // Complete the multipart upload cmur, err := bucket.CompleteMultipartUpload(imur, uploadedParts) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } fmt.Println(cmur) c.JSON(http.StatusOK, gin.H{"message": "upload success"}) } ``` 以上代码中,实现了以下功能: 1. 从请求中获取文件和大小 2. 将文件存储到本地磁盘中 3. 初始化分片上传,并上传每个分片 4. 完成分片上传,将分片合并成一个对象 需要注意的是,代码中使用了阿里云OSS的 SDK 进行操作,因此需要先在阿里云控制台上创建 OSS Bucket,并在代码中填写正确的 access_key、access_secret 和 bucket_name。 另外,由于上传的文件可能很大,因此需要设置分片大小,本例中设置为每个分片的大小为10MB。同时,为了避免服务器崩溃,还需要设置文件大小的最大限制,本例中设置为5GB。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值