1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16: class Horde_Image_Exif
17: {
18: 19: 20: 21: 22: 23: 24: 25:
26: static public function factory($driver = null, $params = array())
27: {
28: if (empty($driver) && function_exists('exif_read_data')) {
29: $driver = 'Php';
30: } elseif (empty($driver)) {
31: $driver = 'Bundled';
32: } else {
33: $driver = basename($driver);
34: }
35:
36: $class = 'Horde_Image_Exif_' . $driver;
37:
38: return new $class($params);
39: }
40:
41: 42: 43: 44: 45: 46: 47: 48:
49: static public function intel2Moto($intel)
50: {
51: $len = strlen($intel);
52: $moto = '';
53: for($i = 0; $i <= $len; $i += 2) {
54: $moto .= substr($intel, $len-$i, 2);
55: }
56:
57: return $moto;
58: }
59:
60: 61: 62: 63: 64: 65: 66:
67: static public function getCategories()
68: {
69: return array(
70: 'IPTC' => array(
71: 'Keywords' => array('description' => Horde_Image_Translation::t("Image keywords"), 'type' => 'array'),
72: 'ObjectName' => array('description' => Horde_Image_Translation::t("Image Title"), 'type' => 'text'),
73: 'By-line' => array('description' => Horde_Image_Translation::t("By"), 'type' => 'text'),
74: 'CopyrightNotice' => array('description' => Horde_Image_Translation::t("Copyright"), 'type' => 'text'),
75: 'Caption-Abstract' => array('description' => Horde_Image_Translation::t("Caption"), 'type' => 'text'),
76: ),
77:
78: 'XMP' => array(
79: 'Creator' => array('description' => Horde_Image_Translation::t("Image Creator"), 'type' => 'text'),
80: 'Rights' => array('description' => Horde_Image_Translation::t("Rights"), 'type' => 'text'),
81: 'UsageTerms' => array('description' => Horde_Image_Translation::t("Usage Terms"), 'type' => 'type'),
82: ),
83:
84: 'EXIF' => array(
85: 'DateTime' => array('description' => Horde_Image_Translation::t("Date Photo Modified"), 'type' => 'date'),
86: 'DateTimeOriginal' => array('description' => Horde_Image_Translation::t("Date Photo Taken"), 'type' => 'date'),
87: 'DateTimeDigitized' => array('description' => Horde_Image_Translation::t("Date Photo Digitized"), 'type' => 'date'),
88: 'GPSLatitude' => array('description' => Horde_Image_Translation::t("Latitude"), 'type' => 'gps'),
89: 'GPSLongitude' => array('description' => Horde_Image_Translation::t("Longitude"), 'type' => 'gps'),
90: 'Make' => array('description' => Horde_Image_Translation::t("Camera Make"), 'type' => 'text'),
91: 'Model' => array('description' => Horde_Image_Translation::t("Camera Model"), 'type' => 'text'),
92: 'Software' => array('description' => Horde_Image_Translation::t("Software Version"), 'type' => 'text'),
93: 'ImageType' => array('description' => Horde_Image_Translation::t("Photo Type"), 'type' => 'text'),
94: 'ImageDescription' => array('description' => Horde_Image_Translation::t("Photo Description"), 'type' => 'text'),
95: 'FileSize' => array('description' => Horde_Image_Translation::t("File Size"), 'type' => 'number'),
96: 'ExifImageWidth' => array('description' => Horde_Image_Translation::t("Width"), 'type' => 'number'),
97: 'ExifImageLength' => array('description' => Horde_Image_Translation::t("Height"), 'type' => 'number'),
98: 'XResolution' => array('description' => Horde_Image_Translation::t("X Resolution"), 'type' => 'number'),
99: 'YResolution' => array('description' => Horde_Image_Translation::t("Y Resolution"), 'type' => 'number'),
100: 'ResolutionUnit' => array('description' => Horde_Image_Translation::t("Resolution Unit"), 'type' => 'text'),
101: 'ShutterSpeedValue' => array('description' => Horde_Image_Translation::t("Shutter Speed"), 'type' => 'number'),
102: 'ExposureTime' => array('description' => Horde_Image_Translation::t("Exposure"), 'type' => 'number'),
103: 'FocalLength' => array('description' => Horde_Image_Translation::t("Focal Length"), 'type' => 'number'),
104: 'FocalLengthIn35mmFilm' => array('description' => Horde_Image_Translation::t("Focal Length (35mm equiv)"), 'type' => 'number'),
105: 'ApertureValue' => array('description' => Horde_Image_Translation::t("Aperture"), 'type' => 'number'),
106: 'FNumber' => array('description' => Horde_Image_Translation::t("F-Number"), 'type' => 'number'),
107: 'ISOSpeedRatings' => array('description' => Horde_Image_Translation::t("ISO Setting"), 'type' => 'number'),
108: 'ExposureBiasValue' => array('description' => Horde_Image_Translation::t("Exposure Bias"), 'type' => 'number'),
109: 'ExposureMode' => array('description' => Horde_Image_Translation::t("Exposure Mode"), 'type' => 'number'),
110: 'ExposureProgram' => array('description' => Horde_Image_Translation::t("Exposure Program"), 'type' => 'number'),
111: 'MeteringMode' => array('description' => Horde_Image_Translation::t("Metering Mode"), 'type' => 'number'),
112: 'Flash' => array('description' => Horde_Image_Translation::t("Flash Setting"), 'type' => 'number'),
113: 'UserComment' => array('description' => Horde_Image_Translation::t("User Comment"), 'type' => 'text'),
114: 'ColorSpace' => array('description' => Horde_Image_Translation::t("Color Space"), 'type' => 'number'),
115: 'SensingMethod' => array('description' => Horde_Image_Translation::t("Sensing Method"), 'type' => 'number'),
116: 'WhiteBalance' => array('description' => Horde_Image_Translation::t("White Balance"), 'type' => 'number'),
117: 'Orientation' => array('description' => Horde_Image_Translation::t("Camera Orientation"), 'type' => 'number'),
118: 'Copyright' => array('description' => Horde_Image_Translation::t("Copyright"), 'type' => 'text'),
119: 'Artist' => array('description' => Horde_Image_Translation::t("Artist"), 'type' => 'text'),
120: 'LightSource' => array('description' => Horde_Image_Translation::t("Light source"), 'type' => 'number'),
121: 'ImageStabalization' => array('description' => Horde_Image_Translation::t("Image Stabilization"), 'type' => 'text'),
122: 'SceneCaptureType' => array('description' => Horde_Image_Translation::t("Scene Type"), 'type' => 'number'),
123: ),
124:
125: 'COMPOSITE' => array(
126: 'LensID' => array('description' => Horde_Image_Translation::t("Lens Id"), 'type' => 'text'),
127: 'Lens' => array('description' => 'Lens', 'type' => 'text'),
128: 'Aperture' => array('description' => Horde_Image_Translation::t("Aperture"), 'type' => 'text'),
129: 'DOF' => array('description' => Horde_Image_Translation::t("Depth of Field"), 'type' => 'text'),
130: 'FOV' => array('description' => Horde_Image_Translation::t("Field of View"), 'type' => 'text')
131: )
132: );
133: }
134:
135: 136: 137: 138: 139: 140:
141: static public function getFields($driver = null, $description_only = false)
142: {
143: if (!is_null($driver) && is_array($driver)) {
144: $driver = self::factory($driver[0], $driver[1]);
145: }
146:
147: if ($driver instanceof Horde_Image_Exif_Base) {
148: $supported = $driver->supportedCategories();
149: } else {
150: $supported = array('XMP', 'IPTC', 'EXIF' );
151: }
152: $categories = self::getCategories();
153: $flattened = array();
154: foreach ($supported as $category) {
155: $flattened = array_merge($flattened, $categories[$category]);
156: }
157:
158: if ($description_only) {
159: foreach ($flattened as $key => $data) {
160: $return[$key] = $data['description'];
161: }
162: return $return;
163: }
164:
165: return $flattened;
166: }
167:
168: 169: 170:
171: static protected function _formatExposure($data)
172: {
173: if ($data > 0) {
174: if ($data > 1) {
175: return sprintf(Horde_Image_Translation::t("%d sec"), round($data, 2));
176: } else {
177: $n = $d = 0;
178: self::_convertToFraction($data, $n, $d);
179: if ($n <> 1) {
180: return sprintf(Horde_Image_Translation::t("%4f sec"), $n / $d);
181: }
182: return sprintf(Horde_Image_Translation::t("%s / %s sec"), $n, $d);
183: }
184: } else {
185: return Horde_Image_Translation::t("Bulb");
186: }
187: }
188:
189: 190: 191: 192: 193: 194:
195: static protected function _convertToFraction($v, &$n, &$d)
196: {
197: $MaxTerms = 15;
198: $MinDivisor = 0.000001;
199: $MaxError = 0.00000001;
200:
201:
202: $f = $v;
203:
204:
205: $n_un = 1;
206: $d_un = 0;
207: $n_deux = 0;
208: $d_deux = 1;
209:
210: for ($i = 0; $i < $MaxTerms; $i++) {
211: $a = floor($f);
212: $f = $f - $a;
213: $n = $n_un * $a + $n_deux;
214: $d = $d_un * $a + $d_deux;
215: $n_deux = $n_un;
216: $d_deux = $d_un;
217: $n_un = $n;
218: $d_un = $d;
219:
220:
221: if ($f < $MinDivisor) {
222: break;
223: }
224: if (abs($v - $n / $d) < $MaxError) {
225: break;
226: }
227:
228:
229: $f = 1 / $f;
230: }
231: }
232:
233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243:
244: static public function getHumanReadable($field, $data)
245: {
246: switch ($field) {
247: case 'ExposureMode':
248: switch ($data) {
249: case 0: return Horde_Image_Translation::t("Auto exposure");
250: case 1: return Horde_Image_Translation::t("Manual exposure");
251: case 2: return Horde_Image_Translation::t("Auto bracket");
252: default: return Horde_Image_Translation::t("Unknown");
253: }
254:
255: case 'ExposureProgram':
256: switch ($data) {
257: case 1: return Horde_Image_Translation::t("Manual");
258: case 2: return Horde_Image_Translation::t("Normal Program");
259: case 3: return Horde_Image_Translation::t("Aperture Priority");
260: case 4: return Horde_Image_Translation::t("Shutter Priority");
261: case 5: return Horde_Image_Translation::t("Creative");
262: case 6: return Horde_Image_Translation::t("Action");
263: case 7: return Horde_Image_Translation::t("Portrait");
264: case 8: return Horde_Image_Translation::t("Landscape");
265: default: return Horde_Image_Translation::t("Unknown");
266: }
267:
268: case 'XResolution':
269: case 'YResolution':
270: if (strpos($data, '/') !== false) {
271: list($n, $d) = explode('/', $data, 2);
272: return sprintf(Horde_Image_Translation::t("%d dots per unit"), $n);
273: }
274: return sprintf(Horde_Image_Translation::t("%d per unit"), $data);
275:
276: case 'ResolutionUnit':
277: switch ($data) {
278: case 1: return Horde_Image_Translation::t("Pixels");
279: case 2: return Horde_Image_Translation::t("Inch");
280: case 3: return Horde_Image_Translation::t("Centimeter");
281: default: return Horde_Image_Translation::t("Unknown");
282: }
283:
284: case 'ExifImageWidth':
285: case 'ExifImageLength':
286: return sprintf(Horde_Image_Translation::t("%d pixels"), $data);
287:
288: case 'Orientation':
289: switch ($data) {
290: case 1:
291: return sprintf(Horde_Image_Translation::t("Normal (O deg)"));
292: case 2:
293: return sprintf(Horde_Image_Translation::t("Mirrored"));
294: case 3:
295: return sprintf(Horde_Image_Translation::t("Upsidedown"));
296: case 4:
297: return sprintf(Horde_Image_Translation::t("Upsidedown Mirrored"));
298: case 5:
299: return sprintf(Horde_Image_Translation::t("90 deg CW Mirrored"));
300: case 6:
301: return sprintf(Horde_Image_Translation::t("90 deg CCW"));
302: case 7:
303: return sprintf(Horde_Image_Translation::t("90 deg CCW Mirrored"));
304: case 8:
305: return sprintf(Horde_Image_Translation::t("90 deg CW"));
306: }
307: break;
308:
309: case 'ExposureTime':
310: if (strpos($data, '/') !== false) {
311: list($n, $d) = explode('/', $data, 2);
312: if ($d == 0) {
313: return;
314: }
315: $data = $n / $d;
316: }
317: return self::_formatExposure($data);
318:
319: case 'ShutterSpeedValue':
320: if (strpos($data, '/') !== false) {
321: list($n, $d) = explode('/', $data, 2);
322: if ($d == 0) {
323: return;
324: }
325: $data = $n / $d;
326: }
327: $data = exp($data * log(2));
328: if ($data > 0) {
329: $data = 1 / $data;
330: }
331: return self::_formatExposure($data);
332:
333: case 'ApertureValue':
334: case 'MaxApertureValue':
335: if (strpos($data, '/') !== false) {
336: list($n, $d) = explode('/', $data, 2);
337: if ($d == 0) {
338: return;
339: }
340: $data = $n / $d;
341: $data = exp(($data * log(2)) / 2);
342:
343:
344: $data = round($data, 1);
345: }
346: return 'f/' . $data;
347:
348: case 'FocalLength':
349: if (strpos($data, '/') !== false) {
350: list($n, $d) = explode('/', $data, 2);
351: if ($d == 0) {
352: return;
353: }
354: return sprintf(Horde_Image_Translation::t("%d mm"), round($n / $d));
355: }
356: return sprintf(Horde_Image_Translation::t("%d mm"), $data);
357:
358: case 'FNumber':
359: if (strpos($data, '/') !== false) {
360: list($n, $d) = explode('/', $data, 2);
361: if ($d != 0) {
362: return 'f/' . round($n / $d, 1);
363: }
364: }
365: return 'f/' . $data;
366:
367: case 'ExposureBiasValue':
368: if (strpos($data, '/') !== false) {
369: list($n, $d) = explode('/', $data, 2);
370: if ($n == 0) {
371: return '0 EV';
372: }
373: }
374: return $data . ' EV';
375:
376: case 'MeteringMode':
377: switch ($data) {
378: case 0: return Horde_Image_Translation::t("Unknown");
379: case 1: return Horde_Image_Translation::t("Average");
380: case 2: return Horde_Image_Translation::t("Center Weighted Average");
381: case 3: return Horde_Image_Translation::t("Spot");
382: case 4: return Horde_Image_Translation::t("Multi-Spot");
383: case 5: return Horde_Image_Translation::t("Multi-Segment");
384: case 6: return Horde_Image_Translation::t("Partial");
385: case 255: return Horde_Image_Translation::t("Other");
386: default: return sprintf(Horde_Image_Translation::t("Unknown: %s"), $data);
387: }
388: break;
389:
390: case 'LightSource':
391: switch ($data) {;
392: case 1: return Horde_Image_Translation::t("Daylight");
393: case 2: return Horde_Image_Translation::t("Fluorescent");
394: case 3: return Horde_Image_Translation::t("Tungsten");
395: case 4: return Horde_Image_Translation::t("Flash");
396: case 9: return Horde_Image_Translation::t("Fine weather");
397: case 10: return Horde_Image_Translation::t("Cloudy weather");
398: case 11: return Horde_Image_Translation::t("Shade");
399: case 12: return Horde_Image_Translation::t("Daylight fluorescent");
400: case 13: return Horde_Image_Translation::t("Day white fluorescent");
401: case 14: return Horde_Image_Translation::t("Cool white fluorescent");
402: case 15: return Horde_Image_Translation::t("White fluorescent");
403: case 17: return Horde_Image_Translation::t("Standard light A");
404: case 18: return Horde_Image_Translation::t("Standard light B");
405: case 19: return Horde_Image_Translation::t("Standard light C");
406: case 20: return 'D55';
407: case 21: return 'D65';
408: case 22: return 'D75';
409: case 23: return 'D50';
410: case 24: return Horde_Image_Translation::t("ISO studio tungsten");
411: case 255: return Horde_Image_Translation::t("other light source");
412: default: return Horde_Image_Translation::t("Unknown");
413: }
414:
415: case 'WhiteBalance':
416: switch ($data) {
417: case 0: return Horde_Image_Translation::t("Auto");
418: case 1: return Horde_Image_Translation::t("Manual");
419: default: Horde_Image_Translation::t("Unknown");
420: }
421: break;
422:
423: case 'FocalLengthIn35mmFilm':
424: return $data . ' mm';
425:
426: case 'Flash':
427: switch ($data) {
428: case 0: return Horde_Image_Translation::t("No Flash");
429: case 1: return Horde_Image_Translation::t("Flash");
430: case 5: return Horde_Image_Translation::t("Flash, strobe return light not detected");
431: case 7: return Horde_Image_Translation::t("Flash, strobe return light detected");
432: case 9: return Horde_Image_Translation::t("Compulsory Flash");
433: case 13: return Horde_Image_Translation::t("Compulsory Flash, Return light not detected");
434: case 15: return Horde_Image_Translation::t("Compulsory Flash, Return light detected");
435: case 16: return Horde_Image_Translation::t("No Flash");
436: case 24: return Horde_Image_Translation::t("No Flash");
437: case 25: return Horde_Image_Translation::t("Flash, Auto-Mode");
438: case 29: return Horde_Image_Translation::t("Flash, Auto-Mode, Return light not detected");
439: case 31: return Horde_Image_Translation::t("Flash, Auto-Mode, Return light detected");
440: case 32: return Horde_Image_Translation::t("No Flash");
441: case 65: return Horde_Image_Translation::t("Red Eye");
442: case 69: return Horde_Image_Translation::t("Red Eye, Return light not detected");
443: case 71: return Horde_Image_Translation::t("Red Eye, Return light detected");
444: case 73: return Horde_Image_Translation::t("Red Eye, Compulsory Flash");
445: case 77: return Horde_Image_Translation::t("Red Eye, Compulsory Flash, Return light not detected");
446: case 79: return Horde_Image_Translation::t("Red Eye, Compulsory Flash, Return light detected");
447: case 89: return Horde_Image_Translation::t("Red Eye, Auto-Mode");
448: case 93: return Horde_Image_Translation::t("Red Eye, Auto-Mode, Return light not detected");
449: case 95: return Horde_Image_Translation::t("Red Eye, Auto-Mode, Return light detected");
450: }
451: break;
452:
453: case 'FileSize':
454: if ($data <= 0) {
455: return '0 Bytes';
456: }
457: $s = array('B', 'kB', 'MB', 'GB');
458: $e = floor(log($data, 1024));
459: return round($data/pow(1024, $e), 2) . ' ' . $s[$e];
460:
461: case 'SensingMethod':
462: switch ($data) {
463: case 1: return Horde_Image_Translation::t("Not defined");
464: case 2: return Horde_Image_Translation::t("One Chip Color Area Sensor");
465: case 3: return Horde_Image_Translation::t("Two Chip Color Area Sensor");
466: case 4: return Horde_Image_Translation::t("Three Chip Color Area Sensor");
467: case 5: return Horde_Image_Translation::t("Color Sequential Area Sensor");
468: case 7: return Horde_Image_Translation::t("Trilinear Sensor");
469: case 8: return Horde_Image_Translation::t("Color Sequential Linear Sensor");
470: default: return Horde_Image_Translation::t("Unknown");
471: }
472:
473: case 'ColorSpace':
474: switch ($data) {
475: case 1: return Horde_Image_Translation::t("sRGB");
476: default: return Horde_Image_Translation::t("Uncalibrated");
477: }
478:
479: case 'SceneCaptureType':
480: switch ($data) {
481: case 0: return Horde_Image_Translation::t("Standard");
482: case 1: return Horde_Image_Translation::t("Landscape");
483: case 2: return Horde_Image_Translation::t("Portrait");
484: case 3: return Horde_Image_Translation::t("Night Scene");
485: default: return Horde_Image_Translation::t("Unknown");
486: }
487:
488: case 'DateTime':
489: case 'DateTimeOriginal':
490: case 'DateTimeDigitized':
491: return date('m/d/Y H:i:s O', $data);
492:
493: case 'UserComment':
494:
495:
496:
497:
498:
499:
500: $data = trim(substr($data, 7)) ;
501:
502:
503: default:
504: return !empty($data) ? $data : '---';
505: }
506: }
507:
508: }
509: