javascript视频截帧的四种方式

1. 阿里云后缀参数

  • 其实OSS本身提供了视频截帧功能,OSS提供的视频截帧功能和OSS图片服务功能使用的方式是类似的,都是通过传入x-oss-process参数的方式来实现的。最简单的使用方式就是直接在public的object的url后面拼接处理字符串。比如如下的url:http://a-image-demo.oss-cn-qingdao.aliyuncs.com/demo.mp4?x-oss-process=video/snapshot,t_10000,m_fast
  • 注意
    • 当前仅支持对视频编码格式为H264的视频文件进行视频截帧。
    • OSS当前没有默认保存视频截帧的操作,视频截帧的图片需手动下载到本地。
    • 如果需要对private的object调用视频截图,需要使用SDK进行鉴权,接口和OSS图片服务的调用接口一致。
    • 图片处理除了能够处理已经存在的图片内容,还能够截取出视频中的指定位置产生图片,完成视频截帧。
参数描述取值范围
t截图时间单位ms,[0,视频时长]
w截图宽度,如果指定为0则自动计算像素值:[0,视频宽度]
h截图高度,如果指定为0则自动计算,如果w和h都为0则输出为原视频宽高像素值:[0,视频高度]
m截图模式,不指定则为默认模式,根据时间精确截图,如果指定为fast则截取该时间点之前的最近的一个关键帧枚举值:fast
f输出图片格式枚举值:jpg、png

2.本地文件截取预览图

获取本地视频文件封面,实现本地预览功能,支持指定秒数

  1. 首先将本地文件通过URL.createObjectURL(file.raw)得到视频url本地地址
  2. 动态创建一个video标签,并利用onloadeddata 方法设置具体时间点,并添加监听事件addEventListener
  3. 然后利用canvas绘制成图片
  4. 输出base64
  • 使用
 GetVideoCover({
         url: URL.createObjectURL(fileList[0].raw),
         time: 2,
         success: (res) => {
         //封面地址res.base64
         //this.videocover=res.base64
         }
});
  • 关键代码
    定义GetVideoCover.js
// 压缩图片
function clipAndCompressCover({ media, currentWidth, currentHeight, success }) {
    // eslint-disable-next-line no-unused-vars
    const that = this
    const canvas = document.createElement('canvas')
    const area = canvas.getContext('2d')
    const currentScale = currentWidth / currentHeight
    const targetScale = 750 / 420
    let targetWidth = 0
    let targetHeight = 0
    let clipWidth = 0
    let clipHeight = 0
    let quality = 0.95 // 不要用1,会额外增大base64大小。

    // // 根据视频宽高,决定截图大小
    // if (currentScale >= targetScale) {
    //     targetHeight = currentHeight > 420 ? 420 : currentHeight
    //     targetWidth = targetHeight * currentScale
    // } else {
    //     targetWidth = currentWidth > 750 ? 750 : currentWidth
    //     targetHeight = targetWidth / currentScale
    // }
    // clipWidth = targetWidth > 750 ? 750 : targetWidth
    // clipHeight = targetHeight > 420 ? 420 : currentHeight
    canvas.width = currentWidth
    canvas.height = currentHeight
    area.drawImage(
        media,
        0, 0,
        currentWidth,
        currentHeight,
    )
    var handler = function() {
        const base64 = canvas.toDataURL('image/jpeg', quality)
        getMediaSize({ src: base64 }).then(response => {
          success && success(base64)
        })
    }
    handler()
}

// 绘制视频封面图片
const GetVideoCover = ({ url, time, success }) => {
        const video1 = document.createElement('video')
        video1.src = url
        video1.style.cssText = 'position:fixed; top:0; left:-100%; visibility:hidden'
        video1.onloadeddata = function() {
                const currentTime = time // 截图时间点
                video1.addEventListener('timeupdate', function() {
                    // console.log('video1', video1.videoWidth, video1.videoHeight)
                    clipAndCompressCover({
                        media: video1,
                        currentWidth: video1.videoWidth,
                        currentHeight: video1.videoHeight,
                        success: function(base64) {
                            video1.remove(video1)
                            success({
                                base64
                            })
                        }
                    })
                })
                video1.currentTime = currentTime < 0 ? 1 : currentTime
            }
            // edge浏览器必须要追加到dom中,才能顺利执行相关事件。
        document.body.appendChild(video1)
    }
    // 获取媒体资源的大小,返回一个Promise对象。用于解决无法直接获取视频或图片的文件大小。
function getMediaSize({
    src
}) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        // eslint-disable-next-line no-unused-vars
        xhr.onreadystatechange = _ => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    resolve({
                        size: xhr.response.size
                    })
                } else {
                    reject()
                }
            }
        }

        xhr.open('get', URL.createObjectURL(transformBase64ToBlob(src)))
        xhr.responseType = 'blob'
        xhr.send()
    })
}

function transformBase64ToBlob(base64) {
    let byteString

    if (base64.split(',')[0].indexOf('base64') >= 0) {
        byteString = atob(base64.split(',')[1])
    } else {
        byteString = unescape(base64.split(',')[1])
    }

    const mimeString = base64.split(',')[0].split(':')[1].split(';')[0]
    const ia = new Uint8Array(byteString.length)

    for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i)
    }

    const blob = new Blob([ia], {
        type: mimeString
    })

    return blob
}

export {
    GetVideoCover
}

3.网络视频截帧(不推荐)

  1. 从后台拿到视频url以及相关配置(宽,高,比例,时长),本地创建video的获取方式存在某些视频格式有问题或者帧率不对,导致无法拿到相关配置的问题,所以可以跟后台协商,将相关配置在拿视频时一起回传过来后,提前计算好帧率,创建好canvas数组容器。
  2. 视频添加监听函数,当视频开始播放时,就创建一个canvas将当前帧绘制出来,将canvas转化为图片地址,再推入准备好的canvas数组。
  3. 处理完后,添加键盘监听事件,在html中用<canvas>标签遍历上一步处理好的canvas数组,直接展示绘制好的canvas,这样用户可以通过键盘的上下左右箭头预览视频的每一帧的画面,也可以根据项目添加滚动条或者输入框做预览帧交互。
  4. 缺点很大,超慢,原视频分辨率越大,处理的越慢,一个8s的视频要处理40s,而且浏览器很容易崩。
  • 关键代码
 <video
   :src="coverFileUrl"
   ref="video"
   id="video"
   controls
   muted
   autoplay
   style="width:300px;"
	></video>
	 <!-- coverFileUrl -->
  <canvas
   v-show="editImg.index==n"
   v-for="(i,n) in canvasList"
   :key="n"
   :id="'myCanvas'+n"
   :width="videoCig.width"
   :height="videoCig.height"
   :style="{transform:`scale(${scale})`}"
    @mousedown="cropMove($event,'start')"
    @mouseup="cropMove($event,'end')"
  ></canvas>
//给视频添加监听函数
initVideoplayer() {
      if (!this.videoPlayer && this.$refs.video) {
        this.videoPlayer = this.$refs.video;
       	// 视频播放 -- 点击播放
        this.videoPlayer.addEventListener("play", e => {
          let { frame, duration } = this.videoCig;
          this.timer = setInterval(() => {
            this.no += 1;
            if (this.no > (duration - 1) * frame) {
              clearInterval(this.timer);
              this.timer = null;
              //   this.listenKeyDown(); //视频帧全部截取完毕再添加
            } else {
              this.cutPicture(null, this.no);
            }
          }, 1000 / frame);
        });

        // 视频播放 -- 播放结束
        this.videoPlayer.addEventListener("ended", e => {
          console.log("视频播放结束");
        });
      }
    }
    //截取当前帧的图片
    cutPicture(item, index) {
      let { width, height } = this.videoCig;
      var v = item
        ? document.getElementById("img" + index)
        : document.querySelector("video");
      let canvas = document.getElementById("myCanvas" + index);
      var ctx = canvas.getContext("2d");
      ctx.drawImage(v, 0, 0, width, height);
      let copyImg = ctx.getImageData(0, 0, width, height);
     var oGrayImg = canvas.toDataURL("image/jpeg");
      this.canvasList.splice(index, 1, copyImg);
      if (index == 0) {
      	//将首帧设为封面
         this.video.cover = oGrayImg;
      }
    }

4.采用ffmpeg插件截帧(推荐)

 // 上传视频后解析视频帧
    async getVideoFrames() {
      try {
        showLoading();
        let { name, file, duration, ccbl } = this.ffVideo;
        ffmpeg.FS("writeFile", name, await FFmpeg.fetchFile(file));
        // 计算每秒需要抽的帧数
        let step = Math.ceil(10 / duration),
          allNum = Math.floor(step * duration);
     	 await ffmpeg.run(
          "-i",
          name,//视频文件名
          "-r",
          `${step}`,//每秒抽取帧数
          "-ss",
          "0",//从0帧开始
          "-vframes",
          `${allNum}`,//所需截取总张数
          "-f",
          "image2",//图片格式
          "-s",
          `88*${Math.ceil((88 * ccbl[1]) / ccbl[0])}`, //图片尺寸需要整数,此处按比例计算高
          "image-%02d.png"//图片命名
        );
        // ffmpeg -i 2.mp4 -r 1  -ss 0 -vframes 5 -f image2 -s 352x240 image-%02d.jpeg
        for (let i = 0; i < allNum; i++) {
          // await ffmpeg.run('-i', 'source.mp4', '-y', '-f', '-ss', averageDura * i, '1', 'frame.png')
          let temp = i + 1;
          if (temp < 10) {
            temp = "0" + temp;
          }
          this.frames.pics.push(
            arrayBufferToBase64(ffmpeg.FS("readFile", "image-" + temp + ".png"))
          );
        }
        hideLoading();
        retutn this.frames.pics
      } catch (err) {
        console.error("getVideoFrames", err);
        hideLoading();
        // throw err;
      }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值