FLAC / MP3 / WAV 批量转 aac
自动检查 ffmpeg 是否有启用 fdkacc-lib
,如果没有则使用 native_aac
编码器。
关于编译带有 fdk 支援的 ffmpeg 请参见官方文档;因为 GPL 病毒的原因,就不放出编译版了。
#!/usr/bin/env node
/**
* FLAC/MP3 to aac script
* MIT License
* (C) 2019 Jixun.Moe
*/
/* configuration */
const srcExt = ['flac', 'mp3', 'wav'];
const maxThreads = 4;
/*
* override with command line options
* node to_acc.js ./rip ./aac
*/
const srcDir = process.argv[2] || './rip';
const destDir = process.argv[3] || './aac';
/* import modules */
const fs = require('fs');
const util = require('util');
const readdir = util.promisify(fs.readdir);
const copyFile = util.promisify(fs.copyFile);
const exec = util.promisify(require('child_process').execFile);
/* global variables */
let threads = [];
let files = [];
let aacCodec = 'aac';
function getExt(name) {
const m = name.toLowerCase().match(/\.(\w+)$/);
if (m) return m[1];
return '';
}
async function scanDir(dir, outDir) {
const scanResult = (await readdir(dir)).filter(d => d[0] != '.');
for (let i = 0; i < scanResult.length; i++) {
const inPath = dir + '/' + scanResult[i];
let outPath = outDir + '/' + scanResult[i];
const stat = fs.statSync(inPath);
// sync directory structure.
if (stat.isDirectory()) {
// create subdirectory
try {
fs.statSync(outPath);
} catch {
fs.mkdirSync(outPath);
}
await scanDir(inPath, outPath);
continue;
}
const inExt = getExt(inPath);
// Music file to convert
if (srcExt.indexOf(inExt) != -1) {
outPath = outPath.slice(0, -inExt.length) + 'aac';
try {
fs.statSync(outPath);
// file exists, skip...
continue;
} catch {}
files.push([inPath, outPath]);
continue;
}
// other file type
console.info('copy %s...', inPath);
await copyFile(inPath, outPath);
}
}
async function doWork(thread) {
while (true) {
let work = files.shift();
if (!work) break;
const [input, output] = work;
console.info('[T%d] converting %s...', thread, input);
await exec('ffmpeg', ['-i', input, '-c:a', aacCodec, '-vbr', '3', output]);
}
console.info('thread T%d complete.', thread);
}
async function detectFdk() {
const {stdout, stderr} = await exec('ffmpeg', ['-codecs']);
// test for libfdk
const useFdk = /encoders:[\w\s]* libfdk_aac/.test(stdout);
console.info('libfdk: %s', useFdk);
aacCodec = useFdk ? 'libfdk_aac' : 'aac';
}
async function main() {
// let detect and scan run at the same time.
const detect = detectFdk();
await scanDir(srcDir, destDir);
await detect;
console.info('to process: %d files.', files.length);
// create threads
for(let i = 1; i <= maxThreads; i++) {
threads.push(doWork(i));
}
// wait for thread complete
// 感谢 orzFly
await Promise.all(threads);
console.info('done');
}
main();