利用 nodejs 解析 m3u8 格式文件,并下 ts 合并为 mp4 您所在的位置:网站首页 m3u8文件获取 利用 nodejs 解析 m3u8 格式文件,并下 ts 合并为 mp4

利用 nodejs 解析 m3u8 格式文件,并下 ts 合并为 mp4

2023-08-13 22:41| 来源: 网络整理| 查看: 265

以前看视频的时候,直接找到 video标签,查看视频地址,然后下载下来。。

后来发现,好多 video 标签打开元素审查,如下:

blob开始的东西,下载不了啦。。。

其实我们打开 network 还是能看见,加载了一堆的 .ts 文件。其实.ts文件就是被切成一段一段的视频。 理论上,把这些文件都下载下来,再合并,就完成了,,,

利用nodejs,request包 定时爬去 网站视频ts接口,大概有1771个文件。

首先,获取到网站的ts视频分段配置文件,获取到后,放入本地文件,方便下次使用。

然后,定时调用下载函数,进行下载,

爬去过程中会有下载失败的,所有我在爬去完毕后,检查下载失败的,再次进行下载,

exec包执行cmd命令 进行合成一个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); } } */

线程下载

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 headers = opt.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' } 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() { var task = [] var taskCount = 10 // 线程下载 var flag = false for (let i = 0; i < taskCount; i++) { var u = arr.shift() if (!u) { flag = true break } var url = host + u console.log('progress---:', url) task.push(downPromise(url, headers)) } Promise.all(task).then(function() { if (flag) { downOver() } else { load() } }) } // 下载完成 function downOver() { 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 downPromise(url, headers) { return new Promise(function(resolve, reject) { var p = url.split('?')[0] var nm = path.parse(p) var nme = nm['name'] + nm['ext'] var rpath = path.join(tsFile, nme) localPath.push(`file ${nme}`) // 缓存本地路径,用来合成 request({ url, headers }, function(err, response, body) { if (!err && response.statusCode == 200) { resolve(true) } else { console.log('错误', err) reject(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) } }

然后再新建 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 }) // host:https://youku.com-qq.net/20190502/181_7ffa42fa/1000k/hls/ // m3u8:https://youku.com-qq.net/20190502/181_7ffa42fa/1000k/hls/index.m3u8

 

里面使用到了 fs-extra 模块,所以先安装

npm i fs-extra

 

 

最后执行:

node main.js



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有