'use strict'; function writeUint24LE(buffer, offset, value) { buffer[offset] = value & 0xff; buffer[offset + 1] = (value >>> 8) & 0xff; buffer[offset + 2] = (value >>> 16) & 0xff; } function chunk(type, payload) { const padding = payload.length & 1; const output = Buffer.alloc(8 + payload.length + padding); output.write(type, 0, 4, 'ascii'); output.writeUInt32LE(payload.length, 4); payload.copy(output, 8); return output; } function frameData(webp) { if (!webp) throw new Error('WebP 帧数据缺失'); const input = Buffer.from(webp); if (input.length < 20 || input.toString('ascii', 0, 4) !== 'RIFF' || input.toString('ascii', 8, 12) !== 'WEBP') { throw new Error('Invalid WebP frame'); } const parts = []; let hasAlpha = false; let offset = 12; while (offset + 8 <= input.length) { const type = input.toString('ascii', offset, offset + 4); const length = input.readUInt32LE(offset + 4); const end = offset + 8 + length + (length & 1); if (end > input.length) throw new Error('Truncated WebP frame'); if (type === 'ALPH' || type === 'VP8 ' || type === 'VP8L') parts.push(input.subarray(offset, end)); if (type === 'ALPH') hasAlpha = true; offset = end; } if (!parts.some((part) => part.toString('ascii', 0, 4) === 'VP8 ' || part.toString('ascii', 0, 4) === 'VP8L')) { throw new Error('WebP frame has no image data'); } return { data: Buffer.concat(parts), hasAlpha }; } function encode(options) { const width = Number(options && options.width); const height = Number(options && options.height); const frames = options && options.frames; if (!Number.isInteger(width) || width < 1 || width > 0x1000000 || !Number.isInteger(height) || height < 1 || height > 0x1000000) { throw new RangeError('Animated WebP dimensions are invalid'); } if (!Array.isArray(frames) || frames.length < 2) throw new RangeError('Animated WebP requires at least two frames'); const preparedFrames = frames.map((frame) => ({ frame, ...frameData(frame.webp) })); const vp8x = Buffer.alloc(10); vp8x[0] = 0x02 | (preparedFrames.some(({ hasAlpha }) => hasAlpha) ? 0x10 : 0); writeUint24LE(vp8x, 4, width - 1); writeUint24LE(vp8x, 7, height - 1); const anim = Buffer.alloc(6); const background = options.background || [30, 30, 30, 255]; anim.set([background[2] || 0, background[1] || 0, background[0] || 0, background[3] === undefined ? 255 : background[3]], 0); anim.writeUInt16LE(Math.max(0, Math.min(0xffff, options.plays || 0)), 4); const chunks = [chunk('VP8X', vp8x), chunk('ANIM', anim)]; for (const { frame, data } of preparedFrames) { const header = Buffer.alloc(16); writeUint24LE(header, 6, width - 1); writeUint24LE(header, 9, height - 1); writeUint24LE(header, 12, Math.max(1, Math.min(0xffffff, Math.round(frame.duration)))); header[15] = 0x02; chunks.push(chunk('ANMF', Buffer.concat([header, data]))); } const body = Buffer.concat([Buffer.from('WEBP'), ...chunks]); const output = Buffer.alloc(8 + body.length); output.write('RIFF', 0, 4, 'ascii'); output.writeUInt32LE(body.length, 4); body.copy(output, 8); return output; } module.exports = { encode };