* @author Jean-Pascal MILCENT * @author Aurelien PERONNET * @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ class ExtracteurMetadonnees { private $meta = array(); private $tableau_ids_tags_exif = array( 'InteropIndex' => '1', 'InteropVersion' => '2', 'ProcessingSoftware' => '11', 'SubfileType' => '254', 'OldSubfileType' => '255', 'ImageWidth' => '256', 'ImageHeight' => '257', 'BitsPerSample' => '258', 'Compression' => '259', 'PhotometricInterpretation' => '262', 'Thresholding' => '263', 'CellWidth' => '264', 'CellLength' => '265', 'FillOrder' => '266', 'DocumentName' => '269', 'ImageDescription' => '270', 'Make' => '271', 'Model' => '272', 'StripOffsets' => '273', 'Orientation' => '274', 'SamplesPerPixel' => '277', 'RowsPerStrip' => '278', 'StripByteCounts' => '279', 'MinSampleValue' => '280', 'MaxSampleValue' => '281', 'XResolution' => '282', 'YResolution' => '283', 'PlanarConfiguration' => '284', 'PageName' => '285', 'XPosition' => '286', 'YPosition' => '287', 'FreeOffsets' => '288', 'FreeByteCounts' => '289', 'GrayResponseUnit' => '290', 'GrayResponseCurve' => '291', 'T4Options' => '292', 'T6Options' => '293', 'ResolutionUnit' => '296', 'PageNumber' => '297', 'ColorResponseUnit' => '300', 'TransferFunction' => '301', 'Software' => '305', 'ModifyDate' => '306', 'Artist' => '315', 'HostComputer' => '316', 'Predictor' => '317', 'WhitePoint' => '318', 'PrimaryChromaticities' => '319', 'ColorMap' => '320', 'HalftoneHints' => '321', 'TileWidth' => '322', 'TileLength' => '323', 'TileOffsets' => '324', 'TileByteCounts' => '325', 'BadFaxLines' => '326', 'CleanFaxData' => '327', 'ConsecutiveBadFaxLines' => '328', 'SubIFD' => '330', 'InkSet' => '332', 'InkNames' => '333', 'NumberofInks' => '334', 'DotRange' => '336', 'TargetPrinter' => '337', 'ExtraSamples' => '338', 'SampleFormat' => '339', 'SMinSampleValue' => '340', 'SMaxSampleValue' => '341', 'TransferRange' => '342', 'ClipPath' => '343', 'XClipPathUnits' => '344', 'YClipPathUnits' => '345', 'Indexed' => '346', 'JPEGTables' => '347', 'OPIProxy' => '351', 'GlobalParametersIFD' => '400', 'ProfileType' => '401', 'FaxProfile' => '402', 'CodingMethods' => '403', 'VersionYear' => '404', 'ModeNumber' => '405', 'Decode' => '433', 'DefaultImageColor' => '434', 'T82Options' => '435', 'JPEGTables' => '437', 'JPEGProc' => '512', 'ThumbnailOffset' => '513', 'ThumbnailLength' => '514', 'JPEGRestartInterval' => '515', 'JPEGLosslessPredictors' => '517', 'JPEGPointTransforms' => '518', 'JPEGQTables' => '519', 'JPEGDCTables' => '520', 'JPEGACTables' => '521', 'YCbCrCoefficients' => '529', 'YCbCrSubSampling' => '530', 'YCbCrPositioning' => '531', 'ReferenceBlackWhite' => '532', 'StripRowCounts' => '559', 'ApplicationNotes' => '700', 'USPTOMiscellaneous' => '999', 'RelatedImageFileFormat' => '4096', 'RelatedImageWidth' => '4097', 'RelatedImageHeight' => '4098', 'Rating' => '18246', 'XP_DIP_XML' => '18247', 'StitchInfo' => '18248', 'RatingPercent' => '18249', 'ImageID' => '32781', 'WangTag1' => '32931', 'WangAnnotation' => '32932', 'WangTag3' => '32933', 'WangTag4' => '32934', 'Matteing' => '32995', 'DataType' => '32996', 'ImageDepth' => '32997', 'TileDepth' => '32998', 'Model2' => '33405', 'CFARepeatPatternDim' => '33421', 'CFAPattern2' => '33422', 'BatteryLevel' => '33423', 'KodakIFD' => '33424', 'Copyright' => '33432', 'ExposureTime' => '33434', 'FNumber' => '33437', 'MDFileTag' => '33445', 'MDScalePixel' => '33446', 'MDColorTable' => '33447', 'MDLabName' => '33448', 'MDSampleInfo' => '33449', 'MDPrepDate' => '33450', 'MDPrepTime' => '33451', 'MDFileUnits' => '33452', 'PixelScale' => '33550', 'AdventScale' => '33589', 'AdventRevision' => '33590', 'UIC1Tag' => '33628', 'UIC2Tag' => '33629', 'UIC3Tag' => '33630', 'UIC4Tag' => '33631', 'IPTC-NAA' => '33723', 'IntergraphPacketData' => '33918', 'IntergraphFlagRegisters' => '33919', 'IntergraphMatrix' => '33920', 'INGRReserved' => '33921', 'ModelTiePoint' => '33922', 'Site' => '34016', 'ColorSequence' => '34017', 'IT8Header' => '34018', 'RasterPadding' => '34019', 'BitsPerRunLength' => '34020', 'BitsPerExtendedRunLength' => '34021', 'ColorTable' => '34022', 'ImageColorIndicator' => '34023', 'BackgroundColorIndicator' => '34024', 'ImageColorValue' => '34025', 'BackgroundColorValue' => '34026', 'PixelIntensityRange' => '34027', 'TransparencyIndicator' => '34028', 'ColorCharacterization' => '34029', 'HCUsage' => '34030', 'TrapIndicator' => '34031', 'CMYKEquivalent' => '34032', 'SEMInfo' => '34118', 'AFCP_IPTC' => '34152', 'PixelMagicJBIGOptions' => '34232', 'ModelTransform' => '34264', 'WB_GRGBLevels' => '34306', 'LeafData' => '34310', 'PhotoshopSettings' => '34377', 'ExifOffset' => '34665', 'ICC_Profile' => '34675', 'TIFF_FXExtensions' => '34687', 'MultiProfiles' => '34688', 'SharedData' => '34689', 'T88Options' => '34690', 'ImageLayer' => '34732', 'GeoTiffDirectory' => '34735', 'GeoTiffDoubleParams' => '34736', 'GeoTiffAsciiParams' => '34737', 'ExposureProgram' => '34850', 'SpectralSensitivity' => '34852', 'GPSInfo' => '34853', 'ISO' => '34855', 'Opto-ElectricConvFactor' => '34856', 'Interlace' => '34857', 'TimeZoneOffset' => '34858', 'SelfTimerMode' => '34859', 'SensitivityType' => '34864', 'StandardOutputSensitivity' => '34865', 'RecommendedExposureIndex' => '34866', 'ISOSpeed' => '34867', 'ISOSpeedLatitudeyyy' => '34868', 'ISOSpeedLatitudezzz' => '34869', 'FaxRecvParams' => '34908', 'FaxSubAddress' => '34909', 'FaxRecvTime' => '34910', 'LeafSubIFD' => '34954', 'ExifVersion' => '36864', 'DateTimeOriginal' => '36867', 'CreateDate' => '36868', 'ComponentsConfiguration' => '37121', 'CompressedBitsPerPixel' => '37122', 'ShutterSpeedValue' => '37377', 'ApertureValue' => '37378', 'BrightnessValue' => '37379', 'ExposureCompensation' => '37380', 'MaxApertureValue' => '37381', 'SubjectDistance' => '37382', 'MeteringMode' => '37383', 'LightSource' => '37384', 'Flash' => '37385', 'FocalLength' => '37386', 'FlashEnergy' => '37387', 'SpatialFrequencyResponse' => '37388', 'Noise' => '37389', 'FocalPlaneXResolution' => '37390', 'FocalPlaneYResolution' => '37391', 'FocalPlaneResolutionUnit' => '37392', 'ImageNumber' => '37393', 'SecurityClassification' => '37394', 'ImageHistory' => '37395', 'SubjectArea' => '37396', 'ExposureIndex' => '37397', 'TIFF-EPStandardID' => '37398', 'SensingMethod' => '37399', 'CIP3DataFile' => '37434', 'CIP3Sheet' => '37435', 'CIP3Side' => '37436', 'StoNits' => '37439', 'MakerNoteCanon' => '37500', 'UserComment' => '37510', 'SubSecTime' => '37520', 'SubSecTimeOriginal' => '37521', 'SubSecTimeDigitized' => '37522', 'MSDocumentText' => '37679', 'MSPropertySetStorage' => '37680', 'MSDocumentTextPosition' => '37681', 'ImageSourceData' => '37724', 'XPTitle' => '40091', 'XPComment' => '40092', 'XPAuthor' => '40093', 'XPKeywords' => '40094', 'XPSubject' => '40095', 'FlashpixVersion' => '40960', 'ColorSpace' => '40961', 'ExifImageWidth' => '40962', 'ExifImageHeight' => '40963', 'RelatedSoundFile' => '40964', 'InteropOffset' => '40965', 'FlashEnergy' => '41483', 'SpatialFrequencyResponse' => '41484', 'Noise' => '41485', 'FocalPlaneXResolution' => '41486', 'FocalPlaneYResolution' => '41487', 'FocalPlaneResolutionUnit' => '41488', 'ImageNumber' => '41489', 'SecurityClassification' => '41490', 'ImageHistory' => '41491', 'SubjectLocation' => '41492', 'ExposureIndex' => '41493', 'TIFF-EPStandardID' => '41494', 'SensingMethod' => '41495', 'FileSource' => '41728', 'SceneType' => '41729', 'CFAPattern' => '41730', 'CustomRendered' => '41985', 'ExposureMode' => '41986', 'WhiteBalance' => '41987', 'DigitalZoomRatio' => '41988', 'FocalLengthIn35mmFormat' => '41989', 'SceneCaptureType' => '41990', 'GainControl' => '41991', 'Contrast' => '41992', 'Saturation' => '41993', 'Sharpness' => '41994', 'DeviceSettingDescription' => '41995', 'SubjectDistanceRange' => '41996', 'ImageUniqueID' => '42016', 'OwnerName' => '42032', 'SerialNumber' => '42033', 'LensInfo' => '42034', 'LensMake' => '42035', 'LensModel' => '42036', 'LensSerialNumber' => '42037', 'GDALMetadata' => '42112', 'GDALNoData' => '42113', 'Gamma' => '42240', 'ExpandSoftware' => '44992', 'ExpandLens' => '44993', 'ExpandFilm' => '44994', 'ExpandFilterLens' => '44995', 'ExpandScanner' => '44996', 'ExpandFlashLamp' => '44997', 'PixelFormat' => '48129', 'Transformation' => '48130', 'Uncompressed' => '48131', 'ImageType' => '48132', 'ImageWidth' => '48256', 'ImageHeight' => '48257', 'WidthResolution' => '48258', 'HeightResolution' => '48259', 'ImageOffset' => '48320', 'ImageByteCount' => '48321', 'AlphaOffset' => '48322', 'AlphaByteCount' => '48323', 'ImageDataDiscard' => '48324', 'AlphaDataDiscard' => '48325', 'OceScanjobDesc' => '50215', 'OceApplicationSelector' => '50216', 'OceIDNumber' => '50217', 'OceImageLogic' => '50218', 'Annotations' => '50255', 'PrintIM' => '50341', 'USPTOOriginalContentType' => '50560', 'DNGVersion' => '50706', 'DNGBackwardVersion' => '50707', 'UniqueCameraModel' => '50708', 'LocalizedCameraModel' => '50709', 'CFAPlaneColor' => '50710', 'CFALayout' => '50711', 'LinearizationTable' => '50712', 'BlackLevelRepeatDim' => '50713', 'BlackLevel' => '50714', 'BlackLevelDeltaH' => '50715', 'BlackLevelDeltaV' => '50716', 'WhiteLevel' => '50717', 'DefaultScale' => '50718', 'DefaultCropOrigin' => '50719', 'DefaultCropSize' => '50720', 'ColorMatrix1' => '50721', 'ColorMatrix2' => '50722', 'CameraCalibration1' => '50723', 'CameraCalibration2' => '50724', 'ReductionMatrix1' => '50725', 'ReductionMatrix2' => '50726', 'AnalogBalance' => '50727', 'AsShotNeutral' => '50728', 'AsShotWhiteXY' => '50729', 'BaselineExposure' => '50730', 'BaselineNoise' => '50731', 'BaselineSharpness' => '50732', 'BayerGreenSplit' => '50733', 'LinearResponseLimit' => '50734', 'CameraSerialNumber' => '50735', 'DNGLensInfo' => '50736', 'ChromaBlurRadius' => '50737', 'AntiAliasStrength' => '50738', 'ShadowScale' => '50739', 'SR2Private' => '50740', 'MakerNoteSafety' => '50741', 'RawImageSegmentation' => '50752', 'CalibrationIlluminant1' => '50778', 'CalibrationIlluminant2' => '50779', 'BestQualityScale' => '50780', 'RawDataUniqueID' => '50781', 'AliasLayerMetadata' => '50784', 'OriginalRawFileName' => '50827', 'OriginalRawFileData' => '50828', 'ActiveArea' => '50829', 'MaskedAreas' => '50830', 'AsShotICCProfile' => '50831', 'AsShotPreProfileMatrix' => '50832', 'CurrentICCProfile' => '50833', 'CurrentPreProfileMatrix' => '50834', 'ColorimetricReference' => '50879', 'PanasonicTitle' => '50898', 'PanasonicTitle2' => '50899', 'CameraCalibrationSig' => '50931', 'ProfileCalibrationSig' => '50932', 'ProfileIFD' => '50933', 'AsShotProfileName' => '50934', 'NoiseReductionApplied' => '50935', 'ProfileName' => '50936', 'ProfileHueSatMapDims' => '50937', 'ProfileHueSatMapData1' => '50938', 'ProfileHueSatMapData2' => '50939', 'ProfileToneCurve' => '50940', 'ProfileEmbedPolicy' => '50941', 'ProfileCopyright' => '50942', 'ForwardMatrix1' => '50964', 'ForwardMatrix2' => '50965', 'PreviewApplicationName' => '50966', 'PreviewApplicationVersion' => '50967', 'PreviewSettingsName' => '50968', 'PreviewSettingsDigest' => '50969', 'PreviewColorSpace' => '50970', 'PreviewDateTime' => '50971', 'RawImageDigest' => '50972', 'OriginalRawFileDigest' => '50973', 'SubTileBlockSize' => '50974', 'RowInterleaveFactor' => '50975', 'ProfileLookTableDims' => '50981', 'ProfileLookTableData' => '50982', 'OpcodeList1' => '51008', 'OpcodeList2' => '51009', 'OpcodeList3' => '51022', 'NoiseProfile' => '51041', 'Padding' => '59932', 'OffsetSchema' => '59933', 'OwnerName' => '65000', 'SerialNumber' => '65001', 'Lens' => '65002', 'KDC_IFD' => '65024', 'RawFile' => '65100', 'Converter' => '65101', 'WhiteBalance' => '65102', 'Exposure' => '65105', 'Shadows' => '65106', 'Brightness' => '65107', 'Contrast' => '65108', 'Saturation' => '65109', 'Sharpness' => '65110', 'Smoothness' => '65111', 'MoireFilter' => '65112', ); public function extraireMetadonnees($cheminImage) { if ($this->peutUtiliserExifTool()) { $this->meta = $this->decoderMetadonneesExifTool($cheminImage); } else { $this->meta = $this->decoderMetadonneesBasique($cheminImage); } $metadonnees = array(); $metadonnees['hauteur'] = $this->obtenirHauteur(); $metadonnees['largeur'] = $this->obtenirLargeur(); $metadonnees['date_prise_de_vue'] = $this->obtenirDatePriseDeVue(); $metadonnees['appareil_fabriquant'] = $this->obtenirAppareilFabricant(); $metadonnees['appareil_modele'] = $this->obtenirAppareilModele(); $metadonnees['meta_exif'] = $this->convertirMetaVersXML('EXIF'); $metadonnees['meta_iptc'] = $this->convertirMetaVersXML('IPTC'); $metadonnees['meta_xmp'] = $this->convertirMetaVersXML('XMP'); $metadonnees['meta_makernote'] = $this->convertirMetaVersXML('MAKERNOTE'); return $metadonnees; } private function peutUtiliserExifTool() { // TODO indiquer ceci dans un fichier de config return file_exists('/usr/bin/exiftool') && is_executable('/usr/bin/exiftool'); } private function decoderMetadonneesExifTool($cheminImage) { $metadata = array(); $res = exec('/usr/bin/exiftool -g -D "'.$cheminImage.'"', $metadata); $metadata_decodees = array(); $categorie = ''; foreach($metadata as &$data) { if ($this->estUnSeparateurCategorieExifTool($data)) { $categorie = trim(str_replace('----', '', $data)); } else { $data_decodee = $this->parserValeurMetadonneeExifTool($data); $cle_metadonnee = str_replace(' ', '', $data_decodee['cle']); $metadata_decodees[$categorie][$cle_metadonnee] = $data_decodee; } } return $metadata_decodees; } private function estUnSeparateurCategorieExifTool($data) { return preg_match('^---- (.)* ----^',$data); } private function parserValeurMetadonneeExifTool($data) { $cle_valeur = explode(':',$data,2); $valeur = ''; if(count($cle_valeur) == 2) { $valeur = trim($cle_valeur[1]); } $id_cle = explode(' ',trim($cle_valeur[0]),2); $id_cle[1] = str_replace(array('-','/'),'',$id_cle[1]); $cle_id_valeur = array('cle' => $id_cle[1], 'id' => str_replace('-','',$id_cle[0]), 'valeur' => $valeur); return $cle_id_valeur; } public function decoderMetadonneesBasique($chemin_fichier) { $exif = @exif_read_data($chemin_fichier, "EXIF,COMPUTED,IFD0,FILE,COMMENT", true, false); // tant pis pour les makernote et xmp, les décoder demande trop de librairies externes, autant installer exiftool alors $metadonnees = array(); $metadonnees['XMP'] = array(); unset($metadonnees['EXIF']['MakerNote']); $metadonnees['MAKERNOTE'] = array(); $metadonnees_non_formatees = array(); if(isset($exif['EXIF'])) { $metadonnees_non_formatees = array_merge($metadonnees_non_formatees, $exif['EXIF']); } if(isset($exif['IFD0'])) { $metadonnees_non_formatees = array_merge($metadonnees_non_formatees, $exif['IFD0']); } $metadonnees['EXIF'] = $this->formaterTableauExif($metadonnees_non_formatees); $metadonnees['IPTC'] = $this->extraireIptc($chemin_fichier); $metadonnees['File'] = array( 'ImageWidth' => array('id' => '', 'valeur' => $exif['COMPUTED']['Width']), 'ImageHeight' => array('id' => '', 'valeur' => $exif['COMPUTED']['Height'])); return $metadonnees ; } private function formaterTableauExif(&$tableau) { $tableau_exif_formate = array(); foreach ($tableau as $nom_tag => $valeur) { $id = ''; if (isset($this->tableau_ids_tags_exif[$nom_tag])) { $id = $this->tableau_ids_tags_exif[$nom_tag]; } $tableau_exif_formate[$nom_tag] = array('id' => $id, 'valeur' => $valeur); } return $tableau_exif_formate; } /** * Extraction des metadonnées iptc */ public function extraireIptc($chemin_fichier) { $meta = array(); // getimagesize renvoie les infos iptc dans le tableau info $info = array(); $size = getimagesize($chemin_fichier, $info); // s'il existe if (isset($info['APP13'])) { // on parse les donnees $iptc = iptcparse($info['APP13']); if ($iptc) { // et on les analyse foreach ($iptc as $marker => $section) { foreach ($section as $nom => $val) { // pour remplir le tableau de donnees $this->decoderValeurIptc($marker, $val, $meta); } } } } return $meta; } /** * Stocke une valeur de metadonnées iptc dans le champ du tableau correspondant * @param String $nom nom de la valeur * @param String $val valeur * @param String $data référence vers le tableau où la donnée sera stockée **/ private function decoderValeurIptc($nom, $val, &$data_tab) { switch ($nom) { case "2#005" :// mots cles iptc $data_tab['Category'] = array('id' => '5', 'valeur' => $val); break; case "2#080" :// champ by line $data_tab['By-Line'] = array('id' => '80', 'valeur' => $val); break ; case "2#085" :// champ by line titre $data_tab['By-LineTitle'] = array('id' => '85', 'valeur' => $val); break ; case "2#090" :// ville $data_tab['City'] = array('id' => '90', 'valeur' => $val); break ; case "2#092" :// sous location $data_tab['SubLocation'] = array('id' => '92', 'valeur' => $val); break ; case "2#095" :// etat (pour les us) $data_tab['ProvinceState'] = array('id' => '95', 'valeur' => $val); break ; case "2#100" :// code pays $data_tab['CountryPrimaryLocationCode'] = array('id' => '100', 'valeur' => $val); break ; case "2#101" :// code pays $data_tab['CountryName'] = array('id' => '101', 'valeur' => $val); break ; case "2#105" :// titre principal $data_tab['Headline'] = array('id' => '105', 'valeur' => $val); break ; case "2#110" :// credit $data_tab['Credit'] = array('id' => '110', 'valeur' => $val); break ; case "2#116" :// copyright $data_tab['CopyrightNotice'] = array('id' => '116', 'valeur' => $val); break ; case "2#118" :// contact $data_tab['Contact'] = array('id' => '118', 'valeur' => $val); break ; default: unset($data_tab['nom']); } } private function obtenirHauteur() { $hauteur = isset($this->meta['File']['ImageHeight']) ? $this->meta['File']['ImageHeight']['valeur'] : ''; return $hauteur; } private function obtenirLargeur() { $largeur = isset($this->meta['File']['ImageWidth']) ? $this->meta['File']['ImageWidth']['valeur'] : ''; return $largeur; } private function obtenirDatePriseDeVue() { $date = isset($this->meta['EXIF']['DateTimeOriginal']) ? $this->meta['EXIF']['DateTimeOriginal']['valeur'] : ''; return $date; } private function obtenirAppareilFabricant() { $fabriquant = isset($this->meta['EXIF']['Make']) ? $this->meta['EXIF']['Make']['valeur'] : ''; return $fabriquant; } private function obtenirAppareilModele() { $modele = isset($this->meta['EXIF']['CameraModelName']) ? $this->meta['EXIF']['CameraModelName']['valeur'] : ''; return $modele; } private function convertirMetaVersXML($type) { $xml = null; if (isset($this->meta[$type])) { $racine = strtolower($type); $xml = ''."\n"; $xml .= "<$racine>"."\n"; foreach ($this->meta[$type] as $prop => &$valeur) { $xml .= '<'.$prop.' id="'.$valeur['id'].'">'.$valeur['valeur'].''."\n"; } $xml .= ""; } return $xml; } }