#!/usr/bin/env node // requirements const os = require('os'); const path = require('path'); const fs = require('fs'); const fse = require('fs-extra'); const async = require('async'); const commander = require('commander'); const recursive = require('recursive-readdir'); const metadata = require('musicmetadata'); const progress = require('progress'); const app = require('./package.json'); // general options commander .version(app.version) .usage('[options] ') .option('-c, --concurrency ', 'specify concurrency level', os.cpus().length); // conversion mode commander .command('convert ') .description('convert .flac to .mp3 files') .option('-b, --bitrate ', 'specify conversion bitrate', 320) .action(function (input, output, options) { console.log('--- CONVERT MODE ---'); console.log('input: ' + input); console.log('output: ' + output); console.log('bitrate: ' + commander.options.bitrate); console.log('conc: ' + commander.concurrency); }); // sort mode commander .command('sort ') .description('sort audio files by tags') .option('-f, --format ', 'specify audio format (\'flac\', \'mp3\')', 'flac') .action(sort); // parse command line arguments commander.parse(process.argv); // functions function sort(input, output, options) { const start = process.hrtime(); const concurrency = commander.concurrency; console.log('--- SORT MODE ---'); console.log('concurrency: ' + concurrency); // get files in input directory recursive(input, [ignoreFilter], function (err, files) { if (err) { exit(err); } // display progressbar const bar = createProgressBar(files.length); // handle each file async.eachLimit(files, concurrency, function (file, eachCallback) { handleSource(output, file, function (err) { bar.tick(); if (err) { return eachCallback(err); } eachCallback(); }); }, function (err) { const diff = process.hrtime(start); console.log('done after ' + (diff[0] * 1000 + diff[1] / 1000000) + ' milliseconds'); exit(err); }); }); function ignoreFilter(file, stats) { return !stats.isDirectory() && path.extname(file).indexOf(options.format) == -1; } } function exit(err) { if (err) { console.error(err); process.exit(1); } console.log('DONE'); process.exit(0); } function handleSource(output, file, callback) { async.waterfall([ function (asyncCallback) { extractMetadata(file, asyncCallback) }, function (metadata, asyncCallback) { pathFromMetadata(output, metadata, asyncCallback); } // TODO: create output directory, append file extension and move file ], function (err, results) { if (err) { return callback(err); } callback(null, results); }); } function extractMetadata(file, callback) { const stream = fs.createReadStream(file); metadata(stream, function (err, metadata) { if (err) { return callback(err); } stream.close(); callback(null, metadata) }); } function pathFromMetadata(output, metadata, callback) { // define directory let directory = path.normalize(path.join(output)); if (metadata.albumartist && metadata.albumartist.length > 0) { let tmp; const artistCount = metadata.albumartist.length; for (let counter = 0; counter < artistCount; counter++) { if (counter > 0) { tmp += ' - ' + metadata.albumartist[counter]; } else { tmp = metadata.albumartist[counter]; } } directory = path.join(directory, tmp); } else { directory = path.join(directory, metadata.artist[0]); } if (metadata.album) { directory = path.join(directory, metadata.album); } // define filename let file = ''; if (metadata.disk.no) { file += frontFill(metadata.disk.no, '0', 2); } if (metadata.track.no) { if (file) { file += '-' + frontFill(metadata.track.no, '0', 2) + ' '; } else { file += frontFill(metadata.track.no, '0', 2) + ' '; } } if (metadata.artist && metadata.albumartist.length > 0) { file += metadata.artist[0] + ' - '; } if (metadata.title) { file += metadata.title; } callback(null, directory, file); } function frontFill(string, fill, length) { while (string.length > length) { string = fill + string; } return string; } function createProgressBar(total) { return new progress(':bar | progress: :current/:total (:percent) | elapsed: :elapseds | eta: :etas', { total: total, width: 32 }); }