db29601a3c68f5fcd7b6771436688d0d765bdbad
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / module.tag.xmp.php
1 <?php
2
3 /////////////////////////////////////////////////////////////////
4 /// getID3() by James Heinrich <info@getid3.org> //
5 // available at https://github.com/JamesHeinrich/getID3 //
6 // or https://www.getid3.org //
7 // or http://getid3.sourceforge.net //
8 // see readme.txt for more details //
9 /////////////////////////////////////////////////////////////////
10 // //
11 // module.tag.xmp.php //
12 // module for analyzing XMP metadata (e.g. in JPEG files) //
13 // dependencies: NONE //
14 // //
15 /////////////////////////////////////////////////////////////////
16 // //
17 // Module originally written [2009-Mar-26] by //
18 // Nigel Barnes <ngbarnesØhotmail*com> //
19 // Bundled into getID3 with permission //
20 // called by getID3 in module.graphic.jpg.php //
21 // ///
22 /////////////////////////////////////////////////////////////////
23
24 /**************************************************************************************************
25 * SWISScenter Source Nigel Barnes
26 *
27 * Provides functions for reading information from the 'APP1' Extensible Metadata
28 * Platform (XMP) segment of JPEG format files.
29 * This XMP segment is XML based and contains the Resource Description Framework (RDF)
30 * data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information.
31 *
32 * This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter.
33 *************************************************************************************************/
34 class Image_XMP
35 {
36 /**
37 * @var string
38 * The name of the image file that contains the XMP fields to extract and modify.
39 * @see Image_XMP()
40 */
41 public $_sFilename = null;
42
43 /**
44 * @var array
45 * The XMP fields that were extracted from the image or updated by this class.
46 * @see getAllTags()
47 */
48 public $_aXMP = array();
49
50 /**
51 * @var boolean
52 * True if an APP1 segment was found to contain XMP metadata.
53 * @see isValid()
54 */
55 public $_bXMPParse = false;
56
57 /**
58 * Returns the status of XMP parsing during instantiation
59 *
60 * You'll normally want to call this method before trying to get XMP fields.
61 *
62 * @return boolean
63 * Returns true if an APP1 segment was found to contain XMP metadata.
64 */
65 public function isValid()
66 {
67 return $this->_bXMPParse;
68 }
69
70 /**
71 * Get a copy of all XMP tags extracted from the image
72 *
73 * @return array - An array of XMP fields as it extracted by the XMPparse() function
74 */
75 public function getAllTags()
76 {
77 return $this->_aXMP;
78 }
79
80 /**
81 * Reads all the JPEG header segments from an JPEG image file into an array
82 *
83 * @param string $filename - the filename of the JPEG file to read
84 * @return array|boolean $headerdata - Array of JPEG header segments,
85 * FALSE - if headers could not be read
86 */
87 public function _get_jpeg_header_data($filename)
88 {
89 // prevent refresh from aborting file operations and hosing file
90 ignore_user_abort(true);
91
92 // Attempt to open the jpeg file - the at symbol supresses the error message about
93 // not being able to open files. The file_exists would have been used, but it
94 // does not work with files fetched over http or ftp.
95 if (is_readable($filename) && is_file($filename) && ($filehnd = fopen($filename, 'rb'))) {
96 // great
97 } else {
98 return false;
99 }
100
101 // Read the first two characters
102 $data = fread($filehnd, 2);
103
104 // Check that the first two characters are 0xFF 0xD8 (SOI - Start of image)
105 if ($data != "\xFF\xD8")
106 {
107 // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
108 echo '<p>This probably is not a JPEG file</p>'."\n";
109 fclose($filehnd);
110 return false;
111 }
112
113 // Read the third character
114 $data = fread($filehnd, 2);
115
116 // Check that the third character is 0xFF (Start of first segment header)
117 if ($data[0] != "\xFF")
118 {
119 // NO FF found - close file and return - JPEG is probably corrupted
120 fclose($filehnd);
121 return false;
122 }
123
124 // Flag that we havent yet hit the compressed image data
125 $hit_compressed_image_data = false;
126
127 $headerdata = array();
128 // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
129 // 2) we have hit the compressed image data (no more headers are allowed after data)
130 // 3) or end of file is hit
131
132 while (($data[1] != "\xD9") && (!$hit_compressed_image_data) && (!feof($filehnd)))
133 {
134 // Found a segment to look at.
135 // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
136 if ((ord($data[1]) < 0xD0) || (ord($data[1]) > 0xD7))
137 {
138 // Segment isn't a Restart marker
139 // Read the next two bytes (size)
140 $sizestr = fread($filehnd, 2);
141
142 // convert the size bytes to an integer
143 $decodedsize = unpack('nsize', $sizestr);
144
145 // Save the start position of the data
146 $segdatastart = ftell($filehnd);
147
148 // Read the segment data with length indicated by the previously read size
149 $segdata = fread($filehnd, $decodedsize['size'] - 2);
150
151 // Store the segment information in the output array
152 $headerdata[] = array(
153 'SegType' => ord($data[1]),
154 'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data[1])],
155 'SegDataStart' => $segdatastart,
156 'SegData' => $segdata,
157 );
158 }
159
160 // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
161 if ($data[1] == "\xDA")
162 {
163 // Flag that we have hit the compressed image data - exit loop as no more headers available.
164 $hit_compressed_image_data = true;
165 }
166 else
167 {
168 // Not an SOS - Read the next two bytes - should be the segment marker for the next segment
169 $data = fread($filehnd, 2);
170
171 // Check that the first byte of the two is 0xFF as it should be for a marker
172 if ($data[0] != "\xFF")
173 {
174 // NO FF found - close file and return - JPEG is probably corrupted
175 fclose($filehnd);
176 return false;
177 }
178 }
179 }
180
181 // Close File
182 fclose($filehnd);
183 // Alow the user to abort from now on
184 ignore_user_abort(false);
185
186 // Return the header data retrieved
187 return $headerdata;
188 }
189
190
191 /**
192 * Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
193 *
194 * @param string $filename - the filename of the JPEG file to read
195 * @return string|boolean $xmp_data - the string of raw XML text,
196 * FALSE - if an APP 1 XMP segment could not be found, or if an error occured
197 */
198 public function _get_XMP_text($filename)
199 {
200 //Get JPEG header data
201 $jpeg_header_data = $this->_get_jpeg_header_data($filename);
202
203 //Cycle through the header segments
204 for ($i = 0; $i < count($jpeg_header_data); $i++)
205 {
206 // If we find an APP1 header,
207 if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
208 {
209 // And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
210 if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
211 {
212 // Found a XMP/RDF block
213 // Return the XMP text
214 $xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
215
216 return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
217 }
218 }
219 }
220 return false;
221 }
222
223 /**
224 * Parses a string containing XMP data (XML), and returns an array
225 * which contains all the XMP (XML) information.
226 *
227 * @param string $xmltext - a string containing the XMP data (XML) to be parsed
228 * @return array|boolean $xmp_array - an array containing all xmp details retrieved,
229 * FALSE - couldn't parse the XMP data.
230 */
231 public function read_XMP_array_from_text($xmltext)
232 {
233 // Check if there actually is any text to parse
234 if (trim($xmltext) == '')
235 {
236 return false;
237 }
238
239 // Create an instance of a xml parser to parse the XML text
240 $xml_parser = xml_parser_create('UTF-8');
241
242 // Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10
243
244 // We would like to remove unneccessary white space, but this will also
245 // remove things like newlines (&#xA;) in the XML values, so white space
246 // will have to be removed later
247 if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false)
248 {
249 // Error setting case folding - destroy the parser and return
250 xml_parser_free($xml_parser);
251 return false;
252 }
253
254 // to use XML code correctly we have to turn case folding
255 // (uppercasing) off. XML is case sensitive and upper
256 // casing is in reality XML standards violation
257 if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false)
258 {
259 // Error setting case folding - destroy the parser and return
260 xml_parser_free($xml_parser);
261 return false;
262 }
263
264 // Parse the XML text into a array structure
265 if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0)
266 {
267 // Error Parsing XML - destroy the parser and return
268 xml_parser_free($xml_parser);
269 return false;
270 }
271
272 // Destroy the xml parser
273 xml_parser_free($xml_parser);
274
275 // Clear the output array
276 $xmp_array = array();
277
278 // The XMP data has now been parsed into an array ...
279
280 // Cycle through each of the array elements
281 $current_property = ''; // current property being processed
282 $container_index = -1; // -1 = no container open, otherwise index of container content
283 foreach ($values as $xml_elem)
284 {
285 // Syntax and Class names
286 switch ($xml_elem['tag'])
287 {
288 case 'x:xmpmeta':
289 // only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit
290 break;
291
292 case 'rdf:RDF':
293 // required element immediately within x:xmpmeta; no data here
294 break;
295
296 case 'rdf:Description':
297 switch ($xml_elem['type'])
298 {
299 case 'open':
300 case 'complete':
301 if (array_key_exists('attributes', $xml_elem))
302 {
303 // rdf:Description may contain wanted attributes
304 foreach (array_keys($xml_elem['attributes']) as $key)
305 {
306 // Check whether we want this details from this attribute
307 // if (in_array($key, $GLOBALS['XMP_tag_captions']))
308 // if (true)
309 // {
310 // Attribute wanted
311 $xmp_array[$key] = $xml_elem['attributes'][$key];
312 // }
313 }
314 }
315 break;
316 case 'cdata':
317 case 'close':
318 break;
319 }
320 break;
321
322 case 'rdf:ID':
323 case 'rdf:nodeID':
324 // Attributes are ignored
325 break;
326
327 case 'rdf:li':
328 // Property member
329 if ($xml_elem['type'] == 'complete')
330 {
331 if (array_key_exists('attributes', $xml_elem))
332 {
333 // If Lang Alt (language alternatives) then ensure we take the default language
334 if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default'))
335 {
336 break;
337 }
338 }
339 if ($current_property != '')
340 {
341 $xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
342 $container_index += 1;
343 }
344 //else unidentified attribute!!
345 }
346 break;
347
348 case 'rdf:Seq':
349 case 'rdf:Bag':
350 case 'rdf:Alt':
351 // Container found
352 switch ($xml_elem['type'])
353 {
354 case 'open':
355 $container_index = 0;
356 break;
357 case 'close':
358 $container_index = -1;
359 break;
360 case 'cdata':
361 break;
362 }
363 break;
364
365 default:
366 // Check whether we want the details from this attribute
367 // if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions']))
368 // if (true)
369 // {
370 switch ($xml_elem['type'])
371 {
372 case 'open':
373 // open current element
374 $current_property = $xml_elem['tag'];
375 break;
376
377 case 'close':
378 // close current element
379 $current_property = '';
380 break;
381
382 case 'complete':
383 // store attribute value
384 $xmp_array[$xml_elem['tag']] = (isset($xml_elem['attributes']) ? $xml_elem['attributes'] : (isset($xml_elem['value']) ? $xml_elem['value'] : ''));
385 break;
386
387 case 'cdata':
388 // ignore
389 break;
390 }
391 // }
392 break;
393 }
394
395 }
396 return $xmp_array;
397 }
398
399
400 /**
401 * Constructor
402 *
403 * @param string $sFilename - Name of the image file to access and extract XMP information from.
404 */
405 public function __construct($sFilename)
406 {
407 $this->_sFilename = $sFilename;
408
409 if (is_file($this->_sFilename))
410 {
411 // Get XMP data
412 $xmp_data = $this->_get_XMP_text($sFilename);
413 if ($xmp_data)
414 {
415 $aXMP = $this->read_XMP_array_from_text($xmp_data);
416 if ($aXMP !== false) {
417 $this->_aXMP = (array) $aXMP;
418 $this->_bXMPParse = true;
419 }
420 }
421 }
422 }
423
424 }
425
426 /**
427 * Global Variable: XMP_tag_captions
428 *
429 * The Property names of all known XMP fields.
430 * Note: this is a full list with unrequired properties commented out.
431 */
432 /*
433 $GLOBALS['XMP_tag_captions'] = array(
434 // IPTC Core
435 'Iptc4xmpCore:CiAdrCity',
436 'Iptc4xmpCore:CiAdrCtry',
437 'Iptc4xmpCore:CiAdrExtadr',
438 'Iptc4xmpCore:CiAdrPcode',
439 'Iptc4xmpCore:CiAdrRegion',
440 'Iptc4xmpCore:CiEmailWork',
441 'Iptc4xmpCore:CiTelWork',
442 'Iptc4xmpCore:CiUrlWork',
443 'Iptc4xmpCore:CountryCode',
444 'Iptc4xmpCore:CreatorContactInfo',
445 'Iptc4xmpCore:IntellectualGenre',
446 'Iptc4xmpCore:Location',
447 'Iptc4xmpCore:Scene',
448 'Iptc4xmpCore:SubjectCode',
449 // Dublin Core Schema
450 'dc:contributor',
451 'dc:coverage',
452 'dc:creator',
453 'dc:date',
454 'dc:description',
455 'dc:format',
456 'dc:identifier',
457 'dc:language',
458 'dc:publisher',
459 'dc:relation',
460 'dc:rights',
461 'dc:source',
462 'dc:subject',
463 'dc:title',
464 'dc:type',
465 // XMP Basic Schema
466 'xmp:Advisory',
467 'xmp:BaseURL',
468 'xmp:CreateDate',
469 'xmp:CreatorTool',
470 'xmp:Identifier',
471 'xmp:Label',
472 'xmp:MetadataDate',
473 'xmp:ModifyDate',
474 'xmp:Nickname',
475 'xmp:Rating',
476 'xmp:Thumbnails',
477 'xmpidq:Scheme',
478 // XMP Rights Management Schema
479 'xmpRights:Certificate',
480 'xmpRights:Marked',
481 'xmpRights:Owner',
482 'xmpRights:UsageTerms',
483 'xmpRights:WebStatement',
484 // These are not in spec but Photoshop CS seems to use them
485 'xap:Advisory',
486 'xap:BaseURL',
487 'xap:CreateDate',
488 'xap:CreatorTool',
489 'xap:Identifier',
490 'xap:MetadataDate',
491 'xap:ModifyDate',
492 'xap:Nickname',
493 'xap:Rating',
494 'xap:Thumbnails',
495 'xapidq:Scheme',
496 'xapRights:Certificate',
497 'xapRights:Copyright',
498 'xapRights:Marked',
499 'xapRights:Owner',
500 'xapRights:UsageTerms',
501 'xapRights:WebStatement',
502 // XMP Media Management Schema
503 'xapMM:DerivedFrom',
504 'xapMM:DocumentID',
505 'xapMM:History',
506 'xapMM:InstanceID',
507 'xapMM:ManagedFrom',
508 'xapMM:Manager',
509 'xapMM:ManageTo',
510 'xapMM:ManageUI',
511 'xapMM:ManagerVariant',
512 'xapMM:RenditionClass',
513 'xapMM:RenditionParams',
514 'xapMM:VersionID',
515 'xapMM:Versions',
516 'xapMM:LastURL',
517 'xapMM:RenditionOf',
518 'xapMM:SaveID',
519 // XMP Basic Job Ticket Schema
520 'xapBJ:JobRef',
521 // XMP Paged-Text Schema
522 'xmpTPg:MaxPageSize',
523 'xmpTPg:NPages',
524 'xmpTPg:Fonts',
525 'xmpTPg:Colorants',
526 'xmpTPg:PlateNames',
527 // Adobe PDF Schema
528 'pdf:Keywords',
529 'pdf:PDFVersion',
530 'pdf:Producer',
531 // Photoshop Schema
532 'photoshop:AuthorsPosition',
533 'photoshop:CaptionWriter',
534 'photoshop:Category',
535 'photoshop:City',
536 'photoshop:Country',
537 'photoshop:Credit',
538 'photoshop:DateCreated',
539 'photoshop:Headline',
540 'photoshop:History',
541 // Not in XMP spec
542 'photoshop:Instructions',
543 'photoshop:Source',
544 'photoshop:State',
545 'photoshop:SupplementalCategories',
546 'photoshop:TransmissionReference',
547 'photoshop:Urgency',
548 // EXIF Schemas
549 'tiff:ImageWidth',
550 'tiff:ImageLength',
551 'tiff:BitsPerSample',
552 'tiff:Compression',
553 'tiff:PhotometricInterpretation',
554 'tiff:Orientation',
555 'tiff:SamplesPerPixel',
556 'tiff:PlanarConfiguration',
557 'tiff:YCbCrSubSampling',
558 'tiff:YCbCrPositioning',
559 'tiff:XResolution',
560 'tiff:YResolution',
561 'tiff:ResolutionUnit',
562 'tiff:TransferFunction',
563 'tiff:WhitePoint',
564 'tiff:PrimaryChromaticities',
565 'tiff:YCbCrCoefficients',
566 'tiff:ReferenceBlackWhite',
567 'tiff:DateTime',
568 'tiff:ImageDescription',
569 'tiff:Make',
570 'tiff:Model',
571 'tiff:Software',
572 'tiff:Artist',
573 'tiff:Copyright',
574 'exif:ExifVersion',
575 'exif:FlashpixVersion',
576 'exif:ColorSpace',
577 'exif:ComponentsConfiguration',
578 'exif:CompressedBitsPerPixel',
579 'exif:PixelXDimension',
580 'exif:PixelYDimension',
581 'exif:MakerNote',
582 'exif:UserComment',
583 'exif:RelatedSoundFile',
584 'exif:DateTimeOriginal',
585 'exif:DateTimeDigitized',
586 'exif:ExposureTime',
587 'exif:FNumber',
588 'exif:ExposureProgram',
589 'exif:SpectralSensitivity',
590 'exif:ISOSpeedRatings',
591 'exif:OECF',
592 'exif:ShutterSpeedValue',
593 'exif:ApertureValue',
594 'exif:BrightnessValue',
595 'exif:ExposureBiasValue',
596 'exif:MaxApertureValue',
597 'exif:SubjectDistance',
598 'exif:MeteringMode',
599 'exif:LightSource',
600 'exif:Flash',
601 'exif:FocalLength',
602 'exif:SubjectArea',
603 'exif:FlashEnergy',
604 'exif:SpatialFrequencyResponse',
605 'exif:FocalPlaneXResolution',
606 'exif:FocalPlaneYResolution',
607 'exif:FocalPlaneResolutionUnit',
608 'exif:SubjectLocation',
609 'exif:SensingMethod',
610 'exif:FileSource',
611 'exif:SceneType',
612 'exif:CFAPattern',
613 'exif:CustomRendered',
614 'exif:ExposureMode',
615 'exif:WhiteBalance',
616 'exif:DigitalZoomRatio',
617 'exif:FocalLengthIn35mmFilm',
618 'exif:SceneCaptureType',
619 'exif:GainControl',
620 'exif:Contrast',
621 'exif:Saturation',
622 'exif:Sharpness',
623 'exif:DeviceSettingDescription',
624 'exif:SubjectDistanceRange',
625 'exif:ImageUniqueID',
626 'exif:GPSVersionID',
627 'exif:GPSLatitude',
628 'exif:GPSLongitude',
629 'exif:GPSAltitudeRef',
630 'exif:GPSAltitude',
631 'exif:GPSTimeStamp',
632 'exif:GPSSatellites',
633 'exif:GPSStatus',
634 'exif:GPSMeasureMode',
635 'exif:GPSDOP',
636 'exif:GPSSpeedRef',
637 'exif:GPSSpeed',
638 'exif:GPSTrackRef',
639 'exif:GPSTrack',
640 'exif:GPSImgDirectionRef',
641 'exif:GPSImgDirection',
642 'exif:GPSMapDatum',
643 'exif:GPSDestLatitude',
644 'exif:GPSDestLongitude',
645 'exif:GPSDestBearingRef',
646 'exif:GPSDestBearing',
647 'exif:GPSDestDistanceRef',
648 'exif:GPSDestDistance',
649 'exif:GPSProcessingMethod',
650 'exif:GPSAreaInformation',
651 'exif:GPSDifferential',
652 'stDim:w',
653 'stDim:h',
654 'stDim:unit',
655 'xapGImg:height',
656 'xapGImg:width',
657 'xapGImg:format',
658 'xapGImg:image',
659 'stEvt:action',
660 'stEvt:instanceID',
661 'stEvt:parameters',
662 'stEvt:softwareAgent',
663 'stEvt:when',
664 'stRef:instanceID',
665 'stRef:documentID',
666 'stRef:versionID',
667 'stRef:renditionClass',
668 'stRef:renditionParams',
669 'stRef:manager',
670 'stRef:managerVariant',
671 'stRef:manageTo',
672 'stRef:manageUI',
673 'stVer:comments',
674 'stVer:event',
675 'stVer:modifyDate',
676 'stVer:modifier',
677 'stVer:version',
678 'stJob:name',
679 'stJob:id',
680 'stJob:url',
681 // Exif Flash
682 'exif:Fired',
683 'exif:Return',
684 'exif:Mode',
685 'exif:Function',
686 'exif:RedEyeMode',
687 // Exif OECF/SFR
688 'exif:Columns',
689 'exif:Rows',
690 'exif:Names',
691 'exif:Values',
692 // Exif CFAPattern
693 'exif:Columns',
694 'exif:Rows',
695 'exif:Values',
696 // Exif DeviceSettings
697 'exif:Columns',
698 'exif:Rows',
699 'exif:Settings',
700 );
701 */
702
703 /**
704 * Global Variable: JPEG_Segment_Names
705 *
706 * The names of the JPEG segment markers, indexed by their marker number
707 */
708 $GLOBALS['JPEG_Segment_Names'] = array(
709 0x01 => 'TEM',
710 0x02 => 'RES',
711 0xC0 => 'SOF0',
712 0xC1 => 'SOF1',
713 0xC2 => 'SOF2',
714 0xC3 => 'SOF4',
715 0xC4 => 'DHT',
716 0xC5 => 'SOF5',
717 0xC6 => 'SOF6',
718 0xC7 => 'SOF7',
719 0xC8 => 'JPG',
720 0xC9 => 'SOF9',
721 0xCA => 'SOF10',
722 0xCB => 'SOF11',
723 0xCC => 'DAC',
724 0xCD => 'SOF13',
725 0xCE => 'SOF14',
726 0xCF => 'SOF15',
727 0xD0 => 'RST0',
728 0xD1 => 'RST1',
729 0xD2 => 'RST2',
730 0xD3 => 'RST3',
731 0xD4 => 'RST4',
732 0xD5 => 'RST5',
733 0xD6 => 'RST6',
734 0xD7 => 'RST7',
735 0xD8 => 'SOI',
736 0xD9 => 'EOI',
737 0xDA => 'SOS',
738 0xDB => 'DQT',
739 0xDC => 'DNL',
740 0xDD => 'DRI',
741 0xDE => 'DHP',
742 0xDF => 'EXP',
743 0xE0 => 'APP0',
744 0xE1 => 'APP1',
745 0xE2 => 'APP2',
746 0xE3 => 'APP3',
747 0xE4 => 'APP4',
748 0xE5 => 'APP5',
749 0xE6 => 'APP6',
750 0xE7 => 'APP7',
751 0xE8 => 'APP8',
752 0xE9 => 'APP9',
753 0xEA => 'APP10',
754 0xEB => 'APP11',
755 0xEC => 'APP12',
756 0xED => 'APP13',
757 0xEE => 'APP14',
758 0xEF => 'APP15',
759 0xF0 => 'JPG0',
760 0xF1 => 'JPG1',
761 0xF2 => 'JPG2',
762 0xF3 => 'JPG3',
763 0xF4 => 'JPG4',
764 0xF5 => 'JPG5',
765 0xF6 => 'JPG6',
766 0xF7 => 'JPG7',
767 0xF8 => 'JPG8',
768 0xF9 => 'JPG9',
769 0xFA => 'JPG10',
770 0xFB => 'JPG11',
771 0xFC => 'JPG12',
772 0xFD => 'JPG13',
773 0xFE => 'COM',
774 );