120 lines
4.4 KiB
JavaScript
120 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Reconvert all existing emoji images to WebP.
|
|
* - Converts non-WebP files (PNG, GIF, JPG, etc.) to lossless WebP via ImageMagick.
|
|
* - Keeps transparency. Handles animated GIFs via -coalesce.
|
|
* - Updates the `url` column in `custom_emojis` to the new .webp path.
|
|
* - Deletes the original file after successful conversion.
|
|
*
|
|
* Usage:
|
|
* STORAGE_DIR=f0ckm-data DB_HOST=localhost DB_PORT=5454 node scripts/reconvert_emojis.mjs
|
|
*/
|
|
|
|
import db from "../src/inc/sql.mjs";
|
|
import cfg from "../src/inc/config.mjs";
|
|
import { promises as fs } from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { execFile as _execFile } from "child_process";
|
|
import { promisify } from "util";
|
|
|
|
const execFile = promisify(_execFile);
|
|
|
|
// Custom IM policy that raises list-length from 128 → 65536 to handle large animated GIFs.
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const MAGICK_POLICY_PATH = path.resolve(__dirname, '../config/magick-policy');
|
|
const magickEnv = { ...process.env, MAGICK_CONFIGURE_PATH: MAGICK_POLICY_PATH };
|
|
|
|
const emojisDir = cfg.paths.emojis;
|
|
console.log(`[reconvert_emojis] Emoji directory: ${emojisDir}`);
|
|
|
|
let converted = 0;
|
|
let skipped = 0;
|
|
let errors = 0;
|
|
|
|
try {
|
|
const emojis = await db`SELECT id, name, url FROM custom_emojis ORDER BY id`;
|
|
console.log(`[reconvert_emojis] Found ${emojis.length} emojis in DB.\n`);
|
|
|
|
for (const emoji of emojis) {
|
|
const { id, name, url } = emoji;
|
|
|
|
// Only handle locally hosted emojis (not external URLs)
|
|
if (!url || !url.startsWith('/s/emojis/')) {
|
|
console.log(`[${id}] "${name}" — skipping (external URL: ${url})`);
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
const filename = path.basename(url);
|
|
const ext = path.extname(filename).toLowerCase().replace('.', '');
|
|
|
|
if (ext === 'webp') {
|
|
// Verify the file actually exists, then skip
|
|
const fullPath = path.join(emojisDir, filename);
|
|
try {
|
|
await fs.access(fullPath);
|
|
console.log(`[${id}] "${name}" — already WebP, skipping.`);
|
|
} catch {
|
|
console.warn(`[${id}] "${name}" — already WebP in DB but file missing: ${fullPath}`);
|
|
}
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
const originalPath = path.join(emojisDir, filename);
|
|
const webpFilename = filename.replace(/\.[^.]+$/, '.webp');
|
|
const webpPath = path.join(emojisDir, webpFilename);
|
|
const newUrl = `/s/emojis/${webpFilename}`;
|
|
|
|
try {
|
|
await fs.access(originalPath);
|
|
} catch {
|
|
console.warn(`[${id}] "${name}" — original file missing: ${originalPath}, skipping.`);
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
console.log(`[${id}] "${name}" — converting ${ext.toUpperCase()} → WebP …`);
|
|
// -coalesce: handles animated GIFs properly
|
|
// -quality 80: lossy WebP — ~50-70% smaller than GIF, transparency preserved
|
|
await execFile('magick', [originalPath, '-coalesce', '-quality', '80', webpPath], { env: magickEnv });
|
|
|
|
// Verify output exists and is non-empty
|
|
const stat = await fs.stat(webpPath);
|
|
if (!stat || stat.size === 0) throw new Error('Output file is empty');
|
|
|
|
// Update DB
|
|
await db`UPDATE custom_emojis SET url = ${newUrl} WHERE id = ${id}`;
|
|
|
|
// Remove original
|
|
await fs.unlink(originalPath);
|
|
|
|
console.log(`[${id}] "${name}" — ✓ converted (${(stat.size / 1024).toFixed(1)} KB) → ${newUrl}`);
|
|
converted++;
|
|
} catch (err) {
|
|
console.error(`[${id}] "${name}" — ✗ FAILED: ${err.message}`);
|
|
// Clean up partial output if it exists
|
|
await fs.unlink(webpPath).catch(() => {});
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
// Notify connected clients of updated emoji list
|
|
if (converted > 0) {
|
|
await db`NOTIFY emojis_updated, '{}'`;
|
|
console.log(`\n[reconvert_emojis] Notified clients of emoji update.`);
|
|
}
|
|
|
|
console.log(`\n[reconvert_emojis] Done.`);
|
|
console.log(` Converted : ${converted}`);
|
|
console.log(` Skipped : ${skipped}`);
|
|
console.log(` Errors : ${errors}`);
|
|
|
|
process.exit(errors > 0 ? 1 : 0);
|
|
} catch (err) {
|
|
console.error('[reconvert_emojis] Fatal error:', err);
|
|
process.exit(1);
|
|
}
|