PHP项目Exif与元数据读取

wen PHP项目 2

本文目录导读:

PHP项目Exif与元数据读取

  1. 基本EXIF读取(使用PHP内置函数)
  2. 完整的EXIF读取类
  3. 使用第三方库(推荐)
  4. 读取其他文件类型的元数据
  5. 实用工具函数
  6. 配置文件示例
  7. 注意事项

我来为您详细介绍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
    ]
];
?>

注意事项

  1. PHP配置:确保php.ini中启用了EXIF扩展

    extension=exif
  2. 安全性

    • 上传图片时注意安全检查
    • 用户隐私保护(GPS位置信息)
    • 文件大小限制
  3. 性能优化

    • 对大文件使用流式处理
    • 实现缓存机制
    • 异步处理批量操作
  4. 错误处理

    • 文件不存在
    • 格式不支持
    • 扩展未启用
    • 损坏的文件

这个完整的EXIF和元数据读取解决方案可以满足大多数PHP项目的需求,根据具体应用场景选择合适的实现方式。

抱歉,评论功能暂时关闭!