代码之家  ›  专栏  ›  技术社区  ›  Liam

如何使用PHP从JPG读取XMP数据?

  •  14
  • Liam  · 技术社区  · 15 年前

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

    8 回复  |  直到 15 年前
        1
  •  24
  •   NullUserException Mark Roddy    13 年前

    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>
        2
  •  12
  •   Bryan Geraghty    11 年前

    我只是在花了这么多时间之后才回复这个问题,因为这似乎是在谷歌搜索如何解析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;
    }
    
        3
  •  4
  •   Fluxine    14 年前

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

    $ exiv2 -e X extract image.jpg
    

        4
  •  3
  •   infiniteegolfer    10 年前

    我知道。。。这是一个古老的线索,但它对我有帮助,当我正在寻找一种方法来做这件事,所以我认为这可能会有助于其他人。

    我采用了这个基本的解决方案,并对其进行了修改,使其能够处理标记在块之间分割的情况。这使得块大小可以是您想要的大小。

    <?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;
    }
    ?>
        6
  •  1
  •   Sebastien B.    12 年前

    Bryan的解决方案是目前为止最好的,但有一些问题,所以我修改了它以简化它,并删除了一些功能。

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

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

    C) 如果没有找到xmp数据,则添加到末尾以中断while循环的else语句有一个副作用,即如果在第一次传递中没有找到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;
    }
    
        7
  •  1
  •   LukáÅ¡ Řádek    11 年前

    感谢塞巴斯蒂安B.的简短版本:)。如果要避免这个问题,当chunk_大小对于某些文件来说太小时,只需添加递归即可。

    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
  •  1
  •   Liam    10 年前

    如果您有可用的ExifTool(一个非常有用的工具),并且可以运行外部命令,那么可以使用它的选项提取XMP数据( -xmp:all -json ),然后可以轻松地将其转换为PHP对象:

    $command = 'exiftool -g -json -struct -xmp:all "'.$image_path.'"';
    exec($command, $output, $return_var);
    $metadata = implode('', $output);
    $metadata = json_decode($metadata);
    
        9
  •  0
  •   Tom_B    3 年前

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

    composer require jeroendesloovere/xmp-metadata-extractor