Как я могу прочитать данные XMP из JPG с PHP?
PHP имеет встроенную поддержку для чтения метаданных EXIF и IPTC, но я не могу найти способ прочитать XMP?
10 ответов
Данные 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);
Всего два замечания:
- XMP интенсивно использует пространства имен XML, поэтому вам придется следить за этим при анализе данных XMP с помощью некоторых инструментов XML.
- учитывая возможный размер файлов изображений, вы, возможно, не сможете использовать
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;
}
?>
Спасибо Себастьену Б. за эту сокращенную версию:). Если вы хотите избежать этой проблемы, когда 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;
}
Пока что решение Брайана было лучшим, но у него было несколько проблем, поэтому я изменил его, чтобы упростить и удалить некоторые функции.
Было три проблемы, которые я нашел с его решением:
A) Если извлеченный кусок окажется прямо между одной из строк, которую мы ищем, он не найдет ее. Небольшие размеры фрагментов чаще вызывают эту проблему.
Б) Если блок содержит начало и конец, он не найдет его. Это легко исправить с помощью дополнительного оператора 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;
}
Если у вас есть 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);
Я разработал расширение Xmp Php Tookit: это расширение php5, основанное на наборе инструментов Adobe XMP, которое предоставляет основные классы и метод для чтения / записи / анализа метаданных xmp из jpeg, psd, pdf, video, audio... This расширение находится под лицензией GPL. Новая версия будет доступна в ближайшее время для php 5.3 (теперь совместима только с php 5.2.x) и должна быть доступна для windows и macosx (теперь только для систем freebsd и linux). http://xmpphptoolkit.sourceforge.net/
Если вы можете установить exiv2 в своей среде:
sudo apt install exiv2
затем, основываясь на ответе Fluxine, можно использовать exiv2 для извлечения всех метаданных изображения (EXIF, IPTC и XMP) в ассоциативный массив:
function image_meta_data($image_path) {
$meta_data = [];
// execute exiv2 via the command line
exec('exiv2 -Pkt ' . $image_path, $output = null, $retval = null);
// process output into associative array
foreach ($output as $line) {
$key = trim(substr($line, 0, 46));
$value = str_replace('lang="x-default" ', '', trim(substr($line, 46))); // remove in-line language tag
$meta_data[$key] = $value;
}
return $meta_data;
}
Использование:
$meta = image_meta_data($image_path);
print_r($meta);
// Examples:
echo $meta['Xmp.dc.title'] ?? '';
echo $meta['Iptc.Application2.DateCreated'] ?? '';
echo $meta['Exif.Image.ImageDescription'] ?? '';
Теперь есть также репозиторий github, который вы можете добавить через композитор, который может читать данные xmp:
https://github.com/jeroendesloovere/xmp-metadata-extractor
composer require jeroendesloovere/xmp-metadata-extractor