diff --git a/README.md b/README.md
index 813fbbc..32eb633 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,25 @@
The rusty metal heart of the Basspistol release machine.
+Given a folder of tracks (supports mp3, ogg, flac, and many more), it will read the track metadata and re-organise them into a Jekyll-friendly layout. Here's the example output for a single album `foo` with two tracks `bar` and `baz`:
+
+```
+_albums/
+ foo.md
+_data/
+ albums.yml
+_tracks/
+ foo/
+ 1-bar.md
+ 2-baz.md
+assets/
+ foo/
+ 1-bar.mp3
+ 2-baz.mp3
+```
+
+All Markdown files will encode the metadata in the [Front Matter](https://jekyllrb.com/docs/frontmatter/).
+
## Installation
1. Make sure `nodejs` is installed and up-tp-date:
@@ -33,3 +52,61 @@ publikator organise pathToMySongs outputPath
```
Use the `--delete` flag to start with a clean output directory.
+
+## Jekyll Configuration
+
+To take advantage of the collections, add the following to your `_config.yml`:
+
+```
+collections:
+ albums:
+ output: true
+ permalink: /albums/:name
+ tracks:
+ output: true
+```
+
+### Albums
+
+To list all albums, create a file named `albums.md` in your Jekyll root with the following contents:
+
+```
+---
+layout: default
+---
+
+
+```
+
+Each individual album will be available at the url `/`. To create a detail page for an album, create a new layout `_layouts/album.html`:
+
+```
+{{ page.album }}
+
+
+```
+
+### Tracks
+
+Each individual track will be available at the url `//`. To create a detail page for a track, create a new layout `_layouts/track.html`:
+
+```
+{{ page.common.title }}
+
+```
diff --git a/package.json b/package.json
index 1ee58ca..8d787e8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "publikator",
- "version": "0.6.0",
+ "version": "0.7.0",
"main": "index.js",
"repository": "https://github.com/aengl/publikator.git",
"author": "Lynn Smeria ",
diff --git a/src/cli.js b/src/cli.js
index 01039e1..2523746 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -41,7 +41,7 @@ program
const files = scan.findFilesSync(source);
const taggedFiles = await scan.readTags(files);
const organisedFiles = await organise.byAlbum(target, taggedFiles);
- generate.generateReleaseInfo(organisedFiles);
+ await generate.generateReleaseInfo(target, organisedFiles);
});
debug(process.argv);
diff --git a/src/generate.js b/src/generate.js
index 9ed5591..57a7165 100644
--- a/src/generate.js
+++ b/src/generate.js
@@ -20,29 +20,71 @@ const collect = (tracks, callback) => {
/**
* Creates release information for a single album.
*/
-const getAlbumInfo = tracks => ({
+const getAlbumInfo = (root, tracks) => ({
+ layout: 'album',
+ slug: path.basename(root),
+ name: tracks[0].common.album || '',
artists: collect(tracks, t => t.common.artists || t.common.artist),
- album: collect(tracks, t => t.common.album),
bitrate: collect(tracks, t => t.format.bitrate),
trackCount: tracks.length,
+ cover: tracks[0].coverUrl || null,
tracks,
});
module.exports = {
/**
- * Generates a release YAML with data
+ * Generates Jekyll-compatible release data
*/
- generateReleaseInfo: taggedFiles => {
+ generateReleaseInfo: async (root, taggedFiles) => {
+ // Create collections
+ const trackCollectionRoot = path.resolve(root, '_tracks');
+ await fs.ensureDir(trackCollectionRoot);
+
const albums = _.groupBy(taggedFiles, file => path.dirname(file.path));
- _.forEach(albums, (albumTracks, albumRoot) => {
- const baseName = path.basename(albumRoot);
- debug(
- `generating release info for album '${baseName}' with ${
- albumTracks.length
- } track(s)`
- );
- const releaseInfo = yaml.safeDump(getAlbumInfo(albumTracks));
- fs.writeFileSync(path.resolve(albumRoot, `${baseName}.yml`), releaseInfo);
- });
+ const albumsInfo = await Promise.all(
+ _.map(albums, async (tracks, albumRoot) => {
+ const baseName = path.basename(albumRoot);
+ const albumCollectionRoot = path.resolve(root, '_albums');
+ await fs.ensureDir(albumCollectionRoot);
+ debug(
+ `generating release info for album '${baseName}' with ${
+ tracks.length
+ } track(s)`
+ );
+ const albumInfo = getAlbumInfo(albumRoot, tracks);
+ const releaseInfo = `---\n${yaml.safeDump(albumInfo)}---\n`;
+ await fs.writeFile(
+ path.resolve(albumCollectionRoot, `${baseName}.md`),
+ releaseInfo
+ );
+
+ // Write track collection
+ await Promise.all(
+ tracks.map(async track => {
+ const trackInfoPath = path.resolve(
+ trackCollectionRoot,
+ baseName,
+ `${track.slug}.md`
+ );
+ await fs.ensureFile(trackInfoPath);
+ await fs.writeFile(
+ trackInfoPath,
+ `---\n${yaml.safeDump({
+ layout: 'track',
+ ...track,
+ })}---\n`
+ );
+ })
+ );
+
+ return albumInfo;
+ })
+ );
+
+ // Create album data
+ debug(`generating data for ${albumsInfo.length} album(s)`);
+ const albumsInfoPath = path.resolve(root, '_data', 'albums.yml');
+ await fs.ensureFile(albumsInfoPath);
+ await fs.writeFile(albumsInfoPath, yaml.safeDump(albumsInfo));
},
};
diff --git a/src/organise.js b/src/organise.js
index 6ca7156..2274a9e 100644
--- a/src/organise.js
+++ b/src/organise.js
@@ -6,13 +6,27 @@ const debug = require('debug')('publikator:organise');
const mime = require('mime-types');
const tags = require('./tags');
-const getFolderName = file => file.common.album.replace(/ /g, '_');
+/**
+ * Given a track file, return the album name.
+ */
+const getAlbumName = file => file.common.album.replace(/ /g, '_');
+/**
+ * Given a track file, return the new file name.
+ */
const getFileName = file =>
`${file.common.track.no}-${file.common.title}${path.extname(
file.path
)}`.replace(/ /g, '_');
+/**
+ * Strips the extension from a file name;
+ */
+const stripExtension = fileName => {
+ const i = fileName.lastIndexOf('.');
+ return fileName.substr(0, i);
+};
+
/**
* Extracts the cover art and saves it to a file with the same name.
*/
@@ -43,6 +57,7 @@ module.exports = {
* Returns `taggedFiles` with the paths changed to the new paths.
*/
byAlbum: async (root, taggedFiles) => {
+ const assetRoot = path.resolve(root, 'assets', 'albums');
const files = taggedFiles.filter(file =>
tags.hasTags(file, [
'common.artists',
@@ -53,36 +68,38 @@ module.exports = {
);
debug(`grouping tracks by album`);
- const folders = _.uniq(files.map(file => getFolderName(file)));
+ const folders = _.uniq(files.map(file => getAlbumName(file)));
debug(`found ${folders.length} album(s)`);
debug(folders);
debug(`creating album directories`);
await Promise.all(
- folders.map(album => fs.ensureDir(path.resolve(root, sanitize(album))))
+ folders.map(album =>
+ fs.ensureDir(path.resolve(assetRoot, sanitize(album)))
+ )
);
debug(`copying tracks & extracting covers`);
return Promise.all(
files.map(async file => {
- const folderName = getFolderName(file);
+ const folderName = getAlbumName(file);
const fileName = getFileName(file);
- const newPath = path.resolve(root, folderName, fileName);
+ const newPath = path.resolve(assetRoot, folderName, fileName);
await fs.copyFile(file.path, newPath);
const coverPath = await extractCoverArt(newPath);
return _.assign(
{},
{
path: newPath,
- relativePath: `${folderName}/${fileName}`,
- folderName,
- fileName,
+ url: `/assets/albums/${folderName}/${fileName}`,
+ slug: stripExtension(fileName),
},
coverPath
? {
coverPath,
- relativeCoverPath: `${folderName}/${path.basename(coverPath)}`,
- coverFileName: path.basename(coverPath),
+ cover: `/assets/albums/${folderName}/${path.basename(
+ coverPath
+ )}`,
}
: {},
_.omit(file, 'path')