2018-08-14 18:26:02 +02:00
|
|
|
const fs = require('fs-extra');
|
|
|
|
const path = require('path');
|
|
|
|
const _ = require('lodash');
|
|
|
|
const sanitize = require('sanitize-filename');
|
|
|
|
const debug = require('debug')('publikator:organise');
|
2018-08-20 15:18:47 +02:00
|
|
|
const mime = require('mime-types');
|
2018-08-17 15:48:56 +02:00
|
|
|
const tags = require('./tags');
|
2018-08-14 18:26:02 +02:00
|
|
|
|
2018-08-24 11:43:27 +02:00
|
|
|
/**
|
|
|
|
* Given a track file, return the album name.
|
|
|
|
*/
|
2018-09-10 11:49:56 +02:00
|
|
|
const getAlbumName = file =>
|
|
|
|
file.common.album.replace(/[\W_]+/g, '-').toLowerCase();
|
2018-08-20 15:18:47 +02:00
|
|
|
|
2018-08-24 11:43:27 +02:00
|
|
|
/**
|
|
|
|
* Given a track file, return the new file name.
|
|
|
|
*/
|
2018-08-14 18:26:02 +02:00
|
|
|
const getFileName = file =>
|
2018-09-10 11:49:56 +02:00
|
|
|
`${file.common.track.no}-${file.common.title
|
|
|
|
.replace(/[\W_]+/g, '-')
|
|
|
|
.toLowerCase()}${path.extname(file.path)}`;
|
2018-08-14 18:26:02 +02:00
|
|
|
|
2018-08-24 11:43:27 +02:00
|
|
|
/**
|
|
|
|
* Strips the extension from a file name;
|
|
|
|
*/
|
|
|
|
const stripExtension = fileName => {
|
|
|
|
const i = fileName.lastIndexOf('.');
|
|
|
|
return fileName.substr(0, i);
|
|
|
|
};
|
|
|
|
|
2018-08-20 15:18:47 +02:00
|
|
|
/**
|
|
|
|
* Extracts the cover art and saves it to a file with the same name.
|
|
|
|
*/
|
|
|
|
const extractCoverArt = async filePath => {
|
|
|
|
const pictures = await tags.extractCoverArt(filePath);
|
|
|
|
if (pictures) {
|
2018-08-20 18:42:59 +02:00
|
|
|
const picture = pictures[0];
|
|
|
|
const pictureExt = mime.extension(picture.format);
|
|
|
|
const picturePath = `${filePath.replace(
|
|
|
|
path.extname(filePath),
|
|
|
|
''
|
|
|
|
)}.${pictureExt}`;
|
|
|
|
await fs.writeFile(picturePath, picture.data);
|
|
|
|
return picturePath;
|
2018-08-20 15:18:47 +02:00
|
|
|
}
|
2018-08-20 18:42:59 +02:00
|
|
|
return null;
|
2018-08-20 15:18:47 +02:00
|
|
|
};
|
|
|
|
|
2018-08-14 18:26:02 +02:00
|
|
|
module.exports = {
|
2018-08-17 15:48:56 +02:00
|
|
|
/**
|
|
|
|
* 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) => {
|
2018-08-24 11:43:27 +02:00
|
|
|
const assetRoot = path.resolve(root, 'assets', 'albums');
|
2018-08-17 15:48:56 +02:00
|
|
|
const files = taggedFiles.filter(file =>
|
2018-08-17 18:53:14 +02:00
|
|
|
tags.hasTags(file, [
|
|
|
|
'common.artists',
|
|
|
|
'common.album',
|
|
|
|
'common.track',
|
|
|
|
'common.title',
|
|
|
|
])
|
2018-08-17 15:48:56 +02:00
|
|
|
);
|
2018-08-14 18:26:02 +02:00
|
|
|
|
|
|
|
debug(`grouping tracks by album`);
|
2018-08-24 11:43:27 +02:00
|
|
|
const folders = _.uniq(files.map(file => getAlbumName(file)));
|
2018-08-14 18:26:02 +02:00
|
|
|
debug(`found ${folders.length} album(s)`);
|
|
|
|
debug(folders);
|
|
|
|
|
|
|
|
debug(`creating album directories`);
|
|
|
|
await Promise.all(
|
2018-08-24 11:43:27 +02:00
|
|
|
folders.map(album =>
|
|
|
|
fs.ensureDir(path.resolve(assetRoot, sanitize(album)))
|
|
|
|
)
|
2018-08-14 18:26:02 +02:00
|
|
|
);
|
|
|
|
|
2018-08-20 15:18:47 +02:00
|
|
|
debug(`copying tracks & extracting covers`);
|
2018-08-14 18:26:02 +02:00
|
|
|
return Promise.all(
|
2018-08-17 15:48:56 +02:00
|
|
|
files.map(async file => {
|
2018-08-24 11:43:27 +02:00
|
|
|
const folderName = getAlbumName(file);
|
2018-08-20 14:35:21 +02:00
|
|
|
const fileName = getFileName(file);
|
2018-08-24 11:43:27 +02:00
|
|
|
const newPath = path.resolve(assetRoot, folderName, fileName);
|
2018-08-17 15:48:56 +02:00
|
|
|
await fs.copyFile(file.path, newPath);
|
2018-08-20 18:42:59 +02:00
|
|
|
const coverPath = await extractCoverArt(newPath);
|
2018-08-20 14:35:21 +02:00
|
|
|
return _.assign(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
path: newPath,
|
2018-08-29 15:48:15 +02:00
|
|
|
audio: `/assets/albums/${folderName}/${fileName}`,
|
|
|
|
albumSlug: folderName,
|
2018-08-24 11:43:27 +02:00
|
|
|
slug: stripExtension(fileName),
|
2018-08-20 14:35:21 +02:00
|
|
|
},
|
2018-08-20 18:42:59 +02:00
|
|
|
coverPath
|
|
|
|
? {
|
|
|
|
coverPath,
|
2018-08-24 11:43:27 +02:00
|
|
|
cover: `/assets/albums/${folderName}/${path.basename(
|
|
|
|
coverPath
|
|
|
|
)}`,
|
2018-08-20 18:42:59 +02:00
|
|
|
}
|
|
|
|
: {},
|
2018-08-20 14:35:21 +02:00
|
|
|
_.omit(file, 'path')
|
|
|
|
);
|
2018-08-14 18:26:02 +02:00
|
|
|
})
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|