PHP имеет встроенную поддержку чтения метаданных EXIF и IPTC, но я не могу найти способ читать XMP?
Данные XMP буквально встроены в файл изображения, поэтому можно извлечь его с помощью строковых функций PHP из самого файла изображения.
Следующее демонстрирует эту процедуру (я использую SimpleXML, но любой другой XML API или даже простой и умный синтаксический анализ строк может дать вам равные результаты):
$content = file_get_contents($image); $xmp_data_start = strpos($content, '<x:xmpmeta'); $xmp_data_end = strpos($content, '</x:xmpmeta>'); $xmp_length = $xmp_data_end - $xmp_data_start; $xmp_data = substr($content, $xmp_data_start, $xmp_length + 12); $xmp = simplexml_load_string($xmp_data);
Всего два замечания:
file_get_contents()
поскольку эта функция загружает все изображение в память. Используя fopen()
чтобы открыть ресурс потока файлов и проверить куски данных для последовательностей ключей <x:xmpmeta
и </x:xmpmeta>
, значительно уменьшит объем памяти. Я только отвечаю на это через столько времени, потому что это, по-видимому, лучший результат при поиске в Google для того, как разбирать данные XMP. Я видел этот почти идентичный фрагмент, используемый в коде несколько раз, и это ужасная трата памяти. Вот пример метода fopen (), который Стефан упоминает после своего примера.
<?php function getXmpData($filename, $chunkSize) { if (!is_int($chunkSize)) { throw new RuntimeException('Expected integer value for argument #2 (chunkSize)'); } if ($chunkSize < 12) { throw new RuntimeException('Chunk size cannot be less than 12 argument #2 (chunkSize)'); } if (($file_pointer = fopen($filename, 'r')) === FALSE) { throw new RuntimeException('Could not open file for reading'); } $startTag = '<x:xmpmeta'; $endTag = '</x:xmpmeta>'; $buffer = NULL; $hasXmp = FALSE; while (($chunk = fread($file_pointer, $chunkSize)) !== FALSE) { if ($chunk === "") { break; } $buffer .= $chunk; $startPosition = strpos($buffer, $startTag); $endPosition = strpos($buffer, $endTag); if ($startPosition !== FALSE && $endPosition !== FALSE) { $buffer = substr($buffer, $startPosition, $endPosition - $startPosition + 12); $hasXmp = TRUE; break; } elseif ($startPosition !== FALSE) { $buffer = substr($buffer, $startPosition); $hasXmp = TRUE; } elseif (strlen($buffer) > (strlen($startTag) * 2)) { $buffer = substr($buffer, strlen($startTag)); } } fclose($file_pointer); return ($hasXmp) ? $buffer : NULL; }
Простым способом в linux является вызов exiv2-программы, доступной в одноименном пакете на debian.
$ exiv2 -e X extract image.jpg
будет создавать image.xmp, содержащий встроенный XMP, который теперь используется для анализа.
Я знаю … это своего рода старая нить, но это было полезно для меня, когда я искал способ сделать это, поэтому я решил, что это может быть полезно кому-то другому.
Я принял это базовое решение и изменил его, поэтому он обрабатывает случай, когда тег разделяется между кусками. Это позволяет размеру блока быть таким же большим или малым, как вы хотите.
<?php function getXmpData($filename, $chunk_size = 1024) { if (!is_int($chunkSize)) { throw new RuntimeException('Expected integer value for argument #2 (chunkSize)'); } if ($chunkSize < 12) { throw new RuntimeException('Chunk size cannot be less than 12 argument #2 (chunkSize)'); } if (($file_pointer = fopen($filename, 'rb')) === FALSE) { throw new RuntimeException('Could not open file for reading'); } $tag = '<x:xmpmeta'; $buffer = false; // find open tag while ($buffer === false && ($chunk = fread($file_pointer, $chunk_size)) !== false) { if(strlen($chunk) <= 10) { break; } if(($position = strpos($chunk, $tag)) === false) { // if open tag not found, back up just in case the open tag is on the split. fseek($file_pointer, -10, SEEK_CUR); } else { $buffer = substr($chunk, $position); } } if($buffer === false) { fclose($file_pointer); return false; } $tag = '</x:xmpmeta>'; $offset = 0; while (($position = strpos($buffer, $tag, $offset)) === false && ($chunk = fread($file_pointer, $chunk_size)) !== FALSE && !empty($chunk)) { $offset = strlen($buffer) - 12; // subtract the tag size just in case it's split between chunks. $buffer .= $chunk; } fclose($file_pointer); if($position === false) { // this would mean the open tag was found, but the close tag was not. Maybe file corruption? throw new RuntimeException('No close tag found. Possibly corrupted file.'); } else { $buffer = substr($buffer, 0, $position + 12); } return $buffer; } ?>
Я разработал расширение Xmp Php Tookit: это расширение php5 на основе инструментария adobe xmp, которые предоставляют основные классы и методы для чтения / записи / анализа метаданных xmp из jpeg, psd, pdf, video, audio … Это расширение находится под лицензией gpl. Скоро будет выпущена новая версия для php 5.3 (теперь она совместима только с php 5.2.x) и должна быть доступна для окон и macosx (теперь только для freebsd и linux систем). http://xmpphptoolkit.sourceforge.net/
Решение Bryan было самым лучшим до сих пор, но у него было несколько проблем, поэтому я изменил его, чтобы упростить его, и удалить некоторые функции.
Было найдено три вопроса с его решением:
A) Если извлеченный фрагмент попадает прямо между одной из строк, которые мы ищем, он не найдет ее. Это может привести к меньшим размерам блоков.
B) Если кусок содержит как начало, так и конец, он не найдет его. Это легко исправить с помощью дополнительного оператора if, чтобы перепроверить кусок, который найден в начале, чтобы узнать, найден ли конец.
C) Оператор else добавлен в конец, чтобы разбить цикл while, если он не обнаружил, что данные xmp имеют побочный эффект: если начальный элемент не найден на первом проходе, он не будет проверять больше кусков. Скорее всего, это легко исправить, но с первой проблемой это не стоит.
Мое решение ниже не так сильно, но оно более надежное. Он будет проверять только один кусок и извлекать данные из этого. Он будет работать только в том случае, если начало и конец находятся в этом фрагменте, поэтому размер блока должен быть достаточно большим, чтобы гарантировать, что он всегда захватывает эти данные. Из моего опыта работы с файлами Adobe Photoshop / Lightroom данные xmp обычно начинаются со скоростью около 20 КБ и заканчиваются примерно на 45 КБ. Размер моего куска размером 50 тыс. Кажется приятным для моих изображений, было бы намного меньше, если бы вы разделили некоторые данные на экспорт, такие как блок CRS, который имеет множество параметров разработки.
function getXmpData($filename) { $chunk_size = 50000; $buffer = NULL; if (($file_pointer = fopen($filename, 'r')) === FALSE) { throw new RuntimeException('Could not open file for reading'); } $chunk = fread($file_pointer, $chunk_size); if (($posStart = strpos($chunk, '<x:xmpmeta')) !== FALSE) { $buffer = substr($chunk, $posStart); $posEnd = strpos($buffer, '</x:xmpmeta>'); $buffer = substr($buffer, 0, $posEnd + 12); } fclose($file_pointer); return $buffer; }
Спасибо Себастьен Б. за эту сокращенную версию :). Если вы хотите избежать проблемы, когда chunk_size слишком мал для некоторых файлов, просто добавьте рекурсию.
function getXmpData($filename, $chunk_size = 50000){ $buffer = NULL; if (($file_pointer = fopen($filename, 'r')) === FALSE) { throw new RuntimeException('Could not open file for reading'); } $chunk = fread($file_pointer, $chunk_size); if (($posStart = strpos($chunk, '<x:xmpmeta')) !== FALSE) { $buffer = substr($chunk, $posStart); $posEnd = strpos($buffer, '</x:xmpmeta>'); $buffer = substr($buffer, 0, $posEnd + 12); } fclose($file_pointer); // recursion here if(!strpos($buffer, '</x:xmpmeta>')){ $buffer = getXmpData($filename, $chunk_size*2); } return $buffer; }
Если вы имеете ExifTool (очень полезный инструмент) и можете запускать внешние команды, вы можете использовать его вариант для извлечения данных XMP ( -xmp:all
) и вывода его в формате JSON ( -json
), который затем можно легко преобразовать в объект PHP:
$command = 'exiftool -g -json -struct -xmp:all "'.$image_path.'"'; exec($command, $output, $return_var); $metadata = implode('', $output); $metadata = json_decode($metadata);