2009-10-16 61 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名稱空間,所以當用一些XML工具解析XMP數據時,您必須留意這一點。
  • 考慮到圖像文件的可能大小,您可能無法使用file_get_contents(),因爲此功能會將整個圖像加載到內存中。使用fopen()打開文件流資源並檢查密鑰序列<x:xmpmeta</x:xmpmeta>的數據塊將顯着減少內存佔用量。
+0

這就可以解釋爲什麼PHP中沒有XMP特定函數。 – Liam 2009-10-16 14:32:29

10

我只在這麼多時間後纔回復此問題,因爲這在搜索Google如何解析XMP數據時似乎是最好的結果。我幾次在代碼中看到這個幾乎相同的代碼片段,這是一個可怕的內存浪費。這是Stefan在他的例子後提到的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數據時,這會掛起,儘管我確信這可以很容易地被知道如何解決的人解決。 – 2011-05-22 18:09:42

+2

如果文件中不存在XMP元素,我在while循環中添加了一個else \ break條件 – 2012-04-30 18:14:37

+0

我重構了此函數以便首先複製塊,然後對緩衝區執行檢測/修改,而​​不是嘗試這樣做大塊。 – 2012-10-17 04:15:44

1

我developped的XMP腓的無線工具包的擴展:它是基於Adobe XMP工具包PHP5的擴展,它提供了主要的類和方法來讀取JPEG,PSD,PDF /寫/解析XMP metadatas,視頻,音頻......此擴展程序在gpl許可下。一個新版本將很快推出,對於PHP 5.3(現在只與php 5.2.x兼容),應該可以在windows和macosx(現在只適用於freebsd和linux系統)上使用。 http://xmpphptoolkit.sourceforge.net/

+1

我試過你的工具包,但是我無法編譯:(抱怨缺少printf。「xmp_toolkit/common/XMP_LibUtils.hpp:179:62:錯誤:'printf'未在此範圍內聲明」 – haggi 2012-11-07 14:54:59

4

Linux上的一個簡單方法是調用exiv2程序,該程序在debian上的同名軟件包中可用。

$ exiv2 -e X extract image.jpg 

將生成包含嵌入式XMP的image.xmp,該嵌入式XMP現在可以解析。

1

布賴恩的解決方案迄今爲止是最好的解決方案,但它有一些問題,所以我修改它以簡化它,並刪除一些功能。

有三個問題,我發現他的解決方案:

A)如果提取的塊落在正確的,我們要搜索的字符串之一之間,也不會發現它。小塊大小更可能導致此問題。 B)如果塊包含開始和結束,它將不會找到它。使用額外的if語句重新檢查啓動發現塊以查看是否也找到了結尾,這是一個很容易解決的問題。

C)else語句添加到最後打破while循環,如果它沒有找到xmp數據有一個副作用,如果第一遍沒有找到start元素,它將不再檢查塊。這可能很容易解決,但第一個問題是不值得的。

我的解決方案不如以前強大,但它更強大。它只會檢查一個塊,並從中提取數據。它只有在開始和結束位於該塊中時纔有效,所以塊大小需要足夠大以確保它始終捕獲該數據。從我使用Adobe Photoshop/Lightroom導出文件的經驗來看,xmp數據通常在20kB左右開始,並在45kB左右結束。我的塊大小爲50k似乎對我的圖像很好,如果您在導出時剝離某些數據(例如具有大量開發設置的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

我更新了我的功能修復了它的邏輯問題:) – 2012-10-18 13:33:28

+0

啊,謝謝布萊恩!我從來沒有注意到你直到現在纔回復。我會檢查你的修改後的代碼,看看它是否適用於我(我還沒有完全理解它,我不是程序員...) – 2013-02-28 19:26:19

+0

Oooh,我現在明白了..你正在構建緩衝區一次一個塊並且總是檢查緩衝區。這可以防止我列出的所有問題。聰明!謝謝。 – 2013-02-28 19:33:50

0

謝謝塞巴斯蒂安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);