diff --git a/src/bundles/full.mjs b/src/bundles/full.mjs index f15523c..7a9b7a5 100644 --- a/src/bundles/full.mjs +++ b/src/bundles/full.mjs @@ -18,6 +18,7 @@ import '../file-readers/Base64Reader.mjs' import '../file-parsers/tiff.mjs' import '../file-parsers/heif.mjs' import '../file-parsers/png.mjs' +import '../file-parsers/webp.mjs' // TIFF - Additional tags import '../dicts/tiff-interop-keys.mjs' diff --git a/src/file-parsers/webp.mjs b/src/file-parsers/webp.mjs new file mode 100644 index 0000000..4853461 --- /dev/null +++ b/src/file-parsers/webp.mjs @@ -0,0 +1,68 @@ +import { FileParserBase } from '../parser.mjs' +import { fileParsers } from '../plugins.mjs' + + +export class WebpFileParser extends FileParserBase { + + static type = 'webp' + + static canHandle(file, firstTwoBytes) { + return firstTwoBytes === 0x5249 + && file.getUint32(0) === 0x52494646 + && file.getUint32(8) === 0x57454250 + } + + async parse() { + let { tiff, xmp, icc } = this.options + + const chunks = {} + const hasChunks = { XMP: false, EXIF: false, ICC: false } + + let head = 0x0C; + while (1) { + await this.file.ensureChunk(head, 8) + if (this.file.chunked && !this.file.available(head, 8) || head + 8 > this.file.byteLength) + break + + const type = this.file.getString(head, 4) + const len = this.file.getUint32(head + 4, true) + + if (type === "VP8X") { + await this.file.ensureChunk(head + 4 + 4, 1) + const flag = this.file.getUint8(head + 4 + 4, 1); + // hasChunks.ANIM = (flag & 1 << 1) >> 1 + hasChunks.XMP = (flag & 1 << 2) >> 2 && xmp.enabled + hasChunks.EXIF = (flag & 1 << 3) >> 3 && tiff.enabled + // hasChunks.ALPHA = (flag & 1 << 4) >> 4 + hasChunks.ICC = (flag & 1 << 5) >> 5 && icc.enabled + } + + chunks[type.trim()] = { head: head + 8, len: len - 8 } + + head += len + 8 + head += head % 2 + + let foundAllChunks = true; + for (const key in hasChunks) + if (hasChunks[key] && !chunks[key]) + foundAllChunks = false + + if (foundAllChunks) break; + } + + if (tiff.enabled && chunks.EXIF != undefined) { + await this.file.ensureChunk(chunks.EXIF.head, chunks.EXIF.len) + this.createParser('tiff', this.file.subarray(chunks.EXIF.head, chunks.EXIF.len)) + } + if (xmp.enabled && chunks.XMP != undefined) { + await this.file.ensureChunk(chunks.XMP.head, chunks.XMP.len) + this.createParser('xmp', this.file.subarray(chunks.XMP.head, chunks.XMP.len)) + } + if (icc.enabled && chunks.ICCP != undefined) { + await this.file.ensureChunk(chunks.ICCP.head, chunks.ICCP.len) + this.createParser('icc', this.file.subarray(chunks.ICCP.head, chunks.ICCP.len)) + } + } +} + +fileParsers.set('webp', WebpFileParser) \ No newline at end of file diff --git a/test/fixtures/webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp b/test/fixtures/webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp new file mode 100644 index 0000000..8695918 Binary files /dev/null and b/test/fixtures/webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp differ diff --git a/test/fixtures/webp/VRChat_3840x2160_2022-09-04_22-48-26.551.webp b/test/fixtures/webp/VRChat_3840x2160_2022-09-04_22-48-26.551.webp new file mode 100644 index 0000000..e624d37 Binary files /dev/null and b/test/fixtures/webp/VRChat_3840x2160_2022-09-04_22-48-26.551.webp differ diff --git a/test/formats/parser.spec.mjs b/test/formats/parser.spec.mjs index a10b87c..8078bac 100644 --- a/test/formats/parser.spec.mjs +++ b/test/formats/parser.spec.mjs @@ -48,6 +48,10 @@ describe('parser core', () => { await exifr.parse(await getFile('avif/Irvine_CA.avif')) }) + it(`accepts WEBP`, async () => { + await exifr.parse(await getFile('webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp')) + }) + }) }) \ No newline at end of file diff --git a/test/formats/webp.spec.mjs b/test/formats/webp.spec.mjs new file mode 100644 index 0000000..3a24907 --- /dev/null +++ b/test/formats/webp.spec.mjs @@ -0,0 +1,36 @@ +import {assert} from '../test-util-core.mjs' +import {getFile} from '../test-util-core.mjs' +import * as exifr from '../../src/bundles/full.mjs' + + +describe('WEBP - WebpFileParser', () => { + + const options = {xmp: true, tiff: true, icc: true, mergeOutput: false, translateKeys: false, translateValues: false, reviveValues: false} + + const MAKE = 271 + const MODEL = 272 + const PROFILE = 36 + + it(`should extract TIFF from fixture1`, async () => { + let input = await getFile('webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp') + let output = await exifr.parse(input, options) + assert.exists(output.ifd0, 'output should contain IFD0') + assert.equal(output.ifd0[MAKE], 'logilabo') + assert.equal(output.ifd0[MODEL], 'VirtualLens2') + }) + + it(`should extract EXIF from fixture1`, async () => { + let input = await getFile('webp/VRChat_2023-02-19_00-04-01.274_2160x3840.webp') + let output = await exifr.parse(input, options) + assert.exists(output.exif, 'output should contain EXIF') + assert.isDefined(output.exif[36867]) + }) + + it(`should extract ICC from fixture2`, async () => { + let input = await getFile('webp/VRChat_3840x2160_2022-09-04_22-48-26.551.webp') + let output = await exifr.parse(input, options) + assert.exists(output.icc, 'output should contain ICC') + assert.equal(output.icc[PROFILE], 'acsp') + }) + +}) \ No newline at end of file diff --git a/test/index.html b/test/index.html index 1287211..f8c38c6 100644 --- a/test/index.html +++ b/test/index.html @@ -52,6 +52,7 @@ import './formats/parser.spec.mjs' import './formats/jpeg.spec.mjs' import './formats/heic.spec.mjs' + import './formats/webp.spec.mjs' import './formats/avif.spec.mjs' import './formats/png.spec.mjs' import './options.spec.mjs'