不会视频截图怎么办?用这些方法分分钟搞定

接之前开发的一个项目,其中有个短视频上传的功能,遭运营人员百般吐槽,原因就是每次都要分别上传视频和视频封面,还得麻烦他们每次先打开视频截取第一帧的视频作为视频封面,今天我就来为您解忧了!

解决的方案有两种,前后端都可解决,这样的话就导致了前后端程序员的互相推诿,奈何本人是全栈,自然没办法推诿了。

前端处理

其原理就是利用了canvas,将视频某一刻画在了canvas。通常我们都是在上传的时刻进行操作,我这里就以element-ui为例演示下过程吧。

创建video对象

1
var oVideo = document.createElement('video');

设置video链接

1
oVideo.setAttribute('src', 视频blob链接);

监听视频帧加载

当音频/视频处于加载过程中时,会依次发生以下事件:

loadstart

当浏览器开始寻找指定的音频/视频时,会发生 loadstart 事件。即当加载过程开始时。

语法

在 HTML 中:

1
<audio|video onloadstart="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.onloadstart=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("loadstart", function()
{
//SomeJavaScriptCode
}
);
durationkchange

当指定音频/视频的时长数据发生变化时,发生 durationchange 事件。

当音频/视频加载后,时长将由 “NaN” 变为音频/视频的实际时长。

语法

在 HTML 中:

1
<audio|video ondurationchange="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.ondurationchange=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("durationchange", function()
{
//SomeJavaScriptCode
}
);
loadedmetadata

当指定的音频/视频的元数据已加载时,会发生 loadedmetadata 事件。

音频/视频的元数据包括:时长、尺寸(仅视频)以及文本轨道。

语法

在 HTML 中:

1
<audio|video onloadedmetadata="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.onloadedmetadata=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("loadedmetadata", function()
{
//SomeJavaScriptCode
}
);
loadeddata

当当前帧的数据已加载,但没有足够的数据来播放指定音频/视频的下一帧时,会发生 loadeddata 事件。

语法

在 HTML 中:

1
<audio|video onloadeddata="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.onloadeddata=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("loadeddata", function()
{
//SomeJavaScriptCode
}
);
progress

当浏览器正在下载指定的音频/视频时,会发生 progress 事件。

语法

在 HTML 中:

1
<audio|video onprogress="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.onprogress=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("progress", function()
{
//SomeJavaScriptCode
}
);
canplay

当浏览器能够开始播放指定的音频/视频时,发生 canplay 事件。

语法

在 HTML 中:

1
<audio|video oncanplay="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.oncanplay=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("canplay", function()
{
//SomeJavaScriptCode
}
);
canplaythrough

当浏览器预计能够在不停下来进行缓冲的情况下持续播放指定的音频/视频时,会发生 canplaythrough 事件。

语法

在 HTML 中:

1
<audio|video oncanplaythrough="SomeJavaScriptCode">

在 JavaScript 中:

1
audio|video.oncanplaythrough=SomeJavaScriptCode;

使用 addEventListener():

1
2
3
4
5
audio|video.addEventListener("canplaythrough", function()
{
//SomeJavaScriptCode
}
);

以上事件好多都能利用,我们随便拿loadeddata为例进行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 设置视频开始播放的当前时间
oVideo.currentTime = 3
// 监听loadeddata
oVideo.addEventListener("loadeddata", function(_event) {
// 当然可以通过oVideo.duration获取视频时长
// 创建canvas对象
let canvas = document.createElement("canvas")
// 设置canvas对象的宽高与视频宽高一致
canvas.width = oVideo.videoWidth
canvas.height = oVideo.videoHeight
// 在画布上创建一个2维的绘图环境对象
let ctx = canvas.getContext('2d')
// 绘制视频大小与视频大小一致
ctx.drawImage(oVideo,0,0,canvas.width,canvas.height)
// 将画布转为dataurl
let img_url = canvas.toDataURL()
})

此时就可以将图片截取下来了,截取的正是oVideo.currentTime我们设置的时间,使用火狐等其他浏览器测试正常,拿到了视频和截图的datauri就可以进行上传操作了,但是使用chrome截取的是一张透明图片,根据观察oVideo.addEventListener(“canplay”, function)时第一张也是透明图片,第二张及以后都是正常的图片,大家可以利用这个来解决chrome上的问题。

后端处理

我们这里后端以python为例,用到了OpenCV。

安装方式

1
pip install opencv-python

代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 获取验证通过的视频字段对象
video = request.data.get("video")
# 根据视频路径读取视频(video.file.name为视频临时上传路径)
cap = cv2.VideoCapture(video.file.name)
# 获取视频总帧数
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
# 获取视频帧速率
fps = cap.get(cv2.CAP_PROP_FPS)
# 计算视频时长单位为秒(总帧数/帧速率=时长),这里除以60转为分钟
duration = '%d:%d' % (frame_count/fps//60, frame_count/fps%60)
# 设置从第一帧开始读取
cap.set(cv2.CAP_PROP_POS_FRAMES, 1)
# 判断能否成功打开视频
if cap.isOpened():
# 开始读取帧,返回值为:是否成功获取视频帧;返回的当前视频帧。
rvals, frame = cap.read()
# 判断当前帧是否读取成功
if rval:
# 将图片转为jpg格式的2进制流
img_bytes = cv2.imencode(".jpg", frame)[1].tobytes()
# 当然也可以保存为图片
# cv2.imwrite('aa/bb/cc.jpg',frame)

此时同样获取到了提交的视频并且截取了图片,就可以将视频进行保存返回给前端保存后的视频地址及图片地址