Skip to content

Instantly share code, notes, and snippets.

@oatmealine
Created October 25, 2021 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oatmealine/56a1a33ca67c779b8b6152e7d291dade to your computer and use it in GitHub Desktop.
Save oatmealine/56a1a33ca67c779b8b6152e7d291dade to your computer and use it in GitHub Desktop.
// rhythmbox database collage tool
//
// dependencies: jimp, xml-js
//
// this code is licensed under a [copyleft](https://en.wikipedia.org/wiki/Copyleft) license: this code is completely okay to modify, copy, redistribute and improve upon, as long as you keep this license notice
// ↄ Jill "oatmealine" Monoids 2021
const Jimp = require('jimp');
const fs = require('fs');
const { xml2js } = require('xml-js');
const { join } = require('path');
const collageTopText = `jill\'s music! ${new Date().getMonth().toString().padStart(2, '0')}-${new Date().getFullYear()}`;
const collageSize = 25;
const imageSize = 512;
const widthMultiplier = 1.75;
const padding = 12;
const backgroundColor = 0x000208ff;
// these have to be .fnt files
// you can use https://ttf2fnt.com/
const bigFont = Jimp.FONT_SANS_32_WHITE;
const smallFont = './Inter-V.ttf.fnt';
const coverartNames = ['cover', 'image', 'folder'];
const coverartExtensions = ['png', 'jpg', 'jpeg'];
const db = xml2js(fs.readFileSync(`${process.env.HOME}/.local/share/rhythmbox/rhythmdb.xml`));
const songs = db.elements[0].elements
.filter(c => c.attributes.type === 'song')
.map(c => {
let m = {};
c.elements.forEach(elem => {
if (!isNaN(Number(elem.elements[0].text))) elem.elements[0].text = Number(elem.elements[0].text);
m[elem.name] = elem.elements[0].text;
});
return m;
});
const mostListenedToPastMonth = songs
.filter(c => ((Date.now() / 1000) - c['first-seen']) < 60 * 60 * 24 * 31)
.sort((a, b) => (b['play-count'] || 0) - (a['play-count'] || 0));
let albums = [];
let albumNames = [];
for (let song of mostListenedToPastMonth) {
if (albums.length >= collageSize) break;
if (albumNames.includes(song.album)) continue;
if (!song.album || song.album === 'Unknown') continue;
const albumLocation = join(decodeURIComponent(song.location.replace('file:/', '/')), '..');
let coverArt;
let files;
try {
files = fs.readdirSync(albumLocation)
.map(f => [f, f.toLowerCase()]);
} catch(err) {
console.log(`error reading ${albumLocation}`);
console.log(err.toString());
continue;
}
for (let name of coverartNames) {
if (coverArt) break;
for (let ext of coverartExtensions) {
if (coverArt) break;
coverArt = files.find(v => v[1] === name.toLowerCase() + '.' + ext.toLowerCase());
}
}
if (!coverArt) coverArt = files.find(v => coverartExtensions.includes(v[1].split('.').pop()));
if (coverArt) {
const fCoverArt = join(albumLocation, coverArt[0]);
if (albums.includes(fCoverArt)) continue;
albums.push(fCoverArt);
albumNames.push(song.album);
}
}
const albumsPick = albums.slice(0, collageSize);
const albumNamesPick = albumNames.slice(0, collageSize);
const gridsize = Math.ceil(Math.sqrt(collageSize));
const imgsize = imageSize / gridsize;
console.log('loaded & sorted album data');
new Jimp(Math.floor(imageSize * widthMultiplier), imageSize, backgroundColor, async (err, image) => {
if (err) return console.log(err);
for (let i in albumsPick) {
const v = albumsPick[i];
const x = i % gridsize;
const y = Math.floor(i / gridsize);
let img = await Jimp.read(v);
image = await image.composite(await img.resize(imgsize, imgsize), x * imgsize, y * imgsize);
console.log(`composited ${albumNamesPick[i]}`);
delete img;
}
const fontBig = await Jimp.loadFont(bigFont);
const font = await Jimp.loadFont(smallFont);
await image.print(fontBig, imageSize + padding, padding, collageTopText, Math.floor(imageSize * (widthMultiplier - 1)) - padding);
let albumString = ' - left to right, top to bottom album names -\n';
for (const album of albumNamesPick) {
albumString += album + '\n';
}
let y = Jimp.measureTextHeight(fontBig, collageTopText, Math.floor(imageSize * (widthMultiplier - 1)) - padding) + padding * 2;
for (const a of albumString.split('\n')) {
await image.print(font, imageSize + padding, y, a, Math.floor(imageSize * (widthMultiplier - 1)) - padding);
y += Jimp.measureTextHeight(font, a, Math.floor(imageSize * (widthMultiplier - 1)) - padding);
}
console.log('added text');
image.write('./collage.png', () => {
console.log('done');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment