remove EXIF information from PNG, JPEG and WebP images jsr.io/@mary/exif-rm
typescript jsr
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: webp support

closes https://codeberg.org/mary-ext/pkg-exif-rm/issues/1

Mary 79344bd1 c7a9ece4

+88
+37
mod.ts
··· 10 10 const indices: [start: number, end: number][] = []; 11 11 12 12 let start = 0; 13 + let webp = false; 13 14 14 15 if (blen >= 2 && view.getUint16(0) === 0xffd8) { 15 16 // JPEG ··· 74 75 75 76 pos = end; 76 77 } 78 + } else if ( 79 + blen >= 12 && 80 + /* RIFF */ view.getUint32(0) === 0x52494646 && 81 + /* WEBP */ view.getUint32(8) === 0x57454250 82 + ) { 83 + // WebP 84 + let pos = 12; 85 + 86 + webp = true; 87 + 88 + // RIFF format uses little-endian 89 + while (pos + 4 + 4 <= blen) { 90 + const marker = view.getUint32(pos, true); 91 + 92 + const chunkSize = view.getUint32(pos + 4, true); 93 + const end = pos + chunkSize + 4 + 4; 94 + 95 + if (/* EXIF */ marker === 0x46495845 || /* "XMP "" */ marker === 0x20504D58) { 96 + indices.push([start, pos]); 97 + start = end; 98 + } 99 + 100 + // Move to the next chunk, add padding byte if chunk size is odd 101 + pos = end; 102 + if (chunkSize & 1) { 103 + pos++; 104 + } 105 + } 77 106 } 78 107 79 108 if (start === 0) { ··· 89 118 return offset + (index[1] - index[0]); 90 119 }, 0), 91 120 ); 121 + 122 + if (webp) { 123 + // Alter the RIFF header to reflect the new size 124 + const view = new DataView(copy.buffer); 125 + const newSize = copy.byteLength - 8; 126 + 127 + view.setUint32(4, newSize, true); 128 + } 92 129 93 130 return copy; 94 131 };
samples/sample.webp

This is a binary file and will not be displayed.

+51
test.ts
··· 153 153 assert(exifRemoved !== null); 154 154 assertEquals(await identify(exifRemoved), {}); 155 155 }); 156 + 157 + Deno.test('removes EXIF from WebP files', async () => { 158 + const image = await Deno.readFile('./samples/sample.webp'); 159 + 160 + // Verify the original image has EXIF data 161 + assertEquals(await identify(image), { 162 + 'exif:ApertureValue': '45/8', 163 + 'exif:ColorSpace': '1', 164 + 'exif:ComponentsConfiguration': '...', 165 + 'exif:CustomRendered': '0', 166 + 'exif:DateTime': '2008:07:31 10:38:11', 167 + 'exif:DateTimeDigitized': '2008:05:30 15:56:01', 168 + 'exif:DateTimeOriginal': '2008:05:30 15:56:01', 169 + 'exif:ExifOffset': '190', 170 + 'exif:ExifVersion': '0221', 171 + 'exif:ExposureBiasValue': '0/1', 172 + 'exif:ExposureMode': '1', 173 + 'exif:ExposureProgram': '1', 174 + 'exif:ExposureTime': '1/160', 175 + 'exif:FNumber': '71/10', 176 + 'exif:Flash': '9', 177 + 'exif:FlashPixVersion': '0100', 178 + 'exif:FocalLength': '135/1', 179 + 'exif:FocalPlaneResolutionUnit': '2', 180 + 'exif:FocalPlaneXResolution': '324000/73', 181 + 'exif:FocalPlaneYResolution': '2592000/583', 182 + 'exif:Make': 'Canon', 183 + 'exif:MeteringMode': '5', 184 + 'exif:Model': 'Canon EOS 40D', 185 + 'exif:Orientation': '1', 186 + 'exif:PhotographicSensitivity': '100', 187 + 'exif:PixelXDimension': '100', 188 + 'exif:PixelYDimension': '68', 189 + 'exif:ResolutionUnit': '2', 190 + 'exif:SceneCaptureType': '0', 191 + 'exif:ShutterSpeedValue': '59/8', 192 + 'exif:Software': 'GIMP 2.4.5', 193 + 'exif:SubSecTime': '00', 194 + 'exif:SubSecTimeDigitized': '00', 195 + 'exif:SubSecTimeOriginal': '00', 196 + 'exif:WhiteBalance': '0', 197 + 'exif:XResolution': '72/1', 198 + 'exif:YResolution': '72/1', 199 + }); 200 + 201 + const exifRemoved = remove(image); 202 + 203 + // Verify the image has no EXIF data 204 + assert(exifRemoved !== null); 205 + assertEquals(await identify(exifRemoved), {}); 206 + });