Created
October 25, 2021 20:54
-
-
Save oatmealine/56a1a33ca67c779b8b6152e7d291dade to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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