trmnlc/src/preview.ts

97 lines
3.2 KiB
TypeScript
Raw Permalink Normal View History

import puppeteer from 'puppeteer-core';
import { devices } from './devices';
import { createPlugin } from './plugins';
import { render } from './template';
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(`Usage: bun run src/preview.ts [options]
Render a plugin to a local PNG file for testing.
Options:
--config <path> Config file (default: config.json)
--device <id> Device ID from config (default: first device)
--output <path> Output PNG path (default: preview.png)
--firefox <path> Firefox executable path (or set FIREFOX env var)
--dither Apply e-ink dithering via ImageMagick
--html Also save the raw HTML to <output>.html
-h, --help Show this help`);
process.exit(0);
}
function getArg(flag: string, fallback: string): string {
const idx = args.indexOf(flag);
return idx !== -1 && args[idx + 1] ? args[idx + 1]! : fallback;
}
const configPath = getArg('--config', Bun.env['CONFIG_FILE'] ?? 'config.json');
const outputPath = getArg('--output', 'preview.png');
const firefoxPath = getArg('--firefox', Bun.env['FIREFOX'] ?? 'firefox');
const dither = args.includes('--dither');
const saveHtml = args.includes('--html');
const config = JSON.parse(await Bun.file(configPath).text());
const deviceEntries = Object.entries(config.devices ?? {});
if (deviceEntries.length === 0) {
console.error('No devices found in config.');
process.exit(1);
}
const deviceIdArg = getArg('--device', '');
const [deviceId, deviceConfig] = deviceIdArg
? [deviceIdArg, config.devices[deviceIdArg]]
: (deviceEntries[0] as [string, any]);
if (!deviceConfig) {
console.error(`Device "${deviceIdArg}" not found. Available: ${deviceEntries.map(([k]) => k).join(', ')}`);
process.exit(1);
}
const model = devices.find((a) => a.name === (deviceConfig.model ?? 'inkplate_10'));
if (!model) {
console.error(`Unknown model: ${deviceConfig.model}`);
process.exit(1);
}
console.log(`Rendering plugin "${deviceConfig.plugin ?? 'calendar'}" for device "${deviceId}" (${model.width}x${model.height})...`);
const plugin = createPlugin(deviceConfig.plugin ?? 'calendar', deviceConfig.settings ?? {});
const html = await render([[plugin, 'full']], model);
if (saveHtml) {
const htmlPath = outputPath.replace(/\.png$/, '.html');
await Bun.write(htmlPath, html);
console.log(`HTML saved to ${htmlPath}`);
}
const browser = await puppeteer.launch({
browser: 'firefox',
args: ['--new-instance'],
executablePath: firefoxPath,
});
const page = await browser.newPage();
await page.setViewport({ width: model.width, height: model.height, deviceScaleFactor: 1 });
await page.setContent(html, { waitUntil: 'networkidle2' });
await Bun.sleep(3000);
const screenshot = await page.screenshot({ encoding: 'binary' });
await page.close();
await browser.close();
if (dither) {
const colormap = Bun.env['COLORMAP'] ?? './colormap.png';
const processed = await Bun.spawn(
['magick', '-', '-dither', 'FloydSteinberg', '-remap', colormap,
'-define', 'png:bit-depth=2', '-define', 'png:color-type=0', '-strip', 'png:-'],
{ stdin: screenshot }
).stdout.bytes();
await Bun.write(outputPath, processed);
} else {
await Bun.write(outputPath, screenshot);
}
console.log(`Preview saved to ${outputPath}`);