'use strict'; const assert = require('node:assert/strict'); const { encode } = require('../js/animated-webp-encoder'); function chunk(type, payload) { const output = Buffer.alloc(8 + payload.length + (payload.length & 1)); output.write(type, 0, 4, 'ascii'); output.writeUInt32LE(payload.length, 4); payload.copy(output, 8); return output; } function stillWebp() { const body = Buffer.concat([Buffer.from('WEBP'), chunk('VP8 ', Buffer.from([1, 2, 3, 4]))]); const output = Buffer.alloc(8 + body.length); output.write('RIFF', 0, 4, 'ascii'); output.writeUInt32LE(body.length, 4); body.copy(output, 8); return output; } function chunks(buffer) { const result = []; for (let offset = 12; offset + 8 <= buffer.length;) { const size = buffer.readUInt32LE(offset + 4); result.push({ type: buffer.toString('ascii', offset, offset + 4), payload: buffer.subarray(offset + 8, offset + 8 + size) }); offset += 8 + size + (size & 1); } return result; } const output = encode({ width: 400, height: 300, frames: [{ webp: stillWebp(), duration: 50 }, { webp: stillWebp(), duration: 75 }], }); assert.equal(output.toString('ascii', 0, 4), 'RIFF'); assert.equal(output.readUInt32LE(4), output.length - 8); assert.equal(output.toString('ascii', 8, 12), 'WEBP'); const parsed = chunks(output); assert.deepEqual(parsed.map((entry) => entry.type), ['VP8X', 'ANIM', 'ANMF', 'ANMF']); assert.equal(parsed[0].payload[0], 0x02, 'VP8X must declare animation without alpha'); assert.deepEqual([...parsed[0].payload.subarray(4, 7)], [143, 1, 0]); assert.deepEqual([...parsed[0].payload.subarray(7, 10)], [43, 1, 0]); assert.equal(parsed[1].payload[3], 255, 'animation background must be opaque'); assert.deepEqual([...parsed[2].payload.subarray(12, 15)], [50, 0, 0]); assert.equal(parsed[2].payload[15], 0x02, 'full frames must replace without blending'); assert.equal(parsed[2].payload.toString('ascii', 16, 20), 'VP8 '); assert.throws(() => encode({ width: 1, height: 1, frames: [{ webp: Buffer.alloc(0), duration: 50 }] }), /at least two frames/); console.log('PASS: animated WebP RIFF structure, timing and opaque full-frame mode');