]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - vendor/james-heinrich/getid3/getid3/module.tag.xmp.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / vendor / james-heinrich / getid3 / 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 );