Как я могу прочитать данные 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

Другие вопросы по тегам