Mercurial > hg > mgallery
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 } |