2009-10-16 12 views

risposta

21

I dati XMP sono letteralmente incorporati nel file immagine in modo da poterli estrarre con le funzioni stringa di PHP dal file immagine stesso.

Il seguito viene illustrato questa procedura (sto usando SimpleXML ma ogni altra API XML o anche semplice e intelligente analisi stringa può dare risultati uguali):

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

Solo due osservazioni:

  • XMP fa un uso intensivo degli spazi dei nomi XML, quindi è necessario tenerlo d'occhio durante l'analisi dei dati XMP con alcuni strumenti XML.
  • considerando la possibile dimensione dei file di immagine, forse non sarà possibile utilizzare file_get_contents() poiché questa funzione carica l'intera immagine in memoria. L'utilizzo di fopen() per aprire una risorsa di flusso di file e il controllo di blocchi di dati per le sequenze di chiavi <x:xmpmeta e </x:xmpmeta> ridurranno significativamente il footprint di memoria.
+0

Questo spiegherebbe perché non ci sono funzioni specifiche XMP in PHP. – Liam

10

Sto solo rispondendo a questo dopo tanto tempo perché questo sembra essere il miglior risultato quando si cerca su Google come analizzare i dati XMP. Ho visto questo frammento quasi identico usato nel codice alcune volte ed è un terribile spreco di memoria. Ecco un esempio del metodo fopen() che Stefan cita dopo il suo esempio.

<?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 notare che questo si blocca quando l'immagine non contiene dati XMP, anche se sono sicuro che questo può essere facilmente risolto da chi sa come. –

+2

Ho aggiunto un'altra condizione \ break al ciclo while che uccide il ciclo se nessun elemento XMP esiste nel file –

+0

Ho refactored questa funzione per copiare prima il blocco e quindi eseguire il rilevamento/modifica sul buffer piuttosto che provare a farlo contro i pezzi. –

1

ho sviluppato l'estensione Xmp Php Tookit: si tratta di un'estensione PHP5 basato sul XMP toolkit Adobe, che forniscono le principali classi e il metodo per leggere metadatas/scrittura/parse XMP da JPEG, PSD, pdf, video, audio ... Questa estensione è sotto licenza gpl. Presto sarà disponibile una nuova versione, per php 5.3 (ora compatibile solo con php 5.2.x), e dovrebbe essere disponibile su windows e macosx (ora solo per sistemi freebsd e linux). http://xmpphptoolkit.sourceforge.net/

+1

Ho provato il tuo toolkit, ma non sono riuscito a compilarlo :(lamentandomi della mancanza di printf. "Xmp_toolkit/common/XMP_LibUtils.hpp: 179: 62: errore:" printf "non è stato dichiarato in questo ambito" – haggi

4

Un modo semplice su linux è chiamare il programma exiv2, disponibile in un pacchetto omonimo su debian.

$ exiv2 -e X extract image.jpg 

produrrà image.xmp contenente XMP incorporato che ora è il vostro da analizzare.

1

La soluzione di Bryan era la migliore finora, ma aveva alcuni problemi quindi l'ho modificata per semplificarla e rimuovere alcune funzionalità.

C'erano tre problemi che ho trovato con la sua soluzione:

A) Se il pezzo estratto cade proprio tra una delle stringhe che stiamo cercando, ma non la troveranno. Piccole dimensioni del blocco hanno più probabilità di causare questo problema.

B) Se il blocco contiene sia l'inizio che la fine, non lo troverà. Questo è facile da risolvere con un'istruzione extra if per ricontrollare il blocco in cui si trova l'avvio per vedere se viene trovata anche la fine.

C) La dichiarazione else aggiunta alla fine per interrompere il ciclo while se non rileva che i dati xmp hanno un effetto collaterale che se l'elemento start non viene trovato al primo passaggio, non controllerà più pezzi. Questo è probabilmente facile da risolvere, ma con il primo problema non ne vale la pena.

La mia soluzione di seguito non è così potente, ma è più robusta. Controllerà solo un chunk ed estrarrà i dati da quello. Funzionerà solo se l'inizio e la fine sono in quel blocco, quindi la dimensione del blocco deve essere abbastanza grande da garantire che acquisisca sempre tali dati. Dalla mia esperienza con i file esportati da Adobe Photoshop/Lightroom, i dati xmp iniziano tipicamente intorno ai 20kB e terminano intorno ai 45kB. La mia dimensione di chunk di 50k sembra funzionare bene per le mie immagini, sarebbe molto meno se si eliminano alcuni di quei dati all'esportazione, come il blocco CRS che ha molte impostazioni di sviluppo.

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

Ho aggiornato la mia funzione con correzioni per i problemi logici che aveva :) –

+0

Ah, grazie Bryan! Non ho mai notato che tu abbia risposto fino ad ora. Esaminerò il tuo codice revisionato e vedrò se funziona per me (non lo capisco ancora del tutto, non sono un programmatore ...) –

+0

Oooh, ho capito ora .. Stai costruendo il buffer uno pezzo alla volta e controllando sempre il buffer. Questo impedisce tutto il problema che ho elencato. Inteligente! Grazie. –

0

Grazie a voi Sebastien B. per quella versione abbreviata :). Se vuoi evitare il problema, quando chunk_size è troppo piccolo per alcuni file, aggiungi la ricorsione.

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 so ... questo è una specie di un vecchio filo, ma è stato utile a me quando ero alla ricerca di un modo per fare questo, così ho pensato che questo potrebbe essere utile a qualcun altro.

Ho preso questa soluzione di base e l'ho modificata in modo che gestisse il caso in cui il tag è diviso tra i blocchi. Ciò consente alle dimensioni del blocco di essere grandi o piccole come si desidera.

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

Se hai ExifTool a disposizione (uno strumento molto utile) e possibile eseguire comandi esterni, è possibile utilizzare è l'opzione per estrarre i dati XMP (-xmp:all) e l'uscita in formato JSON (-json), che puoi convertire facilmente in un oggetto PHP:

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