UNPKG

beatprints.js

Version:

A Node.js version of the original Python BeatPrints project (https://github.com/TrueMyst/BeatPrints/) by TrueMyst. Create eye-catching, Pinterest-style music posters effortlessly. BeatPrints integrates with Spotify and LRClib API to help you design custom

130 lines (129 loc) 5.97 kB
import * as write from './write.js'; import * as image from './image.js'; import { filename, organizeTracks, zip } from './utils.js'; import { ThemeNotFoundError } from './errors.js'; import { Size, Position, ThemesSelector } from './constants.js'; import { join } from 'node:path'; import { writeFile } from 'node:fs/promises'; import { Canvas, createCanvas, Image, loadImage } from '@napi-rs/canvas'; const boldFonts = write.fontPaths('Bold'); const regularFonts = write.fontPaths('Regular'); const lightFonts = write.fontPaths('Light'); /** * A class for generating and saving posters containing track or album information. */ export class Poster { constructor(options) { this.options = options; this.options = this.options || { mimeType: 'image/png', output: { type: 'buffer' } }; } async _handleOutput(img, name) { if (this.options?.output?.type === 'file') { const filePath = join(this.options.output.path ?? process.cwd(), name); await writeFile(filePath, img, { flag: 'w+' }); console.log(`✨ Poster saved to ${filePath}`); return img; } return img; } /** * Adds text like title, artist, and label info. */ async _drawTemplate(ctx, metadata, color) { // Add heading (track or album name) in bold write.heading(ctx, Position.HEADING, Size.HEADING_WIDTH, metadata.name.toUpperCase(), color, boldFonts, Size.HEADING); // Add artist name write.text(ctx, Position.ARTIST, metadata.artist, color, regularFonts, Size.ARTIST, undefined, undefined, 'lt'); // Add release year and label info write.text(ctx, Position.LABEL, `${metadata.released}\n${metadata.label}`, color, regularFonts, Size.LABEL, undefined, undefined, 'rt'); } async track(metadata, lyrics, options = {}) { const theme = options?.theme ?? 'Light'; // Check if the theme is valid or not if (!(theme in ThemesSelector.THEMES)) { throw new ThemeNotFoundError(); } // Get theme and template for the poster const [color, template] = image.getTheme(theme); // Get cover art and scannable const cover = await image.cover(metadata.image, options?.pcover); const scannable = await image.scannable(metadata.id, theme, 'track'); const poster = await loadImage(template); const canvas = createCanvas(poster.width, poster.height); const ctx = canvas.getContext('2d'); ctx.drawImage(poster, 0, 0); // Paste the track cover and scannable Spotify code ctx.drawImage(await this.BFT(cover), ...Position.COVER, ...Size.COVER); ctx.drawImage(await this.BFT(scannable), ...Position.SCANCODE); // Optionally add a color palette or design accents if (options?.palette) await image.drawPalette(ctx, cover, options?.accent); // Add album information (name, artist, etc.) await this._drawTemplate(ctx, metadata, color); // Add the track's duration and lyrics to the poster write.text(ctx, Position.DURATION, metadata.duration, color, regularFonts, Size.DURATION, undefined, undefined, 'rb'); // Lyrics write.text(ctx, Position.LYRICS, lyrics, color, lightFonts, Size.LYRICS, undefined, undefined, 'lt'); return this.save(canvas, metadata); } async album(metadata, options = {}) { const theme = options?.theme ?? 'Light'; const indexing = options?.indexing ?? false; // Check if the theme is valid or not if (!(theme in ThemesSelector.THEMES)) { throw new ThemeNotFoundError(); } const [color, template] = image.getTheme(theme); const cover = await image.cover(metadata.image, options?.pcover); const scannable = await image.scannable(metadata.id, theme, 'album'); const poster = await loadImage(template); const canvas = createCanvas(poster.width, poster.height); const ctx = canvas.getContext('2d'); ctx.drawImage(poster, 0, 0); // Paste the album cover and scannable Spotify code ctx.drawImage(await this.BFT(cover), ...Position.COVER, ...Size.COVER); ctx.drawImage(await this.BFT(scannable), ...Position.SCANCODE); // Optionally add a color palette or design accents if (options?.palette) await image.drawPalette(ctx, cover, options?.accent); // Add album information (name, artist, etc.) await this._drawTemplate(ctx, metadata, color); // Album's tracks const tracks = metadata.tracks; const [tracklist, trackWidths] = organizeTracks(tracks, indexing); // Starting positions let [x, y] = Position.TRACKS; // Render the tracklist, adjusting the position for each column for (const [trackColumn, columnWidth] of zip(tracklist, trackWidths)) { write.text(ctx, [x, y], trackColumn.join('\n'), color, lightFonts, Size.TRACKS, undefined, 2, 'lt'); x += columnWidth + Size.SPACING; // Adjust x for next column } return this.save(canvas, metadata); } async save(canvas, metadata) { let img = null; switch (this.options?.mimeType) { case 'image/png': img = canvas.toBuffer('image/png'); break; case 'image/avif': img = canvas.toBuffer('image/avif', { quality: 100 }); break; case 'image/jpeg': case 'image/webp': img = canvas.toBuffer(this.options.mimeType, 100); break; } const name = this.options?.filename ? this.options.filename : filename(metadata.name, metadata.artist); return this._handleOutput(img, name); } // wtf async BFT(buffer) { return loadImage(buffer); } }