feat: thumbnailer utility
Fix large images in notifications being slow GTK css background image is really slow for scaling for some reason, so thumbnail
This commit is contained in:
parent
3c0b2eae58
commit
d05b124609
3 changed files with 78 additions and 18 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { basename } from "@/utils/strings";
|
||||
import { monitorDirectory } from "@/utils/system";
|
||||
import { execAsync, GLib, GObject, property, register } from "astal";
|
||||
import Thumbnailer from "@/utils/thumbnailer";
|
||||
import { execAsync, GObject, property, register } from "astal";
|
||||
import { wallpapers as config } from "config";
|
||||
|
||||
export interface IWallpaper {
|
||||
|
|
@ -22,8 +22,6 @@ export default class Wallpapers extends GObject.Object {
|
|||
return this.instance;
|
||||
}
|
||||
|
||||
#thumbnailDir = `${CACHE}/thumbnails`;
|
||||
|
||||
#list: IWallpaper[] = [];
|
||||
#categories: ICategory[] = [];
|
||||
|
||||
|
|
@ -37,14 +35,6 @@ export default class Wallpapers extends GObject.Object {
|
|||
return this.#categories;
|
||||
}
|
||||
|
||||
async #thumbnail(path: string) {
|
||||
const dir = path.slice(1, path.lastIndexOf("/")).replaceAll("/", "-");
|
||||
const thumbPath = `${this.#thumbnailDir}/${dir}-${basename(path)}.jpg`;
|
||||
if (!GLib.file_test(thumbPath, GLib.FileTest.EXISTS))
|
||||
await execAsync(`magick -define jpeg:size=1000x500 ${path} -thumbnail 500x250 -unsharp 0x.5 ${thumbPath}`);
|
||||
return thumbPath;
|
||||
}
|
||||
|
||||
#listDir(path: { path: string; recursive: boolean }, type: "f" | "d") {
|
||||
const absPath = path.path.replace("~", HOME);
|
||||
const maxDepth = path.recursive ? "" : "-maxdepth 1";
|
||||
|
|
@ -68,7 +58,7 @@ export default class Wallpapers extends GObject.Object {
|
|||
const files = successes.map(r => r.files.replaceAll("\n", " ")).join(" ");
|
||||
const list = (await execAsync(["fish", "-c", `identify -ping -format '%i\n' ${files} ; true`])).split("\n");
|
||||
|
||||
this.#list = await Promise.all(list.map(async p => ({ path: p, thumbnail: await this.#thumbnail(p) })));
|
||||
this.#list = await Promise.all(list.map(async p => ({ path: p, thumbnail: await Thumbnailer.thumbnail(p) })));
|
||||
this.notify("list");
|
||||
|
||||
const categories = await Promise.all(successes.map(r => this.#listDir(r.path, "d")));
|
||||
|
|
@ -82,8 +72,6 @@ export default class Wallpapers extends GObject.Object {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
GLib.mkdir_with_parents(this.#thumbnailDir, 0o755);
|
||||
|
||||
this.update().catch(console.error);
|
||||
|
||||
let monitors = config.paths
|
||||
|
|
|
|||
69
src/utils/thumbnailer.ts
Normal file
69
src/utils/thumbnailer.ts
Normal file
|
|
@ -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<string>();
|
||||
|
||||
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<string> {
|
||||
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<string> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { desktopEntrySubs } from "@/utils/icons";
|
||||
import Thumbnailer from "@/utils/thumbnailer";
|
||||
import { setupCustomTooltip } from "@/utils/widgets";
|
||||
import { bind, GLib, register, timeout, Variable } from "astal";
|
||||
import { Astal, Gtk, Widget } from "astal/gtk3";
|
||||
|
|
@ -58,9 +59,11 @@ const Image = ({ compact, icon }: { compact?: boolean; icon: string }) => {
|
|||
<box
|
||||
valign={Gtk.Align.START}
|
||||
className={`image ${compact ? "small" : ""}`}
|
||||
css={`
|
||||
background-image: url("${icon}");
|
||||
`}
|
||||
setup={self =>
|
||||
Thumbnailer.thumbnail(icon)
|
||||
.then(p => (self.css = `background-image: url("${p}");`))
|
||||
.catch(console.error)
|
||||
}
|
||||
/>
|
||||
);
|
||||
if (Astal.Icon.lookup_icon(icon))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue