2009-10-16 16 views

Respuesta

21

Los datos XMP están literalmente incrustados en el archivo de imagen, por lo que pueden extraerlo con las funciones de cadena de PHP desde el propio archivo de imagen.

La siguiente muestra este procedimiento (estoy usando SimpleXML pero todos los demás API XML o incluso sencilla e inteligente análisis de cadenas que pueden dar resultados iguales):

$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); 

A sólo dos observaciones:

  • XMP hace un uso intensivo de espacios de nombres XML, por lo que tendrá que vigilarlo al analizar los datos XMP con algunas herramientas XML.
  • teniendo en cuenta el tamaño posible de los archivos de imagen, es posible que no pueda usar file_get_contents() ya que esta función carga toda la imagen en la memoria. El uso de fopen() para abrir un recurso de secuencia de archivos y comprobar fragmentos de datos para las secuencias de teclas <x:xmpmeta y </x:xmpmeta> reducirá significativamente la huella de memoria.
+0

Eso explicaría por qué no hay funciones específicas de XMP en PHP. – Liam

10

Solo estoy respondiendo a esto después de tanto tiempo porque este parece ser el mejor resultado al buscar en Google cómo analizar datos XMP. He visto este fragmento casi idéntico usado en el código varias veces y es una terrible pérdida de memoria. Aquí hay un ejemplo del método fopen() que Stefan menciona después de su ejemplo.

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

Vale la pena señalar que esto se bloquea cuando la imagen no contiene datos XMP, aunque estoy seguro de que esto puede ser fácilmente resuelto por alguien que sepa cómo hacerlo. –

+2

Agregué una condición else \ break al ciclo while que mata el ciclo si no existen elementos XMP en el archivo –

+0

Refactoré esta función para copiar el fragmento primero y luego realizar la detección/modificación contra el buffer en lugar de intentar hacerlo contra los pedazos. –

1

he desarrollé la extensión Xmp PHP Tookit: es una extensión PHP5 basado en el kit de herramientas de XMP de Adobe, que proporcionan las principales clases y método para leer metadatos/escritura/análisis sintáctico XMP de JPEG, PSD, PDF, video, audio ... Esta extensión está bajo licencia gpl. Pronto estará disponible una nueva versión para php 5.3 (ahora solo compatible con php 5.2.x), y debería estar disponible en Windows y macosx (ahora solo para sistemas freebsd y linux). http://xmpphptoolkit.sourceforge.net/

+1

Probé su kit de herramientas, pero no pude conseguir que se compile :(Quejándose de la falta de printf. "Xmp_toolkit/common/XMP_LibUtils.hpp: 179: 62: error: 'printf' no se declaró en este ámbito" – haggi

4

Una manera simple en Linux es llamar al programa exiv2, disponible en un paquete del mismo nombre en Debian.

$ exiv2 -e X extract image.jpg 

producirá image.xmp que contiene XMP incorporado que ahora puede analizar.

1

La solución de Bryan fue la mejor hasta ahora, pero tenía algunos problemas, así que la modifiqué para simplificarla y eliminé algunas funciones.

había tres cuestiones que encontré con su solución:

A) Si el trozo extraído cae justo en medio de una de las cadenas que estamos buscando, no lo encontrará. Pequeños tamaños de fragmentos son más propensos a causar este problema.

B) Si el fragmento contiene el inicio Y el final, no lo encontrará. Este es fácil de corregir con una declaración if adicional para volver a verificar el fragmento en el que se encuentra el inicio para ver si también se encuentra el final.

C) La instrucción else añadida al final para romper el ciclo while si no encuentra los datos xmp tiene un efecto secundario que si el elemento de inicio no se encuentra en la primera pasada, ya no volverá a verificar trozos. Esto también es fácil de arreglar, pero con el primer problema no vale la pena.

Mi solución a continuación no es tan potente, pero es más robusta. Solo verificará un fragmento y extraerá los datos de eso. Solo funcionará si el inicio y el final están en ese fragmento, por lo que el tamaño del fragmento debe ser lo suficientemente grande como para garantizar que siempre capture esos datos. A partir de mi experiencia con los archivos exportados de Adobe Photoshop/Lightroom, los datos xmp generalmente comienzan alrededor de los 20 KB y finalizan alrededor de los 45 KB. El tamaño de mi fragmento de 50k parece funcionar muy bien para mis imágenes, sería mucho menos si quitas algunos de esos datos en la exportación, como el bloque CRS que tiene una gran cantidad de configuraciones de desarrollo.

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

He actualizado mi función con correcciones para los problemas de lógica que tenía :) –

+0

Ahh, gracias Bryan! Nunca noté que respondiste hasta ahora. Revisaré tu código revisado y veré si funciona para mí (todavía no lo entiendo completamente, no soy un programador ...) –

+0

Oooh, lo entiendo ahora ... Estás construyendo el buffer uno pedazo a la vez y siempre revisando el buffer. Esto evita todo el problema que enumeré. ¡Inteligente! Gracias. –

0

Gracias Sebastien B. por esa versión abreviada :). Si desea evitar el problema, cuando chunk_size es demasiado pequeño para algunos archivos, simplemente agregue recursividad.

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

lo sé ... esto es una especie de un viejo hilo, pero fue útil para mí cuando yo estaba buscando una manera de hacer esto, por lo que pensé que esto podría ser útil para otra persona.

Tomé esta solución básica y la modifiqué para que maneje el caso donde la etiqueta se divide entre pedazos. Esto permite que el tamaño del fragmento sea tan grande o pequeño como desee.

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

Si tiene ExifTool disponible (una herramienta muy útil) y puede ejecutar comandos externos, puede utilizar su opción para extraer los datos XMP (-xmp:all) y la saca en formato JSON (-json), que luego puede convertir fácilmente a un objeto PHP:

$command = 'exiftool -g -json -struct -xmp:all "'.$image_path.'"'; 
exec($command, $output, $return_var); 
$metadata = implode('', $output); 
$metadata = json_decode($metadata); 
Cuestiones relacionadas