gomoob/php-metadata-extractor

View on GitHub
src/main/php/Gomoob/MetadataExtractor/Imaging/ImageMetadataReader.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

/**
 * gomoob/php-metadata-extractor
 *
 * @copyright Copyright (c) 2016, GOMOOB SARL (http://gomoob.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE.md file)
 */
namespace Gomoob\MetadataExtractor\Imaging;

use Gomoob\BinaryDriver\MetadataExtractorDriver;

use Gomoob\MetadataExtractor\Lang\Rational;

use Gomoob\MetadataExtractor\Metadata\Metadata;

use Gomoob\MetadataExtractor\Metadata\Directory;
use Gomoob\MetadataExtractor\Metadata\Bmp\BmpHeaderDirectory;
use Gomoob\MetadataExtractor\Metadata\File\FileMetadataDirectory;
use Gomoob\MetadataExtractor\Metadata\Gif\GifHeaderDirectory;
use Gomoob\MetadataExtractor\Metadata\Jpeg\JpegComponent;
use Gomoob\MetadataExtractor\Metadata\Jpeg\JpegDirectory;
use Gomoob\MetadataExtractor\Metadata\Jfif\JfifDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\ExifIFD0Directory;
use Gomoob\MetadataExtractor\Metadata\Exif\ExifSubIFDDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\ExifThumbnailDirectory;
use Gomoob\MetadataExtractor\Metadata\Xmp\XmpDirectory;
use Gomoob\MetadataExtractor\Metadata\Icc\IccDirectory;
use Gomoob\MetadataExtractor\Metadata\Photoshop\PhotoshopDirectory;
use Gomoob\MetadataExtractor\Metadata\Iptc\IptcDirectory;
use Gomoob\MetadataExtractor\Metadata\Adobe\AdobeJpegDirectory;
use Gomoob\MetadataExtractor\Metadata\Jpeg\JpegCommentDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\Makernotes\NikonType1MakernoteDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\ExifInteropDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\Makernotes\CanonMakernoteDirectory;
use Gomoob\MetadataExtractor\Metadata\Exif\GpsDirectory;
use Gomoob\MetadataExtractor\Metadata\Png\PngDirectory;
use Gomoob\MetadataExtractor\Metadata\Photoshop\DuckyDirectory;

/**
 * Reads metadata from any supported file format.
 *
 * This class a lightweight wrapper around other, specific metadata processors. During extraction, the file type is
 * determined from the first few bytes of the file. Parsing is then delegated to one of:
 *
 * * {@link JpegMetadataReader} for JPEG files
 * * {@link TiffMetadataReader} for TIFF and (most) RAW files
 * * {@link PsdMetadataReader} for Photoshop files
 * * {@link PngMetadataReader} for PNG files
 * * {@link BmpMetadataReader} for BMP files
 * * {@link GifMetadataReader} for GIF files
 * * {@link IcoMetadataReader} for ICO files
 * * {@link PcxMetadataReader} for PCX files
 * * {@link WebpMetadataReader} for WebP files
 * * {@link RafMetadataReader} for RAF files
 *
 * If you know the file type you're working with, you may use one of the above processors directly. For most scenarios
 * it is simpler, more convenient and more robust to use this class.
 *
 * {@link FileTypeDetector} is used to determine the provided image's file type, and therefore the appropriate metadata
 * reader to use.
 *
 * @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
 */
class ImageMetadataReader
{
    /**
     * The last console output, this property is only defined for the PHP version of the library. The PHP version of
     * the library parses the console output of the Java `metadata-extractor` library, ths variable contains the last
     * console output.
     *
     * @var string
     */
    private static $lastOutput;

    /**
     * The {@link MetadataExtractorDriver} driver used to manage calls to the `metadata-extractor` library.
     *
     * @var \Gomoob\MetadataExtractor\Driver\MetadataExtractorDriver
     */
    private $metadataExtractorDriver;

    /**
     * Gets the last console output.
     *
     * @return string the last console output.
     */
    public static function getLastOutput()
    {
        return static::$lastOutput;
    }

    public static function readMetadata($file)
    {
        $metadata = new Metadata();
        
        $metadataExtractorDriver = MetadataExtractorDriver::create();
        static::$lastOutput = $metadataExtractorDriver->command(
            [
                $file
            ]
        );
        
        // Parse each line of the output
        foreach (explode(PHP_EOL, static::$lastOutput) as $line) {
            $trimedLine = trim($line);
            
            // We ignore empty lines, metadata-extractor outputs empty line to have a human readable console output but
            // its not useful for us here
            if (strlen(trim($trimedLine)) !== 0 && strpos($trimedLine, '[') === 0) {
                // Parse the name of the directory associated to the current line
                $directoryName = static::parseDirectoryName($line);

                // Try to get an already configured directory
                $directory = static::getDirectoryByName($metadata, $directoryName);
                
                // Otherwise create and configure a new directory
                if (!$directory) {
                    // var_dump($directoryName);
                    
                    $directory = static::createDirectoryWithName($directoryName);
                    
                    // TODO: Ignore unsupported directory names, this should disappear in the futur when all diretories
                    //       are implemented
                    if (!$directory) {
                        continue;
                    }
                    
                    $metadata->addDirectory($directory);
                }
                
                static::addTagToDirectory($directory, $line);
            }
        }
        
        return $metadata;
    }
    
    private static function addTagToDirectory(Directory $directory, $tagLine)
    {
        $start = strpos($tagLine, '] ');
        
        if ($start === false) {
            // TODO: Error
        }
        
        $nameAndDescription = explode('=', substr($tagLine, $start + 2));
        $nameAndDescription[0] = trim($nameAndDescription[0]);
        $nameAndDescription[1] = trim($nameAndDescription[1]);
        
        if ($directory instanceof ExifIFD0Directory) {
            switch ($nameAndDescription[0]) {
                case 'Interoperability Index':
                    break;
                case 'Interoperability Version':
                    break;
                case 'New Subfile Type':
                    break;
                case 'Subfile Type':
                    break;
                case 'Image Width':
                    break;
                case 'Image Height':
                    break;
                case 'Bits Per Sample':
                    break;
                case 'Compression':
                    break;
                case 'Photometric Interpretation':
                    break;
                case 'Thresholding':
                    break;
                case 'Fill Order':
                    break;
                case 'Document Name':
                    break;
                case 'Image Description':
                    break;
                case 'Make':
                    break;
                case 'Model':
                    break;
                case 'Strip Offsets':
                    break;
                case 'Orientation':
                    break;
                case 'Samples Per Pixel':
                    break;
                case 'Rows Per Strip':
                    break;
                case 'Strip Byte Counts':
                    break;
                case 'Minimum Sample Value':
                    break;
                case 'Maximum Sample Value':
                    break;
                case 'X Resolution':
                    $directory->setRational(
                        ExifIFD0Directory::TAG_X_RESOLUTION,
                        new Rational(intval(static::parseDotsString($nameAndDescription[1])), 1)
                    );
                    break;
                case 'Y Resolution':
                    $directory->setRational(
                        ExifIFD0Directory::TAG_Y_RESOLUTION,
                        new Rational(intval(static::parseDotsString($nameAndDescription[1])), 1)
                    );
                    break;
                case 'Planar Configuration':
                    break;
                case 'Page Name':
                    break;
                case 'Resolution Unit':
                    break;
                case 'Transfer Function':
                    break;
                case 'Software':
                    break;
                case 'Date/Time':
                    break;
                case 'Artist':
                    break;
                case 'Predictor':
                    break;
                case 'Host Computer':
                    break;
                case 'White Point':
                    break;
                case 'Primary Chromaticities':
                    break;
                case 'Tile Width':
                    break;
                case 'Tile Length':
                    break;
                case 'Tile Offsets':
                    break;
                case 'Tile Byte Counts':
                    break;
                case 'Sub IFD Pointer(s)':
                    break;
                case 'Transfer Range':
                    break;
                case 'JPEG Tables':
                    break;
                case 'JPEG Proc':
                    break;
                case 'YCbCr Coefficients':
                    break;
                case 'YCbCr Sub-Sampling':
                    break;
                case 'YCbCr Positioning':
                    break;
                case 'Reference Black/White':
                    break;
                case 'Strip Row Counts':
                    break;
                case 'Application Notes':
                    break;
                case 'Related Image File Format':
                    break;
                case 'Related Image Width':
                    break;
                case 'Related Image Height':
                    break;
                case 'Rating':
                    break;
                case 'CFA Repeat Pattern Dim':
                    break;
                case 'CFA Pattern':
                    break;
                case 'Battery Level':
                    break;
                case 'Copyright':
                    break;
                case 'Exposure Time':
                    break;
                case 'F-Number':
                    break;
                case 'IPTC/NAA':
                    break;
                case 'Inter Color Profile':
                    break;
                case 'Exposure Program':
                    break;
                case 'Spectral Sensitivity':
                    break;
                case 'ISO Speed Ratings':
                    break;
                case 'Opto-electric Conversion Function (OECF)':
                    break;
                case 'Interlace':
                    break;
                case 'Time Zone Offset':
                    break;
                case 'Self Timer Mode':
                    break;
                case 'Sensitivity Type':
                    break;
                case 'Standard Output Sensitivity':
                    break;
                case 'Recommended Exposure Index':
                    break;
                case 'Time Zone Offset':
                    break;
                case 'Self Timer Mode':
                    break;
                case 'Exif Version':
                    break;
                case 'Date/Time Original':
                    break;
                case 'Date/Time Digitized':
                    break;
                case 'Components Configuration':
                    break;
                case 'Compressed Bits Per Pixel':
                    break;
                case 'Shutter Speed Value':
                    break;
                case 'Aperture Value':
                    break;
                case 'Brightness Value':
                    break;
                case 'Exposure Bias Value':
                    break;
                case 'Max Aperture Value':
                    break;
                case 'Subject Distance':
                    break;
                case 'Metering Mode':
                    break;
                case 'White Balance':
                    break;
                case 'Flash':
                    break;
                case 'Focal Length':
                    break;
                case 'Flash Energy':
                    break;
                case 'Spatial Frequency Response':
                    break;
                case 'Noise':
                    break;
                case 'Focal Plane X Resolution':
                    break;
                case 'Focal Plane Y Resolution':
                    break;
                case 'Image Number':
                    break;
                case 'Security Classification':
                    break;
                case 'Image History':
                    break;
                case 'Subject Location':
                    break;
                case 'Exposure Index':
                    break;
                case 'TIFF/EP Standard ID':
                    break;
                case 'Makernote':
                    break;
                case 'User Comment':
                    break;
                case 'Sub-Sec Time':
                    break;
                case 'Sub-Sec Time Original':
                    break;
                case 'Sub-Sec Time Digitized':
                    break;
                case 'Windows XP Title':
                    break;
                case 'Windows XP Comment':
                    break;
                case 'Windows XP Author':
                    break;
                case 'Windows XP Keywords':
                    break;
                case 'Windows XP Subject':
                    break;
                case 'FlashPix Version':
                    break;
                case 'Color Space':
                    break;
                case 'Exif Image Width':
                    break;
                case 'Exif Image Height':
                    break;
                case 'Related Sound File':
                    break;
                case 'Flash Energy':
                    break;
                case 'Spatial Frequency Response':
                    break;
                case 'Focal Plane X Resolution':
                    break;
                case 'Focal Plane Y Resolution':
                    break;
                case 'Focal Plane Resolution Unit':
                    break;
                case 'Subject Location':
                    break;
                case 'Exposure Index':
                    break;
                case 'Sensing Method':
                    break;
                case 'File Source':
                    break;
                case 'Scene Type':
                    break;
                case 'CFA Pattern':
                    break;
                case 'Custom Rendered':
                    break;
                case 'Exposure Mode':
                    break;
                case 'White Balance Mode':
                    break;
                case 'Digital Zoom Ratio':
                    break;
                case 'Focal Length 35':
                    break;
                case 'Scene Capture Type':
                    break;
                case 'Gain Control':
                    break;
                case 'Contrast':
                    break;
                case 'Saturation':
                    break;
                case 'Sharpness':
                    break;
                case 'Device Setting Description':
                    break;
                case 'Subject Distance Range':
                    break;
                case 'Unique Image ID':
                    break;
                case 'Camera Owner Name':
                    break;
                case 'Body Serial Number':
                    break;
                case 'Lens Specification':
                    break;
                case 'Lens Make':
                    break;
                case 'Lens Model':
                    break;
                case 'Lens Serial Number':
                    break;
                case 'Gamma':
                    break;
                case 'Print IM':
                    break;
                case 'Panasonic Title':
                    break;
                case 'Panasonic Title (2)':
                    break;
                case 'Padding':
                    break;
                case 'Lens':
                    break;
                default:
                    // TODO: Error
            }
        } elseif ($directory instanceof GifHeaderDirectory) {
            switch ($nameAndDescription[0]) {
                case 'GIF Format Version':
                    break;
                case 'Image Height':
                    $directory->setInt(GifHeaderDirectory::TAG_IMAGE_HEIGHT, intval($nameAndDescription[1]));
                    break;
                case 'Image Width':
                    $directory->setInt(GifHeaderDirectory::TAG_IMAGE_WIDTH, intval($nameAndDescription[1]));
                    break;
                case 'Color Table Size':
                    break;
                case 'Is Color Table Sorted':
                    break;
                case 'Bits per Pixel':
                    break;
                case 'Has Global Color Table':
                    break;
                case 'Background Color Index':
                    break;
                case 'Pixel Aspect Ratio':
                    break;
                default:
            }
        } elseif ($directory instanceof JfifDirectory) {
            switch ($nameAndDescription[0]) {
                case 'Version':
                    if ($nameAndDescription[1] === '1.1') {
                        $directory->setInt(JfifDirectory::TAG_VERSION, 0x0101);
                    } elseif ($nameAndDescription[1] === '1.2') {
                        $directory->setInt(JfifDirectory::TAG_VERSION, 0x0102);
                    } else {
                        // TODO: Version inconnue
                    }
                    break;
                case 'Resolution Units':
                    switch ($nameAndDescription[1]) {
                        case 'none':
                            $directory->setInt(JfifDirectory::TAG_UNITS, 0);
                            break;
                        case 'inch':
                            $directory->setInt(JfifDirectory::TAG_UNITS, 1);
                            break;
                        case 'centimetre':
                            $directory->setInt(JfifDirectory::TAG_UNITS, 2);
                            break;
                        default:
                            $directory->setInt(JfifDirectory::TAG_UNITS, -1);
                    }
                    break;
                case 'Y Resolution':
                    $directory->setInt(JfifDirectory::TAG_RESY, static::parseDotsString($nameAndDescription[1]));
                    break;
                case 'X Resolution':
                    $directory->setInt(JfifDirectory::TAG_RESX, static::parseDotsString($nameAndDescription[1]));
                    break;
                case 'Thumbnail Width Pixels':
                    $directory->setInt(JfifDirectory::TAG_THUMB_WIDTH, $nameAndDescription[1]);
                    break;
                case 'Thumbnail Height Pixels':
                    $directory->setInt(JfifDirectory::TAG_THUMB_HEIGHT, $nameAndDescription[1]);
                    break;
                default:
                    // TODO: Exception
            }
        } elseif ($directory instanceof JpegDirectory) {
            switch ($nameAndDescription[0]) {
                case 'Compression Type':
                    // TODO
                    break;
                case 'Data Precision':
                    $directory->setInt(
                        JpegDirectory::TAG_DATA_PRECISION,
                        static::parseBitsString($nameAndDescription[1])
                    );
                    break;
                case 'Image Width':
                    $directory->setInt(
                        JpegDirectory::TAG_IMAGE_WIDTH,
                        static::parsePixelsString($nameAndDescription[1])
                    );
                    break;
                case 'Image Height':
                    $directory->setInt(
                        JpegDirectory::TAG_IMAGE_HEIGHT,
                        static::parsePixelsString($nameAndDescription[1])
                    );
                    break;
                case 'Number of Components':
                    $directory->setInt(JpegDirectory::TAG_NUMBER_OF_COMPONENTS, intval($nameAndDescription[1]));
                    break;
                case 'Component 1':
                    $directory->setObject(
                        JpegDirectory::TAG_COMPONENT_DATA_1,
                        static::parseJpegComponentString($nameAndDescription[1])
                    );
                    break;
                case 'Component 2':
                    $directory->setObject(
                        JpegDirectory::TAG_COMPONENT_DATA_2,
                        static::parseJpegComponentString($nameAndDescription[1])
                    );
                    break;
                case 'Component 3':
                    $directory->setObject(
                        JpegDirectory::TAG_COMPONENT_DATA_3,
                        static::parseJpegComponentString($nameAndDescription[1])
                    );
                    break;
                case 'Component 4':
                    $directory->setObject(
                        JpegDirectory::TAG_COMPONENT_DATA_4,
                        static::parseJpegComponentString($nameAndDescription[1])
                    );
                    break;
                default:
                    // TODO: Exception
            }
        } elseif ($directory instanceof PngDirectory) {
//             var_dump($tagLine);
//             var_dump($nameAndDescription);
            switch ($nameAndDescription[0]) {
                case 'Image Height':
                    $directory->setInt(PngDirectory::TAG_IMAGE_HEIGHT, intval($nameAndDescription[1]));
                    break;
                case 'Image Width':
                    $directory->setInt(PngDirectory::TAG_IMAGE_WIDTH, intval($nameAndDescription[1]));
                    break;
                case 'Bits Per Sample':
                    break;
                case 'Color Type':
                    break;
                case 'Compression Type':
                    break;
                case 'Filter Method':
                    break;
                case 'Interlace Method':
                    break;
                case 'Palette Size':
                    break;
                case 'Palette Has Transparency':
                    break;
                case 'sRGB Rendering Intent':
                    break;
                case 'Image Gamma':
                    break;
                case 'ICC Profile Name':
                    break;
                case 'Textual Data':
                    break;
                case 'Last Modification Time':
                    break;
                case 'Background Color':
                    break;
                case 'Pixels Per Unit X':
                    break;
                case 'Pixels Per Unit Y':
                    break;
                case 'Unit Specifier':
                    break;
                case 'Significant Bits':
                    break;
                default:
                    break;
            }
        }
    }
    
    private static function parseJpegComponentString($jpegComponentString)
    {
        // TODO
        return new JpegComponent(0, 0, 0);
    }

    private static function parseBitsString($bitsString)
    {
        
        $endPos = strpos($bitsString, ' bits');

        if ($endPos === false) {
            // TODO: Exception
        }
        
        return substr($bitsString, 0, $endPos);
    }
    
    private static function parseDotsString($dotsString)
    {
        $endPos = strpos($dotsString, ' dot');
    
        if ($endPos === false) {
            // TODO: Exception
        }
    
        return substr($dotsString, 0, $endPos);
    }
    
    private static function parsePixelsString($pixelsString)
    {
        
        $endPos = strpos($pixelsString, ' pixels');
        
        if ($endPos === false) {
            // TODO: Exception
        }
         
        return substr($pixelsString, 0, $endPos);
    }
    
    private static function createDirectoryWithName($directoryName)
    {
        
        $directory = null;
         
        // Creates the right kind of directory depending on the name
        switch ($directoryName) {
            case 'Adobe JPEG':
                $directory = new AdobeJpegDirectory();
                break;
            case 'BMP Header':
                $directory = new BmpHeaderDirectory();
                break;
            case 'Canon Makernote':
                $directory = new CanonMakernoteDirectory();
                break;
            case 'Ducky':
                $directory = new DuckyDirectory();
                break;
            case 'Exif IFD0':
                $directory = new ExifIFD0Directory();
                break;
            case 'Exif SubIFD':
                $directory = new ExifSubIFDDirectory();
                break;
            case 'Exif Thumbnail':
                $directory = new ExifThumbnailDirectory();
                break;
            case 'File':
                $directory = new FileMetadataDirectory();
                break;
            case 'GIF Header':
                $directory = new GifHeaderDirectory();
                break;
            case 'GPS':
                $directory = new GpsDirectory();
                break;
            case 'ICC Profile':
                $directory = new IccDirectory();
                break;
            case 'Interoperability':
                $directory = new ExifInteropDirectory();
                break;
            case 'IPTC':
                $directory = new IptcDirectory();
                break;
            case 'JFIF':
                $directory = new JfifDirectory();
                break;
            case 'JPEG':
                $directory = new JpegDirectory();
                break;
            case 'JpegComment':
                $directory = new JpegCommentDirectory();
                break;
            case 'Nikon Makernote':
                $directory = new NikonType1MakernoteDirectory();
                break;
            case 'Photoshop':
                $directory = new PhotoshopDirectory();
                break;
            case 'PNG-IHDR':
                $directory = new PngDirectory('IHDR');
                break;
            case 'PNG-PLTE':
                $directory = new PngDirectory('PLTE');
                break;
            case 'PNG-IDAT':
                $directory = new PngDirectory('IDAT');
                break;
            case 'PNG-IEND':
                $directory = new PngDirectory('IEND');
                break;
            case 'PNG-cHRM':
                $directory = new PngDirectory('cHRM');
                break;
            case 'PNG-gAMA':
                $directory = new PngDirectory('gAMA');
                break;
            case 'PNG-iCCP':
                $directory = new PngDirectory('iCCP');
                break;
            case 'PNG-sBIT':
                $directory = new PngDirectory('sBIT');
                break;
            case 'PNG-sRGB':
                $directory = new PngDirectory('sRGB');
                break;
            case 'PNG-bKGD':
                $directory = new PngDirectory('bKGD');
                break;
            case 'PNG-hIST':
                $directory = new PngDirectory('hIST');
                break;
            case 'PNG-tRNS':
                $directory = new PngDirectory('tRNS');
                break;
            case 'PNG-pHYs':
                $directory = new PngDirectory('pHYs');
                break;
            case 'PNG-sPLT':
                $directory = new PngDirectory('sPLT');
                break;
            case 'PNG-tIME':
                $directory = new PngDirectory('tIME');
                break;
            case 'PNG-iTXt':
                $directory = new PngDirectory('iTXt');
                break;
            case 'XMP':
                $directory = new XmpDirectory();
                break;
            default:
                // TODO: To be enabled in testing
                throw new \RuntimeException('Unknown directory name \'' . $directoryName . '\' !');
        }

        return $directory;
    }
    
    private static function getDirectoryByName(Metadata $metadata, $directoryName)
    {
        
        $foundDirectory = null;
        
        foreach ($metadata->getDirectories() as $directory) {
            if ($directory->getName() === $directoryName) {
                $foundDirectory = $directory;
                break;
            }
        }
        
        return $foundDirectory;
    }
    
    private static function parseDirectoryName($line)
    {
        
        $start = strpos($line, '[');
        
        // If no '[' character is found this is an error
        if ($start === false) {
            // TODO
        }
        
        $end = strpos($line, ']', $start);
        
        // If no ']' character is found this is an error
        if ($end === false) {
            // TODO
        }
        
        return substr($line, $start + 1, $end - 1);
    }
}