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

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

  • 前言
  • 一、准备工作以及介绍
    • 1、什么是FFmpeg
    • 2、FFmpeg下载
    • 3、准备B站视频
    • 4、创建项目文件
    • 5、node环境准备
  • 二、项目代码
    • 1.代码部分
    • 2.完整项目地址
  • 总结


前言

最近被一首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即可。
Node.js脚本项目合集(一):Node.js+FFmpeg实现批量从B站导出离线缓存视频到mp4格式,mp4转mp3,实现听歌自由_第1张图片
(2)解压文件至本地,并进入bin目录下,查看是否有ffmpeg.exe、ffmplay.exe和ffprobe.exe文件,若有三个文件,将bin的根目录路径复制下来,并配置到环境变量中去。
Node.js脚本项目合集(一):Node.js+FFmpeg实现批量从B站导出离线缓存视频到mp4格式,mp4转mp3,实现听歌自由_第2张图片
(3)验证是否成功,WIN+R输入cmd窗口下输入指令ffmpeg -version查看是否查询到ffmpeg指令生效。
Node.js脚本项目合集(一):Node.js+FFmpeg实现批量从B站导出离线缓存视频到mp4格式,mp4转mp3,实现听歌自由_第3张图片

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": [
                "/**"
            ],
            "program": "${workspaceFolder}\\BilibiliM4s_mp4.js",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        },
        {
            "type": "node",
            "request": "launch",
            "name": "mp4_mp3",
            "skipFiles": [
                "/**"
            ],
            "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中学习

你可能感兴趣的:(nodejs,ffmpeg,ffmpeg,音视频,缓存)