Docs/Skills and code/skill video-visual-production
.claude/skills/video-visual-production/scripts/contact-sheet.js
What the file says about itself
Make a labelled grid of PNG frames so a reviewer (Claude or the user) can see the whole beat at a glance. node contact-sheet.js --files a.png,b.png --labels "01 end,02 end" --out sheet.png [--cols 4] [--w 480]
#!/usr/bin/env node
// Make a labelled grid of PNG frames so a reviewer (Claude or the user) can see the whole beat at a glance.
// node contact-sheet.js --files a.png,b.png --labels "01 end,02 end" --out sheet.png [--cols 4] [--w 480]
const fs = require('fs');
const path = require('path');
const U = require('./lib/util');
const { createCanvas, loadImage, GlobalFonts } = require(path.join(U.SKILL, 'render', 'node_modules', '@napi-rs', 'canvas'));
async function sheet(files, labels, out, cols = 4, w = 480) {
const h = Math.round((w * 9) / 16), pad = 10, lab = 26;
const rows = Math.ceil(files.length / cols);
const cv = createCanvas(cols * (w + pad) + pad, rows * (h + lab + pad) + pad);
const g = cv.getContext('2d');
g.fillStyle = '#0b0908'; g.fillRect(0, 0, cv.width, cv.height);
g.font = '18px Arial'; g.textBaseline = 'middle';
for (let i = 0; i < files.length; i++) {
const x = pad + (i % cols) * (w + pad), y = pad + Math.floor(i / cols) * (h + lab + pad);
const img = await loadImage(files[i]);
g.drawImage(img, x, y + lab, w, h);
g.fillStyle = '#d9d0bd'; g.fillText(labels[i] || path.basename(files[i]), x, y + lab / 2);
}
fs.writeFileSync(out, cv.toBuffer('image/png'));
}
module.exports = { sheet };
if (require.main === module) {
const files = U.arg('files', '').split(',').filter(Boolean);
if (!files.length) U.die('--files required');
sheet(files, U.arg('labels', '').split(',').filter(Boolean), U.arg('out', 'contact-sheet.png'), parseInt(U.arg('cols', '4'), 10), parseInt(U.arg('w', '480'), 10)).then(() => U.say('wrote ' + U.arg('out', 'contact-sheet.png')));
}
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.