diff --git a/badger-am.js b/badger-am.js index 1a289c0..0bdc984 100755 --- a/badger-am.js +++ b/badger-am.js @@ -28,18 +28,24 @@ function parseCLI() { .version(app.version) .usage('[options] ') .option('-c, --concurrency ', 'specify concurrency level', os.cpus().length); - // conversion mode + // conversion commander .command('convert ') .description('convert .flac to .mp3 files') .option('-b, --bitrate ', 'specify conversion bitrate', 320) .action(convert); - // sort mode + // sort commander .command('sort ') .description('sort audio files by tags') .option('-f, --format ', 'specify audio format (\'flac\', \'mp3\')', 'flac') .action(sort); + // artwork + commander + .command('artwork ') + .description('extract cover artwork') + .option('-f, --format ', 'specify audio format (\'flac\', \'mp3\')', 'flac') + .action(artwork); // parse command line arguments commander.parse(process.argv); } @@ -79,6 +85,26 @@ function convert(input, output, options) { } } +// move file to location defined by its metadata +function processFileConvert(output, sourceFile, options, callback) { + async.waterfall([ + function (asyncCallback) { + audio.extractMetadata(sourceFile, asyncCallback) + }, + function (metadata, asyncCallback) { + util.pathFromMetadata(sourceFile, output, metadata, asyncCallback); + }, + function (targetFile, asyncCallback) { + audio.convert(sourceFile, targetFile, options.bitrate, asyncCallback); + } + ], function (err, results) { + if (err) { + return callback(err); + } + callback(null, results); + }); +} + // sort files function sort(input, output, options) { async.waterfall([ @@ -114,26 +140,6 @@ function sort(input, output, options) { } } -// move file to location defined by its metadata -function processFileConvert(output, sourceFile, options, callback) { - async.waterfall([ - function (asyncCallback) { - audio.extractMetadata(sourceFile, asyncCallback) - }, - function (sourceFile, metadata, asyncCallback) { - util.pathFromMetadata(sourceFile, output, metadata, asyncCallback); - }, - function (targetFile, asyncCallback) { - audio.convert(sourceFile, targetFile, options.bitrate, asyncCallback); - } - ], function (err, results) { - if (err) { - return callback(err); - } - callback(null, results); - }); -} - // move file to location defined by its metadata function processFileSort(output, sourceFile, callback) { async.waterfall([ @@ -152,4 +158,39 @@ function processFileSort(output, sourceFile, callback) { } callback(null, results); }); +} + +// extract artwork +function artwork(input, options) { + async.waterfall([ + function (asyncCallback) { + recursive(input, [ignoreFilter], asyncCallback); + }, + function (files, asyncCallback) { + console.log(files.length + ' \'' + options.format + '\' files found'); + // display progressbar + const bar = util.createProgressBar(files.length); + // handle each file + async.eachLimit(files, commander.concurrency, function (file, eachCallback) { + audio.extractArtwork(file, function (err) { + bar.tick(); + if (err) { + return eachCallback(err); + } + eachCallback(); + }); + }, function (err) { + if (err) { + return asyncCallback(err); + } + asyncCallback(); + }); + } + ], function (err, result) { + util.exit(err, start); + }); + + function ignoreFilter(file, stats) { + return !stats.isDirectory() && path.extname(file).indexOf(options.format) == -1; + } } \ No newline at end of file diff --git a/lib/audio.js b/lib/audio.js index f86e04f..a59f229 100644 --- a/lib/audio.js +++ b/lib/audio.js @@ -5,9 +5,10 @@ const async = require('async'); const metadata = require('musicmetadata'); const ffmpeg = require('fluent-ffmpeg'); const fse = require('fs-extra'); +const util = require('./util'); // convert file to mp3 at specified bitrate -exports.convert = function convert(input, output, bitrate, callback) { +function convert(input, output, bitrate, callback) { output = path.join(path.dirname(output), path.basename(output, path.extname(output)) + '.mp3'); async.series([ function (asyncCallback) { @@ -28,16 +29,44 @@ exports.convert = function convert(input, output, bitrate, callback) { } callback(); }); -}; +} + +// extract cover artwork +function extractArtwork(sourceFile, callback) { + async.waterfall([ + function (asyncCallback) { + extractMetadata(sourceFile, asyncCallback); + }, + function (metadata, asyncCallback) { + metadata.picture.forEach(function (picture) { + const pic = path.join(path.dirname(sourceFile), path.basename(sourceFile, path.extname(sourceFile)) + '.' + picture.format); + const stream = fs.createWriteStream(pic); + stream.write(picture.data); + stream.end(); + asyncCallback(); + }); + } + ], function (err, result) { + if (err) { + return callback(err); + } + callback(); + }); +} // extract metadata for further processing -exports.extractMetadata = function extractMetadata(sourceFile, callback) { +function extractMetadata(sourceFile, callback) { const stream = fs.createReadStream(sourceFile); metadata(stream, function (err, metadata) { if (err) { return callback(err); } stream.close(); - callback(null, sourceFile, metadata) + callback(null, metadata); }); -}; \ No newline at end of file +} + +// api +exports.convert = convert; +exports.extractArtwork = extractArtwork; +exports.extractMetadata = extractMetadata; \ No newline at end of file diff --git a/package.json b/package.json index a26805e..eccf262 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "badger-am", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "description": "audio manager", "author": "Daniel Sommer ",