1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
10	header('location: index.php');
11	exit;
12}
13/*
14 * Manipulates EXIF metadata included within a file
15 */
16class Exif
17{
18	/**
19	 * Legend, label, suffix and format information for each field
20	 * See www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf for specification source for Exif version 2.3
21	 *
22	 * @var array
23	 */
24	var	$specs = [
25		//the PHP function exif_read_data adds the FILE group when returning exif data
26		'FILE' => [
27			'FileDateTime'	=> [
28				'label'		=> 'Data Extraction Time',
29			],
30			'FileSize' => [
31				'suffix'	=> 'bytes',
32			],
33			'FileType' => [
34				//from http://php.net/manual/function.exif-imagetype.php
35				'options'	=> [
36					'1'		=> 'GIF',
37					'2'		=> 'JPEG',
38					'3'		=> 'PNG',
39					'4'		=> 'SWF',
40					'5'		=> 'PSD',
41					'6'		=> 'BMP',
42					'7'		=> 'TIFF II (Intel byte order)',
43					'8'		=> 'TIFF MM (Motorola byte order)',
44					'9'		=> 'JPC',
45					'10'	=> 'JP2',
46					'11'	=> 'JPX',
47					'12'	=> 'JB2',
48					'13'	=> 'SWC',
49					'14'	=> 'IFF',
50					'15'	=> 'WBMP',
51					'16'	=> 'XBM',
52					'17'	=> 'ICO',
53				],
54			],
55		],
56		'COMPUTED' => [
57			'html' => [
58				'label' => 'HTML',
59			],
60			'Height' => [
61				'suffix' => 'pixels',
62			],
63			'Width' => [
64				'suffix' => 'pixels',
65			],
66			'IsColor' => [
67				'options' => [
68					0		=> 'No',
69					1		=> 'Yes',
70				],
71			],
72			'ByteOrderMotorola' => [
73				'options' => [
74					0		=> 'No',
75					1		=> 'Yes',
76				],
77			],
78			'Thumbnail.FileType' => [
79				'label'		=> 'Thumbnail File Type',
80				//from http://php.net/manual/function.exif-imagetype.php
81				'options' => [
82					'1'		=> 'GIF',
83					'2'		=> 'JPEG',
84					'3'		=> 'PNG',
85					'4'		=> 'SWF',
86					'5'		=> 'PSD',
87					'6'		=> 'BMP',
88					'7'		=> 'TIFF II (Intel byte order)',
89					'8'		=> 'TIFF MM (Motorola byte order)',
90					'9'		=> 'JPC',
91					'10'	=> 'JP2',
92					'11'	=> 'JPX',
93					'12'	=> 'JB2',
94					'13'	=> 'SWC',
95					'14'	=> 'IFF',
96					'15'	=> 'WBMP',
97					'16'	=> 'XBM',
98					'17'	=> 'ICO',
99				],
100			],
101			'Thumbnail.MimeType' => [
102				'label'		=> 'Thumbnail Mime Type',
103			],
104		],
105		'IFD0' => [
106			'ImageWidth' => [
107				'suffix'	=> 'pixels',
108			],
109			'ImageLength' => [
110				'suffix'	=> 'pixels',
111			],
112			'Compression' => [
113				'options' => [
114					'1'		=> 'Uncompressed',
115					'2'		=> 'CCITT 1D',
116					'3'		=> 'T4/Group 3 Fax',
117					'4'		=> 'T6/Group 4 Fax',
118					'5'		=> 'LZW',
119					'6'		=> 'JPEG (old-style)',
120					'7'		=> 'JPEG',
121					'8'		=> 'Adobe Deflate',
122					'9'		=> 'JBIG B&W',
123					'10'	=> 'JBIG Color',
124					'99'	=> 'JPEG',
125					'262'	=> 'Kodak 262',
126					'32766'	=> 'Next',
127					'32767'	=> 'Sony ARW Compressed',
128					'32769'	=> 'Packed RAW',
129					'32770'	=> 'Samsung SRW Compressed',
130					'32771'	=> 'CCIRLEW',
131					'32773'	=> 'PackBits',
132					'32809'	=> 'Thunderscan',
133					'32867'	=> 'Kodak KDC Compressed',
134					'32895'	=> 'IT8CTPAD',
135					'32896'	=> 'IT8LW',
136					'32897'	=> 'IT8MP',
137					'32898'	=> 'IT8BL',
138					'32908'	=> 'PixarFilm',
139					'32909'	=> 'PixarLog',
140					'32946'	=> 'Deflate',
141					'32947'	=> 'DCS',
142					'34661'	=> 'JBIG',
143					'34676'	=> 'SGILog',
144					'34677'	=> 'SGILog24',
145					'34712'	=> 'JPEG 2000',
146					'34713'	=> 'Nikon NEF Compressed',
147					'34715'	=> 'JBIG2 TIFF FX',
148					'34718'	=> 'Microsoft Document Imaging (MDI) Binary Level Codec',
149					'34719'	=> 'Microsoft Document Imaging (MDI) Progressive Transform Codec',
150					'34720'	=> 'Microsoft Document Imaging (MDI) Vector',
151					'65000'	=> 'Kodak DCR Compressed',
152					'65535'	=> 'Pentax PEF Compressed)',
153				],
154			],
155			'PhotometricInterpretation' => [
156				'options' => [
157					'0'		=> 'WhiteIsZero',
158					'1'		=> 'BlackIsZero',
159					'2'		=> 'RGB',
160					'3'		=> 'RGB Palette',
161					'4'		=> 'Transparency Mask',
162					'5'		=> 'CMYK',
163					'6'		=> 'YCbCr',
164					'8'		=> 'CIELab',
165					'9'		=> 'ICCLab',
166					'10'	=> 'ITULab',
167					'32803'	=> 'Color Filter Array',
168					'32844'	=> 'Pixar LogL',
169					'32845'	=> 'Pixar LogLuv',
170					'34892'	=> 'Linear Raw',
171				],
172			],
173			'Orientation' => [
174				'options' => [
175					'1'		=> 'Horizontal (normal)',
176					'2'		=> 'Mirror horizontal',
177					'3'		=> 'Rotate 180',
178					'4'		=> 'Mirror vertical',
179					'5'		=> 'Mirror horizontal and rotate 270 CW',
180					'6'		=> 'Rotate 90 CW',
181					'7'		=> 'Mirror horizontal and rotate 90 CW',
182					'8'		=> 'Rotate 270 CW',
183				],
184			],
185			'XResolution' => [
186				'format'	=> 'rational',
187				'suffix'	=> 'pixels (dots) per unit',
188			],
189			'YResolution' => [
190				'format'	=> 'rational',
191				'suffix'	=> 'pixels (dots) per unit',
192			],
193			'PlanarConfiguration' => [
194				'options' => [
195					'1'		=> 'Chunky',
196					'2'		=> 'Planar',
197				],
198			],
199			'ResolutionUnit' => [
200				'options' => [
201					'2'		=> 'inch',
202					'3'		=> 'cm',
203				],
204			],
205			'WhitePoint' => [
206				'format'	=> 'rational',
207			],
208			'PrimaryChromaticities' => [
209				'format'	=> 'rational',
210			],
211			'YCbCrCoefficients' => [
212				'format'	=> 'rational',
213			],
214			'YCbCrSubSampling' => [
215				'options' => [
216					'1 1'	=> 'YCbCr4:4:4 (1 1)',
217					'1 2'	=> 'YCbCr4:4:0 (1 2)',
218					'1 4'	=> 'YCbCr4:4:1 (1 4)',
219					'2 1'	=> 'YCbCr4:2:2 (2 1)',
220					'2 2'	=> 'YCbCr4:2:0 (2 2)',
221					'2 4'	=> 'YCbCr4:2:1 (2 4)',
222					'4 1'	=> 'YCbCr4:1:1 (4 1)',
223					'4 2'	=> 'YCbCr4:1:0 (4 2)',
224				],
225			],
226			'YCbCrPositioning' => [
227				'options' => [
228					1		=> 'Centered',
229					2		=> 'Co-sited',
230				],
231			],
232			'ReferenceBlackWhite' => [
233				'format'	=> 'rational',
234			],
235			'JPEGInterchangeFormat' => [
236				'label'		=> 'Thumbnail Offset',
237			],
238			'JPEGInterchangeFormatLength' => [
239				'label'		=> 'Thumbnail Length',
240			],
241			'Exif_IFD_Pointer' => [
242				'label'		=> 'EXIF IFD Pointer',
243			],
244			'GPS_IFD_Pointer' => [
245				'label'		=> 'GPS IFD Pointer',
246			],		],
247		'EXIF' => [
248			'ExposureTime' => [
249				'format'	=> 'rational',
250				'suffix'	=> 'seconds',
251			],
252			'FNumber' => [
253				'format'	=> 'rational',
254			],
255			'ExposureProgram' => [
256				'options' => [
257					'0'		=> 'Not Defined',
258					'1'		=> 'Manual',
259					'2'		=> 'Program AE',
260					'3'		=> 'Aperture-priority AE',
261					'4'		=> 'Shutter speed priority AE',
262					'5'		=> 'Creative (Slow speed)',
263					'6'		=> 'Action (High speed)',
264					'7'		=> 'Portrait',
265					'8'		=> 'Landscape',
266					'9'		=> 'Bulb',
267				],
268			],
269			'SensitivityType' => [
270				'options' => [
271					'0'		=> 'Unknown',
272					'1'		=> 'Standard Output Sensitivity',
273					'2'		=> 'Recommended Exposure Index',
274					'3'		=> 'ISO Speed',
275					'4'		=> 'Standard Output Sensitivity and Recommended Exposure Index',
276					'5'		=> 'Standard Output Sensitivity and ISO Speed',
277					'6'		=> 'Recommended Exposure Index and ISO Speed',
278					'7'		=> 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
279				],
280			],
281			'ComponentsConfiguration' => [
282				'binary' => true,
283				'options' => [
284					'00'	=> '- ',
285					'01'	=> 'Y ',
286					'02'	=> 'Cb',
287					'03'	=> 'Cr',
288					'04'	=> 'R ',
289					'05'	=> 'G ',
290					'06'	=> 'B ',
291				],
292			],
293			'CompressedBitsPerPixel' => [
294				'format'	=> 'rational',
295			],
296			'ShutterSpeedValue' => [
297				'format'	=> 'rational',
298				'suffix'	=> 'APEX'
299			],
300			'ApertureValue' => [
301				'format'	=> 'rational',
302				'suffix'	=> 'APEX'
303			],
304			'BrightnessValue' => [
305				'format'	=> 'rational',
306				'suffix'	=> 'APEX'
307			],
308			'ExposureBiasValue' => [
309				'format'	=> 'rational',
310				'suffix'	=> 'APEX'
311			],
312			'MaxApertureValue' => [
313				'format'	=> 'rational',
314				'suffix'	=> 'APEX'
315			],
316			'SubjectDistance' => [
317				'format'	=> 'rational',
318				'suffix'	=> 'meters'
319			],
320			'MeteringMode' => [
321				'options' => [
322					'0'		=> 'Unknown',
323					'1'		=> 'Average',
324					'2'		=> 'Center-weighted average',
325					'3'		=> 'Spot',
326					'4'		=> 'Multi-spot',
327					'5'		=> 'Multi-segment',
328					'6'		=> 'Partial',
329					'255'	=> 'Other',
330				],
331			],
332			'LightSource' => [
333				'options' => [
334					'0'		=> 'Unknown',
335					'1'		=> 'Daylight',
336					'2'		=> 'Fluorescent',
337					'3'		=> 'Tungsten (Incandescent)',
338					'4'		=> 'Flash',
339					'9'		=> 'Fine Weather',
340					'10'	=> 'Cloudy',
341					'11'	=> 'Shade',
342					'12'	=> 'Daylight Fluorescent',
343					'13'	=> 'Day White Fluorescent',
344					'14'	=> 'Cool White Fluorescent',
345					'15'	=> 'White Fluorescent',
346					'16'	=> 'Warm White Fluorescent',
347					'17'	=> 'Standard Light A',
348					'18'	=> 'Standard Light B',
349					'19'	=> 'Standard Light C',
350					'20'	=> 'D55',
351					'21'	=> 'D65',
352					'22'	=> 'D75',
353					'23'	=> 'D50',
354					'24'	=> 'ISO Studio Tungsten',
355					'255'	=> 'Other',
356				],
357			],
358			'Flash' => [
359				'options' => [
360					'0'		=> 'No Flash',
361					'1'		=> 'Fired',
362					'5'		=> 'Fired, Return not detected',
363					'7'		=> 'Fired, Return detected',
364					'8'		=> 'On, Did not fire',
365					'9'		=> 'On, Fired',
366					'13'	=> 'On, Return not detected',
367					'15'	=> 'On, Return detected',
368					'16'	=> 'Off, Did not fire',
369					'20'	=> 'Off, Did not fire, Return not detected',
370					'24'	=> 'Auto, Did not fire',
371					'25'	=> 'Auto, Fired',
372					'29'	=> 'Auto, Fired, Return not detected',
373					'31'	=> 'Auto, Fired, Return detected',
374					'32'	=> 'No flash function',
375					'48'	=> 'Off, No flash function',
376					'65'	=> 'Fired, Red-eye reduction',
377					'69'	=> 'Fired, Red-eye reduction, Return not detected',
378					'71'	=> 'Fired, Red-eye reduction, Return detected',
379					'73'	=> 'On, Red-eye reduction',
380					'77'	=> 'On, Red-eye reduction, Return not detected',
381					'79'	=> 'On, Red-eye reduction, Return detected',
382					'80'	=> 'Off, Red-eye reduction',
383					'88'	=> 'Auto, Did not fire, Red-eye reduction',
384					'89'	=> 'Auto, Fired, Red-eye reduction',
385					'93'	=> 'Auto, Fired, Red-eye reduction, Return not detected',
386					'95'	=> 'Auto, Fired, Red-eye reduction, Return detected',
387				],
388			],
389			'FocalLength' => [
390				'format'	=> 'rational',
391				'suffix'	=> 'mm',
392			],
393			/*				//don't convert since already converted by PHP
394			   'UserComment' => array(
395				   'binary'	=> true,
396			   ),*/
397			'ColorSpace' => [
398				'options' => [
399					'1'		=> 'sRGB',
400					'65533'	=> 'Wide Gamut RGB',
401					'65534'	=> 'ICC Profile',
402					'65535'	=> 'Uncalibrated',
403				],
404			],
405			'ExifImageWidth' => [
406				'label'		=> 'Image Width',
407				'suffix'	=> 'pixels',
408			],
409			'ExifImageLength' => [
410				'label'		=> 'Image Height',
411				'suffix'	=> 'pixels',
412			],
413			'FlashEnergy'	=> [
414				'format'	=> 'rational',
415				'suffix'	=> 'BCPS',
416			],
417			'FocalPlaneXResolution' => [
418				'format'	=> 'rational',
419				'suffix'	=> 'pixels per unit',
420			],
421			'FocalPlaneYResolution' => [
422				'format'	=> 'rational',
423				'suffix'	=> 'pixels per unit',
424			],
425			'FocalPlaneResolutionUnit' => [
426				'options'	=> [
427					1		=> 'None',
428					2		=> 'inch',
429					3		=> 'cm',
430					4		=> 'mm',
431					5		=> 'um',
432				],
433			],
434			'ExposureIndex' => [
435				'format'	=> 'rational',
436			],
437			'SensingMethod' => [
438				'options'	=> [
439					1		=> 'Not defined',
440					2		=> 'One-chip color area',
441					3		=> 'Two-chip color area',
442					4		=> 'Three-chip color area',
443					5		=> 'Color sequential area',
444					7		=> 'Trilinear',
445					8		=> 'Color sequential linear',
446				],
447			],
448			'FileSource'	=> [
449				'binary'	=> true,
450				'options'	=> [
451					'00'	=> 'Others',
452					'01'	=> 'Film Scanner ',
453					'02'	=> 'Reflection Print Scanner',
454					'03'	=> 'Digital Camera',
455				],
456			],
457			'SceneType'		=> [
458				'binary'	=> true,
459				'options'	=> [
460					'01'	=> 'Directly photographed',
461				],
462			],
463			'CFAPattern'	=> [
464				'binary'	=> true,
465				'options'	=> [
466					'00'	=> 'Red',
467					'01'	=> 'Green',
468					'02'	=> 'Blue',
469					'03'	=> 'Cyan',
470					'04'	=> 'Magenta',
471					'05'	=> 'Yellow',
472					'06'	=> 'White',
473				],
474			],
475			'CustomRendered' => [
476				'options' => [
477					'0'	=> 'Normal',
478					'1'	=> 'Custom',
479				],
480			],
481			'ExposureMode' => [
482				'options' => [
483					'0'	=> 'Auto',
484					'1'	=> 'Manual',
485					'2'	=> 'Auto bracket',
486				],
487			],
488			'WhiteBalance' => [
489				'options' => [
490					'0'	=> 'Auto',
491					'1'	=> 'Manual',
492				],
493			],
494			'SceneCaptureType'	=> [
495				'options'	=> [
496					'0'		=> 'Standard',
497					'1'		=> 'Landscape',
498					'2'		=> 'Portrait',
499					'3'		=> 'Night',
500				],
501			],
502			'GainControl' => [
503				'options' => [
504					'0'	=> 'None',
505					'1'	=> 'Low gain up',
506					'2'	=> 'High gain up',
507					'3'	=> 'Low gain down',
508					'4'	=> 'High gain down',
509				],
510			],
511			'Contrast' => [
512				'options' => [
513					'0'	=> 'Normal',
514					'1'	=> 'Low',
515					'2'	=> 'High',
516				],
517			],
518			'Saturation' => [
519				'options' => [
520					'0'	=> 'Normal',
521					'1'	=> 'Low',
522					'2'	=> 'High',
523				],
524			],
525			'Sharpness' => [
526				'options' => [
527					'0'	=> 'Normal',
528					'1'	=> 'Soft',
529					'2'	=> 'Hard',
530				],
531			],
532			'SubjectDistanceRange' => [
533				'options' => [
534					'0'	=> 'Unknown',
535					'1'	=> 'Macro',
536					'2'	=> 'Close',
537					'3'	=> 'Distant',
538				],
539			],
540			//some legitimate exif fields are apparently only partially handled by php
541			//an undefined tag is returned instead of the label
542			'UndefinedTag:0xA431' => [
543				'label'	=> 'Serial Number',
544			],
545			'UndefinedTag:0xA432' => [
546				'label'	=> 'Lens Specification',
547				'suffix' => 'Min - max focal length in mm; Min - max Fnumber'
548			],
549			'UndefinedTag:0xA434' => [
550				'label'	=> 'Lens Model',
551			],
552		],
553		'GPS' => [
554			'GPSVersion' => [
555				'binary'	=> true,
556			],
557			'GPSLatitudeRef' => [
558				'options' => [
559					'N'	=> 'North (+)',
560					'S'	=> 'South (-)',
561				],
562			],
563			'GPSLatitude' => [
564				'format'	=> 'rational',
565			],
566			'GPSLongitudeRef' => [
567				'options' => [
568					'E'	=> 'East (+)',
569					'W'	=> 'West (-)',
570				],
571			],
572			'GPSLongitude' => [
573				'format'	=> 'rational',
574			],
575			'GPSAltitudeRef' => [
576				'binary' => true,
577				'options' => [
578					'00'	=> 'Meters above sea level',
579					'01'	=> 'Meters below sea level',
580				],
581			],
582			'GPSAltitude' => [
583				'format'	=> 'rational',
584			],
585			'GPSTimeStamp' => [
586				'format'	=> 'rational',
587				'suffix'	=> '(24-hour clock)',
588			],
589			'GPSStatus' => [
590				'options' => [
591					'A'	=> 'Measurement Active',
592					'V'	=> 'Measurement Void',
593				],
594			],
595			'GPSMeasureMode' => [
596				'options' => [
597					'2'	=> '2-Dimensional Measurement',
598					'3'	=> '3-Dimensional Measurement',
599				],
600			],
601			'GPSSpeedRef' => [
602				'options' => [
603					'K'	=> 'km/h',
604					'M'	=> 'mph',
605					'N'	=> 'knots',
606				],
607			],
608			'GPSSpeed' => [
609				'format'	=> 'rational',
610			],
611			'GPSTrackRef' => [
612				'options' => [
613					'M'	=> 'Magnetic North',
614					'T'	=> 'True North',
615				],
616			],
617			'GPSTrack' => [
618				'format'	=> 'rational',
619			],
620			'GPSImgDirectionRef' => [
621				'options' => [
622					'M'	=> 'Magnetic North',
623					'T'	=> 'True North',
624				],
625			],
626			'GPSImgDirection' => [
627				'format'	=> 'rational',
628			],
629			'GPSDifferential' => [
630				'options' => [
631					'0'	=> 'No Corection',
632					'T'	=> 'Differential Corrected',
633				],
634			],
635			'GPSDestLatitudeRef' => [
636				'options' => [
637					'N'	=> 'North (+)',
638					'S'	=> 'South (-)',
639				],
640			],
641			'GPSDestLatitude' => [
642				'format'	=> 'rational',
643			],
644			'GPSDestLongitudeRef' => [
645				'options' => [
646					'E'	=> 'East (+)',
647					'W'	=> 'West (-)',
648				],
649			],
650			'GPSDestLongitude' => [
651				'format'	=> 'rational',
652			],
653			'GPSDestBearingRef' => [
654				'options' => [
655					'M'	=> 'Magnetic North',
656					'T'	=> 'True North',
657				],
658			],
659			'GPSDestBearing' => [
660				'format'	=> 'rational',
661			],
662			'GPSDestDistanceRef' => [
663				'options' => [
664					'K'	=> 'Kilometers',
665					'M'	=> 'Miles',
666					'N'	=> 'Nautical Miles',
667				],
668			],
669			'GPSDestDistance' => [
670				'format'	=> 'rational',
671			],
672			'GPSHPositioningError' => [
673				'format'	=> 'rational',
674			],
675		],
676	];
677
678	/**
679	 * Process raw EXIF data by converting binary or hex information, replacing legend codes with their meanings,
680	 * fixing labels, etc.
681	 *
682	 * @param		array		$exifraw		Array of raw EXIF data
683	 *
684	 * @return 		array		$exif			Array of processed EXIF data, including label, newval and suffix values
685	 * 												for each field
686	 */
687	function processRawData($exifraw)
688	{
689		$filter   = new Zend\Filter\Word\CamelCaseToSeparator();
690		//array of tags to match exif array from file
691		foreach ($exifraw as $group => $fields) {
692			foreach ($fields as $name => $field) {
693				if (isset($field)) {
694					//store raw value
695					$exif[$group][$name]['rawval'] = $field;
696
697					//thumbnail and ifd0 groups share the same specifications
698					$groupmask = $group == 'THUMBNAIL' ? 'IFD0' : $group;
699
700					//get tag data from $specs array
701					if (isset($this->specs[$groupmask][$name])) {
702						//shorten the variable
703						$specname = $this->specs[$groupmask][$name];
704
705						//convert binary values
706						if (isset($specname['binary']) && $specname['binary']) {
707							$exif[$group][$name]['rawval'] = bin2hex($exif[$group][$name]['rawval']);
708						}
709
710						//start processing rawval into newval
711						if (is_array($exif[$group][$name]['rawval'])) {
712							$exif[$group][$name]['newval'] = $this->processArray($exif[$group][$name]['rawval'], $name);
713						//perform division for rational fields, but only if not an array
714						} elseif (isset($specname['format']) && $specname['format'] == 'rational') {
715							$exif[$group][$name]['newval'] = $this->divide($field);
716						//move rest of rawvals into newvals
717						} else {
718							$exif[$group][$name]['newval'] = $exif[$group][$name]['rawval'];
719						}
720
721						//now determine display values using option values where they exist
722						if (isset($specname['options'])) {
723							//first handle special cases
724							if ($name == 'ComponentsConfiguration') {
725								$str = $exif[$group][$name]['newval'];
726								$opt = $specname['options'];
727								$disp = $opt[substr($str, 0, 2)] . ' ' . $opt[substr($str, 2, 2)] . ' '
728										. $opt[substr($str, 4, 2)] . ' ' . $opt[substr($str, 6, 2)];
729								$exif[$group][$name]['newval'] = $disp;
730							} elseif ($name == 'CFAPattern') {
731								$str = $exif[$group][$name]['newval'];
732								$opt = $specname['options'];
733								$disp = '[' . $opt[substr($str, 8, 2)] . ', ' . $opt[substr($str, 10, 2)] . '] ['
734										. $opt[substr($str, 12, 2)] . ', ' . $opt[substr($str, 14, 2)] . ']';
735								$exif[$group][$name]['newval'] = $disp;
736							} else {
737								$exif[$group][$name]['newval'] = $specname['options'][$exif[$group][$name]['newval']];
738							}
739						}
740
741						//fix labels
742						if (isset($specname['label'])) {
743							$exif[$group][$name]['label'] = $specname['label'];
744						} else {
745							//create reading-friendly labels from camel case tags
746							$exif[$group][$name]['label'] = $filter->filter($name);
747						}
748
749						if (isset($specname['suffix']) && ! is_array($exif[$group][$name]['newval'])) {
750							$exif[$group][$name]['suffix'] = $specname['suffix'];
751						}
752					} else {
753						//those not covered in $specs
754						$exif[$group][$name]['newval'] = $exif[$group][$name]['rawval'];
755						//create reading-friendly labels from camel case tags
756						$exif[$group][$name]['label'] = $filter->filter($name);
757					}
758				}
759			}
760		}
761		//*******Special Handling*********//
762		//file name is computed by PHP and is meaningless when file is stored in tiki,
763		//and dialog box has real name in title so not needed
764		unset($exif['FILE']['FileName']);
765		//file date is also computed by PHP and represents the time the metadata was extracted. This data is included
766		//elsewhere and is not needed here
767		unset($exif['FILE']['FileDateTime']);
768		//No processing of maker notes yet as specific code is needed for each manufacturer
769		//Blank out field since it is very long and will distort the dialog box
770		if (! empty($exif['EXIF']['MakerNote']['newval'])) {
771			$exif['EXIF']['MakerNote']['newval'] = '(Not processed)';
772			unset($exif['EXIF']['MakerNote']['rawval']);
773		}
774		if (isset($exif['MAKERNOTE'])) {
775			$exif['MAKERNOTE'] = [];
776			$exif['MAKERNOTE']['Note']['label'] = "";
777			$exif['MAKERNOTE']['Note']['newval'] = "(Not processed)";
778		}
779		//Interpret GPSVersion field
780		if (isset($exif['GPS']['GPSVersion'])) {
781			$exif['GPS']['GPSVersion']['newval'] = '';
782			$len = strlen($exif['GPS']['GPSVersion']['rawval']);
783			for ($i = 0; $i < $len; $i = $i + 2) {
784				if ($i > 0) {
785					$exif['GPS']['GPSVersion']['newval'] .= '.';
786				}
787				$exif['GPS']['GPSVersion']['newval'] .= (int) substr($exif['GPS']['GPSVersion']['rawval'], $i, 2);
788			}
789		}
790		//PHP already converts UserComment in the Computed group so use that value
791		if (isset($exif['EXIF']['UserComment']) && isset($exif['COMPUTED']['UserComment'])) {
792			$exif['EXIF']['UserComment']['newval'] = $exif['COMPUTED']['UserComment']['rawval'];
793			unset($exif['COMPUTED']['UserComment']);
794		}
795		//PHP already converts the FNumber in the Computed group so use that value
796		if (isset($exif['EXIF']['FNumber']) && isset($exif['COMPUTED']['ApertureFNumber'])) {
797			$exif['EXIF']['FNumber']['newval'] = $exif['COMPUTED']['ApertureFNumber']['rawval'];
798			unset($exif['COMPUTED']['ApertureFNumber']);
799		}
800
801		return $exif;
802	}
803
804	/**
805	 * Perform division on values in a rational format, e.g. '100/5'. Accepts arrays or single values
806	 *
807	 * @param 		string or array		$fractionString
808	 *
809	 * @return 		array|float
810	 */
811	function divide($fractionString)
812	{
813		if (! is_array($fractionString)) {
814			$fraction = explode('/', $fractionString);
815			$ret = $fraction[0] / $fraction[1];
816		} else {
817			foreach ($fractionString as $fs) {
818				$fraction = explode('/', $fs);
819				$ret[] = $fraction[0] / $fraction[1];
820			}
821		}
822		return $ret;
823	}
824
825	/**
826	 * Deal with field values that are arrays, giving unique treatment to certain unique fields, otherwise
827	 * converting to a string
828	 *
829	 * @param 		array		$array			field value
830	 * @param 		string		$fieldname		field name used to identify where unique treatment should be applied
831	 *
832	 * @return		string		$ret			field array converted into a string
833	 */
834	function processArray($array, $fieldname)
835	{
836		$ret = '';
837		if ($fieldname == 'GPSLatitude' || $fieldname == 'GPSLongitude') {
838			$calcarray = $this->divide($array);
839			$ret = $calcarray[0] + (($calcarray[1] + ($calcarray[2] / 60)) / 60);
840		} elseif ($fieldname == 'GPSTimeStamp') {
841			$array = $this->divide($array);
842			$ret = $array[0] . ':' . $array[1] . ':' . $array[2];
843		} elseif ($fieldname == 'UndefinedTag:0xA432') {
844			$array = $this->divide($array);
845			$ret = $array[0] . ' - ' . $array[1] . '; ' . $array[2] . ' - ' . $array[3];
846		} else {
847			foreach ($array as $value) {
848				$ret .= $value . '; ';
849			}
850			$ret .= tra('(values not interpreted)');
851		}
852		return $ret;
853	}
854}
855