Create Jekyll collections

This commit is contained in:
Lynn Smeria 2018-08-24 11:43:27 +02:00
parent 7c8138c386
commit 328c56fa19
5 changed files with 162 additions and 26 deletions

View file

@ -2,6 +2,25 @@
The rusty metal heart of the Basspistol release machine. 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 ## Installation
1. Make sure `nodejs` is installed and up-tp-date: 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. 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
---
<ul>
{% for album in site.albums %}
<li>
<a href="/{{ album.slug }}">
{{ album.name }}
</a>
</li>
{% endfor %}
</ul>
```
Each individual album will be available at the url `/<album_slug>`. To create a detail page for an album, create a new layout `_layouts/album.html`:
```
<h1>{{ page.album }}</h1>
<img src="{{ page.cover }}" />
<ul>
{% for track in page.tracks %}
<li>
<a href="/{{ page.slug }}/{{ track.slug }}">
{{ track.common.title }}
</a>
</li>
{% endfor %}
</ul>
```
### Tracks
Each individual track will be available at the url `/<album_slug>/<track_slug>`. To create a detail page for a track, create a new layout `_layouts/track.html`:
```
<h1>{{ page.common.title }}</h1>
<img src="{{ page.cover }}" />
```

View file

@ -1,6 +1,6 @@
{ {
"name": "publikator", "name": "publikator",
"version": "0.6.0", "version": "0.7.0",
"main": "index.js", "main": "index.js",
"repository": "https://github.com/aengl/publikator.git", "repository": "https://github.com/aengl/publikator.git",
"author": "Lynn Smeria <ae@cephea.de>", "author": "Lynn Smeria <ae@cephea.de>",

View file

@ -41,7 +41,7 @@ program
const files = scan.findFilesSync(source); const files = scan.findFilesSync(source);
const taggedFiles = await scan.readTags(files); const taggedFiles = await scan.readTags(files);
const organisedFiles = await organise.byAlbum(target, taggedFiles); const organisedFiles = await organise.byAlbum(target, taggedFiles);
generate.generateReleaseInfo(organisedFiles); await generate.generateReleaseInfo(target, organisedFiles);
}); });
debug(process.argv); debug(process.argv);

View file

@ -20,29 +20,71 @@ const collect = (tracks, callback) => {
/** /**
* Creates release information for a single album. * 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), artists: collect(tracks, t => t.common.artists || t.common.artist),
album: collect(tracks, t => t.common.album),
bitrate: collect(tracks, t => t.format.bitrate), bitrate: collect(tracks, t => t.format.bitrate),
trackCount: tracks.length, trackCount: tracks.length,
cover: tracks[0].coverUrl || null,
tracks, tracks,
}); });
module.exports = { 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)); const albums = _.groupBy(taggedFiles, file => path.dirname(file.path));
_.forEach(albums, (albumTracks, albumRoot) => { const albumsInfo = await Promise.all(
_.map(albums, async (tracks, albumRoot) => {
const baseName = path.basename(albumRoot); const baseName = path.basename(albumRoot);
const albumCollectionRoot = path.resolve(root, '_albums');
await fs.ensureDir(albumCollectionRoot);
debug( debug(
`generating release info for album '${baseName}' with ${ `generating release info for album '${baseName}' with ${
albumTracks.length tracks.length
} track(s)` } track(s)`
); );
const releaseInfo = yaml.safeDump(getAlbumInfo(albumTracks)); const albumInfo = getAlbumInfo(albumRoot, tracks);
fs.writeFileSync(path.resolve(albumRoot, `${baseName}.yml`), releaseInfo); 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));
}, },
}; };

View file

@ -6,13 +6,27 @@ const debug = require('debug')('publikator:organise');
const mime = require('mime-types'); const mime = require('mime-types');
const tags = require('./tags'); 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 => const getFileName = file =>
`${file.common.track.no}-${file.common.title}${path.extname( `${file.common.track.no}-${file.common.title}${path.extname(
file.path file.path
)}`.replace(/ /g, '_'); )}`.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. * 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. * Returns `taggedFiles` with the paths changed to the new paths.
*/ */
byAlbum: async (root, taggedFiles) => { byAlbum: async (root, taggedFiles) => {
const assetRoot = path.resolve(root, 'assets', 'albums');
const files = taggedFiles.filter(file => const files = taggedFiles.filter(file =>
tags.hasTags(file, [ tags.hasTags(file, [
'common.artists', 'common.artists',
@ -53,36 +68,38 @@ module.exports = {
); );
debug(`grouping tracks by album`); 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(`found ${folders.length} album(s)`);
debug(folders); debug(folders);
debug(`creating album directories`); debug(`creating album directories`);
await Promise.all( 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`); debug(`copying tracks & extracting covers`);
return Promise.all( return Promise.all(
files.map(async file => { files.map(async file => {
const folderName = getFolderName(file); const folderName = getAlbumName(file);
const fileName = getFileName(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); await fs.copyFile(file.path, newPath);
const coverPath = await extractCoverArt(newPath); const coverPath = await extractCoverArt(newPath);
return _.assign( return _.assign(
{}, {},
{ {
path: newPath, path: newPath,
relativePath: `${folderName}/${fileName}`, url: `/assets/albums/${folderName}/${fileName}`,
folderName, slug: stripExtension(fileName),
fileName,
}, },
coverPath coverPath
? { ? {
coverPath, coverPath,
relativeCoverPath: `${folderName}/${path.basename(coverPath)}`, cover: `/assets/albums/${folderName}/${path.basename(
coverFileName: path.basename(coverPath), coverPath
)}`,
} }
: {}, : {},
_.omit(file, 'path') _.omit(file, 'path')