2009-10-16 3 views

ответ

21

Данные 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> значительно уменьшат объем памяти.
+0

Это объясняет, почему в PHP нет определенных функций XMP. – Liam

10

Я только отвечаю на это после столь долгого времени, потому что это, по-видимому, лучший результат при поиске в 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; 
} 
+0

стоит отметить, что это зависает, когда изображение не содержит данных XMP, хотя я уверен, что это легко решить тот, кто знает, как это сделать. –

+2

Я добавил условие else \ break для цикла while, который убивает цикл, если в файле нет элементов XMP. –

+0

Я реорганизовал эту функцию, чтобы сначала скопировать кусок, а затем выполнить обнаружение/модификацию в отношении буфера, а не пытаться сделать это против куски. –

1

Я разработана расширение XMP Php Tookit: это расширение php5 на основе XMP инструментария самана, которые обеспечивают основные классы и методы для чтения/записи/синтаксического анализа XMP метаданные из JPEG, PSD, PDF, видео, аудио ... Это расширение под лицензией gpl. Скоро будет выпущена новая версия для php 5.3 (теперь она совместима только с php 5.2.x) и должна быть доступна для окон и macosx (теперь только для freebsd и linux систем). http://xmpphptoolkit.sourceforge.net/

+1

Я пробовал ваш инструментарий, но я не смог его скомпилировать :(Жалуется на отсутствие printf. «Xmp_toolkit/common/XMP_LibUtils.hpp: 179: 62: ошибка:« printf »не была объявлена ​​в этой области» – haggi

4

Простым способом в linux является вызов программы exiv2, доступной в одноименном пакете на debian.

$ exiv2 -e X extract image.jpg 

будет производить изображение.xmp, содержащее встроенный XMP, который теперь используется для анализа.

1

Решение 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; 
} 
+0

Я обновил моя функция с исправлениями для логических проблем, которые она имела :) –

+0

Ах, спасибо Брайан! Я никогда не замечал, что вы ответили до сих пор. Я просмотрю ваш пересмотренный код и посмотрю, будет ли он работать для меня (я еще не все понял, я не программист ...) –

+0

Ооо, я понял это сейчас. Вы создаете буферный кусок за раз и всегда проверяя буфер. Это предотвратит все перечисленные проблемы. Умная! Благодарю. –

0

Спасибо Sebastien B. за эту укороченную версию :). Если вы хотите избежать проблемы, когда 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; 
} 
3

Я знаю ... это вроде старой нити, но это было полезно для меня, когда я искал способ сделать это, поэтому я решил, что это может быть полезным для кого-то другого.

Я принял это основное решение и изменил его, чтобы он обрабатывал случай, когда тег разделен между кусками. Это позволяет размеру блока быть таким же большим или малым, как вы хотите.

<?php 
 
function getXmpData($filename, $chunk_size = 1024) 
 
{ 
 
\t if (!is_int($chunkSize)) { 
 
\t \t throw new RuntimeException('Expected integer value for argument #2 (chunkSize)'); 
 
\t } 
 

 
\t if ($chunkSize < 12) { 
 
\t \t throw new RuntimeException('Chunk size cannot be less than 12 argument #2 (chunkSize)'); 
 
\t } 
 

 
\t if (($file_pointer = fopen($filename, 'rb')) === FALSE) { 
 
\t \t throw new RuntimeException('Could not open file for reading'); 
 
\t } 
 

 
\t $tag = '<x:xmpmeta'; 
 
\t $buffer = false; 
 

 
\t // find open tag 
 
\t while ($buffer === false && ($chunk = fread($file_pointer, $chunk_size)) !== false) { 
 
\t \t if(strlen($chunk) <= 10) { 
 
\t \t \t break; 
 
\t \t } 
 
\t \t if(($position = strpos($chunk, $tag)) === false) { 
 
\t \t \t // if open tag not found, back up just in case the open tag is on the split. 
 
\t \t \t fseek($file_pointer, -10, SEEK_CUR); 
 
\t \t } else { 
 
\t \t \t $buffer = substr($chunk, $position); 
 
\t \t } 
 
\t } 
 

 
\t if($buffer === false) { 
 
\t \t fclose($file_pointer); 
 
\t \t return false; 
 
\t } 
 

 
\t $tag = '</x:xmpmeta>'; 
 
\t $offset = 0; 
 
\t while (($position = strpos($buffer, $tag, $offset)) === false && ($chunk = fread($file_pointer, $chunk_size)) !== FALSE && !empty($chunk)) { 
 
\t \t $offset = strlen($buffer) - 12; // subtract the tag size just in case it's split between chunks. 
 
\t \t $buffer .= $chunk; 
 
\t } 
 

 
\t fclose($file_pointer); 
 

 
\t if($position === false) { 
 
\t \t // this would mean the open tag was found, but the close tag was not. Maybe file corruption? 
 
\t \t throw new RuntimeException('No close tag found. Possibly corrupted file.'); 
 
\t } else { 
 
\t \t $buffer = substr($buffer, 0, $position + 12); 
 
\t } 
 

 
\t return $buffer; 
 
} 
 
?>

0

Если у вас есть 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); 
Смежные вопросы