comparison mgtool.php @ 314:020d155a179d

Refactor EXIF handling and integrate it with XMP handling. With a disgusting hack we now also support EXIF in WebP.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 06 Apr 2020 19:05:13 +0300
parents d94b0ebe97c6
children 8a69e693e08c
comparison
equal deleted inserted replaced
313:d94b0ebe97c6 314:020d155a179d
246 return TRUE; 246 return TRUE;
247 } 247 }
248 248
249 249
250 // 250 //
251 // Read XMP data block from given file 251 // Read EXIF and XMP data from a file
252 // .. it's a horrible hack. 252 //
253 // 253 // TODO XXX: Perhaps support XMP sidecar files?
254 function mgReadXMPFromRAWData($filename, $xmpBlockSize = 1024*64) 254 //
255 { 255 function mgReadEXIFAndXMPData($filename, &$exif, &$xmp)
256 {
257 $exif = FALSE;
258 $xmp = FALSE;
259
256 if (($fh = @fopen($filename, 'rb')) === FALSE) 260 if (($fh = @fopen($filename, 'rb')) === FALSE)
257 return FALSE; 261 return "Could not open file for reading.";
258 262
259 $xmpStartTag = "<x:xmpmeta"; 263 $fileData = fstat($fh);
260 $xmpEndTag = "</x:xmpmeta>"; 264
261 265 // Probe the file for type
262 // Check for start tag 266 $probeSize = 4 * 3;
263 $buffer = ""; 267 if (($probeData = @fread($fh, $probeSize)) === FALSE)
264 $xmpOK = FALSE; 268 return "Error reading file for type probe";
265 while (!feof($fh)) 269
266 { 270 $probe = unpack("C4magic/L1riffsize/c4riffid", $probeData);
267 if (($tmp = @fread($fh, $xmpBlockSize)) === FALSE) 271
268 return FALSE; 272 // Check for RIFF / WEBP
269 273 if ($probe["magic1"] == 0x52 && $probe["magic2"] == 0x49 &&
270 $buffer .= $tmp; 274 $probe["magic3"] == 0x46 && $probe["magic4"] == 0x46 &&
271 if (($spos1 = strpos($buffer, "<")) !== FALSE) 275 $probe["riffid1"] == 0x57 && $probe["riffid2"] == 0x45 &&
272 { 276 $probe["riffid3"] == 0x42 && $probe["riffid4"] == 0x50)
273 $buffer = substr($buffer, $spos1); 277 {
274 if (($spos2 = strpos($buffer, $xmpStartTag)) !== FALSE) 278 if ($probe["riffsize"] > $fileData["size"])
275 { 279 return "Invalid WebP file, chunk size larger than file size";
276 $buffer = substr($buffer, $spos2); 280
277 $xmpOK = TRUE; 281 $done = 0;
278 break; 282 while (!feof($fh) && $done < 2)
279 } 283 {
280 } 284 // Read chunk header
281 else 285 if (($data = @fread($fh, 2 * 4)) == FALSE)
282 $buffer = ""; 286 return "File read error in WebP RIFF chunk header";
283 } 287
284 288 $chunk = unpack("c4id/L1size", $data);
285 // Check for end tag if start tag was found 289
286 if ($xmpOK) 290 /*
287 { 291 printf("chunk: '%c%c%c%c' (%02x %02x %02x %02x) osize=%d\n",
292 $chunk["id1"], $chunk["id2"], $chunk["id3"], $chunk["id4"],
293 $chunk["id1"], $chunk["id2"], $chunk["id3"], $chunk["id4"],
294 $chunk["size"]);
295 */
296
297 // Check for EXIF chunk
298 if ($chunk["id1"] == 0x45 && $chunk["id2"] == 0x58 &&
299 $chunk["id3"] == 0x49 && $chunk["id4"] == 0x46)
300 {
301 // This is an incredibly stupid hack to work around the
302 // fact that PHP's exif_read_data() wants to seek to stream
303 // start and probe things .. if we just had a direct parser
304 // function we would not need this shit.
305 if (($tmpEXIF = @fread($fh, $chunk["size"])) === FALSE)
306 return "Error reading WebP EXIF chunk";
307
308 // Create a temporary file for the EXIF data
309 if (($tmpFile = @tmpfile()) === FALSE)
310 return "Could not create temporary WebP EXIF data file";
311
312 if ((@fwrite($tmpFile, $tmpEXIF, $chunk["size"])) === FALSE)
313 {
314 fclose($tmpFile);
315 return "Error writing WebP EXIT chunk to temporary file";
316 }
317
318 // Parse the EXIF from the temp file
319 $exif = @exif_read_data($tmpFile);
320 fclose($tmpFile);
321
322 $done++;
323 }
324 else
325 if ($chunk["id1"] == 0x58 && $chunk["id2"] == 0x4d &&
326 $chunk["id3"] == 0x50 && $chunk["id4"] == 0x20)
327 {
328 // Read and parse XMP data chunk
329 if (($xmpStr = fread($fh, $chunk["size"])) === FALSE)
330 return "File read error in XMP data read";
331
332 $xmp = mgParseXMPData($xmpStr);
333
334 $done++;
335 }
336 else
337 {
338 // Skip other chunks
339 if (fseek($fh, $chunk["size"], SEEK_CUR) < 0)
340 return "File seek error in chunk skip";
341 }
342
343 // If the chunk size is not aligned, skip one byte
344 if ($chunk["size"] & 1)
345 {
346 if (fseek($fh, 1, SEEK_CUR) < 0)
347 return "File seek error in chunk skip";
348 }
349 }
350
351 return TRUE;
352 }
353 else
354 {
355 // Other fileformats, e.g. JPEG, PNG, GIF, ..
356
357 // Read EXIF ..
358 if (fseek($fh, 0, SEEK_SET) < 0)
359 return "File seek error in EXIF fptr restore";
360
361 $exif = @exif_read_data($fh);
362
363 if (fseek($fh, 0, SEEK_SET) < 0)
364 return "File seek error in EXIF fptr restore";
365
366 // Read XMP data block from the file .. it's a horrible hack.
367 $xmpStartTag = "<x:xmpmeta";
368 $xmpEndTag = "</x:xmpmeta>";
369 $xmpBlockSize = 64 * 1024;
370
371 // Check for start tag
372 $buffer = "";
288 $xmpOK = FALSE; 373 $xmpOK = FALSE;
289 $buffer2 = $buffer; 374 while (!feof($fh))
290 do 375 {
291 { 376 if (($tmp = fread($fh, $xmpBlockSize)) === FALSE)
292 if (($spos1 = strpos($buffer2, "<")) !== FALSE) 377 return "File read error in JPEG XMP read";
293 { 378
294 $buffer2 = substr($buffer2, $spos1); 379 $buffer .= $tmp;
295 if (($spos2 = strpos($buffer2, $xmpEndTag)) !== FALSE) 380 if (($spos1 = strpos($buffer, "<")) !== FALSE)
381 {
382 $buffer = substr($buffer, $spos1);
383 if (($spos2 = strpos($buffer, $xmpStartTag)) !== FALSE)
296 { 384 {
385 $buffer = substr($buffer, $spos2);
297 $xmpOK = TRUE; 386 $xmpOK = TRUE;
298 break; 387 break;
299 } 388 }
300 } 389 }
301
302 if (($tmp = @fread($fh, $xmpBlockSize)) !== FALSE)
303 {
304 $buffer2 .= $tmp;
305 $buffer .= $tmp;
306 }
307 else 390 else
308 { 391 $buffer = "";
309 $xmpOK = FALSE; 392 }
310 break; 393
311 } 394 // Check for end tag if start tag was found
312 } while (!$xmpOK);
313
314 if ($xmpOK) 395 if ($xmpOK)
315 { 396 {
316 if (($spos = strpos($buffer, $xmpEndTag)) !== FALSE) 397 $xmpOK = FALSE;
317 $buffer = substr($buffer, 0, $spos + strlen($xmpEndTag)); 398 $buffer2 = $buffer;
318 else 399 do
319 $xmpOK = FALSE; 400 {
320 } 401 if (($spos1 = strpos($buffer2, "<")) !== FALSE)
321 } 402 {
322 403 $buffer2 = substr($buffer2, $spos1);
323 fclose($fh); 404 if (($spos2 = strpos($buffer2, $xmpEndTag)) !== FALSE)
324 405 {
325 return $xmpOK ? $buffer : FALSE; 406 $xmpOK = TRUE;
407 break;
408 }
409 }
410
411 if (($tmp = @fread($fh, $xmpBlockSize)) !== FALSE)
412 {
413 $buffer2 .= $tmp;
414 $buffer .= $tmp;
415 }
416 else
417 {
418 $xmpOK = FALSE;
419 break;
420 }
421 } while (!$xmpOK);
422
423 if ($xmpOK)
424 {
425 if (($spos = strpos($buffer, $xmpEndTag)) !== FALSE)
426 $buffer = substr($buffer, 0, $spos + strlen($xmpEndTag));
427 else
428 $xmpOK = FALSE;
429 }
430 }
431
432 $xmp = mgParseXMPData($buffer);
433
434 return TRUE;
435 }
326 } 436 }
327 437
328 438
329 function mgParseXMPData($xmpStr) 439 function mgParseXMPData($xmpStr)
330 { 440 {
980 if (file_exists($capFilename) && 1090 if (file_exists($capFilename) &&
981 mgNeedUpdate($galEntry, "mtime", filemtime($capFilename))) 1091 mgNeedUpdate($galEntry, "mtime", filemtime($capFilename)))
982 $updFlags |= GUPD_CAPTION; 1092 $updFlags |= GUPD_CAPTION;
983 1093
984 // Check for EXIF and XMP info 1094 // Check for EXIF and XMP info
985 if ($updFlags & GUPD_EXIF_INFO) 1095 if (($updFlags & GUPD_EXIF_INFO) &&
1096 ($res = mgReadEXIFAndXMPData($efilename, $exif, $xmp)) === TRUE)
986 { 1097 {
987 // TODO XXX: Perhaps support XMP sidecar files 1098 if ($xmp !== FALSE)
988 if (($xmpStr = mgReadXMPFromRAWData($efilename)) !== FALSE &&
989 ($xmp = mgParseXMPData($xmpStr)) !== FALSE)
990 { 1099 {
1100 echo "@";
991 foreach ($galExifConversions as $conv) 1101 foreach ($galExifConversions as $conv)
992 mgCopyEntryData($edata, $xmp, $conv[GEC_TYPE], $conv[GEC_NAME], $conv[GEC_FIELDS]); 1102 mgCopyEntryData($edata, $xmp, $conv[GEC_TYPE], $conv[GEC_NAME], $conv[GEC_FIELDS]);
993 } 1103 }
994 1104
995 if (($exif = @exif_read_data($efilename)) !== FALSE) 1105 if ($exif !== FALSE)
996 { 1106 {
997 echo "%"; 1107 echo "%";
998 foreach ($galExifConversions as $conv) 1108 foreach ($galExifConversions as $conv)
999 mgCopyEntryData($edata, $exif, $conv[GEC_TYPE], $conv[GEC_NAME], $conv[GEC_FIELDS]); 1109 mgCopyEntryData($edata, $exif, $conv[GEC_TYPE], $conv[GEC_NAME], $conv[GEC_FIELDS]);
1000 } 1110 }