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