From d05b124609ca56c99ff9ef32aa2e5217bcde295e Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Tue, 1 Apr 2025 13:36:23 +1100 Subject: feat: thumbnailer utility Fix large images in notifications being slow GTK css background image is really slow for scaling for some reason, so thumbnail --- src/utils/thumbnailer.ts | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/utils/thumbnailer.ts (limited to 'src/utils/thumbnailer.ts') diff --git a/src/utils/thumbnailer.ts b/src/utils/thumbnailer.ts new file mode 100644 index 0000000..a922590 --- /dev/null +++ b/src/utils/thumbnailer.ts @@ -0,0 +1,69 @@ +import { execAsync, Gio, GLib } from "astal"; +import { basename } from "./strings"; + +export default class Thumbnailer { + static readonly thumbnailDir = `${CACHE}/thumbnails`; + + static lazy = true; + static thumbWidth = 500; + static thumbHeight = 250; + static maxAttempts = 5; + static timeBetweenAttempts = 300; + + static readonly #running = new Set(); + + static getThumbPath(path: string) { + const dir = path.slice(1, path.lastIndexOf("/")).replaceAll("/", "-"); + return `${this.thumbnailDir}/${dir}-${basename(path)}.jpg`; + } + + static async #thumbnail(path: string, attempts: number): Promise { + const thumbPath = this.getThumbPath(path); + + try { + await execAsync( + `magick -define jpeg:size=${this.thumbWidth * 2}x${this.thumbHeight * 2} ${path} -thumbnail ${ + this.thumbWidth + }x${this.thumbHeight} -unsharp 0x.5 ${thumbPath}` + ); + } catch { + if (attempts >= this.maxAttempts) { + console.error(`Failed to generate thumbnail for ${path}`); + return path; + } + + await new Promise(r => setTimeout(r, this.timeBetweenAttempts)); + return this.#thumbnail(path, attempts + 1); + } + + return thumbPath; + } + + static async thumbnail(path: string): Promise { + let thumbPath = this.getThumbPath(path); + + // If not lazy (i.e. force gen), delete existing thumbnail + if (!this.lazy) Gio.File.new_for_path(thumbPath).delete(null); + + // Wait for existing thumbnail for path to finish + while (this.#running.has(path)) await new Promise(r => setTimeout(r, 100)); + + // If no thumbnail, generate + if (!GLib.file_test(thumbPath, GLib.FileTest.EXISTS)) { + this.#running.add(path); + + thumbPath = await this.#thumbnail(path, 0); + + this.#running.delete(path); + } + + return thumbPath; + } + + // Static class + private constructor() {} + + static { + GLib.mkdir_with_parents(this.thumbnailDir, 0o755); + } +} -- cgit v1.2.3-freya