Exif.php 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @ingroup Media
  19. * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
  20. * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
  22. * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
  23. * @file
  24. */
  25. /**
  26. * @todo document (e.g. one-sentence class-overview description)
  27. * @ingroup Media
  28. */
  29. class Exif {
  30. const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
  31. const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
  32. const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
  33. const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
  34. const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
  35. const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
  36. const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
  37. const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
  38. //@{
  39. /* @var array
  40. * @private
  41. */
  42. /**
  43. * Exif tags grouped by category, the tagname itself is the key and the type
  44. * is the value, in the case of more than one possible value type they are
  45. * separated by commas.
  46. */
  47. var $mExifTags;
  48. /**
  49. * A one dimentional array of all Exif tags
  50. */
  51. var $mFlatExifTags;
  52. /**
  53. * The raw Exif data returned by exif_read_data()
  54. */
  55. var $mRawExifData;
  56. /**
  57. * A Filtered version of $mRawExifData that has been pruned of invalid
  58. * tags and tags that contain content they shouldn't contain according
  59. * to the Exif specification
  60. */
  61. var $mFilteredExifData;
  62. /**
  63. * Filtered and formatted Exif data, see FormatExif::getFormattedData()
  64. */
  65. var $mFormattedExifData;
  66. //@}
  67. //@{
  68. /* @var string
  69. * @private
  70. */
  71. /**
  72. * The file being processed
  73. */
  74. var $file;
  75. /**
  76. * The basename of the file being processed
  77. */
  78. var $basename;
  79. /**
  80. * The private log to log to, e.g. 'exif'
  81. */
  82. var $log = false;
  83. //@}
  84. /**
  85. * Constructor
  86. *
  87. * @param $file String: filename.
  88. */
  89. function __construct( $file ) {
  90. /**
  91. * Page numbers here refer to pages in the EXIF 2.2 standard
  92. *
  93. * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
  94. */
  95. $this->mExifTags = array(
  96. # TIFF Rev. 6.0 Attribute Information (p22)
  97. 'tiff' => array(
  98. # Tags relating to image structure
  99. 'structure' => array(
  100. 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
  101. 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
  102. 'BitsPerSample' => Exif::SHORT, # Number of bits per component
  103. # "When a primary image is JPEG compressed, this designation is not"
  104. # "necessary and is omitted." (p23)
  105. 'Compression' => Exif::SHORT, # Compression scheme #p23
  106. 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
  107. 'Orientation' => Exif::SHORT, # Orientation of image #p24
  108. 'SamplesPerPixel' => Exif::SHORT, # Number of components
  109. 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
  110. 'YCbCrSubSampling' => Exif::SHORT, # Subsampling ratio of Y to C #p24
  111. 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
  112. 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
  113. 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
  114. 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
  115. ),
  116. # Tags relating to recording offset
  117. 'offset' => array(
  118. 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
  119. 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
  120. 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
  121. 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
  122. 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
  123. ),
  124. # Tags relating to image data characteristics
  125. 'characteristics' => array(
  126. 'TransferFunction' => Exif::SHORT, # Transfer function
  127. 'WhitePoint' => Exif::RATIONAL, # White point chromaticity
  128. 'PrimaryChromaticities' => Exif::RATIONAL, # Chromaticities of primarities
  129. 'YCbCrCoefficients' => Exif::RATIONAL, # Color space transformation matrix coefficients #p27
  130. 'ReferenceBlackWhite' => Exif::RATIONAL # Pair of black and white reference values
  131. ),
  132. # Other tags
  133. 'other' => array(
  134. 'DateTime' => Exif::ASCII, # File change date and time
  135. 'ImageDescription' => Exif::ASCII, # Image title
  136. 'Make' => Exif::ASCII, # Image input equipment manufacturer
  137. 'Model' => Exif::ASCII, # Image input equipment model
  138. 'Software' => Exif::ASCII, # Software used
  139. 'Artist' => Exif::ASCII, # Person who created the image
  140. 'Copyright' => Exif::ASCII, # Copyright holder
  141. ),
  142. ),
  143. # Exif IFD Attribute Information (p30-31)
  144. 'exif' => array(
  145. # Tags relating to version
  146. 'version' => array(
  147. # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
  148. # to the EXIF 2.1 AND 2.2 standards
  149. 'ExifVersion' => Exif::UNDEFINED, # Exif version
  150. 'FlashpixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
  151. ),
  152. # Tags relating to Image Data Characteristics
  153. 'characteristics' => array(
  154. 'ColorSpace' => Exif::SHORT, # Color space information #p32
  155. ),
  156. # Tags relating to image configuration
  157. 'configuration' => array(
  158. 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
  159. 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
  160. 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
  161. 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valind image height
  162. ),
  163. # Tags relating to related user information
  164. 'user' => array(
  165. 'MakerNote' => Exif::UNDEFINED, # Manufacturer notes
  166. 'UserComment' => Exif::UNDEFINED, # User comments #p34
  167. ),
  168. # Tags relating to related file information
  169. 'related' => array(
  170. 'RelatedSoundFile' => Exif::ASCII, # Related audio file
  171. ),
  172. # Tags relating to date and time
  173. 'dateandtime' => array(
  174. 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
  175. 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
  176. 'SubSecTime' => Exif::ASCII, # DateTime subseconds
  177. 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
  178. 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
  179. ),
  180. # Tags relating to picture-taking conditions (p31)
  181. 'conditions' => array(
  182. 'ExposureTime' => Exif::RATIONAL, # Exposure time
  183. 'FNumber' => Exif::RATIONAL, # F Number
  184. 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
  185. 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
  186. 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
  187. 'OECF' => Exif::UNDEFINED, # Optoelectronic conversion factor
  188. 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
  189. 'ApertureValue' => Exif::RATIONAL, # Aperture
  190. 'BrightnessValue' => Exif::SRATIONAL, # Brightness
  191. 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
  192. 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
  193. 'SubjectDistance' => Exif::RATIONAL, # Subject distance
  194. 'MeteringMode' => Exif::SHORT, # Metering mode #p40
  195. 'LightSource' => Exif::SHORT, # Light source #p40-41
  196. 'Flash' => Exif::SHORT, # Flash #p41-42
  197. 'FocalLength' => Exif::RATIONAL, # Lens focal length
  198. 'SubjectArea' => Exif::SHORT, # Subject area
  199. 'FlashEnergy' => Exif::RATIONAL, # Flash energy
  200. 'SpatialFrequencyResponse' => Exif::UNDEFINED, # Spatial frequency response
  201. 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
  202. 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
  203. 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
  204. 'SubjectLocation' => Exif::SHORT, # Subject location
  205. 'ExposureIndex' => Exif::RATIONAL, # Exposure index
  206. 'SensingMethod' => Exif::SHORT, # Sensing method #p46
  207. 'FileSource' => Exif::UNDEFINED, # File source #p47
  208. 'SceneType' => Exif::UNDEFINED, # Scene type #p47
  209. 'CFAPattern' => Exif::UNDEFINED, # CFA pattern
  210. 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
  211. 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
  212. 'WhiteBalance' => Exif::SHORT, # White Balance #p49
  213. 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
  214. 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
  215. 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
  216. 'GainControl' => Exif::RATIONAL, # Scene control #p49-50
  217. 'Contrast' => Exif::SHORT, # Contrast #p50
  218. 'Saturation' => Exif::SHORT, # Saturation #p50
  219. 'Sharpness' => Exif::SHORT, # Sharpness #p50
  220. 'DeviceSettingDescription' => Exif::UNDEFINED, # Desice settings description
  221. 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
  222. ),
  223. 'other' => array(
  224. 'ImageUniqueID' => Exif::ASCII, # Unique image ID
  225. ),
  226. ),
  227. # GPS Attribute Information (p52)
  228. 'gps' => array(
  229. 'GPSVersionID' => Exif::BYTE, # GPS tag version
  230. 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
  231. 'GPSLatitude' => Exif::RATIONAL, # Latitude
  232. 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
  233. 'GPSLongitude' => Exif::RATIONAL, # Longitude
  234. 'GPSAltitudeRef' => Exif::BYTE, # Altitude reference
  235. 'GPSAltitude' => Exif::RATIONAL, # Altitude
  236. 'GPSTimeStamp' => Exif::RATIONAL, # GPS time (atomic clock)
  237. 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
  238. 'GPSStatus' => Exif::ASCII, # Receiver status #p54
  239. 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
  240. 'GPSDOP' => Exif::RATIONAL, # Measurement precision
  241. 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
  242. 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
  243. 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
  244. 'GPSTrack' => Exif::RATIONAL, # Direction of movement
  245. 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
  246. 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
  247. 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
  248. 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
  249. 'GPSDestLatitude' => Exif::RATIONAL, # Latitude destination
  250. 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
  251. 'GPSDestLongitude' => Exif::RATIONAL, # Longitude of destination
  252. 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
  253. 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
  254. 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
  255. 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
  256. 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
  257. 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
  258. 'GPSDateStamp' => Exif::ASCII, # GPS date
  259. 'GPSDifferential' => Exif::SHORT, # GPS differential correction
  260. ),
  261. );
  262. $this->file = $file;
  263. $this->basename = wfBaseName( $this->file );
  264. $this->makeFlatExifTags();
  265. $this->debugFile( $this->basename, __FUNCTION__, true );
  266. wfSuppressWarnings();
  267. $data = exif_read_data( $this->file );
  268. wfRestoreWarnings();
  269. /**
  270. * exif_read_data() will return false on invalid input, such as
  271. * when somebody uploads a file called something.jpeg
  272. * containing random gibberish.
  273. */
  274. $this->mRawExifData = $data ? $data : array();
  275. $this->makeFilteredData();
  276. $this->makeFormattedData();
  277. $this->debugFile( __FUNCTION__, false );
  278. }
  279. /**#@+
  280. * @private
  281. */
  282. /**
  283. * Generate a flat list of the exif tags
  284. */
  285. function makeFlatExifTags() {
  286. $this->extractTags( $this->mExifTags );
  287. }
  288. /**
  289. * A recursing extractor function used by makeFlatExifTags()
  290. *
  291. * Note: This used to use an array_walk function, but it made PHP5
  292. * segfault, see `cvs diff -u -r 1.4 -r 1.5 Exif.php`
  293. */
  294. function extractTags( &$tagset ) {
  295. foreach( $tagset as $key => $val ) {
  296. if( is_array( $val ) ) {
  297. $this->extractTags( $val );
  298. } else {
  299. $this->mFlatExifTags[$key] = $val;
  300. }
  301. }
  302. }
  303. /**
  304. * Make $this->mFilteredExifData
  305. */
  306. function makeFilteredData() {
  307. $this->mFilteredExifData = $this->mRawExifData;
  308. foreach( $this->mFilteredExifData as $k => $v ) {
  309. if ( !in_array( $k, array_keys( $this->mFlatExifTags ) ) ) {
  310. $this->debug( $v, __FUNCTION__, "'$k' is not a valid Exif tag" );
  311. unset( $this->mFilteredExifData[$k] );
  312. }
  313. }
  314. foreach( $this->mFilteredExifData as $k => $v ) {
  315. if ( !$this->validate($k, $v) ) {
  316. $this->debug( $v, __FUNCTION__, "'$k' contained invalid data" );
  317. unset( $this->mFilteredExifData[$k] );
  318. }
  319. }
  320. }
  321. /**
  322. * @todo document
  323. */
  324. function makeFormattedData( ) {
  325. $format = new FormatExif( $this->getFilteredData() );
  326. $this->mFormattedExifData = $format->getFormattedData();
  327. }
  328. /**#@-*/
  329. /**#@+
  330. * @return array
  331. */
  332. /**
  333. * Get $this->mRawExifData
  334. */
  335. function getData() {
  336. return $this->mRawExifData;
  337. }
  338. /**
  339. * Get $this->mFilteredExifData
  340. */
  341. function getFilteredData() {
  342. return $this->mFilteredExifData;
  343. }
  344. /**
  345. * Get $this->mFormattedExifData
  346. */
  347. function getFormattedData() {
  348. return $this->mFormattedExifData;
  349. }
  350. /**#@-*/
  351. /**
  352. * The version of the output format
  353. *
  354. * Before the actual metadata information is saved in the database we
  355. * strip some of it since we don't want to save things like thumbnails
  356. * which usually accompany Exif data. This value gets saved in the
  357. * database along with the actual Exif data, and if the version in the
  358. * database doesn't equal the value returned by this function the Exif
  359. * data is regenerated.
  360. *
  361. * @return int
  362. */
  363. public static function version() {
  364. return 1; // We don't need no bloddy constants!
  365. }
  366. /**#@+
  367. * Validates if a tag value is of the type it should be according to the Exif spec
  368. *
  369. * @private
  370. *
  371. * @param $in Mixed: the input value to check
  372. * @return bool
  373. */
  374. function isByte( $in ) {
  375. if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
  376. $this->debug( $in, __FUNCTION__, true );
  377. return true;
  378. } else {
  379. $this->debug( $in, __FUNCTION__, false );
  380. return false;
  381. }
  382. }
  383. function isASCII( $in ) {
  384. if ( is_array( $in ) ) {
  385. return false;
  386. }
  387. if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
  388. $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
  389. return false;
  390. }
  391. if ( preg_match( '/^\s*$/', $in ) ) {
  392. $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
  393. return false;
  394. }
  395. return true;
  396. }
  397. function isShort( $in ) {
  398. if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
  399. $this->debug( $in, __FUNCTION__, true );
  400. return true;
  401. } else {
  402. $this->debug( $in, __FUNCTION__, false );
  403. return false;
  404. }
  405. }
  406. function isLong( $in ) {
  407. if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
  408. $this->debug( $in, __FUNCTION__, true );
  409. return true;
  410. } else {
  411. $this->debug( $in, __FUNCTION__, false );
  412. return false;
  413. }
  414. }
  415. function isRational( $in ) {
  416. $m = array();
  417. if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
  418. return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
  419. } else {
  420. $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
  421. return false;
  422. }
  423. }
  424. function isUndefined( $in ) {
  425. if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
  426. $this->debug( $in, __FUNCTION__, true );
  427. return true;
  428. } else {
  429. $this->debug( $in, __FUNCTION__, false );
  430. return false;
  431. }
  432. }
  433. function isSlong( $in ) {
  434. if ( $this->isLong( abs( $in ) ) ) {
  435. $this->debug( $in, __FUNCTION__, true );
  436. return true;
  437. } else {
  438. $this->debug( $in, __FUNCTION__, false );
  439. return false;
  440. }
  441. }
  442. function isSrational( $in ) {
  443. $m = array();
  444. if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
  445. return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
  446. } else {
  447. $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
  448. return false;
  449. }
  450. }
  451. /**#@-*/
  452. /**
  453. * Validates if a tag has a legal value according to the Exif spec
  454. *
  455. * @private
  456. *
  457. * @param $tag String: the tag to check.
  458. * @param $val Mixed: the value of the tag.
  459. * @return bool
  460. */
  461. function validate( $tag, $val ) {
  462. $debug = "tag is '$tag'";
  463. // Does not work if not typecast
  464. switch( (string)$this->mFlatExifTags[$tag] ) {
  465. case (string)Exif::BYTE:
  466. $this->debug( $val, __FUNCTION__, $debug );
  467. return $this->isByte( $val );
  468. case (string)Exif::ASCII:
  469. $this->debug( $val, __FUNCTION__, $debug );
  470. return $this->isASCII( $val );
  471. case (string)Exif::SHORT:
  472. $this->debug( $val, __FUNCTION__, $debug );
  473. return $this->isShort( $val );
  474. case (string)Exif::LONG:
  475. $this->debug( $val, __FUNCTION__, $debug );
  476. return $this->isLong( $val );
  477. case (string)Exif::RATIONAL:
  478. $this->debug( $val, __FUNCTION__, $debug );
  479. return $this->isRational( $val );
  480. case (string)Exif::UNDEFINED:
  481. $this->debug( $val, __FUNCTION__, $debug );
  482. return $this->isUndefined( $val );
  483. case (string)Exif::SLONG:
  484. $this->debug( $val, __FUNCTION__, $debug );
  485. return $this->isSlong( $val );
  486. case (string)Exif::SRATIONAL:
  487. $this->debug( $val, __FUNCTION__, $debug );
  488. return $this->isSrational( $val );
  489. case (string)Exif::SHORT.','.Exif::LONG:
  490. $this->debug( $val, __FUNCTION__, $debug );
  491. return $this->isShort( $val ) || $this->isLong( $val );
  492. default:
  493. $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
  494. return false;
  495. }
  496. }
  497. /**
  498. * Convenience function for debugging output
  499. *
  500. * @private
  501. *
  502. * @param $in Mixed:
  503. * @param $fname String:
  504. * @param $action Mixed: , default NULL.
  505. */
  506. function debug( $in, $fname, $action = NULL ) {
  507. if ( !$this->log ) {
  508. return;
  509. }
  510. $type = gettype( $in );
  511. $class = ucfirst( __CLASS__ );
  512. if ( $type === 'array' )
  513. $in = print_r( $in, true );
  514. if ( $action === true )
  515. wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
  516. elseif ( $action === false )
  517. wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
  518. elseif ( $action === null )
  519. wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
  520. else
  521. wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
  522. }
  523. /**
  524. * Convenience function for debugging output
  525. *
  526. * @private
  527. *
  528. * @param $fname String: the name of the function calling this function
  529. * @param $io Boolean: Specify whether we're beginning or ending
  530. */
  531. function debugFile( $fname, $io ) {
  532. if ( !$this->log ) {
  533. return;
  534. }
  535. $class = ucfirst( __CLASS__ );
  536. if ( $io ) {
  537. wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
  538. } else {
  539. wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
  540. }
  541. }
  542. }
  543. /**
  544. * @todo document (e.g. one-sentence class-overview description)
  545. * @ingroup Media
  546. */
  547. class FormatExif {
  548. /**
  549. * The Exif data to format
  550. *
  551. * @var array
  552. * @private
  553. */
  554. var $mExif;
  555. /**
  556. * Constructor
  557. *
  558. * @param $exif Array: the Exif data to format ( as returned by
  559. * Exif::getFilteredData() )
  560. */
  561. function FormatExif( $exif ) {
  562. $this->mExif = $exif;
  563. }
  564. /**
  565. * Numbers given by Exif user agents are often magical, that is they
  566. * should be replaced by a detailed explanation depending on their
  567. * value which most of the time are plain integers. This function
  568. * formats Exif values into human readable form.
  569. *
  570. * @return array
  571. */
  572. function getFormattedData() {
  573. global $wgLang;
  574. $tags =& $this->mExif;
  575. $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
  576. unset( $tags['ResolutionUnit'] );
  577. foreach( $tags as $tag => $val ) {
  578. switch( $tag ) {
  579. case 'Compression':
  580. switch( $val ) {
  581. case 1: case 6:
  582. $tags[$tag] = $this->msg( $tag, $val );
  583. break;
  584. default:
  585. $tags[$tag] = $val;
  586. break;
  587. }
  588. break;
  589. case 'PhotometricInterpretation':
  590. switch( $val ) {
  591. case 2: case 6:
  592. $tags[$tag] = $this->msg( $tag, $val );
  593. break;
  594. default:
  595. $tags[$tag] = $val;
  596. break;
  597. }
  598. break;
  599. case 'Orientation':
  600. switch( $val ) {
  601. case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
  602. $tags[$tag] = $this->msg( $tag, $val );
  603. break;
  604. default:
  605. $tags[$tag] = $val;
  606. break;
  607. }
  608. break;
  609. case 'PlanarConfiguration':
  610. switch( $val ) {
  611. case 1: case 2:
  612. $tags[$tag] = $this->msg( $tag, $val );
  613. break;
  614. default:
  615. $tags[$tag] = $val;
  616. break;
  617. }
  618. break;
  619. // TODO: YCbCrSubSampling
  620. // TODO: YCbCrPositioning
  621. case 'XResolution':
  622. case 'YResolution':
  623. switch( $resolutionunit ) {
  624. case 2:
  625. $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
  626. break;
  627. case 3:
  628. $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
  629. break;
  630. default:
  631. $tags[$tag] = $val;
  632. break;
  633. }
  634. break;
  635. // TODO: YCbCrCoefficients #p27 (see annex E)
  636. case 'ExifVersion': case 'FlashpixVersion':
  637. $tags[$tag] = "$val"/100;
  638. break;
  639. case 'ColorSpace':
  640. switch( $val ) {
  641. case 1: case 'FFFF.H':
  642. $tags[$tag] = $this->msg( $tag, $val );
  643. break;
  644. default:
  645. $tags[$tag] = $val;
  646. break;
  647. }
  648. break;
  649. case 'ComponentsConfiguration':
  650. switch( $val ) {
  651. case 0: case 1: case 2: case 3: case 4: case 5: case 6:
  652. $tags[$tag] = $this->msg( $tag, $val );
  653. break;
  654. default:
  655. $tags[$tag] = $val;
  656. break;
  657. }
  658. break;
  659. case 'DateTime':
  660. case 'DateTimeOriginal':
  661. case 'DateTimeDigitized':
  662. if( $val == '0000:00:00 00:00:00' ) {
  663. $tags[$tag] = wfMsg('exif-unknowndate');
  664. } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
  665. $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
  666. }
  667. break;
  668. case 'ExposureProgram':
  669. switch( $val ) {
  670. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
  671. $tags[$tag] = $this->msg( $tag, $val );
  672. break;
  673. default:
  674. $tags[$tag] = $val;
  675. break;
  676. }
  677. break;
  678. case 'SubjectDistance':
  679. $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
  680. break;
  681. case 'MeteringMode':
  682. switch( $val ) {
  683. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
  684. $tags[$tag] = $this->msg( $tag, $val );
  685. break;
  686. default:
  687. $tags[$tag] = $val;
  688. break;
  689. }
  690. break;
  691. case 'LightSource':
  692. switch( $val ) {
  693. case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
  694. case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
  695. case 21: case 22: case 23: case 24: case 255:
  696. $tags[$tag] = $this->msg( $tag, $val );
  697. break;
  698. default:
  699. $tags[$tag] = $val;
  700. break;
  701. }
  702. break;
  703. case 'Flash':
  704. $flashDecode = array(
  705. 'fired' => $val & bindec( '00000001' ),
  706. 'return' => ($val & bindec( '00000110' )) >> 1,
  707. 'mode' => ($val & bindec( '00011000' )) >> 3,
  708. 'function' => ($val & bindec( '00100000' )) >> 5,
  709. 'redeye' => ($val & bindec( '01000000' )) >> 6,
  710. // 'reserved' => ($val & bindec( '10000000' )) >> 7,
  711. );
  712. # We do not need to handle unknown values since all are used.
  713. foreach( $flashDecode as $subTag => $subValue ) {
  714. # We do not need any message for zeroed values.
  715. if( $subTag != 'fired' && $subValue == 0) {
  716. continue;
  717. }
  718. $fullTag = $tag . '-' . $subTag ;
  719. $flashMsgs[] = $this->msg( $fullTag, $subValue );
  720. }
  721. $tags[$tag] = $wgLang->commaList( $flashMsgs );
  722. break;
  723. case 'FocalPlaneResolutionUnit':
  724. switch( $val ) {
  725. case 2:
  726. $tags[$tag] = $this->msg( $tag, $val );
  727. break;
  728. default:
  729. $tags[$tag] = $val;
  730. break;
  731. }
  732. break;
  733. case 'SensingMethod':
  734. switch( $val ) {
  735. case 1: case 2: case 3: case 4: case 5: case 7: case 8:
  736. $tags[$tag] = $this->msg( $tag, $val );
  737. break;
  738. default:
  739. $tags[$tag] = $val;
  740. break;
  741. }
  742. break;
  743. case 'FileSource':
  744. switch( $val ) {
  745. case 3:
  746. $tags[$tag] = $this->msg( $tag, $val );
  747. break;
  748. default:
  749. $tags[$tag] = $val;
  750. break;
  751. }
  752. break;
  753. case 'SceneType':
  754. switch( $val ) {
  755. case 1:
  756. $tags[$tag] = $this->msg( $tag, $val );
  757. break;
  758. default:
  759. $tags[$tag] = $val;
  760. break;
  761. }
  762. break;
  763. case 'CustomRendered':
  764. switch( $val ) {
  765. case 0: case 1:
  766. $tags[$tag] = $this->msg( $tag, $val );
  767. break;
  768. default:
  769. $tags[$tag] = $val;
  770. break;
  771. }
  772. break;
  773. case 'ExposureMode':
  774. switch( $val ) {
  775. case 0: case 1: case 2:
  776. $tags[$tag] = $this->msg( $tag, $val );
  777. break;
  778. default:
  779. $tags[$tag] = $val;
  780. break;
  781. }
  782. break;
  783. case 'WhiteBalance':
  784. switch( $val ) {
  785. case 0: case 1:
  786. $tags[$tag] = $this->msg( $tag, $val );
  787. break;
  788. default:
  789. $tags[$tag] = $val;
  790. break;
  791. }
  792. break;
  793. case 'SceneCaptureType':
  794. switch( $val ) {
  795. case 0: case 1: case 2: case 3:
  796. $tags[$tag] = $this->msg( $tag, $val );
  797. break;
  798. default:
  799. $tags[$tag] = $val;
  800. break;
  801. }
  802. break;
  803. case 'GainControl':
  804. switch( $val ) {
  805. case 0: case 1: case 2: case 3: case 4:
  806. $tags[$tag] = $this->msg( $tag, $val );
  807. break;
  808. default:
  809. $tags[$tag] = $val;
  810. break;
  811. }
  812. break;
  813. case 'Contrast':
  814. switch( $val ) {
  815. case 0: case 1: case 2:
  816. $tags[$tag] = $this->msg( $tag, $val );
  817. break;
  818. default:
  819. $tags[$tag] = $val;
  820. break;
  821. }
  822. break;
  823. case 'Saturation':
  824. switch( $val ) {
  825. case 0: case 1: case 2:
  826. $tags[$tag] = $this->msg( $tag, $val );
  827. break;
  828. default:
  829. $tags[$tag] = $val;
  830. break;
  831. }
  832. break;
  833. case 'Sharpness':
  834. switch( $val ) {
  835. case 0: case 1: case 2:
  836. $tags[$tag] = $this->msg( $tag, $val );
  837. break;
  838. default:
  839. $tags[$tag] = $val;
  840. break;
  841. }
  842. break;
  843. case 'SubjectDistanceRange':
  844. switch( $val ) {
  845. case 0: case 1: case 2: case 3:
  846. $tags[$tag] = $this->msg( $tag, $val );
  847. break;
  848. default:
  849. $tags[$tag] = $val;
  850. break;
  851. }
  852. break;
  853. case 'GPSLatitudeRef':
  854. case 'GPSDestLatitudeRef':
  855. switch( $val ) {
  856. case 'N': case 'S':
  857. $tags[$tag] = $this->msg( 'GPSLatitude', $val );
  858. break;
  859. default:
  860. $tags[$tag] = $val;
  861. break;
  862. }
  863. break;
  864. case 'GPSLongitudeRef':
  865. case 'GPSDestLongitudeRef':
  866. switch( $val ) {
  867. case 'E': case 'W':
  868. $tags[$tag] = $this->msg( 'GPSLongitude', $val );
  869. break;
  870. default:
  871. $tags[$tag] = $val;
  872. break;
  873. }
  874. break;
  875. case 'GPSStatus':
  876. switch( $val ) {
  877. case 'A': case 'V':
  878. $tags[$tag] = $this->msg( $tag, $val );
  879. break;
  880. default:
  881. $tags[$tag] = $val;
  882. break;
  883. }
  884. break;
  885. case 'GPSMeasureMode':
  886. switch( $val ) {
  887. case 2: case 3:
  888. $tags[$tag] = $this->msg( $tag, $val );
  889. break;
  890. default:
  891. $tags[$tag] = $val;
  892. break;
  893. }
  894. break;
  895. case 'GPSSpeedRef':
  896. case 'GPSDestDistanceRef':
  897. switch( $val ) {
  898. case 'K': case 'M': case 'N':
  899. $tags[$tag] = $this->msg( 'GPSSpeed', $val );
  900. break;
  901. default:
  902. $tags[$tag] = $val;
  903. break;
  904. }
  905. break;
  906. case 'GPSTrackRef':
  907. case 'GPSImgDirectionRef':
  908. case 'GPSDestBearingRef':
  909. switch( $val ) {
  910. case 'T': case 'M':
  911. $tags[$tag] = $this->msg( 'GPSDirection', $val );
  912. break;
  913. default:
  914. $tags[$tag] = $val;
  915. break;
  916. }
  917. break;
  918. case 'GPSDateStamp':
  919. $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
  920. break;
  921. // This is not in the Exif standard, just a special
  922. // case for our purposes which enables wikis to wikify
  923. // the make, model and software name to link to their articles.
  924. case 'Make':
  925. case 'Model':
  926. case 'Software':
  927. $tags[$tag] = $this->msg( $tag, '', $val );
  928. break;
  929. case 'ExposureTime':
  930. // Show the pretty fraction as well as decimal version
  931. $tags[$tag] = wfMsg( 'exif-exposuretime-format',
  932. $this->formatFraction( $val ), $this->formatNum( $val ) );
  933. break;
  934. case 'FNumber':
  935. $tags[$tag] = wfMsg( 'exif-fnumber-format',
  936. $this->formatNum( $val ) );
  937. break;
  938. case 'FocalLength':
  939. $tags[$tag] = wfMsg( 'exif-focallength-format',
  940. $this->formatNum( $val ) );
  941. break;
  942. default:
  943. $tags[$tag] = $this->formatNum( $val );
  944. break;
  945. }
  946. }
  947. return $tags;
  948. }
  949. /**
  950. * Convenience function for getFormattedData()
  951. *
  952. * @private
  953. *
  954. * @param $tag String: the tag name to pass on
  955. * @param $val String: the value of the tag
  956. * @param $arg String: an argument to pass ($1)
  957. * @return string A wfMsg of "exif-$tag-$val" in lower case
  958. */
  959. function msg( $tag, $val, $arg = null ) {
  960. global $wgContLang;
  961. if ($val === '')
  962. $val = 'value';
  963. return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg );
  964. }
  965. /**
  966. * Format a number, convert numbers from fractions into floating point
  967. * numbers
  968. *
  969. * @private
  970. *
  971. * @param $num Mixed: the value to format
  972. * @return mixed A floating point number or whatever we were fed
  973. */
  974. function formatNum( $num ) {
  975. $m = array();
  976. if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
  977. return $m[2] != 0 ? $m[1] / $m[2] : $num;
  978. else
  979. return $num;
  980. }
  981. /**
  982. * Format a rational number, reducing fractions
  983. *
  984. * @private
  985. *
  986. * @param $num Mixed: the value to format
  987. * @return mixed A floating point number or whatever we were fed
  988. */
  989. function formatFraction( $num ) {
  990. $m = array();
  991. if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
  992. $numerator = intval( $m[1] );
  993. $denominator = intval( $m[2] );
  994. $gcd = $this->gcd( $numerator, $denominator );
  995. if( $gcd != 0 ) {
  996. // 0 shouldn't happen! ;)
  997. return $numerator / $gcd . '/' . $denominator / $gcd;
  998. }
  999. }
  1000. return $this->formatNum( $num );
  1001. }
  1002. /**
  1003. * Calculate the greatest common divisor of two integers.
  1004. *
  1005. * @param $a Integer: FIXME
  1006. * @param $b Integer: FIXME
  1007. * @return int
  1008. * @private
  1009. */
  1010. function gcd( $a, $b ) {
  1011. /*
  1012. // http://en.wikipedia.org/wiki/Euclidean_algorithm
  1013. // Recursive form would be:
  1014. if( $b == 0 )
  1015. return $a;
  1016. else
  1017. return gcd( $b, $a % $b );
  1018. */
  1019. while( $b != 0 ) {
  1020. $remainder = $a % $b;
  1021. // tail recursion...
  1022. $a = $b;
  1023. $b = $remainder;
  1024. }
  1025. return $a;
  1026. }
  1027. }
  1028. /**
  1029. * MW 1.6 compatibility
  1030. */
  1031. define( 'MW_EXIF_BYTE', Exif::BYTE );
  1032. define( 'MW_EXIF_ASCII', Exif::ASCII );
  1033. define( 'MW_EXIF_SHORT', Exif::SHORT );
  1034. define( 'MW_EXIF_LONG', Exif::LONG );
  1035. define( 'MW_EXIF_RATIONAL', Exif::RATIONAL );
  1036. define( 'MW_EXIF_UNDEFINED', Exif::UNDEFINED );
  1037. define( 'MW_EXIF_SLONG', Exif::SLONG );
  1038. define( 'MW_EXIF_SRATIONAL', Exif::SRATIONAL );