From 48a2302053c626a8c25df37af1832cf8401fc1c4 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Thu, 16 Jul 2026 01:02:20 +0200 Subject: [PATCH] video sticker preview img --- src/inc/routes/emojis.mjs | 41 ++++++++++++++++++++++++++++++++++++--- views/admin/emojis.html | 4 ++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/inc/routes/emojis.mjs b/src/inc/routes/emojis.mjs index 85a6a2b..55da646 100644 --- a/src/inc/routes/emojis.mjs +++ b/src/inc/routes/emojis.mjs @@ -6,6 +6,7 @@ import path from "path"; import { fileURLToPath } from "url"; import { execFile as _execFile } from "child_process"; import { promisify } from "util"; +import crypto from "crypto"; const execFile = promisify(_execFile); @@ -281,19 +282,53 @@ export default (router, tpl) => { return res.reply({ code: 400, body: JSON.stringify({ success: false, message: 'thumb_url must be a local /s/emojis/ path' }) }); } try { + let finalThumbUrl = thumbUrl; + + // For .webm stickers: extract first frame -> save as WebP so tabs always show a static image + if (thumbUrl.endsWith('.webm')) { + try { + const webmPath = path.join(cfg.paths.emojis, path.basename(thumbUrl)); + const randSuffix = crypto.randomBytes(24).toString('hex'); + const tmpPng = path.join(cfg.paths.emojis, randSuffix + '_tmp.png'); + const webpPath = path.join(cfg.paths.emojis, randSuffix + '_thumb.webp'); + + // Step 1: ffmpeg extracts first frame to a temp PNG file + await execFile('ffmpeg', [ + '-y', '-i', webmPath, + '-vframes', '1', + tmpPng + ], { maxBuffer: 20 * 1024 * 1024 }); + + // Step 2: ImageMagick converts PNG -> WebP + try { + await execFile('magick', [tmpPng, '-quality', '85', webpPath], { env: magickEnv }); + } finally { + await fs.unlink(tmpPng).catch(() => {}); + } + + const stat = await fs.stat(webpPath).catch(() => null); + if (stat && stat.size > 0) { + finalThumbUrl = '/s/emojis/' + randSuffix + '_thumb.webp'; + console.log('[SET THUMB] Generated WebP thumbnail from .webm: ' + finalThumbUrl); + } + } catch (thumbErr) { + console.warn('[SET THUMB] Could not generate WebP from .webm:', thumbErr.message); + } + } + try { - await db`UPDATE sticker_packs SET thumb_url = ${thumbUrl} WHERE id = ${id}`; + await db`UPDATE sticker_packs SET thumb_url = ${finalThumbUrl} WHERE id = ${id}`; } catch (colErr) { // Column might not exist yet — add it and retry once if (colErr.message && colErr.message.includes('thumb_url')) { await db`ALTER TABLE public.sticker_packs ADD COLUMN IF NOT EXISTS thumb_url text`; - await db`UPDATE sticker_packs SET thumb_url = ${thumbUrl} WHERE id = ${id}`; + await db`UPDATE sticker_packs SET thumb_url = ${finalThumbUrl} WHERE id = ${id}`; } else { throw colErr; } } await db`NOTIFY emojis_updated, '{}'`; - return res.reply({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true }) }); + return res.reply({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true, thumb_url: finalThumbUrl }) }); } catch (e) { console.error(e); return res.reply({ code: 500, body: JSON.stringify({ success: false, message: "Database error" }) }); diff --git a/views/admin/emojis.html b/views/admin/emojis.html index 452a474..17872bc 100644 --- a/views/admin/emojis.html +++ b/views/admin/emojis.html @@ -420,9 +420,9 @@ .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { - // Update local pack data so re-render shows correct current icon + // Use the URL the server actually stored (may be a generated WebP for .webm stickers) var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; }); - if (pack) pack.thumb_url = thumbUrl; + if (pack) pack.thumb_url = data.thumb_url || thumbUrl; // Re-render only this pack's grid — grid stays open renderPackGrid(packId); } else {