行业知识
javascript视频截帧的四种方式
2026-10-26 16:23:43
来源:互联网转载
前端视频截帧的四种方式
- 1. 阿里云后缀参数
- 2.本地文件截取预览图
- 3.网络视频截帧(不推荐)
- 4.采用`ffmpeg`插件截帧(推荐)
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 - 示例
- 找到视频5s处的内容,输出为宽为800像素,高自动计算的png格式图片http://a-image-demo.oss-cn-qingdao.aliyuncs.com/demo.mp4?x-oss-process=video/snapshot,t_5000,m_fast,w_800,f_png
详见文档:阿里云oss视频上传后,如何获取视频封面
2.本地文件截取预览图
获取本地视频文件封面,实现本地预览功能,支持指定秒数
- 首先将本地文件通过URL.createObjectURL(file.raw)得到视频url本地地址
- 动态创建一个video标签,并利用onloadeddata 方法设置具体时间点,并添加监听事件addEventListener
- 然后利用canvas绘制成图片
- 输出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 { const xhr = new XMLHttpRequest() // eslint-disable-next-line no-unused-vars xhr.onreadystatechange = _ => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status = 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; i3.网络视频截帧(不推荐)
- 从后台拿到视频url以及相关配置(宽,高,比例,时长),本地创建video的获取方式存在某些视频格式有问题或者帧率不对,导致无法拿到相关配置的问题,所以可以跟后台协商,将相关配置在拿视频时一起回传过来后,提前计算好帧率,创建好canvas数组容器。
- 视频添加监听函数,当视频开始播放时,就创建一个canvas将当前帧绘制出来,将canvas转化为图片地址,再推入准备好的canvas数组。
- 处理完后,添加键盘监听事件,在html中用标签遍历上一步处理好的canvas数组,直接展示绘制好的canvas,这样用户可以通过键盘的上下左右箭头预览视频的每一帧的画面,也可以根据项目添加滚动条或者输入框做预览帧交互。
- 缺点很大,超慢,原视频分辨率越大,处理的越慢,一个8s的视频要处理40s,而且浏览器很容易崩。
- 关键代码
//给视频添加监听函数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插件截帧(推荐)
-
需引入ffmpeg插件,详见文章vue2+vite利用ffmpeg实现纯前端视频剪切
-
可指定开始帧率、输出张数、尺寸、格式
-
缺点是加载该插件需要一定时间
-
优点是相比第三种视频播放+canvas的方法要快很多,二十多秒的视频几秒钟就处理完了,也不用担心浏览器崩的问题,除非你的视频文件很长。
-
参考文档: FFmpeg基本命令使用 、FFmpeg官网、 FFmpeg——在Vue项目中使用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
-
- 关键代码
- 找到视频5s处的内容,输出为宽为800像素,高自动计算的png格式图片http://a-image-demo.oss-cn-qingdao.aliyuncs.com/demo.mp4?x-oss-process=video/snapshot,t_5000,m_fast,w_800,f_png
- 示例