本文目录导读:

我来为您详细介绍PHP项目中读取EXIF和元数据的方法。
基本EXIF读取(使用PHP内置函数)
<?php
// 检查EXIF扩展是否可用
if (function_exists('exif_read_data')) {
$imagePath = 'photo.jpg';
// 读取EXIF数据
$exif = exif_read_data($imagePath, 'ANY_TAG', true);
if ($exif !== false) {
echo "相机型号: " . ($exif['IFD0']['Model'] ?? '未知');
echo "拍摄时间: " . ($exif['EXIF']['DateTimeOriginal'] ?? '未知');
echo "ISO: " . ($exif['EXIF']['ISOSpeedRatings'] ?? '未知');
echo "焦距: " . ($exif['EXIF']['FocalLength'] ?? '未知');
echo "光圈: " . ($exif['EXIF']['FNumber'] ?? '未知');
echo "快门速度: " . ($exif['EXIF']['ExposureTime'] ?? '未知');
}
} else {
echo "需要启用PHP EXIF扩展";
}
?>
完整的EXIF读取类
<?php
class ExifReader
{
private $imagePath;
private $exifData;
public function __construct($imagePath)
{
$this->imagePath = $imagePath;
$this->loadExif();
}
private function loadExif()
{
if (!file_exists($this->imagePath)) {
throw new Exception("文件不存在");
}
if (!function_exists('exif_read_data')) {
throw new Exception("EXIF扩展未启用");
}
$this->exifData = @exif_read_data($this->imagePath, 'ANY_TAG', true);
if ($this->exifData === false) {
throw new Exception("无法读取EXIF数据");
}
}
// 获取拍摄信息
public function getCameraInfo()
{
$info = [];
if (isset($this->exifData['IFD0'])) {
$info = [
'make' => $this->exifData['IFD0']['Make'] ?? '未知',
'model' => $this->exifData['IFD0']['Model'] ?? '未知',
'software' => $this->exifData['IFD0']['Software'] ?? '未知'
];
}
return $info;
}
// 获取拍摄参数
public function getCameraSettings()
{
$settings = [];
if (isset($this->exifData['EXIF'])) {
$settings = [
'iso' => $this->exifData['EXIF']['ISOSpeedRatings'] ?? '未知',
'focal_length' => $this->exifData['EXIF']['FocalLength'] ?? '未知',
'f_number' => $this->exifData['EXIF']['FNumber'] ?? '未知',
'exposure_time' => $this->exifData['EXIF']['ExposureTime'] ?? '未知',
'flash' => $this->getFlashStatus(),
'white_balance' => $this->getWhiteBalance()
];
}
return $settings;
}
// 获取GPS信息
public function getGPSInfo()
{
$gps = [];
if (isset($this->exifData['GPS'])) {
$latitude = $this->getGPSValue('GPSLatitude', 'GPSLatitudeRef');
$longitude = $this->getGPSValue('GPSLongitude', 'GPSLongitudeRef');
$altitude = $this->exifData['GPS']['GPSAltitude'] ?? null;
$gps = [
'latitude' => $latitude,
'longitude' => $longitude,
'altitude' => $altitude,
'latitude_decimal' => $this->convertDMSToDecimal($latitude),
'longitude_decimal' => $this->convertDMSToDecimal($longitude)
];
}
return $gps;
}
// 获取日期时间信息
public function getDateTimeInfo()
{
$dates = [];
if (isset($this->exifData['EXIF']['DateTimeOriginal'])) {
$dates['original'] = $this->exifData['EXIF']['DateTimeOriginal'];
$dates['digitized'] = $this->exifData['EXIF']['DateTimeDigitized'] ?? null;
}
return $dates;
}
// 获取图像信息
public function getImageInfo()
{
$info = [];
if (isset($this->exifData['COMPUTED'])) {
$info = [
'width' => $this->exifData['COMPUTED']['Width'] ?? 0,
'height' => $this->exifData['COMPUTED']['Height'] ?? 0,
'orientation' => $this->getOrientation(),
'color_space' => $this->exifData['EXIF']['ColorSpace'] ?? '未知'
];
}
return $info;
}
// 辅助方法:获取Flash状态
private function getFlashStatus()
{
$flash = $this->exifData['EXIF']['Flash'] ?? 0;
$flashStatus = [
0 => '未使用闪光灯',
1 => '使用闪光灯',
5 => '使用闪光灯(防红眼)',
7 => '使用闪光灯(强制闪光)',
9 => '使用闪光灯(慢同步)',
16 => '未使用闪光灯(强制关闭)',
24 => '自动模式,未使用闪光灯',
25 => '自动模式,使用闪光灯'
];
return $flashStatus[$flash] ?? '未知 (' . $flash . ')';
}
// 辅助方法:获取白平衡
private function getWhiteBalance()
{
$wb = $this->exifData['EXIF']['WhiteBalance'] ?? 0;
return $wb == 0 ? '自动' : '手动';
}
// 辅助方法:获取方向
private function getOrientation()
{
$orientation = $this->exifData['IFD0']['Orientation'] ?? 1;
$orientations = [
1 => '正常',
3 => '旋转180°',
6 => '顺时针旋转90°',
8 => '逆时针旋转90°'
];
return $orientations[$orientation] ?? '未知';
}
// 辅助方法:转换GPS坐标
private function convertDMSToDecimal($dmsArray)
{
if (!is_array($dmsArray) || count($dmsArray) != 3) {
return null;
}
$degrees = $this->convertFraction($dmsArray[0]);
$minutes = $this->convertFraction($dmsArray[1]);
$seconds = $this->convertFraction($dmsArray[2]);
return $degrees + ($minutes / 60) + ($seconds / 3600);
}
// 辅助方法:转换分数格式
private function convertFraction($fraction)
{
if (is_numeric($fraction)) {
return (float) $fraction;
}
$parts = explode('/', $fraction);
if (count($parts) == 2 && $parts[1] != 0) {
return (float) ($parts[0] / $parts[1]);
}
return 0;
}
// 辅助方法:获取GPS值
private function getGPSValue($coordKey, $refKey)
{
if (!isset($this->exifData['GPS'][$coordKey])) {
return null;
}
$coord = $this->exifData['GPS'][$coordKey];
$ref = $this->exifData['GPS'][$refKey] ?? '';
return ['coordinate' => $coord, 'ref' => $ref];
}
// 获取所有EXIF数据
public function getAllData()
{
return [
'camera' => $this->getCameraInfo(),
'settings' => $this->getCameraSettings(),
'gps' => $this->getGPSInfo(),
'datetime' => $this->getDateTimeInfo(),
'image' => $this->getImageInfo()
];
}
}
// 使用示例
try {
$reader = new ExifReader('photo.jpg');
$data = $reader->getAllData();
echo "<pre>";
print_r($data);
echo "</pre>";
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>
使用第三方库(推荐)
使用 composer 安装
composer require pel/pel composer require php-exif/php-exif
使用 php-exif 库
<?php
require_once 'vendor/autoload.php';
use PHPExif\Exif;
use PHPExif\Reader\Reader;
// 创建读取器
$reader = Reader::factory(Reader::TYPE_EXIFTOOL); // 或 TYPE_NATIVE
// 读取EXIF数据
$exif = $reader->read('photo.jpg');
if ($exif) {
echo "标题: " . $exif->getTitle() . "\n";
echo "描述: " . $exif->getDescription() . "\n";
echo "拍摄时间: " . $exif->getCreationDate()->format('Y-m-d H:i:s') . "\n";
echo "相机型号: " . $exif->getCamera() . "\n";
echo "焦距: " . $exif->getFocalLength() . "mm\n";
echo "光圈: f/" . $exif->getAperture() . "\n";
echo "ISO: " . $exif->getIso() . "\n";
echo "快门速度: " . $exif->getExposure() . "\n";
echo "GPS纬度: " . $exif->getLatitude() . "\n";
echo "GPS经度: " . $exif->getLongitude() . "\n";
echo "方向: " . $exif->getOrientation() . "\n";
}
?>
使用 PEL 库(更专业)
<?php
require_once 'vendor/autoload.php';
use lsolesen\pel\PelJpeg;
use lsolesen\pel\PelTag;
// 读取JPEG文件
$file = new PelJpeg('photo.jpg');
$exif = $file->getExif();
if ($exif !== null) {
$tiff = $exif->getTiff();
if ($tiff !== null) {
$ifd0 = $tiff->getIfd();
// 获取相机信息
$make = $ifd0->getEntry(PelTag::MAKE);
$model = $ifd0->getEntry(PelTag::MODEL);
echo "制造商: " . ($make ? $make->getValue() : '未知') . "\n";
echo "型号: " . ($model ? $model->getValue() : '未知') . "\n";
// 获取拍摄设置
$exifIfd = $ifd0->getSubIfd(PelIfd::EXIF);
if ($exifIfd !== null) {
$iso = $exifIfd->getEntry(PelTag::ISO_SPEED_RATINGS);
$fNumber = $exifIfd->getEntry(PelTag::FNUMBER);
$focalLength = $exifIfd->getEntry(PelTag::FOCAL_LENGTH);
echo "ISO: " . ($iso ? $iso->getValue() : '未知') . "\n";
echo "光圈: f/" . ($fNumber ? $fNumber->getValue() : '未知') . "\n";
echo "焦距: " . ($focalLength ? $focalLength->getValue() : '未知') . "mm\n";
}
}
}
?>
读取其他文件类型的元数据
<?php
// 读取PDF元数据
function getPdfMetadata($filePath)
{
$content = file_get_contents($filePath);
$metadata = [];
// 提取基本PDF信息
preg_match('/\/Title\s*\(([^)]*)\)/', $content, $matches);
$metadata['title'] = $matches[1] ?? '未知';
preg_match('/\/Author\s*\(([^)]*)\)/', $content, $matches);
$metadata['author'] = $matches[1] ?? '未知';
preg_match('/\/Subject\s*\(([^)]*)\)/', $content, $matches);
$metadata['subject'] = $matches[1] ?? '未知';
preg_match('/\/Keywords\s*\(([^)]*)\)/', $content, $matches);
$metadata['keywords'] = $matches[1] ?? '未知';
// 获取页面数
preg_match('/\/Type\s*\/Pages[^>]*\/Count\s*(\d+)/', $content, $matches);
$metadata['pages'] = $matches[1] ?? '未知';
return $metadata;
}
// 读取Word文档元数据
function getDocxMetadata($filePath)
{
$zip = new ZipArchive();
if ($zip->open($filePath) === true) {
$content = $zip->getFromName('docProps/core.xml');
$zip->close();
if ($content) {
$xml = simplexml_load_string($content);
$namespaces = $xml->getNamespaces(true);
$metadata = [];
$metadata['title'] = (string)$xml->children('dc', true)->title;
$metadata['creator'] = (string)$xml->children('dc', true)->creator;
$metadata['description'] = (string)$xml->children('dc', true)->description;
$metadata['created'] = (string)$xml->children('dcterms', true)->created;
$metadata['modified'] = (string)$xml->children('dcterms', true)->modified;
return $metadata;
}
}
return null;
}
// 读取MP3元数据
function getMp3Metadata($filePath)
{
// 使用getID3库(composer require james-heinrich/getid3)
require_once 'vendor/autoload.php';
$getID3 = new getID3();
$file = $getID3->analyze($filePath);
if (isset($file['tags']['id3v2'])) {
$tags = $file['tags']['id3v2'];
return [
'title' => $tags['title'][0] ?? '未知',
'artist' => $tags['artist'][0] ?? '未知',
'album' => $tags['album'][0] ?? '未知',
'year' => $tags['year'][0] ?? '未知',
'genre' => $tags['genre'][0] ?? '未知',
'duration' => $file['playtime_seconds'] ?? 0,
'bitrate' => $file['audio']['bitrate'] ?? 0,
'sample_rate' => $file['audio']['sample_rate'] ?? 0
];
}
return null;
}
?>
实用工具函数
<?php
// 修正图片方向(根据EXIF方向信息)
function fixImageOrientation($sourcePath, $destinationPath = null)
{
if (!function_exists('exif_read_data')) {
return false;
}
$exif = @exif_read_data($sourcePath);
if ($exif === false) {
return false;
}
$orientation = $exif['IFD0']['Orientation'] ?? 1;
$image = imagecreatefromjpeg($sourcePath);
if ($image === false) {
return false;
}
switch ($orientation) {
case 2: // 水平翻转
imageflip($image, IMG_FLIP_HORIZONTAL);
break;
case 3: // 旋转180度
$image = imagerotate($image, 180, 0);
break;
case 4: // 垂直翻转
imageflip($image, IMG_FLIP_VERTICAL);
break;
case 5: // 水平翻转后旋转90度
imageflip($image, IMG_FLIP_HORIZONTAL);
$image = imagerotate($image, -90, 0);
break;
case 6: // 旋转90度
$image = imagerotate($image, -90, 0);
break;
case 7: // 垂直翻转后旋转90度
imageflip($image, IMG_FLIP_VERTICAL);
$image = imagerotate($image, -90, 0);
break;
case 8: // 旋转270度
$image = imagerotate($image, 90, 0);
break;
}
$destPath = $destinationPath ?? $sourcePath;
imagejpeg($image, $destPath, 100);
imagedestroy($image);
return true;
}
// 清除EXIF数据(保护隐私)
function stripExifData($sourcePath, $destinationPath)
{
$image = imagecreatefromjpeg($sourcePath);
if ($image === false) {
return false;
}
// 创建新图像,不包含EXIF数据
$newImage = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($newImage, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
imagejpeg($newImage, $destinationPath, 100);
imagedestroy($image);
imagedestroy($newImage);
return true;
}
// 批量处理文件夹中的图片
function batchProcessImages($directory, $callback)
{
$files = glob($directory . '/*.{jpg,jpeg,png,gif}', GLOB_BRACE);
$results = [];
foreach ($files as $file) {
try {
$result = $callback($file);
$results[$file] = $result;
} catch (Exception $e) {
$results[$file] = '错误: ' . $e->getMessage();
}
}
return $results;
}
?>
配置文件示例
<?php
// config/exif.php
return [
// EXIF读取设置
'exif' => [
'enabled' => true,
'read_all_tags' => false,
'thumbnail' => false,
'sections' => [
'FILE',
'COMPUTED',
'IFD0',
'THUMBNAIL',
'COMMENT',
'EXIF',
'GPS',
'INTEROP',
'MAKERNOTE'
]
],
// 支持的图片格式
'supported_formats' => [
'image/jpeg',
'image/tiff',
'image/png'
],
// GPS坐标格式
'gps_format' => 'decimal', // decimal, dms
// 缓存设置
'cache' => [
'enabled' => true,
'ttl' => 3600, // 1小时
'driver' => 'file' // file, redis, memcached
],
// 隐私设置
'privacy' => [
'strip_gps' => false,
'strip_copyright' => false,
'strip_all' => false
]
];
?>
注意事项
-
PHP配置:确保php.ini中启用了EXIF扩展
extension=exif
-
安全性:
- 上传图片时注意安全检查
- 用户隐私保护(GPS位置信息)
- 文件大小限制
-
性能优化:
- 对大文件使用流式处理
- 实现缓存机制
- 异步处理批量操作
-
错误处理:
- 文件不存在
- 格式不支持
- 扩展未启用
- 损坏的文件
这个完整的EXIF和元数据读取解决方案可以满足大多数PHP项目的需求,根据具体应用场景选择合适的实现方式。