經過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文件

 

結語

山窮水復疑無路,柳暗花明又一村
每次碰到問題都是這樣,總感受沒轍了,查查資料,翻翻帖子,總能找到靈感。
這可能就是代碼的魅力吧。函數

相關文章
相關標籤/搜索