【问题标题】:How can I read XMP data from a JPG with PHP?如何使用 PHP 从 JPG 读取 XMP 数据?
【发布时间】:2009-10-16 13:50:40
【问题描述】:

PHP 已内置支持读取 EXIF 和 IPTC 元数据,但我找不到任何读取 XMP 的方法?

【问题讨论】:

    标签: php metadata jpeg xmp


    【解决方案1】:

    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() 打开文件流资源并检查键序列&lt;x:xmpmeta&lt;/x:xmpmeta&gt; 的数据块将显着减少内存占用。

    【讨论】:

    • 这可以解释为什么 PHP 中没有 XMP 特定的函数。
    • 这可能不再可靠。如今,jpeg 文件中可能有多个 XMP 块。
    • @hippietrail 是的。在这些情况下,需要调整逻辑以确定文字 XMP 包的开始和结束。如(find &lt;x:xmpmeta, find next &lt;/x:xmpmeta&gt;) repeat until no more &lt;x:xmpmeta can be found
    【解决方案2】:

    过了这么久我才回复这个问题,因为这似乎是在谷歌搜索如何解析 XMP 数据时最好的结果。我已经在代码中看到过几次几乎相同的 sn-p ,这是对内存的严重浪费。这是 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;
    }
    

    【讨论】:

    • 值得注意的是,当图像不包含 XMP 数据时,这会挂起,尽管我确信知道如何解决的人可以轻松解决此问题。
    • 我在 while 循环中添加了 else\break 条件,如果文件中不存在 XMP 元素,则终止循环
    • 我重构了这个函数,首先复制块,然后对缓冲区执行检测/修改,而​​不是尝试对块执行此操作。
    • 难道不是一个块包含“
    • 你是绝对正确的。此函数在 chunkSize
    【解决方案3】:

    在 linux 上一个简单的方法是调用 exiv2 程序,该程序在 debian 上的同名包中提供。

    $ exiv2 -e X extract image.jpg
    

    将生成包含嵌入式 XMP 的 image.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's 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 扩展:它是一个基于 adobe xmp 工具包的 php5 扩展,它提供了从 jpeg、psd、pdf、视频、音频读取/写入/解析 xmp 元数据的主要类和方法。 .. 这个扩展是在 gpl 许可下的。一个新版本将很快推出,适用于 php 5.3(现在仅与 php 5.2.x 兼容),并且应该可以在 windows 和 macosx 上使用(现在仅适用于 freebsd 和 linux 系统)。 http://xmpphptoolkit.sourceforge.net/

        【讨论】:

        • 我试过你的工具包,但我无法编译它:(抱怨缺少 printf。“xmp_toolkit/common/XMP_LibUtils.hpp:179:62: 错误: 'printf' 没有声明在这个范围内”
        【解决方案6】:

        Bryan 的解决方案是迄今为止最好的解决方案,但它存在一些问题,因此我对其进行了修改以简化它,并删除了一些功能。

        我发现他的解决方案存在三个问题:

        A) 如果提取的块正好位于我们正在搜索的字符串之一之间,它将找不到它。小块更可能导致此问题。

        B) 如果块同时包含开头和结尾,它将找不到它。这是一个很容易解决的问题,使用额外的 if 语句来重新检查找到开始的块以查看是否也找到了结束。

        C) 如果没有找到 xmp 数据,则添加到末尾以中断 while 循环的 else 语句有一个副作用,即如果在第一次通过时找不到开始元素,它将不再检查块.这可能也很容易解决,但对于第一个问题,它不值得。

        我下面的解决方案没有那么强大,但它更强大。它只会检查一个块,并从中提取数据。只有当开始和结束都在该块中时它才会起作用,因此块大小需要足够大以确保它始终捕获该数据。根据我对 Adob​​e 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;
        }
        

        【讨论】:

        • 我更新了我的函数,修复了它的逻辑问题:)
        • 啊,谢谢布莱恩!直到现在我才注意到你回复了。我会查看您修改后的代码,看看它是否适合我(我还没有完全理解它,我不是程序员......)
        • 哦,我现在明白了.. 您一次构建一个缓冲区并始终检查缓冲区。这可以防止我列出的所有问题。聪明的!谢谢。
        • 我查看了代码,最后一个 elseif 语句,如果我没看错的话,是为了擦除缓冲区(保存最后一位,以防开始标签挂在那里) ..但是根据我对 substr 函数的理解...不应该是 $buffer = substr($buffer, -strlen($startTag)); (注意减号,从字符串的末尾开始)。就像现在一样,没有减号,新的 $buffer 值将与以前大致相同,不会被擦除。它会起作用,但效率不如预期。如果我错了,请纠正我(并对百万厘米表示抱歉)
        【解决方案7】:

        感谢 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;
        }
        

        【讨论】:

          【解决方案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);
          

          【讨论】:

            【解决方案9】:

            现在还有一个 github 存储库,您可以通过 composer 添加可以读取 xmp 数据:

            https://github.com/jeroendesloovere/xmp-metadata-extractor

            composer require jeroendesloovere/xmp-metadata-extractor

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-05-16
              • 2019-07-14
              • 2017-07-26
              • 1970-01-01
              • 1970-01-01
              • 2016-04-08
              • 1970-01-01
              相关资源
              最近更新 更多