import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { spawn } from 'node:child_process'; import { once } from 'node:events'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; const root = resolve(import.meta.dirname, '..'); const outputPath = resolve(root, 'media/hdr-wordmark.png'); const width = 2200; const height = 500; function run(command, args, options = {}) { const child = spawn(command, args, options); return once(child, 'close').then(([code]) => { if (code !== 0) throw new Error(`${command} exited with code ${code}`); }); } function capture(command, args) { return new Promise((resolveBuffer, reject) => { const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'inherit'] }); const chunks = []; child.stdout.on('data', (chunk) => chunks.push(chunk)); child.on('error', reject); child.on('close', (code) => { if (code !== 0) { reject(new Error(`${command} exited with code ${code}`)); return; } resolveBuffer(Buffer.concat(chunks)); }); }); } const tempDir = await mkdtemp(join(tmpdir(), 'hdr-wordmark-')); const svgPath = join(tempDir, 'wordmark.svg'); const maskPath = join(tempDir, 'wordmark-mask.png'); const svg = ` HDR Web Plugin HDR Web Plugin HDR Web Plugin HDR Web Plugin HDR Web Plugin HDR Web Plugin `; await mkdir(dirname(outputPath), { recursive: true }); await writeFile(svgPath, svg, 'utf8'); await run('/usr/bin/sips', [ '-s', 'format', 'png', svgPath, '--out', maskPath ], { stdio: ['ignore', 'inherit', 'inherit'] }); const rgba = await capture('ffmpeg', [ '-hide_banner', '-loglevel', 'error', '-i', maskPath, '-f', 'rawvideo', '-pix_fmt', 'rgba', 'pipe:1' ]); const frame = Buffer.alloc(width * height * 8); for (let index = 0; index < width * height; index += 1) { const src = index * 4; const dst = index * 8; const alpha = rgba[src + 3] / 255; const red = alpha === 0 ? 0 : rgba[src] / 255; const green = alpha === 0 ? 0 : rgba[src + 1] / 255; const blue = alpha === 0 ? 0 : rgba[src + 2] / 255; const channels = [red, green, blue]; for (let channel = 0; channel < 3; channel += 1) { const lifted = Math.pow(channels[channel], 0.42); const value = Math.min(65535, Math.round(lifted * 65535)); frame.writeUInt16LE(value, dst + channel * 2); } const alphaValue = Math.min(65535, Math.round(Math.pow(alpha, 0.42) * 65535)); frame.writeUInt16LE(alphaValue, dst + 6); } const encode = spawn('ffmpeg', [ '-y', '-f', 'rawvideo', '-pix_fmt', 'rgba64le', '-s:v', `${width}x${height}`, '-i', 'pipe:0', '-vf', 'setparams=range=full:color_primaries=bt2020:color_trc=smpte2084:colorspace=gbr', '-frames:v', '1', '-update', '1', '-c:v', 'png', '-pix_fmt', 'rgba64be', '-pred', 'mixed', '-color_primaries', 'bt2020', '-color_trc', 'smpte2084', outputPath ], { stdio: ['pipe', 'inherit', 'inherit'] }); encode.stdin.end(frame); const [encodeCode] = await once(encode, 'close'); await rm(tempDir, { recursive: true, force: true }); if (encodeCode !== 0) throw new Error(`ffmpeg png exited with code ${encodeCode}`); console.log(`Wrote ${outputPath}`);