// requirements const path = require('path'); const async = require('async'); const fse = require('fs-extra'); const recursive = require('recursive-readdir'); const progress = require('progress'); const cli = require('./cli'); const audio = require('./audio'); // move all files from input to output directory function batchSort(config, callback) { let timestamp = process.hrtime(); async.waterfall([ // get files function (waterfallCallback) { readDirRecursive(config.input, config.format, waterfallCallback); }, // display info, prompt user and create progressbar function (files, waterfallCallback) { console.log(files.length + ' files found after ' + getTimeDiff(timestamp) + ' seconds'); cli.askForConfirmation('start sorting now?', ['yes', 'y'], function (err) { if (err) { return waterfallCallback(err); } waterfallCallback(null, files, cli.createProgressBar(files.length)); }); }, // process each file function (files, bar, waterfallCallback) { let skipped = []; timestamp = process.hrtime(); async.eachLimit(files, config.concurrency, function (file, eachCallback) { moveFile(file, config, function (err, moved) { bar.tick(); if (err) { return eachCallback(err); } if (!moved) { skipped.push(file); } eachCallback(); }) }, function (err, result) { if (err) { return waterfallCallback(err); } waterfallCallback(null, skipped, timestamp); }); } ], callback); } // create target directory and move the file function moveFile(source, config, callback) { async.waterfall([ // get metadata function (waterfallCallback) { audio.extractMetadata(source, waterfallCallback); }, // create path from metadata function (metadata, waterfallCallback) { getPathByMetadata(source, config.output, metadata, waterfallCallback); }, // create target directory function (target, waterfallCallback) { fse.mkdirs(path.dirname(target), function (err) { if (err) { return waterfallCallback(err); } waterfallCallback(null, target); }); }, // move file function (target, waterfallCallback) { fse.move(source, target, function (err) { if (err) { if (err.code === 'EEXIST') { return waterfallCallback(); } else { return waterfallCallback(err); } } waterfallCallback(null, true); }); } ], callback); } // create path for target file function getPathByMetadata(source, output, metadata, callback) { // define directory let filePath = path.normalize(output); if (metadata.albumartist && metadata.albumartist.length > 0) { let tmp; for (let counter = 0, length = metadata.albumartist.length; counter < length; counter++) { if (counter > 0) { tmp += ' - ' + metadata.albumartist[counter]; } else { tmp = metadata.albumartist[counter]; } } filePath = path.join(filePath, replaceIllegalChars(tmp.trim())); } else { filePath = path.join(filePath, replaceIllegalChars(metadata.artist[0].trim())); } if (metadata.album) { filePath = path.join(filePath, replaceIllegalChars(metadata.album.trim())); } // define filename let fileName = ''; if (metadata.disk.no) { fileName += frontFill(metadata.disk.no, '0', 2); } if (metadata.track.no) { if (fileName) { fileName += '-' + frontFill(metadata.track.no, '0', 2) + ' '; } else { fileName += frontFill(metadata.track.no, '0', 2) + ' '; } } if (metadata.artist) { fileName += metadata.artist[0].trim() + ' - '; } if (metadata.title) { fileName += metadata.title.trim(); } // append extension fileName += replaceIllegalChars(path.extname(source)).trim(); // join directory and name callback(null, path.join(filePath, fileName)); } // fill a string beginning from the front function frontFill(string, fill, length) { while (string.toString().length < length) { string = fill + string; } return string; } // replace illegal characters function replaceIllegalChars(string) { return string.replace(/[/\\:*?"<>|]/g, '-'); } // list files in directory function readDirRecursive(where, extension, callback) { console.log('getting list of \'' + extension + '\' files in directory \'' + where + '\' ...'); if (extension.indexOf('.') !== 0) { extension = '.' + extension; } recursive(where, [ignoreFilter], callback); function ignoreFilter(file, stats) { return !stats.isDirectory() && extension.indexOf(path.extname(file)) === -1; } } // get difference to timestamp function getTimeDiff(timestamp) { const diff = process.hrtime(timestamp); return (diff[0] + diff[1] / 1e9).toFixed(2); } // api exports.batchSort = batchSort; exports.moveFile = moveFile; exports.getPathByMetadata = getPathByMetadata; exports.frontFill = frontFill; exports.readDirRecursive = readDirRecursive; exports.getTimeDiff = getTimeDiff;