I need to get Description
metadata of an image
$exif = exif_read_data('img.jpg', 0, true);
foreach ($exif as $key => $section) {
foreach ($section as $name => $val) {
echo "$key.$name: $val<br />\n";
}
}
result:
FILE.FileName: img.jpg<br />
FILE.FileDateTime: 1657032928<br />
FILE.FileSize: 89183<br />
FILE.FileType: 2<br />
FILE.MimeType: image/jpeg<br />
FILE.SectionsFound: ANY_TAG, IFD0, GPS<br />
COMPUTED.html: width="576" height="680"<br />
COMPUTED.Height: 680<br />
COMPUTED.Width: 576<br />
COMPUTED.IsColor: 1<br />
COMPUTED.ByteOrderMotorola: 0<br />
IFD0.Orientation: 1<br />
IFD0.XResolution: 300/1<br />
IFD0.YResolution: 300/1<br />
IFD0.ResolutionUnit: 2<br />
IFD0.Software: GIMP 2.10.32<br />
IFD0.DateTime: 2022:07:05 16:54:23<br />
IFD0.GPS_IFD_Pointer: 148<br />
GPS.GPSAltitude: 0/100<br />
there is no Description
metadata (the data is clearly visible inside GIMP, for example)
so how can I get Description
metadata of an image ?
(Without any link to the file you tested, I assume that...)
The reason is that Exif (Exchangeable image file format) is not the only metadata format and that it does not know any item for descriptions. Most likely, because it is primarily inserted by cameras, not programs. There are other metadata formats:
2
("application"), dataset 120
("Caption/Abstract: A textual description of the objectdata, particularly used where the object is not text.")description
element in any namespace, mostly <dc:description>
INFO
chunk, but without any "description" item©des
, desc
, ldes
, sdes
, dscp
, or key atom com.apple.quicktime.description
Which meta formats can be expected in which file formats?
File formats \ Metadata formats | Exif | IPTC | XMP | RIFF | QTFF | proprietary |
---|---|---|---|---|---|---|
JFIF/JPEG | ✓ | ✓ | ✓ | comment | ||
TIFF, CR2, ORF, DNG, RAW, JPEG-XR, NIFF, MDI | ✓ | ✓ | ✓ | many | ||
PNG, JNG, MNG | ✓ | ✓ | ✓ | free text | ||
GIF | ✓ | comment | ||||
WebP | ✓ | ✓ | ✓ | many | ||
JPEG2000, JPEG-XL, HEIF | ✓ | ✓ | ✓ | ✓ | many | |
PSD | ✓ | ✓ | ✓ | caption |
PHP can by default parse Exif and IPTC and XML (XMP's format), along with JFIFiles. Combining all this we can find a JFIF's description either in IPTC or XMP metadata, should it be there. Technically descriptions can occur more than once per metadata, and (of course) all of those can differ.
<?php
// Don't let the browser interpret this as HTML
header( 'Content-Type: text/plain; charset=UTF-8' );
// Add finding to overall array and take note of metadata format in which it was found
function found( &$aFound, $sText, $sMeta ) {
if( !isset( $aFound[$sText] ) ) { // Text is new?
$aFound[$sText]= $sMeta;
} else {
$aFound[$sText].= ', '. $sMeta; // Text is known already, just add metadata format
}
}
// Store all findings: key=text, value=metadata format(s)
$aFound= array();
// Parse picture, only JFIF/JPEG picture is supported for metadata
getimagesize( 'C:/mypic.jpg', $aMeta );
// Any metadata found at all?
if( is_array( $aMeta ) )
foreach( $aMeta as $sType=> $sData ) {
switch( $sType ) {
case 'APP13': // IPTC
$aIptc= iptcparse( $sData );
if( is_array( $aIptc ) ) { // Might have failed
if( isset( $aIptc['2#120'] ) ) {
// In theory tags can occur multiple times
foreach( $aIptc['2#120'] as $sDesc ) {
found( $aFound, $sDesc, 'IPTC' );
}
}
}
break;
case 'APP1': // Mostly XMP, but can also be Exif
$iStart= strpos( $sData, '<x:xmpmeta' );
$iEnd= strpos( $sData, '</x:xmpmeta>', $iStart+ 10 );
if( $iStart> 0&& $iEnd> 0 ) { // Valid XMP
$sData= substr( $sData, $iStart, $iEnd- $iStart+ 12 );
$oDom= new DOMDocument();
$oDom-> loadXml( $sData ); // Parse XML data
$oXpath= new DOMXpath( $oDom );
$oXpath-> registerNamespace( 'rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ); // Resource Description Framework
$oXpath-> registerNamespace( 'dc', 'http://purl.org/dc/elements/1.1/' ); // Dublin Core
// First the document/file itself, then in its context the picture related info
foreach( $oXpath-> evaluate( '//rdf:Description//dc:description' ) as $oElem ) {
if( $oElem-> nodeValue ) found( $aFound, $oElem-> nodeValue, 'XMP' ); else
if( $oElem-> textContent ) found( $aFound, $oElem-> textContent, 'XMP' );
}
}
break;
}
}
// Which texts have we found?
print_r( $aFound );