web2png
Version:
A Node.js tool to serve web pages as PNG images, ideal for OpenHAB HABPanel and other web integrations.
153 lines (140 loc) • 5.5 kB
JavaScript
#!/usr/bin/env node
import express from 'express';
import Pageres from 'pageres';
import fs from 'fs';
import yaml from 'js-yaml';
import sharp from 'sharp';
import yargs from "yargs";
import {hideBin} from "yargs/helpers";
const app = express();
const SAMPLE_CONFIG = `default:
resolution: '1280x720' # Default resolution for all devices
delay: 1000 # Delay in milliseconds to allow content to fully load
grayscale: false # Render in color by default
negate: false # Do not invert colors
devices:
device_1:
url: 'https://example.com' # Replace with your desired webpage URL
resolution: '1920x1080' # Device-specific resolution override
delay: 2000 # Additional delay for complex pages
grayscale: true # Grayscale rendering enabled for this device
device_2:
url: 'https://example2.com' # URL for another device`;
const yargsInstance = yargs(hideBin(process.argv))
.scriptName("web2png")
.usage('Usage: $0 [options]')
.option('config', {
alias: 'c',
type: 'string',
description: 'Path to the configuration file',
default: process.env.CONFIG_FILE || './device-mappings.yaml',
})
.option('port', {
alias: 'p',
type: 'number',
description: 'Port to run the server on',
default: process.env.CONFIG_PORT || 3001,
})
.option('print-sample-config', {
alias: 's',
type: 'boolean',
description: 'Print a sample device-mappings.yaml file and exit',
default: false,
})
.option('generate-sample', {
alias: 'g',
type: 'boolean',
description: 'Generate a sample config file (if not exists) and exit',
default: false,
})
.help('h')
.alias('h', 'help')
.alias('v', 'version')
.epilog(`Author: Rafal Klimonda\n` + `GitHub: https://github.com/maniekes/web2png\n` +
`License: MIT`)
.strict()
.fail((msg, err) => {
console.error(msg || err);
yargsInstance.showHelp(); // Use the yargs instance to show the help text
process.exit(1);
});
const argv = yargsInstance.argv;
if (argv['print-sample-config']) {
console.log("Sample device-mappings.yaml configuration:\n");
console.log(SAMPLE_CONFIG);
process.exit(0);
}
const configPath = argv.config;
if (argv['generate-sample']) {
if (fs.existsSync(configPath)) {
console.log(`Configuration file already exists at: ${configPath}`);
} else {
console.log(`Generating sample configuration file at: ${configPath}`);
fs.writeFileSync(configPath, SAMPLE_CONFIG.trim(), 'utf-8');
console.log(`Sample configuration file created at: ${configPath}`);
}
process.exit(0);
}
function loadConfig(filePath) {
const fileContents = fs.readFileSync(filePath, 'utf-8');
return yaml.load(fileContents);
}
const configPort = argv.port;
try {
loadConfig(configPath);
} catch (error) {
console.error(`Error loading configuration file(${configPath}):`, error);
process.exit(1);
}
app.get('/screenshot', async (req, res) => {
try {
const mappings = loadConfig(configPath);
const foundDevice = mappings.devices[req.query.device];
if (!foundDevice) {
console.error(`dashboard ${req.query.device} not in allowed list!`);
res.status(500).send('device not found in configuration list!');
return;
}
console.log(`device: ${JSON.stringify(foundDevice)}`);
const device = {...mappings.default, ...foundDevice};
console.log(`defaults: ${JSON.stringify(mappings.default)}`);
console.log(`device with defaults: ${JSON.stringify(device)}`);
const resolution = req.query.resolution || device.resolution;
const delayTime = req.query.delay || device.delay;
const negate = req.query.negate || device.negate;
const grayscale = req.query.grayscale || device.grayscale;
const url = device.url;
console.log(`[${req.query.device}] fetching ${url} with resolution ${resolution}, delay ${delayTime}, negate ${negate}, grayscale ${grayscale}`);
console.log(`grayscale ${grayscale} ${typeof grayscale}`);
console.log(`negate ${negate} ${typeof negate}`);
// Capture the screenshot with Pageres
const pageres = new Pageres({delay: delayTime})
.source(url, [resolution]);
const screenshots = await pageres.run();
if (screenshots.length > 0) {
const buffer = sharp(screenshots[0]);
if (grayscale) {
buffer
.grayscale()
.toFormat('png', {colours: 16});
}
if (negate) {
buffer.negate();
}
const processedBuffer = await buffer.toBuffer();
res.setHeader('Content-Length', processedBuffer.length);
res.setHeader('Content-Type', 'image/png');
res.setHeader('Content-Disposition', 'inline; filename="screenshot.png"');
res.end(processedBuffer);
} else {
res.status(500).send('No screenshot captured.');
}
} catch (error) {
console.error('Error capturing screenshot:', error);
res.status(500).send('Failed to capture screenshot.');
}
console.log();
});
app.listen(configPort, () => {
console.log(`Server running at http://localhost:${configPort}`);
});