利用 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

利用 nodejs 解析 m3u8 格式文件,并下 ts 合并为 mp4的更多相关文章

  1. C语言解析Ini格式文件

    引用别人的博文: http://www.open-open.com/lib/view/open1402278076447.html 可以解析 INI 格式的字符串.解析文件.保存到文件. 下面是头文件 ...

  2. WP8解析XML格式文件

    DOTA2 WebAPI请求返回的格式有两种,一种是XML,一种是JSON,默认是返回JSON格式,如果要返回XML格式的话,需要在加上format=xml. 这里举一个简单的解析XML格式的例子(更 ...

  3. python之爬虫(爬取.ts文件并将其合并为.MP4文件——以及一些异常的注意事项)

    //20200115 最近在看“咱们裸熊——we bears”第一季和第三季都看完了,单单就第二季死活找不到,只有腾讯有资源,但是要vip……而且还是国语版……所以就瞄上了一个视频网站——可以在线观看 ...

  4. dom4解析xml格式文件实例

    以下给4种常见的xml文件的解析方式的分析对比: DOM DOM4J JDOM SAX 解析XML文件的几种方式和区别答: Dom解析 在内存中创建一个DOM树,该结构通常需要加载整个文档然后才能做工 ...

  5. python利用lxml读写xml格式文件

    之前在转换数据集格式的时候需要将json转换到xml文件,用lxml包进行操作非常方便. 1. 写xml文件 a) 用etree和objectify from lxml import etree, o ...

  6. dom4j解析xml格式文件实例

    以下给4种常见的xml文件的解析方式的分析对比: DOM  DOM4J  JDOM  SAX Dom解析    在内存中创建一个DOM树,该结构通常需要加载整个文档然后才能做工作.由于它是基于信息层次 ...

  7. XmlDocument解析Soap格式文件案例:

    private static string Analysis(string strResult) { var doc = new System.Xml.XmlDocument(); //加载soap文 ...

  8. 利用SAX解析读取XML文件

    xml     这是我的第一个BLOG,今天在看<J2EE应用开发详解>一书,书中讲到XML编程,于是就按照书中的步骤自己测试了起来,可是怎么测试都不成功,后来自己查看了一遍源码,发现在读 ...

  9. shell解析ini格式文件

    功能 本脚本实现了ini文件中的查询修改指定value 百度云连接地址 链接:https://pan.baidu.com/s/12_T5yST7Y3L1H4_MkVEcvA 密码:fo5p 解压后先看 ...

随机推荐

  1. 微信JS-SDK分享功能的.Net实现代码

    JS-SDK接口是什么? 为了方便开发者实现微信内的网页(基于微信浏览器访问的网页)功能,比如拍照.选图.语音.位置等手机系统的能力,并方便开发者直接使用微信分享.扫一扫等微信特有的能力,微信推出了J ...

  2. 【Java/Json】Java对Json进行建模,分词,递归向下解析构建Json对象树

    伸手党的福音 代码下载:https://files.cnblogs.com/files/xiandedanteng/JsonLexerBuilder20191202.rar 互联网上成型的对Json进 ...

  3. 【idea】idea远程调试代码

    一.前置条件 1.idea的代码和远程服务器代码保持一致 二.远程服务器配置 服务启动时,需要给jvm添加指定参数,进行启动 -agentlib:jdwp=transport=dt_socket,se ...

  4. api-ms-win-crt-runtime-l1-1-0.dll 丢失

    api-ms-win-crt-runtime就是MFC的运行时环境的库,python在windows上编译也是用微软的visual studio C++编译的,底层也会用到微软提供的C++库和runt ...

  5. [转]Git 代码撤销、回滚到任意版本(当误提代码到本地或master分支时)

    两种情况(场景) 情况一      代码还只在本地,未push到运程仓库,想把代码还原到上一次commit的代码,此时操作为代码撤销 解决方案: 1 git reset [--hard|soft|mi ...

  6. Flutter 中SimpleDialog简单弹窗使用

    import 'package:flutter/material.dart'; import 'dart:async'; enum Option { A, B, C } class SimpleDia ...

  7. .NET Core入门程序

    易百教程 https://www.yiibai.com/dotnet_core/dotnet_core_getting_started.html

  8. netty5客户端监测服务端断连后重连

    服务端挂了或者主动拒绝客户端的连接后,客户端不死心,每15秒重连试试,3次都不行就算了.修改下之前的客户端引导类(NettyClient,参见netty5心跳与业务消息分发实例),新增两个成员变量,在 ...

  9. [转]Winform打包工具SetupFactory 9 的使用

    写了个WinForm的小程序..以前没打过包..只是直接把Bin里的东西复制出来使用..自己使用是足够.但是发给别人毕竟不太好看(不牛逼)..所以就想着打包.. Vs2012自带的有打包的功能..相信 ...

  10. LeetCode_345. Reverse Vowels of a String

    345. Reverse Vowels of a String Easy Write a function that takes a string as input and reverse only ...