利用 nodejs 解析 m3u8 格式文件,並下 ts 合並為 mp4
以前看視頻的時候,直接找到 video標簽,查看視頻地址,然后下載下來。。
后來發現,好多 video 標簽打開元素審查,如下:

blob開始的東西,下載不了啦。。。
其實我們打開 network 還是能看見,加載了一堆的 .ts 文件。其實.ts文件就是被切成一段一段的視頻。 理論上,把這些文件都下載下來,再合並,就完成了,,,
理論一句話,代碼上千行...
一、問題
1、ts文件到底有多少和,地址從哪來。。。
答案: ts 相關的信息,都存在一個叫 m3u8 的文件。 如果仔細點觀察 network 是可以找到這個文件的請求的。該文件內容大致如下:

這個文件,很顯然,存了每個 ts 的文件名稱,當然也有存完整的地址的。。只需要提取出里面的ts文件名稱,再加上目標網站的域名,就可以下載了。。
我這里是手動的把 m3u8 下載到了本地,當然也可以自己寫腳本來下載m3u8文件
解析代碼如下:
const fs = require("fs");
var source = fs.readFileSync("./test.m3u8","utf-8"); //讀取 m3u8
var arr = source.split("\n");
arr = arr.filter((item)=>{
return item.match(/\.ts$/);
});
2、使用什么技術來合並這些 ts
這里我嘗試了兩種辦法
第一種: 使用node js 直接讀取文件流,合並到一個文件。。。最后結果,合並確實成功了,也能播放,但是有卡頓現象,應該是視頻幀被破壞了。
第二種: 使用一款強大的工具, ffmpeg 來合並,成功了。具體 ffmpeg 安裝看這里 :https://www.cnblogs.com/xswl/p/10042195.html
其中 ffmpeg 的視頻合成指令,我找到到了三類:
ffmpeg -i "concat:1.ts|2.ts" -acodec copy out.mp3
ffmpeg -i "concat:1.ts|2.ts" -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4
前兩類,都是要文件名稱拼接到 指令里面,,考慮到 cmd 指令的長度有限制,所以並未采用。
采用了如下文件輸入辦法:
ffmpeg -i input.txt -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4
其中 input.txt 是一個輸入配置文件,內容為需要合並的文件名稱,如下:
ffconcat version 1.0 file 0.ts file 1.ts
二、正式開始
新建 down.js 寫入:
const request = require("request");
const fs = require("fs");
const path = require("path");
const child_process = require('child_process');
const fsextra = require('fs-extra');
module.exports = function(opt){
opt = opt || {};
var arr = opt.arr || []; //所有 ts的文件名或者地址
var host = opt.host || ""; //下載 ts 的 域名,如果 arr 里面的元素已經包含,可以不傳
var outputName = opt.name || `output${(new Date()).getTime()}.mp4`; //導出視頻的名稱
const tsFile = path.join(__dirname,`./source/${arr[0].split(".")[0]}`,);
createDir(tsFile);//遞歸創建文件
console.log("本次資源臨時文件:",tsFile);
const resultDir = path.join(__dirname,"./result");
createDir(resultDir);//遞歸創建文件
const resultFile = path.join(resultDir,outputName);
var localPath = [] ; //下載到本地的路徑
//開始下載ts文件
load();
function load(){
if(arr.length > 0){
var u = arr.shift();
var url = host + u;
console.log("progress---:",url);
down(url);
}else{
//下載完成
console.log("下載完成--開始生成配置");
localPath.unshift("ffconcat version 1.0");
try{
fs.writeFileSync(path.join(tsFile,"./input.txt"), localPath.join("\n") , undefined, 'utf-8')
}catch(e){
console.log("寫入配置出錯--",e);
return ;
}
//開始依賴配置合成
console.log("開始合成-----");
child_process.exec(`cd ${tsFile} && ffmpeg -i input.txt -acodec copy -vcodec copy -absf aac_adtstoasc ${resultFile}`,function(error, stdout, stderr){
if(error){
console.error("合成失敗---",error);
}else{
console.log("合成成功--",stdout);
//刪除臨時文件
fsextra.remove(tsFile, err => {
if (err) return console.error("刪除文件是失敗",err)
console.log('刪除文件成功!')
});
}
});
}
}
//下載 ts 文件
function down(url){
var p = url.split("?")[0];
var nm = path.parse(p);
var nme = nm["name"] + nm["ext"];
rpath = path.join(tsFile,nme);
localPath.push(`file ${nme}`); //緩存本地路徑,用來合成
request({
url:url,
headers:{
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
},function (err, response, body) {
if (!err && response.statusCode == 200) {
load();
}else{
console.log("錯誤",err)
}
}).pipe(fs.createWriteStream(rpath));
}
//遞歸的創建文件夾
function mkdirs(dirpath) {
if (!fs.existsSync(path.dirname(dirpath))) {
mkdirs(path.dirname(dirpath));
}
fs.mkdirSync(dirpath);
}
function createDir(myPath){
fs.existsSync(myPath) == false && mkdirs(myPath);
}
}
//ffmpeg -i "concat:1.ts|2.ts" -acodec copy out.mp3
//ffmpeg -i "concat:1.ts|2.ts" -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4
// ffmpeg -i input.txt -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4
/*
ffconcat version 1.0
file 0.ts
file 1.ts
*/
/*
//文件移動
function moveFile(oldPath,newPath){
try {
fs.renameSync(oldPath, newPath);
}
catch (e) {
console.log("報錯后強制移動",e);
fs.renameSync(oldPath, newPath);
}
}
*/
然后再新建 main.js
const fs = require("fs");
const down = require("./down");
var host = 'https://xxxx/';目標網站
var outputName = "output.mp4";
var source = fs.readFileSync("./test.m3u8","utf-8"); //讀取 m3u8
var arr = source.split("\n");
arr = arr.filter((item)=>{
return item.match(/\.ts$/);
});
down({
arr,
host,
name:outputName
})
里面使用到了 fs-extra 模塊,所以先安裝
npm i fs-extra
最后執行:
node main.js
