zoukankan      html  css  js  c++  java
  • 利用 nodejs 解析 m3u8 格式文件,并下 ts 合并为 mp4

    利用 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("
    ");
    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("
    ") , 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("
    ");
    arr = arr.filter((item)=>{
    	return item.match(/.ts$/);
    });
    
    down({
    	arr,
    	host,
    	name:outputName
    })
    

      

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

    npm i fs-extra
    

      

    最后执行:

    node main.js

  • 相关阅读:
    [APUE]第十章 信号
    [APUE]第十一章 线程
    android 音乐播放器中播放模式的设计
    php socket 发送HTTP请求 POST json
    php 扫描url死链接 \033[31m ANSI Linux终端输出带颜色
    Redis学习——Redis持久化之RDB备份方式保存数据
    exit与return的区别
    MQ入门总结(一)消息队列概念和使用场景
    微软放缓.NET动态语言开发计划,徐汇区网站设计 狼人:
    构建高性能ASP.NET站点 网站优化需要考虑的方面,徐汇区网站设计 狼人:
  • 原文地址:https://www.cnblogs.com/muamaker/p/11589410.html
Copyright © 2011-2022 走看看