Вход Регистрация
Файл: CMS/core/getid3/module.tag.xmp.php
Строк: 590
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or http://www.getid3.org                         //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details                             //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.tag.xmp.php                                          //
// module for analyzing XMP metadata (e.g. in JPEG files)      //
// dependencies: NONE                                          //
//                                                             //
/////////////////////////////////////////////////////////////////
//                                                             //
// Module originally written [2009-Mar-26] by                  //
//      Nigel Barnes <ngbarnesШhotmail*com>                    //
// Bundled into getID3 with permission                         //
//   called by getID3 in module.graphic.jpg.php                //
//                                                            ///
/////////////////////////////////////////////////////////////////

/**************************************************************************************************
 * SWISScenter Source                                                              Nigel Barnes
 *
 *     Provides functions for reading information from the 'APP1' Extensible Metadata
 *    Platform (XMP) segment of JPEG format files.
 *    This XMP segment is XML based and contains the Resource Description Framework (RDF)
 *    data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information.
 *
 *     This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter.
 *************************************************************************************************/
class Image_XMP
{
    
/**
    * @var string
    * The name of the image file that contains the XMP fields to extract and modify.
    * @see Image_XMP()
    */
    
var $_sFilename null;

    
/**
    * @var array
    * The XMP fields that were extracted from the image or updated by this class.
    * @see getAllTags()
    */
    
var $_aXMP = array();

    
/**
    * @var boolean
    * True if an APP1 segment was found to contain XMP metadata.
    * @see isValid()
    */
    
var $_bXMPParse false;

    
/**
    * Returns the status of XMP parsing during instantiation
    *
    * You'll normally want to call this method before trying to get XMP fields.
    *
    * @return boolean
    * Returns true if an APP1 segment was found to contain XMP metadata.
    */
    
function isValid()
    {
        return 
$this->_bXMPParse;
    }

    
/**
    * Get a copy of all XMP tags extracted from the image
    *
    * @return array - An array of XMP fields as it extracted by the XMPparse() function
    */
    
function getAllTags()
    {
        return 
$this->_aXMP;
    }

    
/**
    * Reads all the JPEG header segments from an JPEG image file into an array
    *
    * @param string $filename - the filename of the JPEG file to read
    * @return array $headerdata - Array of JPEG header segments
    * @return boolean FALSE - if headers could not be read
    */
    
function _get_jpeg_header_data($filename)
    {
        
// prevent refresh from aborting file operations and hosing file
        
ignore_user_abort(true);

        
// Attempt to open the jpeg file - the at symbol supresses the error message about
        // not being able to open files. The file_exists would have been used, but it
        // does not work with files fetched over http or ftp.
        
if (is_readable($filename) && is_file($filename) && ($filehnd fopen($filename'rb'))) {
            
// great
        
} else {
            return 
false;
        }

        
// Read the first two characters
        
$data fread($filehnd2);

        
// Check that the first two characters are 0xFF 0xD8  (SOI - Start of image)
        
if ($data != "xFFxD8")
        {
            
// No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
            
echo '<p>This probably is not a JPEG file</p>'."n";
            
fclose($filehnd);
            return 
false;
        }

        
// Read the third character
        
$data fread($filehnd2);

        
// Check that the third character is 0xFF (Start of first segment header)
        
if ($data{0} != "xFF")
        {
            
// NO FF found - close file and return - JPEG is probably corrupted
            
fclose($filehnd);
            return 
false;
        }

        
// Flag that we havent yet hit the compressed image data
        
$hit_compressed_image_data false;

        
// Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
        //                                       2) we have hit the compressed image data (no more headers are allowed after data)
        //                                       3) or end of file is hit

        
while (($data{1} != "xD9") && (!$hit_compressed_image_data) && (!feof($filehnd)))
        {
            
// Found a segment to look at.
            // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
            
if ((ord($data{1}) < 0xD0) || (ord($data{1}) > 0xD7))
            {
                
// Segment isn't a Restart marker
                // Read the next two bytes (size)
                
$sizestr fread($filehnd2);

                
// convert the size bytes to an integer
                
$decodedsize unpack('nsize'$sizestr);

                
// Save the start position of the data
                
$segdatastart ftell($filehnd);

                
// Read the segment data with length indicated by the previously read size
                
$segdata fread($filehnd$decodedsize['size'] - 2);

                
// Store the segment information in the output array
                
$headerdata[] = array(
                    
'SegType'      => ord($data{1}),
                    
'SegName'      => $GLOBALS['JPEG_Segment_Names'][ord($data{1})],
                    
'SegDataStart' => $segdatastart,
                    
'SegData'      => $segdata,
                );
            }

            
// If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
            
if ($data{1} == "xDA")
            {
                
// Flag that we have hit the compressed image data - exit loop as no more headers available.
                
$hit_compressed_image_data true;
            }
            else
            {
                
// Not an SOS - Read the next two bytes - should be the segment marker for the next segment
                
$data fread($filehnd2);

                
// Check that the first byte of the two is 0xFF as it should be for a marker
                
if ($data{0} != "xFF")
                {
                    
// NO FF found - close file and return - JPEG is probably corrupted
                    
fclose($filehnd);
                    return 
false;
                }
            }
        }

        
// Close File
        
fclose($filehnd);
        
// Alow the user to abort from now on
        
ignore_user_abort(false);

        
// Return the header data retrieved
        
return $headerdata;
    }


    
/**
    * Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
    *
    * @param string $filename - the filename of the JPEG file to read
    * @return string $xmp_data - the string of raw XML text
    * @return boolean FALSE - if an APP 1 XMP segment could not be found, or if an error occured
    */
    
function _get_XMP_text($filename)
    {
        
//Get JPEG header data
        
$jpeg_header_data $this->_get_jpeg_header_data($filename);

        
//Cycle through the header segments
        
for ($i 0$i count($jpeg_header_data); $i++)
        {
            
// If we find an APP1 header,
            
if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
            {
                
// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/x00) ,
                
if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."x00"29) == 0)
                {
                    
// Found a XMP/RDF block
                    // Return the XMP text
                    
$xmp_data substr($jpeg_header_data[$i]['SegData'], 29);

                    return 
trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see http://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
                
}
            }
        }
        return 
false;
    }

    
/**
    * Parses a string containing XMP data (XML), and returns an array
    * which contains all the XMP (XML) information.
    *
    * @param string $xml_text - a string containing the XMP data (XML) to be parsed
    * @return array $xmp_array - an array containing all xmp details retrieved.
    * @return boolean FALSE - couldn't parse the XMP data
    */
    
function read_XMP_array_from_text($xmltext)
    {
        
// Check if there actually is any text to parse
        
if (trim($xmltext) == '')
        {
            return 
false;
        }

        
// Create an instance of a xml parser to parse the XML text
        
$xml_parser xml_parser_create('UTF-8');

        
// Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10

        // We would like to remove unneccessary white space, but this will also
        // remove things like newlines (&#xA;) in the XML values, so white space
        // will have to be removed later
        
if (xml_parser_set_option($xml_parserXML_OPTION_SKIP_WHITE0) == false)
        {
            
// Error setting case folding - destroy the parser and return
            
xml_parser_free($xml_parser);
            return 
false;
        }

        
// to use XML code correctly we have to turn case folding
        // (uppercasing) off. XML is case sensitive and upper
        // casing is in reality XML standards violation
        
if (xml_parser_set_option($xml_parserXML_OPTION_CASE_FOLDING0) == false)
        {
            
// Error setting case folding - destroy the parser and return
            
xml_parser_free($xml_parser);
            return 
false;
        }

        
// Parse the XML text into a array structure
        
if (xml_parse_into_struct($xml_parser$xmltext$values$tags) == 0)
        {
            
// Error Parsing XML - destroy the parser and return
            
xml_parser_free($xml_parser);
            return 
false;
        }

        
// Destroy the xml parser
        
xml_parser_free($xml_parser);

        
// Clear the output array
        
$xmp_array = array();

        
// The XMP data has now been parsed into an array ...

        // Cycle through each of the array elements
        
$current_property ''// current property being processed
        
$container_index = -1// -1 = no container open, otherwise index of container content
        
foreach ($values as $xml_elem)
        {
            
// Syntax and Class names
            
switch ($xml_elem['tag'])
            {
                case 
'x:xmpmeta':
                    
// only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit
                    
break;

                case 
'rdf:RDF':
                    
// required element immediately within x:xmpmeta; no data here
                    
break;

                case 
'rdf:Description':
                    switch (
$xml_elem['type'])
                    {
                        case 
'open':
                        case 
'complete':
                            if (
array_key_exists('attributes'$xml_elem))
                            {
                                
// rdf:Description may contain wanted attributes
                                
foreach (array_keys($xml_elem['attributes']) as $key)
                                {
                                    
// Check whether we want this details from this attribute
                                    
if (in_array($key$GLOBALS['XMP_tag_captions']))
                                    {
                                        
// Attribute wanted
                                        
$xmp_array[$key] = $xml_elem['attributes'][$key];
                                    }
                                }
                            }
                        case 
'cdata':
                        case 
'close':
                            break;
                    }

                case 
'rdf:ID':
                case 
'rdf:nodeID':
                    
// Attributes are ignored
                    
break;

                case 
'rdf:li':
                    
// Property member
                    
if ($xml_elem['type'] == 'complete')
                    {
                        if (
array_key_exists('attributes'$xml_elem))
                        {
                            
// If Lang Alt (language alternatives) then ensure we take the default language
                            
if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default'))
                            {
                                break;
                            }
                        }
                        if (
$current_property != '')
                        {
                            
$xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
                            
$container_index += 1;
                        }
                    
//else unidentified attribute!!
                    
}
                    break;

                case 
'rdf:Seq':
                case 
'rdf:Bag':
                case 
'rdf:Alt':
                    
// Container found
                    
switch ($xml_elem['type'])
                    {
                        case 
'open':
                             
$container_index 0;
                             break;
                        case 
'close':
                            
$container_index = -1;
                            break;
                        case 
'cdata':
                            break;
                    }
                    break;

                default:
                    
// Check whether we want the details from this attribute
                    
if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions']))
                    {
                        switch (
$xml_elem['type'])
                        {
                            case 
'open':
                                
// open current element
                                
$current_property $xml_elem['tag'];
                                break;

                            case 
'close':
                                
// close current element
                                
$current_property '';
                                break;

                            case 
'complete':
                                
// store attribute value
                                
$xmp_array[$xml_elem['tag']] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
                                break;

                            case 
'cdata':
                                
// ignore
                                
break;
                        }
                    }
                    break;
            }

        }
        return 
$xmp_array;
    }


    
/**
    * Constructor
    *
    * @param string - Name of the image file to access and extract XMP information from.
    */
    
function Image_XMP($sFilename)
    {
        
$this->_sFilename $sFilename;

        if (
is_file($this->_sFilename))
        {
            
// Get XMP data
            
$xmp_data $this->_get_XMP_text($sFilename);
            if (
$xmp_data)
            {
                
$this->_aXMP $this->read_XMP_array_from_text($xmp_data);
                
$this->_bXMPParse true;
            }
        }
    }

}

/**
* Global Variable: XMP_tag_captions
*
* The Property names of all known XMP fields.
* Note: this is a full list with unrequired properties commented out.
*/
$GLOBALS['XMP_tag_captions'] = array(
// IPTC Core
    
'Iptc4xmpCore:CiAdrCity',
    
'Iptc4xmpCore:CiAdrCtry',
    
'Iptc4xmpCore:CiAdrExtadr',
    
'Iptc4xmpCore:CiAdrPcode',
    
'Iptc4xmpCore:CiAdrRegion',
    
'Iptc4xmpCore:CiEmailWork',
    
'Iptc4xmpCore:CiTelWork',
    
'Iptc4xmpCore:CiUrlWork',
    
'Iptc4xmpCore:CountryCode',
    
'Iptc4xmpCore:CreatorContactInfo',
    
'Iptc4xmpCore:IntellectualGenre',
    
'Iptc4xmpCore:Location',
    
'Iptc4xmpCore:Scene',
    
'Iptc4xmpCore:SubjectCode',
// Dublin Core Schema
    
'dc:contributor',
    
'dc:coverage',
    
'dc:creator',
    
'dc:date',
    
'dc:description',
    
'dc:format',
    
'dc:identifier',
    
'dc:language',
    
'dc:publisher',
    
'dc:relation',
    
'dc:rights',
    
'dc:source',
    
'dc:subject',
    
'dc:title',
    
'dc:type',
// XMP Basic Schema
    
'xmp:Advisory',
    
'xmp:BaseURL',
    
'xmp:CreateDate',
    
'xmp:CreatorTool',
    
'xmp:Identifier',
    
'xmp:Label',
    
'xmp:MetadataDate',
    
'xmp:ModifyDate',
    
'xmp:Nickname',
    
'xmp:Rating',
    
'xmp:Thumbnails',
    
'xmpidq:Scheme',
// XMP Rights Management Schema
    
'xmpRights:Certificate',
    
'xmpRights:Marked',
    
'xmpRights:Owner',
    
'xmpRights:UsageTerms',
    
'xmpRights:WebStatement',
// These are not in spec but Photoshop CS seems to use them
    
'xap:Advisory',
    
'xap:BaseURL',
    
'xap:CreateDate',
    
'xap:CreatorTool',
    
'xap:Identifier',
    
'xap:MetadataDate',
    
'xap:ModifyDate',
    
'xap:Nickname',
    
'xap:Rating',
    
'xap:Thumbnails',
    
'xapidq:Scheme',
    
'xapRights:Certificate',
    
'xapRights:Copyright',
    
'xapRights:Marked',
    
'xapRights:Owner',
    
'xapRights:UsageTerms',
    
'xapRights:WebStatement',
// XMP Media Management Schema
    
'xapMM:DerivedFrom',
    
'xapMM:DocumentID',
    
'xapMM:History',
    
'xapMM:InstanceID',
    
'xapMM:ManagedFrom',
    
'xapMM:Manager',
    
'xapMM:ManageTo',
    
'xapMM:ManageUI',
    
'xapMM:ManagerVariant',
    
'xapMM:RenditionClass',
    
'xapMM:RenditionParams',
    
'xapMM:VersionID',
    
'xapMM:Versions',
    
'xapMM:LastURL',
    
'xapMM:RenditionOf',
    
'xapMM:SaveID',
// XMP Basic Job Ticket Schema
    
'xapBJ:JobRef',
// XMP Paged-Text Schema
    
'xmpTPg:MaxPageSize',
    
'xmpTPg:NPages',
    
'xmpTPg:Fonts',
    
'xmpTPg:Colorants',
    
'xmpTPg:PlateNames',
// Adobe PDF Schema
    
'pdf:Keywords',
    
'pdf:PDFVersion',
    
'pdf:Producer',
// Photoshop Schema
    
'photoshop:AuthorsPosition',
    
'photoshop:CaptionWriter',
    
'photoshop:Category',
    
'photoshop:City',
    
'photoshop:Country',
    
'photoshop:Credit',
    
'photoshop:DateCreated',
    
'photoshop:Headline',
    
'photoshop:History',
// Not in XMP spec
    
'photoshop:Instructions',
    
'photoshop:Source',
    
'photoshop:State',
    
'photoshop:SupplementalCategories',
    
'photoshop:TransmissionReference',
    
'photoshop:Urgency',
// EXIF Schemas
    
'tiff:ImageWidth',
    
'tiff:ImageLength',
    
'tiff:BitsPerSample',
    
'tiff:Compression',
    
'tiff:PhotometricInterpretation',
    
'tiff:Orientation',
    
'tiff:SamplesPerPixel',
    
'tiff:PlanarConfiguration',
    
'tiff:YCbCrSubSampling',
    
'tiff:YCbCrPositioning',
    
'tiff:XResolution',
    
'tiff:YResolution',
    
'tiff:ResolutionUnit',
    
'tiff:TransferFunction',
    
'tiff:WhitePoint',
    
'tiff:PrimaryChromaticities',
    
'tiff:YCbCrCoefficients',
    
'tiff:ReferenceBlackWhite',
    
'tiff:DateTime',
    
'tiff:ImageDescription',
    
'tiff:Make',
    
'tiff:Model',
    
'tiff:Software',
    
'tiff:Artist',
    
'tiff:Copyright',
    
'exif:ExifVersion',
    
'exif:FlashpixVersion',
    
'exif:ColorSpace',
    
'exif:ComponentsConfiguration',
    
'exif:CompressedBitsPerPixel',
    
'exif:PixelXDimension',
    
'exif:PixelYDimension',
    
'exif:MakerNote',
    
'exif:UserComment',
    
'exif:RelatedSoundFile',
    
'exif:DateTimeOriginal',
    
'exif:DateTimeDigitized',
    
'exif:ExposureTime',
    
'exif:FNumber',
    
'exif:ExposureProgram',
    
'exif:SpectralSensitivity',
    
'exif:ISOSpeedRatings',
    
'exif:OECF',
    
'exif:ShutterSpeedValue',
    
'exif:ApertureValue',
    
'exif:BrightnessValue',
    
'exif:ExposureBiasValue',
    
'exif:MaxApertureValue',
    
'exif:SubjectDistance',
    
'exif:MeteringMode',
    
'exif:LightSource',
    
'exif:Flash',
    
'exif:FocalLength',
    
'exif:SubjectArea',
    
'exif:FlashEnergy',
    
'exif:SpatialFrequencyResponse',
    
'exif:FocalPlaneXResolution',
    
'exif:FocalPlaneYResolution',
    
'exif:FocalPlaneResolutionUnit',
    
'exif:SubjectLocation',
    
'exif:SensingMethod',
    
'exif:FileSource',
    
'exif:SceneType',
    
'exif:CFAPattern',
    
'exif:CustomRendered',
    
'exif:ExposureMode',
    
'exif:WhiteBalance',
    
'exif:DigitalZoomRatio',
    
'exif:FocalLengthIn35mmFilm',
    
'exif:SceneCaptureType',
    
'exif:GainControl',
    
'exif:Contrast',
    
'exif:Saturation',
    
'exif:Sharpness',
    
'exif:DeviceSettingDescription',
    
'exif:SubjectDistanceRange',
    
'exif:ImageUniqueID',
    
'exif:GPSVersionID',
    
'exif:GPSLatitude',
    
'exif:GPSLongitude',
    
'exif:GPSAltitudeRef',
    
'exif:GPSAltitude',
    
'exif:GPSTimeStamp',
    
'exif:GPSSatellites',
    
'exif:GPSStatus',
    
'exif:GPSMeasureMode',
    
'exif:GPSDOP',
    
'exif:GPSSpeedRef',
    
'exif:GPSSpeed',
    
'exif:GPSTrackRef',
    
'exif:GPSTrack',
    
'exif:GPSImgDirectionRef',
    
'exif:GPSImgDirection',
    
'exif:GPSMapDatum',
    
'exif:GPSDestLatitude',
    
'exif:GPSDestLongitude',
    
'exif:GPSDestBearingRef',
    
'exif:GPSDestBearing',
    
'exif:GPSDestDistanceRef',
    
'exif:GPSDestDistance',
    
'exif:GPSProcessingMethod',
    
'exif:GPSAreaInformation',
    
'exif:GPSDifferential',
    
'stDim:w',
    
'stDim:h',
    
'stDim:unit',
    
'xapGImg:height',
    
'xapGImg:width',
    
'xapGImg:format',
    
'xapGImg:image',
    
'stEvt:action',
    
'stEvt:instanceID',
    
'stEvt:parameters',
    
'stEvt:softwareAgent',
    
'stEvt:when',
    
'stRef:instanceID',
    
'stRef:documentID',
    
'stRef:versionID',
    
'stRef:renditionClass',
    
'stRef:renditionParams',
    
'stRef:manager',
    
'stRef:managerVariant',
    
'stRef:manageTo',
    
'stRef:manageUI',
    
'stVer:comments',
    
'stVer:event',
    
'stVer:modifyDate',
    
'stVer:modifier',
    
'stVer:version',
    
'stJob:name',
    
'stJob:id',
    
'stJob:url',
// Exif Flash
    
'exif:Fired',
    
'exif:Return',
    
'exif:Mode',
    
'exif:Function',
    
'exif:RedEyeMode',
// Exif OECF/SFR
    
'exif:Columns',
    
'exif:Rows',
    
'exif:Names',
    
'exif:Values',
// Exif CFAPattern
    
'exif:Columns',
    
'exif:Rows',
    
'exif:Values',
// Exif DeviceSettings
    
'exif:Columns',
    
'exif:Rows',
    
'exif:Settings',
);


/**
* Global Variable: JPEG_Segment_Names
*
* The names of the JPEG segment markers, indexed by their marker number
*/
$GLOBALS['JPEG_Segment_Names'] = array(
    
0x01 => 'TEM',
    
0x02 => 'RES',
    
0xC0 => 'SOF0',
    
0xC1 => 'SOF1',
    
0xC2 => 'SOF2',
    
0xC3 => 'SOF4',
    
0xC4 => 'DHT',
    
0xC5 => 'SOF5',
    
0xC6 => 'SOF6',
    
0xC7 => 'SOF7',
    
0xC8 => 'JPG',
    
0xC9 => 'SOF9',
    
0xCA => 'SOF10',
    
0xCB => 'SOF11',
    
0xCC => 'DAC',
    
0xCD => 'SOF13',
    
0xCE => 'SOF14',
    
0xCF => 'SOF15',
    
0xD0 => 'RST0',
    
0xD1 => 'RST1',
    
0xD2 => 'RST2',
    
0xD3 => 'RST3',
    
0xD4 => 'RST4',
    
0xD5 => 'RST5',
    
0xD6 => 'RST6',
    
0xD7 => 'RST7',
    
0xD8 => 'SOI',
    
0xD9 => 'EOI',
    
0xDA => 'SOS',
    
0xDB => 'DQT',
    
0xDC => 'DNL',
    
0xDD => 'DRI',
    
0xDE => 'DHP',
    
0xDF => 'EXP',
    
0xE0 => 'APP0',
    
0xE1 => 'APP1',
    
0xE2 => 'APP2',
    
0xE3 => 'APP3',
    
0xE4 => 'APP4',
    
0xE5 => 'APP5',
    
0xE6 => 'APP6',
    
0xE7 => 'APP7',
    
0xE8 => 'APP8',
    
0xE9 => 'APP9',
    
0xEA => 'APP10',
    
0xEB => 'APP11',
    
0xEC => 'APP12',
    
0xED => 'APP13',
    
0xEE => 'APP14',
    
0xEF => 'APP15',
    
0xF0 => 'JPG0',
    
0xF1 => 'JPG1',
    
0xF2 => 'JPG2',
    
0xF3 => 'JPG3',
    
0xF4 => 'JPG4',
    
0xF5 => 'JPG5',
    
0xF6 => 'JPG6',
    
0xF7 => 'JPG7',
    
0xF8 => 'JPG8',
    
0xF9 => 'JPG9',
    
0xFA => 'JPG10',
    
0xFB => 'JPG11',
    
0xFC => 'JPG12',
    
0xFD => 'JPG13',
    
0xFE => 'COM',
);

?>
Онлайн: 3
Реклама