Node.js脚本项目合集(一):Node.js+FFmpeg实现批量从B站导出离线缓存视频到mp4格式,mp4转mp3,实现听歌自由

Node.js脚本项目合集(一):Node.js+FFmpeg实现批量从B站导出离线缓存视频到mp4格式,mp4转mp3,实现听歌自由


前言

最近被一首JISOO的FLOWER歌洗脑,但碍于版权原因,只能在B站上看mv视频,盯着尬舞听歌着实有些尴尬,突发奇想,如果能将视频中的音频和视频分开不就能只听音乐,不用看尴尬的舞蹈吗?刚好手机上有不少B站本地的学习视频想导入到电脑上看,可是B站下载的格式.m4s文件,普通播放器根本点不开,有没有什么工具能将B站中的.m4s文件转换成.mp4文件,方便观看呢。下面介绍一个十分强大的开源工具FFmpeg,一款视频解码器,配合nodejs实现听歌自由(仅个人学习开发使用,切勿商用)

一、准备工作以及介绍

1、什么是FFmpeg

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。详情可参考FFmpeg官网

2、FFmpeg下载

(1)在github下载地址进入github下载页面,选择要下载的zip包(根据自己的计算机系统选择)并进行解压,我这里选择win64即可。
FFmpeg下载
(2)解压文件至本地,并进入bin目录下,查看是否有ffmpeg.exe、ffmplay.exe和ffprobe.exe文件,若有三个文件,将bin的根目录路径复制下来,并配置到环境变量中去。
FFmpeg配置环境变量截图
(3)验证是否成功,WIN+R输入cmd窗口下输入指令ffmpeg -version查看是否查询到ffmpeg指令生效。
查看ffmpeg是否生效截图

3、准备B站视频

(1)手机下载B站视频
(2)打开手机文件管理,进入Android->data->tv.danmaku.bili->download目录下,将download里面的文件全部拷贝到电脑要运行的脚本文件目录中(这里我们copy到bilibiliFile文件中。

4、创建项目文件

(1)创建目录bilibiliFile_outMP4文件,用来保存转换后的数据
(2)创建mp4File文件,用来保存原始文件(该文件下的后缀名因保持一致)
(3)创建mp3File文件,用来保存转码后的文件

5、node环境准备

(1)node版本14.0.0以上
(2)需要用到fs模块、util模块、child_process模块和path模块

二、项目代码

1.代码部分

(1) VScode通过launch.json运行:本地使用Visual Studio Code这个工具生成launch.json来进行本地debug,可以直接添加配置,一共有两个脚本,分别是BilibiliM4s_mp4.js(执行B站m4s视频转码至mp4文件格式)和mp4_mp3.js(mp4转码成mp3格式文件)。
代码如下(示例):

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "BilibiliM4s_mp4",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\BilibiliM4s_mp4.js",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        },
        {
            "type": "node",
            "request": "launch",
            "name": "mp4_mp3",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\mp4_mp3.js",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
    ]
}

大致讲述一下就是name指的是运行的名字,runtimeExecutable指的是运行指令,下载了node后就可以使用npm来启动项目,runtimeArgs指的是运行的参数,里面的debug在package.json里面的script里面会有debug的key值。

(2)BilibiliM4s_mp4.js

代码如下(示例):

/**
 * 该脚本主要做了将bilibiliFile(B站)中的缓存视频M4S文件转化为普通的mp4文件(node+FFmpeg)
 * 
 * 1、FFmpeg是开源的解决视频、音频相互转码的工具
 *  (1)github地址:https://github.com/FFmpeg/FFmpeg
 *  (2)安装包:./FFmpeg安装包(该安装包是win系统)
 *  (3)运行此脚本,请先安装FFmpeg的插件(直接把(2)解压至电脑,再把这个目录配置到环境变量中去),并配置到电脑的环境变量中去
 *  (4)验证是否安装成功FFmpeg(cmd窗口输入FFmpeg version,出现有数据则安装成功)
 * 2、找到手机缓存的B站的视频,路径./Android/data/tv.danmaku.bili/download
 * 3、将B站缓存视频download文件下的所有文件放到bilibilifile文件夹下
 * 4、调用bilibiliVideoCache2Mp4Wrap方法,输入参数后方可实现
 */

// bilibili中m4s缓存文件转换成mp4文件
// bilibili手机中保存的地址:./Android/data/tv.danmaku.bili/download

const fs = require('fs');
const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

/**
 * 创建要转换mp4的文件夹
 * @param {*} targetPath 
 * @returns 
 */
const mkdirSync = async (targetPath) => {
  try {
    if (!fs.existsSync(targetPath)) {
      fs.mkdirSync(targetPath)
    } 
  } catch (error) {
    console.log(error);
  }
}

/**
 * 利用FFmpeg通过创建子进程去操作,将audio.m4s和video.m4s文件转化为mp4文件
 * @param {*} output 输出的文件夹路径
 * @param {*} curDirPath 当前递归目录位置(一层一层找entry.json的数据) 
 * @param {*} isTarget 是否找到了需要转换成mp4的目标文件夹
 * @param {*} outputFileName 转换成mp4的视频名(通过entry.json拿到原来视频的名字)
 * @param {*} dir 整个视频合集文件夹的名字
 */
const bilibiliVideoCache2Mp4 = async ({output, curDirPath, isTarget, outputFileName, dir}) => {
  if (isTarget) {
    /* 
    使用replace去除空格不然ffmpeg会报错(eg:Unable to find a suitable output format for 'XXX')
    这里需要注意targetPath和outputFileName不应该有特殊的字符比如【 】,不然ffmpeg也会报错(eg:Unable to find a suitable output format for 'XXX')
    */
    let dirName = dir.replace(new RegExp(/[\\\\/:*?\"<>|\s]/,'g'), '');
    let targetPath = `${output}\\${dirName}`
    await mkdirSync(targetPath)
    outputFileName = outputFileName?.replace(new RegExp(/[\\\\/:*?\"<>|\s]/,'g'), '') // 有可能不存在,因为else调用bilibiliVideoCache2Mp4方法的时候没有传outputFileName,所以要outputFileName?判断是否存在
    try {
      console.log(`---开始将名为《${outputFileName}》的音频、视频文件合并输出成mp4文件---`)
      await exec(`ffmpeg -i ${curDirPath}\\video.m4s -i ${curDirPath}\\audio.m4s -codec copy ${targetPath}/${outputFileName}.mp4`)
      console.log(`------已完成对《${outputFileName}》文件的合成------`)
    } catch (error) {
      console.error(`***《${outputFileName}》文件转换失败***`)
      console.log(error.stderr);
    }
    return
  }
  let files = fs.readdirSync(curDirPath);
  files.forEach(function (file) {
    let stat = fs.statSync(curDirPath + '/' + file)
    // stat.isDirectory()判断路径是否存在
    if (stat.isDirectory()) {
      // 文件夹名字如果是80的话,说明已经到了目标文件夹audio.m4s和video.m4s就在里面
      // 判断是否能读到文件entry.json的文件
      let videoInfojson = {};
      try {
        videoInfojson = JSON.parse(fs.readFileSync(path.join(curDirPath, 'entry.json'), 'utf-8'));
      } catch (error) {
        console.log(`${curDirPath}目录下未找到entry.json文件,将继续递归继续找子文件`);
      }
      if (Object.keys(videoInfojson).length > 0) {
        // json.page_data.part是这组视频的名字,json.title是单个视频的名字
        bilibiliVideoCache2Mp4({ 
          output, 
          curDirPath: path.join(curDirPath, file), 
          isTarget: true, 
          outputFileName: videoInfojson.page_data?.part ? videoInfojson.page_data.part : videoInfojson.time_create_stamp.toString(), // 没有文件名字就去那下载视频的时间戳当名字
          dir: videoInfojson.title 
        })
      }
      else bilibiliVideoCache2Mp4({ 
        output, 
        curDirPath: path.join(curDirPath, file) 
      })
    }
  });
}

/**
 * 执行转换的管道
 * @param {*} entry 当前递归目录位置(这里是bilibiliFile文件夹)
 * @param {*} output 创建新的文件夹
 */
async function bilibiliVideoCache2Mp4Wrap(entry, output) {
  await mkdirSync(output);
  bilibiliVideoCache2Mp4({
    curDirPath: entry,
    output,
  })
}

bilibiliVideoCache2Mp4Wrap(path.join(__dirname, 'bilibiliFile'), path.join(__dirname, 'bilibiliFile_outMP4'))



(3)mp4_mp3.js: 这里面输入的初始参数文件格式可以定义为别的模式(mov,mp4,m4a,3gp,3g2,mj2等都可以)。这里简单介绍一下FFmpeg的指令:
-i 传入的mp4路径
-vn保留原视频,不对原始平做处理
-ar采样率(22050, 441000, 48000)
-ac声道数
-b:a比特率(也可以用ab来代替。mp3有96k, 128k, 192k, 256k, 320k)
-f转成的格式(默认也会是mp3这种自动
代码如下(示例):

/**
 * 该脚本主要将视频软件切换成无损画质文件(主要看传参),本代码实例依靠FFmpeg+node批量将mp4格式转换成mp3格式
 * 1、先将视频文件导入到mp4File文件中
 * 2、新建mp3File文件
 * 3、将原始文件的路径,原始文件的风格(即文件后缀名,支持mov,mp4,m4a,3gp,3g2,mj2等视频格式),目标保存路径,目标格式四个参数写入到mp4Tomp3方法中去即可
 * 4、运行脚本等待输出结果
 */

const fs = require('fs');
const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);


/**
 * 
 * @param {*} param 
 * @param {string} param.originDir 原始文件路径
 * @param {string} param.originFormat 原始文件风格(可选mp4等等)
 * @param {string} param.targetDir 目标文件路径
 * @param {string} param.targetFormat 目标文件风格(可选mp3等等)
 */
const mp4Tomp3 = async ({originDir, originFormat, targetDir, targetFormat}) => {
  // 1、新建歌曲异常和歌曲列表清单
  const SongListDir = `${targetDir}\\SongListDir.txt`;
  const SongListDirError = `${targetDir}\\SongListDirError.txt`
  let files = fs.readdirSync(originDir);
  let errorNum = 0;
  let errorItemName = ''
  for (const file of files) {
    // 截取后缀名
    const fileLastName = file.substring(file.lastIndexOf('.') + 1);
    const filefistName = file.substring(0, file.lastIndexOf('.'))
    if (fileLastName === originFormat) {
      // 简单解释一下:
      // -i 传入的mp4路径    -vn保留原视频,不对原始平做处理    -ar采样率(22050, 441000, 48000)    -ac声道数    -b:a比特率(也可以用ab来代替。mp3有96k, 128k, 192k, 256k, 320k)    -f转成的格式(默认也会是mp3这种自动
      // ffmpeg.exe -i xxx.mp4 -vn -ar 44100 -ac 2 -b:a 192k -f mp3 xxx.mp3
      try {
        console.log(`---开始将名为《${filefistName}》的${fileLastName}文件类型转换为${targetFormat}类型---`);
        await exec(`ffmpeg -i ${originDir}\\${filefistName}.${originFormat} -vn -ar 44100 -ac 2 -b:a 192k -f mp3 ${targetDir}\\${filefistName}.${targetFormat}`)
        console.log(`------已完成对《${filefistName}》的${fileLastName}文件类型进行转换------`);
        fs.writeFileSync(SongListDir, `\n${file}`, { flag: 'a' });
      } catch (error) {
        errorNum++;
        errorItemName = `${errorItemName}${file}`
        console.error(`***《${filefistName}》转换失败***`)
        console.log(error)
        fs.writeFileSync(SongListDir, `\n${file}`, { flag: 'a' });
        fs.writeFileSync(SongListDirError, `\n报错提示如下:${error}`, { flag: 'a' });
      }
    }
  }
  console.log(`!!!共有${errorNum}个异常,异常的名字为${errorItemName}!!!`)
  fs.writeFileSync(SongListDir, `\n\n\n!!!共有${errorNum}个异常,异常的名字为${errorItemName}!!!`, { flag: 'a' });
}

mp4Tomp3({
  originDir: path.join(__dirname, 'mp4File'),
  originFormat: 'mp4',
  targetDir: path.join(__dirname, 'mp3File'),
  targetFormat: 'mp3'
})

2.完整项目地址

https://github.com/lp970703/node_job/tree/master/ffmpeg_node

总结

以上就是通过FFmpeg+nodejs实现批量视频转音频的功能,FFmpeg的功能远不止如此,更多的学习可以去FFmpeg官网学习,本人还开放了其他关于nodejs实用脚本,感兴趣可以去本人github地址:node_job中学习

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 你可以使用以下代码来完成此操作: import os# 获取当前工作目录 path = os.getcwd()# 将ts文件换为mp4 for filename in os.listdir(path): if filename.endswith('.ts'): os.system('ffmpeg -i ' + filename + ' -c:v libx264 -crf 23 -preset veryfast -c:a aac -strict -2 ' + filename[:-2] + 'mp4') ### 回答2: 当然可以!以下是使用Python编写的将.ts文件批量换为.mp4文件的代码: ```python import os def batch_convert(): # 获取当前目录下所有的.ts文件 ts_files = [file for file in os.listdir('.') if file.endswith('.ts')] # 遍历每个.ts文件 for ts_file in ts_files: # 构建换后的文件名 mp4_file = os.path.splitext(ts_file)[0] + '.mp4' # 使用ffmpeg进行换 cmd = f'ffmpeg -i {ts_file} {mp4_file}' os.system(cmd) # 输出换成功的提示信息 print(f'换 {ts_file} 成功!') if __name__ == '__main__': batch_convert() ``` 这段代码首先获取当前目录下所有以`.ts`为后缀的文件,然后遍历每个.ts文件,构建换后的文件名(将.ts替换为.mp4),使用`ffmpeg`命令进行换,并输出换成功的提示信息。 请注意,使用此代码之前请确保已经安装并配置好了`ffmpeg`命令,同时,该代码需要将.ts和.mp4文件放在同一个目录下运行。 希望这能帮到你! ### 回答3: 当然可以帮您编写用Python批量将.ts后缀的视频文件换成.mp4后缀的视频文件的代码。您可以使用Python中的subprocess库来调用FFmpeg命令行工具进行换操作。以下是一个示例代码: ```python import os import subprocess # 定义输入视频文件夹路径和输出视频文件夹路径 input_folder = '/path/to/input/folder/' output_folder = '/path/to/output/folder/' # 遍历输入文件夹中的所有文件 for filename in os.listdir(input_folder): if filename.endswith('.ts'): input_file = os.path.join(input_folder, filename) output_file = os.path.join(output_folder, filename[:-3] + 'mp4') # 使用subprocess库调用FFmpeg命令行工具进行换操作 subprocess.run(['ffmpeg', '-i', input_file, output_file]) ``` 请将上述代码中的`/path/to/input/folder/`和`/path/to/output/folder/`分别替换为您实际的输入视频文件夹路径和输出视频文件夹路径。代码中的`subprocess.run(['ffmpeg', '-i', input_file, output_file])`将调用FFmpeg命令行工具执行换操作,使用`-i`参数指定输入文件,最终将换后的视频保存到输出文件路径。 请确保您已经安装了FFmpeg,并将其添加至系统环境变量中,以便能够成功执行FFmpeg命令行工具。 希望上述代码能够满足您的需求!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值