ffmpeg截取视频某一帧图像 | 青训营笔记

388 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第8天

前言

在字节跳动青训营的项目之中,有一个需求是对创作的视频截取一张封面图片。经调研,采用ffmpeg来实现。

环境配置

FFmpeg的名称来自MPEG视频编码标准,前面的“FF”代表“Fast Forward”,FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。可以轻易地实现多种视频格式之间的相互转换。包括如下几个部分:

  • libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能,包含demuxers和muxer库。
  • libavcodec:用于各种类型声音/图像编解码。
  • libavutil:包含一些公共的工具函数。
  • libswscale:用于视频场景比例缩放、色彩映射转换。
  • libpostproc:用于后期效果处理。
  • ffmpeg:是一个命令行工具,用来对视频文件转换格式,也支持对电视卡实时编码。
  • ffsever:是一个HTTP多媒体实时广播流服务器,支持时光平移。
  • ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示。
  • ffprobe:收集多媒体文件或流的信息,并以人和机器可读的方式输出。

下载ffmpeg

A complete, cross-platform solution to record, convert and stream audio and video.

下载地址:ffmpeg.org/download.ht…

下载完之后,放入某个文件夹内(例如D:/software/ffmpeg),无需安装。

配置PATH

将上述文件夹路径添加到PATH之中

测试

打开cmd,出现以下内容,即代表环境配置成功。

image.png

Golang 调用

在Golang中,需要使用 github.com/u2takey/ffm… 来实现。

// GenerateVideoCover 获取封面
func GenerateVideoCover(inFileName string, frameNum int) io.Reader {
   buf := bytes.NewBuffer(nil)
   err := ffmpeg.Input(inFileName).
      Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
      Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
      WithOutput(buf, os.Stdout).
      Run()
   if err != nil {
      panic(err)
   }
   return buf
}

视频发布

// Publish 视频投稿
func Publish(c *gin.Context) {
   //1.校验token
   getUserId, _ := c.Get("user_id")
   var userId uint
   if v, ok := getUserId.(uint); ok {
      userId = v
   }
   //2.解析参数
   title := c.PostForm("title")
   data, err := c.FormFile("data")
   if err != nil {
      c.JSON(http.StatusOK, common.Response{
         Code: 1,
         Msg:  err.Error(),
      })
      return
   }

   fmt.Println("标题: " + title)

   //3.返回至前端页面的展示信息
   fileName := filepath.Base(data.Filename)
   finalName := fmt.Sprintf("%d_%s", userId, fileName) // user_id + filename
   //先存储到本地文件夹,再保存到云端,获取封面后最后删除
   saveFile := filepath.Join("../../videos/", finalName)

   // 如果不存在viceos文件夹创建
   if _, err := os.Stat("../../videos"); os.IsNotExist(err) {
      err := os.Mkdir("../../videos", os.ModePerm)
      if err != nil {
         return
      }
   }

   if err := c.SaveUploadedFile(data, saveFile); err != nil {
      c.JSON(http.StatusOK, common.Response{
         Code: 1,
         Msg:  err.Error(),
      })
      return
   }

   f, err := data.Open()
   if err != nil {
      err.Error()
   }

   //从本地上传到云端,并获取云端地址
   playUrl, err := service.CosUpload(finalName, f)
   if err != nil {
      c.JSON(http.StatusOK, common.Response{
         Code: 1,
         Msg:  err.Error(),
      })
      return
   }
   c.JSON(http.StatusOK, common.Response{
      Code: 0,
      Msg:  finalName + " 上传成功",
   })

   fmt.Println(playUrl)

   coverName := strings.Replace(finalName, ".mp4", ".jpeg", 1)

   //获取第3帧封面
   img := service.GenerateVideoCover(saveFile, 3)

   //img, _ := jpeg.Decode(buf)//保存到本地时要用到
   //imgw, _ := os.Create(saveImage) //先创建,后写入
   //jpeg.Encode(imgw, img, &jpeg.Options{100})

   // 使用腾讯云,
   //直接传至云端,不用存储到本地,
   coverUrl, err := service.CosUpload(coverName, img)
   if err != nil {
      c.JSON(http.StatusOK, common.Response{
         Code: 1,
         Msg:  err.Error(),
      })
      return
   }

   //删除保存在本地中的视频
   err = os.Remove(saveFile) // ignore_security_alert
   if err != nil {
      logging.Info(err)
   }

   //4.保存发布信息至数据库,刚开始发布,喜爱和评论默认为0
   video := db.Video{
      Model:         gorm.Model{},
      AuthorId:      userId,
      PlayUrl:       playUrl,
      CoverUrl:      coverUrl,
      FavoriteCount: 0,
      CommentCount:  0,
      Title:         title,
   }
   service.AddVideo(&video)
}

踩坑

exec: "ffmpeg": executable file not found in %PATH%

若出现以上错误,且cmd中能正常调用ffmpeg,说明Goland的权限可能不够,使用管理员权限运Goland可以解决。

参考文章