15

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

4

9 回答 9

24

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>着减少内存占用。
于 2009-10-16T14:15:30.530 回答
12

我只是在很长一段时间后才回复这个问题,因为这似乎是在谷歌搜索如何解析 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;
}
于 2010-05-14T20:13:21.097 回答
4

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

$ exiv2 -e X extract image.jpg

将生成包含嵌入式 XMP 的 image.xmp,现在您可以对其进行解析。

于 2011-01-26T09:40:11.150 回答
3

我知道......这是一个旧线程,但是当我正在寻找一种方法时它对我很有帮助,所以我认为这可能对其他人有帮助。

我采用了这个基本解决方案并对其进行了修改,以便它处理标签在块之间拆分的情况。这允许块大小随您的需要而变大或变小。

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

于 2014-11-14T15:50:23.807 回答
1

我开发了 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/

于 2010-06-08T16:03:38.580 回答
1

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;
}
于 2012-05-15T16:21:52.103 回答
1

感谢 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;
}
于 2014-01-28T08:51:56.757 回答
1

如果您有可用的 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);
于 2014-11-17T16:09:49.040 回答
0

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

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

composer require jeroendesloovere/xmp-metadata-extractor

于 2021-08-23T10:37:33.540 回答