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:
ecmaVersion: 2017
ecmaVersion: 2018
sourceType: module
env:
node: true

View file

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

View file

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

View file

@ -3,31 +3,27 @@ const path = require('path');
const _ = require('lodash');
const sanitize = require('sanitize-filename');
const debug = require('debug')('publikator:organise');
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 tags = require('./tags');
const getFolderName = file => `${file.tags.artist} - ${file.tags.album}`;
const getFileName = file =>
`${file.tags.track} - ${file.tags.title}${path.extname(file.path)}`;
module.exports = {
byAlbum: async (root, filesWithTags) => {
const files = ensureTags(filesWithTags, [
'artist',
'album',
'track',
'title',
]);
/**
* Organises tracks into a new folder structure in `root`, as follows:
*
* {artist} - {album}/
* {track} - {title}.{ext}
* {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`);
const folders = _.uniq(files.map(file => getFolderName(file)));
@ -41,13 +37,13 @@ module.exports = {
debug(`copying tracks`);
return Promise.all(
files.map(file => {
files.map(async file => {
const newPath = path.resolve(
root,
getFolderName(file),
getFileName(file)
);
fs.copyFileSync(file.path, newPath);
await fs.copyFile(file.path, newPath);
return _.assign({}, file, { path: newPath });
})
);

View file

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