Refactor and add documentation
This commit is contained in:
parent
73d5c3335f
commit
09fa089cfe
|
@ -1,5 +1,5 @@
|
||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2017
|
ecmaVersion: 2018
|
||||||
sourceType: module
|
sourceType: module
|
||||||
env:
|
env:
|
||||||
node: true
|
node: true
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
38
src/scan.js
38
src/scan.js
|
@ -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, {
|
path: file,
|
||||||
onSuccess: info => {
|
size: info.size,
|
||||||
resolve({
|
tags: info.tags,
|
||||||
path: file,
|
};
|
||||||
size: info.size,
|
})
|
||||||
tags: info.tags,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: error => {
|
|
||||||
debug(error.type);
|
|
||||||
debug(error.info);
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
43
src/tags.js
Normal file
43
src/tags.js
Normal 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;
|
||||||
|
}, {}),
|
||||||
|
};
|
Loading…
Reference in a new issue