持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
一、问题
在上一篇小文中,已经介绍了如何从mp4文件结构信息中获取time scale
duration
两个参数,进而计算出视频时长。本来事情到此结束,却在后面的测试中出了问题。上文中的getTime函数直接返回了false,也就是没有找到mvhd
字段。
二、查找原因
mp4文件内部是由一系列的box组成,我们所关心的是moov
>mvhd
,下表截取了部分mp4文件结构的描述,包含了我们所关心的moov
,其中打勾的字段是必选的。
一级 | 二级 | 三级 | 是否必选 | 字段描述 |
---|---|---|---|---|
ftyp | √ | file type and compatibility | ||
pdin | progressive download information | |||
moov | √ | container for all the metadata | ||
mvhd | √ | movie header, overall declarations | ||
trak | √ | container for an individual track or stream | ||
tkhd | √ | track header, overall information about the track |
我们可以看到mvhd
也是必选的。其实网上的描述具有一定的误导性,往往我们认为moov
会放在文件的开头,事实上并非如此。举一个现实的例子,比如某些监控的录像,由于采用流存储,也不可能都放到内存,文件不断的写入到磁盘,在最终结束之前,谁也不知道文件有多长,除非事先定义好。因此,moov
字段的位置不固定,可以在头部,也可以在尾部。 为了验证我们的猜想,使用专用的软件mp4info进行查看,结果如下图
这是常规结构,moov
在文件头部
这是异常结构,moov
在文件尾部
三、解决方案
既然找到了原因,那么我们就对症下药。想当然的,我们可以把整个文件放到buffer中,但是要考虑到视频文件有可能在10GB级别,放到内存既不明智也不现实,我们不妨利用循环,从文件头部一直向后扫描,直到找到moov
字段为止。由于存在异步操作,我们使用async/await关键字,使结构更清晰。下面一起看如何实现。
step 1
正常读取文件,构造read()函数 返回mp4文件数据
async function read(){
return new Promise((resolve,reject)=>{
fs.open('./6.mp4', 'r',(err,fd)=>{
if(err){
reject(err)
}else{
resolve(fd)
}
})
})
}
复制代码
step 2
声明readfile函数,将mp4文件数据放入buffer中,参数是文件流、buffer数组,开始位置,这里也比较简单,就不展开了。
async function readfile(fd,buff,start_position){
return new Promise((resolve,reject)=>{
fs.read(fd, buff , 0 ,10000 ,start_position, function(err, bytesRead, buffer) {
if(err){
reject(err)
}else{
resolve(buffer)
}
})
})
}
复制代码
step 3
主函数,首先创建一个10kb大小的buffer区,将文件不断送入进行检测,每次指针移动9900个字节位置,直到检测到moov
字段为止。这里有个疑问,为什么10kb的buffer,但是下一次循环指针只移动9900呢,主要考虑到可能恰巧将moov
字段切断了,有一定冗余空间,虽然这个概率可能比彩票中500万高不了多少。这里简单定义循环参数i最大为100w,这样理论上可以检测的文件大小约为9.9x100w/1024= 9668MB。这里考虑比较简单,没有对文件参数进行过多限制,主要是为了验证可行性。
var main = async function (){
var start_position = 0
const buff = Buffer.alloc(10000);
var fd = await read();
for(i = 0 ; i<1000000 ;i++){
var buff_data = await readfile(fd,buff,start_position);
const time = getTime(buff_data);
if(time){
console.log(i);
console.log(time);
i = 1000000;
}else{
start_position = start_position + 9900;
}
}
};
复制代码
step 4
运行验证
main()
复制代码
本人测试了多个视频,均能准确给出结果,最小的文件2.46M,最大的959M。
结束
nodejs环境下获取mp4视频时长的分享到这里就结束了。这是本人原创文章,也可以在本人博客上找到。如果不想自己这么麻烦的话,可以用npm上的开源包,比如 get-video-duration。
(完)