在nodejs服務器上,獲取mp4視頻時長比較容易想到的方法是經過nodejs調用ffmpeg命令去獲取,這是確定行得通的,可是必須安裝FFmpeg插件,而若是直接經過nodejs從MP4文件信息解析出時長,就更方便了。html
準備
先去學習mp4文件格式解析,網上有不少解釋文檔。
參考:https://www.cnblogs.com/ztteng/articles/3048152.html
其實就是找到幾個關鍵字,這些關鍵字以後的信息大小是固定的,一個是ftyp
字段,全文檔有且只有一個,裏面都是文件應用信息,具體是什麼,咱們不關心。
還有一個字段是moov
字段,這個字段同File Type Box同樣,有且只有一個,通常狀況下包含1個mvhd
和若干個trak
。node
咱們如今只要從mvhd
中獲取兩個信息就能夠,一個是」time scale」,另外一個是」duration」,兩個都是4字節,從mvhd
以後的第16個字節開始讀4個字節就是」time scale」,再讀4個字節就是」duration」,duration/time scale = 視頻時長的秒數promise
操做
- 咱們先定義一個函數getTime,用來在buffer中找到須要的數據,參數就是讀出類的buffer
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; }
- 使用fs模塊對一個實例MP4文件進行操做,代碼以下
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。服務器
這樣就致使了buffer中讀取的100個字節可能根本就包含不了moov
字段,因而理所固然的想法就是把整個文件都放到buffer中,可是理智告訴我這樣並不明智,若是一個視頻都好幾個G,加上併發,那服務器就GG了。因此更合理的方法應該是,將這段代碼封裝到一個函數中,只要沒有找到moov
,遞歸調用查找函數,就能夠不斷查下去,直到找到這段信息。
考慮到回調函數的複雜性,重構了一下代碼,使用async/await關鍵字,結構更加晰
完整代碼以下:併發
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
結語
山窮水復疑無路,柳暗花明又一村
每次碰到問題都是這樣,總感受沒轍了,查查資料,翻翻帖子,總能找到靈感。
這可能就是代碼的魅力吧。函數