经过nodejs的buffer计算mp4视频时长

在nodejs服务器上,获取mp4视频时长比较容易想到的方法是经过nodejs调用ffmpeg命令去获取,这是确定行得通的,可是必须安装FFmpeg插件,而若是直接经过nodejs从MP4文件信息解析出时长,就更方便了。html

准备

先去学习mp4文件格式解析,网上有不少解释文档。
参考:https://www.cnblogs.com/ztteng/articles/3048152.html
其实就是找到几个关键字,这些关键字以后的信息大小是固定的,一个是ftyp字段,全文档有且只有一个,里面都是文件应用信息,具体是什么,咱们不关心。
还有一个字段是moov字段,这个字段同File Type Box同样,有且只有一个,通常状况下包含1个mvhd和若干个traknode

mvhd结构
mvhd结构


咱们如今只要从mvhd中获取两个信息就能够,一个是”time scale”,另外一个是”duration”,两个都是4字节,从mvhd以后的第16个字节开始读4个字节就是”time scale”,再读4个字节就是”duration”,duration/time scale = 视频时长的秒数promise

 

操做

  1. 咱们先定义一个函数getTime,用来在buffer中找到须要的数据,参数就是读出类的buffer
 
none
function getTime(buffer){//这一段是借鉴网上的方法
    if(buffer.indexOf(Buffer.from('mvhd')) != -1){
        var start = buffer.indexOf(Buffer.from('mvhd'));
    }else{
        return false;
    } 
    const box_size = buffer.readUInt32BE(start);
    const box_type = buffer.readUInt32BE(start+4);
    const create_time = buffer.readUInt32BE(start+8);
    const modi_time = buffer.readUInt32BE(start+12);
    const timeScale = buffer.readUInt32BE(start+16);
    const duration = buffer.readUInt32BE(start + 20);
    const movieLength = Math.floor(duration / timeScale);
    // console.log(start,box_size,box_type,create_time,modi_time,duration,timeScale,movieLength);//注释打开,就能看到更多的信息
    return movieLength;
}
  1. 使用fs模块对一个实例MP4文件进行操做,代码以下
 
test1.js
fs.open('./yy.mp4', 'r',function(err,fd){
        if (err) {throw err;}
        const buff = Buffer.alloc(100);
        fs.read(fd, buff , 0 ,100 ,0, function(err, bytesRead, buffer) {
            if (err) {throw err;}
            const time = getTime(buffer);
                console.log(time);
        })
})

 

结果
结果

 

原本觉得这样就结束了,没有想到测试另一个MP4文件时,返回了false,经过分析文件结构才发现,并非全部文件都是moov跟随ftyp,有的是在文件中间,也有点在最后,好比这个yy.mp4。服务器

moov在前面
moov在前面

 

moov在后面
moov在后面


这样就致使了buffer中读取的100个字节可能根本就包含不了moov字段,因而理所固然的想法就是把整个文件都放到buffer中,可是理智告诉我这样并不明智,若是一个视频都好几个G,加上并发,那服务器就GG了。因此更合理的方法应该是,将这段代码封装到一个函数中,只要没有找到moov,递归调用查找函数,就能够不断查下去,直到找到这段信息。
考虑到回调函数的复杂性,重构了一下代码,使用async/await关键字,结构更加晰
完整代码以下:并发

test2.js
const fs = require('fs');
//声明getTime函数,查找关键字,并计算时长
function getTime(buffer){
    if(buffer.indexOf(Buffer.from('mvhd')) != -1){
        var start = buffer.indexOf(Buffer.from('mvhd'));
    }else{
        return false;
    } 
    const box_size = buffer.readUInt32BE(start);
    const box_type = buffer.readUInt32BE(start+4);
    const create_time = buffer.readUInt32BE(start+8);
    const modi_time = buffer.readUInt32BE(start+12);
    const timeScale = buffer.readUInt32BE(start+16);
    const duration = buffer.readUInt32BE(start + 20);
    const movieLength = Math.floor(duration / timeScale);
    console.log(start,box_size,box_type,create_time,modi_time,duration,timeScale,movieLength);
    if(movieLength === 0){
        return false;
    } 
    return movieLength;
}
//声明read()函数,打开文件
async function read(){//使用read() 先返回一个promise
    return new Promise((resolve,reject)=>{
        fs.open('./6.mp4', 'r',(err,fd)=>{
            if(err){
                reject(err)
            }else{
                resolve(fd)
            }
        })
    })
}
//声明readfile函数,读数据
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)
        }
    })
  })
}
//声明主函数
var main = async function (){//声明这个函数中有异步操做,read()会先收到一个promise
    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;
        }
    }
  };
main();

每次建立的buffer长度是10000,也就是10KB,而游标移动的长度是9900,这样是为了保证mvhd字段信息的完整,下一段和前一段是有重合的部分的,不知道这样有没有意义,由于我相信绝大多数的状况只要读取一次buffer就能找到,个别的状况须要屡次,再有极少数的状况可能将mvhd截为两段。目前来讲能用,不知道还有没有更好的方案?异步

测试

测试了6个视频,均可以准确得出结果,最小的文件2.46M,最大的959M。主要针对的是mp4格式,其它格式应该不行。async

959M文件

959M文件

 

结语

山穷水复疑无路,柳暗花明又一村
每次碰到问题都是这样,总感受没辙了,查查资料,翻翻帖子,总能找到灵感。
这可能就是代码的魅力吧。函数

相关文章
相关标签/搜索