Refactor and add documentation

This commit is contained in:
Lynn Smeria 2018-08-17 16:48:56 +03:00
parent 73d5c3335f
commit 09fa089cfe
6 changed files with 88 additions and 55 deletions

View file

@ -1,5 +1,5 @@
parserOptions: parserOptions:
ecmaVersion: 2017 ecmaVersion: 2018
sourceType: module sourceType: module
env: env:
node: true node: true

View file

@ -39,8 +39,8 @@ program
} }
fs.ensureDirSync(args.target); fs.ensureDirSync(args.target);
const files = scan.findFilesSync(source); const files = scan.findFilesSync(source);
const filesWithTags = await scan.readTags(files); const taggedFiles = await scan.readTags(files);
const organisedFiles = await organise.byAlbum(target, filesWithTags); const organisedFiles = await organise.byAlbum(target, taggedFiles);
const releaseInfo = generate.releaseInfo(organisedFiles); const releaseInfo = generate.releaseInfo(organisedFiles);
fs.writeFileSync(path.resolve(target, 'releases.yml'), releaseInfo); fs.writeFileSync(path.resolve(target, 'releases.yml'), releaseInfo);
}); });

View file

@ -2,17 +2,15 @@ const path = require('path');
const _ = require('lodash'); const _ = require('lodash');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const debug = require('debug')('publikator:generate'); const debug = require('debug')('publikator:generate');
const tags = require('./tags');
const getTags = (track, tags) =>
tags.reduce((all, tag) => {
all[tag] = track[tag]; // eslint-disable-line
return all;
}, {});
module.exports = { module.exports = {
releaseInfo: files => { /**
debug(`generating release info for ${files.length} file(s)`); * Generates a release YAML with data
const albums = _.groupBy(files, file => path.dirname(file.path)); */
releaseInfo: taggedFiles => {
debug(`generating release info for ${taggedFiles.length} file(s)`);
const albums = _.groupBy(taggedFiles, file => path.dirname(file.path));
return yaml.safeDump( return yaml.safeDump(
Object.keys(albums).map(key => { Object.keys(albums).map(key => {
const tracks = albums[key]; const tracks = albums[key];
@ -22,7 +20,7 @@ module.exports = {
path: track.path, path: track.path,
size: track.size, size: track.size,
position: i, position: i,
tags: getTags(track.tags, [ ...tags.getTags(track, [
'title', 'title',
'artist', 'artist',
'album', 'album',

View file

@ -3,31 +3,27 @@ const path = require('path');
const _ = require('lodash'); const _ = require('lodash');
const sanitize = require('sanitize-filename'); const sanitize = require('sanitize-filename');
const debug = require('debug')('publikator:organise'); const debug = require('debug')('publikator:organise');
const tags = require('./tags');
const ensureTags = (files, tags) => {
return files.filter(file => {
if (tags.some(tag => file.tags[tag] === undefined)) {
debug(
`ignored '${file.path}' because it is missing one or more required tags`
);
return false;
}
return true;
});
};
const getFolderName = file => `${file.tags.artist} - ${file.tags.album}`; const getFolderName = file => `${file.tags.artist} - ${file.tags.album}`;
const getFileName = file => const getFileName = file =>
`${file.tags.track} - ${file.tags.title}${path.extname(file.path)}`; `${file.tags.track} - ${file.tags.title}${path.extname(file.path)}`;
module.exports = { module.exports = {
byAlbum: async (root, filesWithTags) => { /**
const files = ensureTags(filesWithTags, [ * Organises tracks into a new folder structure in `root`, as follows:
'artist', *
'album', * {artist} - {album}/
'track', * {track} - {title}.{ext}
'title', * {track} - {title}.{ext}
]); * ...
*
* Returns `taggedFiles` with the paths changed to the new paths.
*/
byAlbum: async (root, taggedFiles) => {
const files = taggedFiles.filter(file =>
tags.hasTags(file, ['artist', 'album', 'track', 'title'])
);
debug(`grouping tracks by album`); debug(`grouping tracks by album`);
const folders = _.uniq(files.map(file => getFolderName(file))); const folders = _.uniq(files.map(file => getFolderName(file)));
@ -41,13 +37,13 @@ module.exports = {
debug(`copying tracks`); debug(`copying tracks`);
return Promise.all( return Promise.all(
files.map(file => { files.map(async file => {
const newPath = path.resolve( const newPath = path.resolve(
root, root,
getFolderName(file), getFolderName(file),
getFileName(file) getFileName(file)
); );
fs.copyFileSync(file.path, newPath); await fs.copyFile(file.path, newPath);
return _.assign({}, file, { path: newPath }); return _.assign({}, file, { path: newPath });
}) })
); );

View file

@ -1,8 +1,11 @@
const debug = require('debug')('publikator:scan'); const debug = require('debug')('publikator:scan');
const jsmediatags = require('jsmediatags');
const walk = require('walkdir'); const walk = require('walkdir');
const tags = require('./tags');
module.exports = { module.exports = {
/**
* Recursively searches for files.
*/
findFilesSync: (root, extension = '.mp3') => { findFilesSync: (root, extension = '.mp3') => {
debug( debug(
`scanning directory '${root}' for files with extension '${extension}'` `scanning directory '${root}' for files with extension '${extension}'`
@ -17,28 +20,21 @@ module.exports = {
return files; return files;
}, },
/**
* Reads ID3 tags from all files and returns an array in the form of:
* [{ path, size, tags }, ...]
*/
readTags: files => { readTags: files => {
debug(`reading tags for ${files.length} file(s)`); debug(`reading tags from ${files.length} file(s)`);
return Promise.all( return Promise.all(
files.map( files.map(async file => {
file => const info = await tags.readTags(file);
new Promise((resolve, reject) => { return {
jsmediatags.read(file, {
onSuccess: info => {
resolve({
path: file, path: file,
size: info.size, size: info.size,
tags: info.tags, tags: info.tags,
}); };
},
onError: error => {
debug(error.type);
debug(error.info);
reject(error);
},
});
}) })
)
); );
}, },
}; };

43
src/tags.js Normal file
View file

@ -0,0 +1,43 @@
const jsmediatags = require('jsmediatags');
const debug = require('debug')('publikator:tags');
module.exports = {
/**
* Reads tags from a track.
*/
readTags: file =>
new Promise((resolve, reject) => {
jsmediatags.read(file, {
onSuccess: info => {
resolve(info);
},
onError: error => {
debug(error.type);
debug(error.info);
reject(error);
},
});
}),
/**
* Returns true if a file has all required tags.
*/
hasTags: (taggedFile, tags) => {
if (tags.some(tag => taggedFile.tags[tag] === undefined)) {
debug(`track'${taggedFile.path}' is missing one or more required tags`);
return false;
}
return true;
},
/**
* Extracts tags from a file into an object, ignoring missing tags.
*/
getTags: (taggedFile, tags) =>
tags.reduce((all, tag) => {
if (taggedFile.tags[tag] !== undefined) {
all[tag] = taggedFile.tags[tag]; // eslint-disable-line
}
return all;
}, {}),
};