audio-converter
Version:
A utility tool for batch converting wave files to ogg/mp3. This tool currently depends on [SoX](http://sox.sourceforge.net/).
193 lines (177 loc) • 7 kB
JavaScript
const pkg = require("./package");
const path = require("path"); // parse/build file paths
const Q = require("q"); // Promise library
const chunk = require("lodash.chunk");
const uniq = require("lodash.uniq");
const Pace = require("pace"); // progress bar
const glob = Q.nfbind(require("glob")); // filepath pattern matching
const mkdirp = Q.nfbind(require("mkdirp")); // mkdir -p
const exec = Q.nfbind(require("child_process").exec); // for running sox commands
const wavRegex = /.wav$/;
const defaultMp3Quality = "192";
const defaultOggQuality = "6";
const defaultChunkSize = 100;
if(require.main === module) { // called directly
const program = require("commander"); // parsing command line arguments
program
.version(pkg.version)
.usage("[options] <input-directory> <output-directory>")
.option("-c, --chunkSize <i>", "The number of files to be converted in parallel. Default: " + defaultChunkSize, parseInt)
.option("-p, --progressBar", "Display progress bar")
.option("-v, --verbose", "Print additional information")
.option("-m, --mp3Only", "Only generate MP3s")
.option("-o, --oggOnly", "Only generate OGGs")
.option("-M, --mp3Quality <n>", "MP3 quality argument provided to Sox. Default: " + defaultMp3Quality)
.option("-O, --oggQuality <n>", "OGG quality argument provided to Sox. Default: " + defaultOggQuality)
.parse(process.argv);
if(program.args.length < 2) {
program.outputHelp();
}
else {
convertDirectory(program.args[0], program.args[1], program)
.catch(function(error) {
console.log("Error: " + error);
});
}
}
else { // required as a module
module.exports = convertDirectory;
}
// Convert all .wav files in `inputDir` into .ogg and .mp3 files and output them to `outputDir`
function convertDirectory(inputDir, outputDir, options) {
var mp3Quality,
oggQuality,
chunkSize,
fileCount,
pace,
started = new Date();
inputDir = resolvePath(inputDir);
outputDir = resolvePath(outputDir);
options = options || {};
mp3Quality = options.mp3Quality || defaultMp3Quality;
oggQuality = options.oggQuality || defaultOggQuality;
chunkSize = options.chunkSize || defaultChunkSize;
mp3Only = options.mp3Only;
oggOnly = options.oggOnly;
progressBar = options.progressBar;
if(typeof chunkSize === "number" && chunkSize <= 0) {
return Q.reject("Chunk size must be a positive integer");
}
if(mp3Only && oggOnly) {
return Q.reject("Cannot use both mp3Only and oggOnly flags at the same time");
}
return Q.resolve()
.then(glob.bind(null, path.join(inputDir, "/**/*.wav")))
.then(function(files) {
return files.filter(function(file) { // filter out all non-wave files
return file.match(wavRegex);
});
})
.then(function(files) {
var outDirectories = uniq(files.map(path.dirname)).map(function(directory) {
return directory.replace(inputDir, outputDir);
});
fileCount = files.length;
if(progressBar) {
pace = Pace({total: fileCount}); // progress bar
pace.op(-1); // can't use op(0) to force an initial render
pace.op();
}
return Q.all(outDirectories.map(function(dir) {
return mkdirp(dir);
})).then(function() {
return files;
});
})
.then(function(files) {
verbose("Converting with chunk size " + chunkSize);
return chunk(files, chunkSize).reduce(function(promise, chunk) {
return promise.then(function() { // after the last chunk has finished
return Q.all(chunk.map(convertFile));
});
}, Q.resolve());
})
.then(function() {
if(!progressBar) {
verbose("Finished converting " + fileCount + " files in " + Math.abs(new Date() - started) + " ms");
}
});
// Convert a file to ogg, mp3, or both depending on mp3Only/oggOnly flags
function convertFile(file) {
var promise;
if(mp3Only) {
promise = buildMP3(file, inputDir, outputDir, mp3Quality);
}
else if(oggOnly) {
promise = buildOGG(file, inputDir, outputDir, oggQuality);
}
else {
promise = Q.all([
buildOGG(file, inputDir, outputDir, oggQuality),
buildMP3(file, inputDir, outputDir, mp3Quality)
]);
}
return promise
.then(function() {
if(progressBar) {
pace.op();
}
});
}
/*
Converts a .wav sound file to a .ogg sound file
Arguments:
inFile - File to be converted
inBase - Base path of the file to be converted
outBase - Base path of the output
quality - Quality argument provided to Sox
*/
function buildOGG(inFile, inBase, outBase, quality) {
var inDir = path.dirname(inFile),
outFile = inFile.replace(new RegExp("^"+inBase), outBase).replace(wavRegex, ".ogg");
verbose("Generating OGG");
verbose(" " + inFile);
verbose(" " + outFile);
return exec([
"sox",
"'"+inFile+"'",
(quality === false || quality === "false") ? "" : "-C"+quality,
"'" + outFile + "'"
].join(" "));
}
/*
Converts a .wav sound file to a .mp3 sound file
Arguments:
inFile - File to be converted
inBase - Base path of the file to be converted
outBase - Base path of the output
quality - Quality argument provided to Sox
*/
function buildMP3(inFile, inBase, outBase, quality) {
var inDir = path.dirname(inFile),
outFile = inFile.replace(new RegExp("^"+inBase), outBase).replace(wavRegex, ".mp3");
verbose("Generating MP3");
verbose(" " + inFile);
verbose(" " + outFile);
return exec([
"sox",
"'" + inFile + "'",
(quality === false || quality === "false") ? "" : "-C"+quality,
"'" + outFile + "'"
].join(" "));
}
// log if in verbose mode
function verbose() {
if(options.verbose) {
return console.log.apply(console, arguments);
}
}
}
// Same as `path.resolve` except this function handles "~" in path strings
function resolvePath(string) {
if(string[0] === '~') {
string = process.env.HOME + string.substr(1)
}
return path.resolve(string);
}