Как я могу читать данные XMP из JPG с помощью PHP?

PHP имеет встроенную поддержку чтения метаданных EXIF ​​и IPTC, но я не могу найти способ прочитать XMP?

Ответы

Ответ 1

Данные XMP буквально внедряются в файл изображения, поэтому можно извлечь его с помощью строковых функций PHP из самого файла изображения.

Ниже демонстрируется эта процедура (я использую SimpleXML, но каждый другой XML-интерфейс или даже простой и умный синтаксический анализ строк могут дать вам равные результаты):

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

Ответ 2

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

Ответ 3

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

$ exiv2 -e X extract image.jpg

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

Ответ 4

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

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

<?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 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;
}
?>

Ответ 5

Я разработал расширение 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/

Ответ 6

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

Ответ 7

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

Ответ 8

Если у вас есть 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);