上周一个学姐问我有没有兴趣毕业去她那做图像处理,抛给我个模式识别问题和一段4000+秒的mp4视频。大周末的我正犯五月病,就跟群里大佬问了下视频抓帧用什么合适,知道神奇的FFmpeg后顺手写了个python脚本做一下批量抓帧。至于为什么要用python不直接写shell,因为FFmpeg自带的批量抓帧命令是针对连续时间序列进行的,执行起来特别慢,用python是要做一下时间序列离散化,然后并行处理。
FFmpeg
先引用百度百科简单介绍下FFmpeg:
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。
关于FFmpeg的业界地位,有很多视音频播放器是通过给FFmpeg加壳完成的。它是跨平台的,linux、win、mac os下都有发行版。想要安装,可以去官网看看。关于文档,我找到的总结、教程、手册都比较零散,官方的英语文档又让新手无从看起,这次我只查到够用的资料就放着了,如果读者找到比较全面的实用手册,欢迎留言。
实现
我试着直接执行FFmpeg的批量抓帧命令时发现特别慢,几乎总要花费目标视频一半的播放时间。但是单张抓帧,不论时间点在哪里,其实都很快。于是我的思路是把视频的总时长拿出来,然后获得一个均匀分布的时间点集合,最后统统扔给gevent的pool并行抓帧。gevent是python一个著名的coroutine(协程)框架,初衷是处理高并发的网络IO,要安装pip一下就好。思路很简单,脚本也很短,life is short, I choose python!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from commands import getoutput
import re
from gevent.pool import Pool as Gpool
from time import time
# get arguments
file_path = ''
ouput_path = ''
interval = 1
while True:
file_path = raw_input('Vedio path: ')
if os.path.isfile(file_path):
break
else:
print 'Not a file.'
while True:
ouput_path = raw_input('Output path( current directory for default ) : ')
if ouput_path == '':
break
if os.path.exists(ouput_path) and not os.path.isfile(ouput_path):
ouput_path += '/'
break
else:
print 'Not a directory.'
while True:
s = raw_input('Snap interval( 1 second for default ) : ')
if s == '':
interval = 1
break
if re.match(r'^[0-9]+(.[0-9]+){0,1}$', s) is None:
print 'Not a number.'
else:
interval = float(s)
break
# get vedio duration via os.popen with "ffmpeg -i"
info = getoutput('ffmpeg -i ' + file_path)
dur = re.search(r'(?<=Duration: ).*?(?=,)', info).group(0).split(':')
dur = int(dur[0])*3600 + int(dur[1])*60 + float(dur[2])
print 'Vedio duration: %.2f seconds.' % dur
# make time stamps pool
time_stamp_pool = []
time_stamp = 0
while time_stamp < dur:
time_stamp_pool.append(time_stamp)
time_stamp += interval
# os.system + gevent snap by batch
gpool = Gpool()
snap_cmd = 'ffmpeg -ss %f -i %s -nostats -loglevel 0 -q:v 2 -f image2 %s%d.jpg' # execute quietly
n_snap = len(time_stamp_pool)
print '%d frames to be snapped.' % n_snap
print 'Handling...'
time0 = time()
for i in xrange(n_snap):
gpool.spawn(os.system, snap_cmd % (time_stamp_pool[i], file_path, ouput_path, i))
gpool.join()
time_cost = time() - time0
print 'Done in %.2f seconds.' % time_cost