Skip to content
Snippets Groups Projects
Select Git revision
  • ef124442d46b5feeed40e3c9c9a7f07ad7cf4db8
  • master default protected
  • 9.0
  • marcus/wix3
  • 8.0
  • nt-tools
  • 7.8
  • 7.6
  • 7.4
  • 7.2
  • 7.0
  • 0.6
  • rosuav/latex-markdown-renderer
  • rxnpatch/rxnpatch
  • marcus/gobject-introspection
  • rxnpatch/8.0
  • rosuav/pre-listening-ports
  • rosuav/async-annotations
  • rosuav/pgsql-ssl
  • rxnpatch/rxnpatch-broken/2023-10-06T094250
  • grubba/fdlib
  • v8.0.2020
  • v8.0.2018
  • v8.0.2016
  • v8.0.2014
  • v8.0.2012
  • v8.0.2008
  • v8.0.2006
  • v8.0.2004
  • v8.0.2002
  • v8.0.2000
  • v8.0.1998
  • v8.0.1996
  • v8.0.1994
  • v8.0.1992
  • v8.0.1990
  • v8.0.1988
  • v8.0.1986
  • rxnpatch/clusters/8.0/2025-04-29T124414
  • rxnpatch/2025-04-29T124414
  • v8.0.1984
41 results

EXIF.pmod

Blame
  • user avatar
    Martin Nilsson authored
    Rev: lib/modules/Standards.pmod/EXIF.pmod:1.25
    ef124442
    History
    EXIF.pmod 34.80 KiB
    #pike __REAL_VERSION__
    
    //! This module implements EXIF (Exchangeable image file format for
    //! Digital Still Cameras) 2.2 parsing.
    
    // $Id: EXIF.pmod,v 1.25 2004/08/10 13:26:13 nilsson Exp $
    //  Johan Schn <js@roxen.com>, July 2001.
    //  Based on Exiftool by Robert F. Tobler <rft@cg.tuwien.ac.at>.
    //
    // Some URLs:
    // http://www.exif.org/
    // http://www.dicasoft.de/casiomn.htm
    // http://www.dalibor.cz/minolta/
    
    
    static void add_field(mapping m, string field,
    		      mapping|array alts,
    		      array(int) from, int index)
    {
      if(index>=sizeof(from))
        return;
      index = from[index];
      if(mappingp(alts)) {
        if(alts[index])
          m[field]=alts[index];
        else
          m["failed "+field]=index;
      }
      else {
        if(index<sizeof(alts))
          m[field]=alts[index];
        else
          m["failed "+field]=index;
      }
    }
    
    string components_config(string data)
    {
      mapping val = ([ 00: "",
    		   01: "Y",
    		   02: "Cb",
    		   03: "Cr",
    		   04: "R",
    		   05: "G",
    		   06: "B" ]);
      string res = "";
      foreach(data/"", string b)
        res += val[b[0]] || b;
      return res;
    }
    
    #define SIZETEST(X) if(sizeof(data)<(X)+1) return res
    static mapping canon_multi0(array(int) data)
    {
      mapping res=([]);
    
      add_field(res, "CanonMacroMode",
    	    ([1: "Macro", 2: "Normal"]),
    	    data, 1);
    
      SIZETEST(2);
      if(data[2])
        res->CanonSelfTimerLength = sprintf("%.1f s", data[2]/10.0);
    
      add_field(res, "CanonQuality",
    	    ([2: "Normal", 3: "Fine", 4: "Superfine", 5: "Superfine"]),
    	    data, 3);
    
      add_field(res, "CanonFlashMode",
    	    ([0: "Flash not fired",
    	     1: "Auto",
    	     2: "On",
    	     3: "Red-eye reduction",
    	     4: "Slow synchro",
    	     5: "Auto + red-eye reduction",
    	     6: "On + red-eye reduction",
    	     16: "External flash", ]),
    	    data, 4);
    
      add_field(res, "CanonDriveMode",
    	    ({"Single", "Continuous"}),
    	    data, 5);
    
      add_field(res, "CanonFocusMode",
    	    ({ "One-Shot", "AI Servo",
    	       "AI Focus", "MF", "Single",
    	       "Continuous", "MF" }),
    	    data, 7);
    
      add_field(res, "CanonImageSize",
    	    ({ "Large", "Medium", "Small" }),
    	    data, 10);
    
      add_field(res, "CanonShootingMode",
    	    ({ "Full Auto", "Manual",
    	       "Landscape", "Fast Shutter",
    	       "Slow Shutter", "Night",
    	       "B&W", "Sepia", "Portrait",
    	       "Sports", "Macro / Close-Up",
    	       "Pan Focus" }),
    	    data, 11);
    
      add_field(res, "CanonDigitalZoom",
    	    ({ "None", "2X", "4X" }),
    	    data, 12);
    
      add_field(res, "CanonContrast",
    	    ([0xffff:"Low", 0x0000:"Normal", 0x0001: "High"]),
    	    data, 13);
    
      add_field(res, "CanonSaturation",
    	    ([0xffff:"Low", 0x0000:"Normal", 0x0001: "High"]),
    	    data, 14);
    
      add_field(res, "CanonSharpness",
    	    ([0xffff:"Low", 0x0000:"Normal", 0x0001: "High"]),
    	    data, 15);
    
      add_field(res, "CanonISO",
    	    ([ 0:"100", 1:"200", 2:"400",
    	       15:"Auto", 16:"50", 17:"100", 18:"200", 19:"400"]),
    	    data, 16);
    
      add_field(res, "CanonMeteringMode",
    	    ([ 0:"Central importance", 1:"Spot",
    	       3:"Evaluative", 4:"Partial", 5:"Center-weighted" ]),
    	    data, 17);
    
      add_field(res, "CanonFocusType",
    	    ([0: "Manual", 1: "Auto", 3: "Close-up (macro)",
    	     8: "Locked (pan mode)"]),
    	    data, 18);
    
      add_field(res, "CanonAutoFocusPointSelected",
    	    ([0x3000: "None (MF)",
    	     0x3001: "Auto-selected",
    	     0x3002: "Right",
    	     0x3003: "Center",
    	     0x3004: "Left"]),
    	    data, 19);
    
      add_field(res, "CanonExposureMode",
    	    ({ "Easy shooting", "Program",
    	       "Tv-priority", "Av-priority",
    	       "Manual", "A-DEP" }),
    	    data, 20);
    
      SIZETEST(25);
      float unit=(float)data[25];
      res->CanonLensFocalLengthRange=sprintf("%.0f-%.0f mm", data[24]/unit,
    					 data[23]/unit);
    
      add_field(res, "CanonFlashActivity",
    	    ({ "Did not fire", "Fired" }), data, 28);
    
      SIZETEST(29);
      array flashdetails=({});
      if(data[29] & 1<<14)
        flashdetails+=({"External E-TTL"});
      if(data[29] & 1<<13)
        flashdetails+=({"Internal flash"});
      if(data[29] & 1<<11)
        flashdetails+=({"FP sync used"});
      if(data[29] & 1<<7)
        flashdetails+=({"Rear-curtain sync used"});
      if(data[29] & 1<<4)
        flashdetails+=({"FP sync enabled"});
    
      if(sizeof(flashdetails))
        res->CanonFlashDetails=flashdetails*", ";
      else
        res->CanonFlashDetails="No flash";
    
      if (sizeof(data)>32)
      {
         add_field(res, "CanonFocusMode",
    	       ({ "Single", "Continuous" }), data, 32);
      }
    
      return res;
    }
    
    static mapping canon_multi1(array(int) data) {
      mapping res = ([]);
    
      SIZETEST(1);
      if(data[1]) res->CanonFocalLength = data[1]/32.0;
      return res;
    }
    
    static mapping canon_multi3(array(int) data)
    {
      mapping res=([]);
    
      SIZETEST(6);
      if(data[6]) {
        float v = 0.0;
        if(data[6]<65)
          v = v/32.0;
        else
          v = (0x10000-data[6])/32.0;
        res->CanonFlashBias = sprintf("%s%1.2f EV", (v>0?"+":""), v);
      }
    
      add_field(res, "CanonWhiteBalance",
        ({ "Auto", "Sunny", "Cloudy",
           "Tungsten", "Flourescent", "Flash",
           "Custom" }), data, 7);
    
      SIZETEST(9);
      res->CanonBurstSequenceNumber=(string)data[9];
    
      SIZETEST(10);
      res->CanonOpticalZoon=(string)data[10];
    
      SIZETEST(14);
      if(data[14]) {
        res->CanonAutoFocusPoint = ([
          0:"Right",
          1:"Center",
          2:"Left" ])[ data[14]&3 ];
        res->CanonAutoFocusPoints = (string)( data[14]>>11 );
      }
    
      add_field(res, "CanonFlashBias",
    	    ([ 0xffc0: "-2 EV",
    	       0xffcc: "-1.67 EV",
    	       0xffd0: "-1.50 EV",
    	       0xffd4: "-1.33 EV",
    	       0xffe0: "-1 EV",
    	       0xffec: "-0.67 EV",
    	       0xfff0: "-0.50 EV",
    	       0xfff4: "-0.33 EV",
    	       0x0000: "0 EV",
    	       0x000c: "+0.33 EV",
    	       0x0010: "+0.50 EV",
    	       0x0014: "+0.67 EV",
    	       0x0020: "+1 EV",
    	       0x002c: "+1.33 EV",
    	       0x0030: "+1.50 EV",
    	       0x0034: "+1.67 EV",
    	       0x0040: "+2 EV" ]),
    	    data, 15);
    
      SIZETEST(19);
      res->CanonSubjectDistance=sprintf("%.2f",data[19]/100.0);
      return res;
    }
    
    static mapping canon_multi4(array(int) data) {
      mapping res = ([]);
      add_field(res, "CanonStichDirection",
    	    ({ "Left to right", "Right to left", "Bottom to top", "Top to bottom" }),
    	    data, 5);
      return res;
    }
    
    static mapping CANON_D30_MAKERNOTE = ([
      0x0001:       ({"MN_Multi0",                  "CUSTOM", canon_multi0 }),
      0x0002:       ({"MN_Multi1",                  "CUSTOM", canon_multi1 }),
      0x0004:       ({"MN_Multi3",                  "CUSTOM", canon_multi3 }),
      0x0005:       ({"MN_Multi4",                  "CUSTOM", canon_multi4 }),
      0x0006:	({"MN_ImageType",	    	}),
      0x0007:	({"MN_FirmwareVersion",         }),
      0x0008:	({"MN_ImageNumber", 	    	}),
      0x0009:	({"MN_OwnerName", 	    	}),
      0x000C:	({"MN_CameraSerialNumber",  	}),
      //  0x000F:       ({"MN_CustomFunctions",         "CUSTOM", canon_custom }),
    ]);
    
    static mapping NIKON_D_MAKERNOTE = ([
      0x0001: ({ "MN_FirmwareVersion" }),
      0x0002: ({ "MN_ISO" }),
      0x0004: ({ "MN_Quality" }),
      0x0005: ({ "MN_WhiteBalance" }),
      0x0006: ({ "MN_Sharpening" }),
      0x0007: ({ "MN_FocusMode" }),
      0x0008: ({ "MN_FlashSetting" }),
      0x0009: ({ "MN_FlashMode" }),
      0x000b: ({ "MN_WhiteBalanceFine" }),
      0x000c: ({ "MN_WhiteBalandeRBCoefficients" }),
      0x0012: ({ "MN_FlashCompensation" }),
      0x0013: ({ "MN_ISO2" }),
      0x0081: ({ "MN_ToneCompensation" }),
      0x0083: ({ "MN_LensType", "MAP",
    	     ([ "0" : "AF non D",
    		"1" : "Manual",
    		"2" : "AF-D/AF-S",
    		"6" : "AF-D G",
    		"10" : "AF-D VR",
    	     ]) }),
      0x0084: ({ "MN_Lens" }),
      0x0087: ({ "MN_FlashUsed", "MAP",
    	     ([ "0" : "Flash not fired",
    		"4" : "Unknown",
    		"7" : "External",
    		"9" : "On camera",
    	     ]) }),
      0x008c: ({ "MN_ContrastCurve" }),
      0x008d: ({ "MN_ColorMode" }),
      0x0090: ({ "MN_LightType" }),
      0x0092: ({ "MN_Hue" }),
      0x0e01: ({ "MN_CaptureEditorData" }),
    ]);
    
    static mapping|string nikon_D70_makernote(string data, mapping real_tags) {
      object f = Stdio.FakeFile(data);
      if(f->read(10)!="Nikon\0\2\20\0\0") return data;
      string order = f->read(2);
      string code = f->read(2);
      if(sizeof(code)!=2 || short_value(code, order)!=42) return data;
    
      mapping tags = ([]);
      int offset=long_value(f->read(4), order);
      while(offset>0)
      {
        exif_seek(f,offset,10);
        int num_entries=short_value(f->read(2), order);
        for(int i=0; i<num_entries; i++)
          tags|=parse_tag(f, tags, NIKON_D_MAKERNOTE, 10, order);
    
        offset=long_value(f->read(4), order);
    
        if(offset == 0)
          if(tags["ExifOffset"])
          {
    	offset=(int)tags["ExifOffset"];
    	m_delete(tags, "ExifOffset");
          }
      }
    
      foreach(tags; string name; mixed value)
        if(has_prefix(name, "MN_"))
          real_tags[name] = m_delete(tags, name);
      if(sizeof(tags))
        return tags;
      return UNDEFINED;
    }
    
    static mapping nikon_iso(array(int) data) {
      mapping res=([]);
    
      SIZETEST(1);
      if(data[1]) res->NikonISOSetting = "ISO"+data[1];
      return res;
    }
    
    // NIKON 990, D1 (more?)
    static mapping NIKON_990_MAKERNOTE = ([
      0x0001:	({"MN_0x0001",	    	    	}),
      0x0002:	({"MN_ISOSetting",   	    	"CUSTOM", nikon_iso }),
      0x0003:	({"MN_ColorMode",    	    	}),
      0x0004:	({"MN_Quality",	    	    	}),
      0x0005:	({"MN_Whitebalance", 	    	}),
      0x0006:	({"MN_ImageSharpening",	    	}),
      0x0007:	({"MN_FocusMode",    	    	}),
      0x0008:	({"MN_FlashSetting", 	    	}),
      0x000A:	({"MN_0x000A",	    	    	}),
      0x000F:	({"MN_ISOSelection", 	    	}),
      0x0080:	({"MN_ImageAdjustment",	    	}),
      0x0082:	({"MN_AuxiliaryLens",	    	}),
      0x0085:	({"MN_ManualFocusDistance",  	"FLOAT" }),
      0x0086:	({"MN_DigitalZoomFactor",    	"FLOAT" }),
      0x0088:	({"MN_AFFocusPosition",	    	"MAP",
    		    ([ "\00\00\00\00": "Center",
    		       "\00\01\00\00": "Top",
    		       "\00\02\00\00": "Bottom",
    		       "\00\03\00\00": "Left",
    		       "\00\04\00\00": "Right",
    		    ])  }),
      0x0094:       ({"MN_Saturation", "MAP",
    		  ([ -3 : "B&W",
    		     -2 : "-2",
    		     -1 : "-1",
    		     0 : "0",
    		     1 : "1",
    		     2 : "2"
    		  ]) }),
      0x0095:       ({"MN_NoiseReduction",          }),
      0x0010:	({"MN_DataDump",     	    	}),
    ]);
    
    static mapping sanyo_specialmode(array(int) data) {
      mapping res = ([]);
    
      add_field(res, "SanyoSpecialMode",
    	    ([ 0:"Normal",
    	       2:"Fast",
    	       3:"Panorama" ]), data, 0);
    
      SIZETEST(1);
      if(data[1]) res->SanyoSpecialModeSequence = (string)data[1];
    
      add_field(res, "SanyoSpecialModeDirection",
                ([ 0:"Non-panoramic",
    	       1:"Left to right",
    	       2:"Right to left",
    	       3:"Bottom to top",
    	       4:"Top to bottom" ]), data, 2);
      return res;
    }
    
    static mapping sanyo_jpegquality(array(int) data) {
      mapping res=([]);
    
      int r = data[0]&0xff;
      add_field(res, "SanyoJPEGQualityResolution",
    	    ({ "Very low", "Low", "Medium low", "Medium", "Medium high",
    	       "High", "Very High", "Super high" }), ({ r }), 0);
    
      r = (data[0]&0xff00)>>8;
      add_field(res, "SanyoJPEGQualityDetail",
    	    ({ "Normal", "Fine", "Super Fine" }), ({ r }), 0);
      return res;
    }
    
    static mapping SANYO_MAKERNOTE = ([
      0x00ff:       ({"MN_StartOffset",             }),
      0x0100:       ({"MN_JPEGThumbnail",           }),
      0x0200:       ({"MN_SpecialMode",             "CUSTOM", sanyo_specialmode }),
      0x0201:       ({"MN_JPEGQuality",             "CUTSOM", sanyo_jpegquality }),
      0x0202:       ({"MN_Macro",                   "MAP",
    		  ([ 0:"Normal",
    		     1:"Macro",
    		     2:"View",
    		     3:"Manual" ]) }),
      0x0204:       ({"MN_DigitalZoom",             }),
      0x0207:       ({"MN_SoftwareRelease",         }),
      0x0208:       ({"MN_PictInfo",                }),
      0x0209:       ({"MN_CameraID",                }),
      0x020e:       ({"MN_SequentialShotMethod",    "MAP",
    		  ([ 0:"None",
    		     1:"Standard",
    		     2:"Best",
    		     3:"Adjust exposure" ]) }),
      0x020f:       ({"MN_WideRange",               "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0210:       ({"MN_ColourAdjustmentMode",    "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0213:       ({"MN_QuickShot",               "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0214:       ({"MN_SelfTimer",               "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0216:       ({"MN_VoiceMemo",               "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0217:       ({"MN_RecordShutterRelease",    "MAP",
    		  ([ 0:"Record whilst held",
    		     1:"Press to start. Press to stop." ]) }),
      0x0218:       ({"MN_FlickerReduce",           "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x0219:       ({"MN_OpticalZoom",             "MAP", ([ 0:"Disabled", 1:"Enabled" ]) }),
      0x021b:       ({"MN_DigitalZoom",             "MAP", ([ 0:"Disabled", 1:"Enabled" ]) }),
      0x021d:       ({"MN_LightSourceSpecial",      "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x021e:       ({"MN_Resaved",                 "MAP", ([ 0:"No", 1:"Yes" ]) }),
      0x021f:       ({"MN_SceneSelect",             "MAP",
    		  ([ 0:"Off",
    		     1:"Sport",
    		     2:"TV",
    		     3:"Night",
    		     4:"User 1",
    		     5:"User 2" ]) }),
      0x0223:       ({"MN_ManualFocalDistance",     }),
      0x0224:       ({"MN_SequentialShotInterval",  "MAP",
    		  ([ 0:"5 frames/s",
    		     1:"10 frames/s",
    		     2:"15 frames/s",
    		     3:"20 frames/s" ]) }),
      0x0225:       ({"MN_FlashMode",               "MAP",
    		  ([ 0:"Auto",
    		     1:"Force",
    		     2:"Disabled",
    		     3:"Red eye" ]) }),
      0x0e00:       ({"MN_PrintIMFlags",            }),
      0x0f00:       ({"MN_DataDump",                }),
    ]);
    
    static mapping OLYMPUS_MAKERNOTE = ([
      0x0100:       ({"MN_JPEGThumbnail"            }),
      0x0200:       ({"MN_SpecialMode",             "CUSTOM", sanyo_specialmode }),
      0x0201:       ({"MN_JPEGQuality",             "MAP",
    		  ([ 0:"SQ",
    		     1:"HQ",
    		     2:"SHQ" ]) }),
      0x0202:       ({"MN_Macro",                   "MAP", ([ 0:"No", 1:"Yes" ]) }),
      0x0204:       ({"MN_DigitalZoom",             "MAP", ([ 0:"1x", 1:"2x" ]) }),
      0x0207:       ({"MN_SoftwareRelease",         }),
      0x0208:       ({"MN_PictInfo",                }),
      0x0209:       ({"MN_CameraID",                }),
      0x0f00:       ({"MN_DataDump",                }),
    ]);
    
    // Nikon E700/E800/E900/E900S/E910/E950
    static mapping NIKON_MAKERNOTE = ([
      0x0002:       ({"MN_0x0002",                   }),
      0x0003:       ({"MN_Quality",                 "MAP",
    		  ([ 1:"VGA Basic",
    		     2:"VGA Normal",
    		     3:"VGA Fine",
    		     4:"SXGA Basic",
    		     5:"SXGA Normal",
    		     6:"SXGA Fine" ]) }),
      0x0004:       ({"MN_ColorMode",               "MAP", ([ 1:"Color", 2:"Monochrome" ]) }),
      0x0005:       ({"MN_ImageAdjustment",         "MAP",
    		  ([ 0:"Normal",
    		     1:"Bright+",
    		     2:"Bright-",
    		     3:"Contrast+",
    		     4:"Contrast-" ]) }),
      0x0006:       ({"MN_CCDSensitivity",          "MAP",
    		  ([ 0:"ISO80",
    		     1:"ISO160",
    		     4:"ISO320",
    		     5:"ISO100" ]) }),
      0x0007:       ({"MN_WhiteBalance",            "MAP",
    		  ([ 0:"Auto",
    		     1:"Preset",
    		     2:"Daylight",
    		     3:"Incandescense",
    		     4:"Flourescense",
    		     5:"Cloudy",
    		     6:"SpeedLight" ]) }),
      0x0008:       ({"MN_Focus",                   }),
      0x0009:       ({"MN_0x0009",                  }),
      0x000a:       ({"MN_DigitalZoom",             }),
      0x000b:       ({"MN_Converter",               "MAP", ([ 1:"Fisheye converter" ]) }),
      0x0f00:       ({"MN_0x0f00",                  }),
    ]);
    
    static mapping CASIO_MAKERNOTE = ([
      0x0001:       ({"MN_RecordingMode",           "MAP",
    		  ([ 1:"Single Shutter",
    		     2:"Panorama",
    		     7:"Panorama",
    		     3:"Night Scene",
    		     10:"Night Scene",
    		     4:"Portrait",
    		     15:"Portrait",
    		     5:"Landscape",
    		     16:"Landscape" ]) }),
      0x0002:       ({"MN_Quality",                 "MAP",
    		  ([ 1:"Economy",
    		     2:"Normal",
    		     3:"Fine" ]) }),
      0x0003:       ({"MN_FocusingMode",            "MAP",
    		  ([ 2:"Macro",
    		     3:"Auto Focus",
    		     4:"Manual Focus",
    		     5:"Infinity" ]) }),
      0x0004:       ({"MN_FlashMode",               "MAP",
    		  ([ 1:"Auto",
    		     2:"On",
    		     3:"Off",
    		     4:"Red Eye Reduction" ]) }),
      0x0005:       ({"MN_FlashIntensity",          "MAP",
    		  ([ 11:"Weak",
    		     13:"Normal",
    		     15:"Strong" ]) }),
      0x0006:       ({"MN_ObjectDistance",          }),
      0x0007:       ({"MN_WhiteBalance",            "MAP",
    		  ([ 1:"Auto",
    		     2:"Tungsten",
    		     3:"Daylight",
    		     4:"Flourescent",
    		     5:"Shade",
    		     129:"Manual" ]) }),
      0x000a:       ({"MN_DigitalZoom",             "MAP",
    		  ([ 0x10000:"Off",
    		     0x10001:"2X Digital Zoom" ]) }),
      0x000b:       ({"MN_Sharpness",               "MAP",
    		  ([ 0:"Normal",
    		     1:"Soft",
    		     2:"Hard" ]) }),
      0x000c:       ({"MN_Contrast",                "MAP",
    		  ([ 0:"Normal",
    		     1:"Low",
    		     2:"High" ]) }),
      0x000d:       ({"MN_Saturation",              "MAP",
    		  ([ 0:"Normal",
    		     1:"Low",
    		     2:"High" ]) }),
      0x0014:       ({"MN_CCDSensitivity",          "MAP",
    		  ([ 64:"Normal",
    		     125:"+1.0",
    		     250:"+2.0",
    		     244:"+3.0",
    		     80:"Normal",
    		     100:"High", ]) }),
    ]);
    
    static mapping FUJIFILM_MAKERNOTE = ([
      0x0000:       ({"MN_Version",                 }),
      0x1000:       ({"MN_Quality",                 }),
      0x1001:       ({"MN_Sharpness",               "MAP",
    		  ([ 1:"Soft",
    		     2:"Soft",
    		     3:"Normal",
    		     4:"Hard",
    		     5:"Hard" ]) }),
      0x1002:       ({"MN_WhiteBalance",            "MAP",
    		  ([ 0:"Auto",
    		     256:"Daylight",
    		     512:"Cloudy",
    		     768:"DaylightColor-fluorescence",
    		     769:"DaywhiteColor-fluorescence",
    		     770:"White-fluorescence",
    		     1024:"Incandenscence",
    		     3840:"Custom white balance" ]) }),
      0x1003:       ({"MN_Saturation",              "MAP",
    		  ([ 0:"Normal",
    		     256:"High",
    		     512:"Low" ]) }),
      0x1004:       ({"MN_Contrast",                "MAP",
    		  ([ 0:"Normal",
    		     256:"High",
    		     512:"Low" ]) }),
      0x1010:       ({"MN_FlashMode",               "MAP",
    		  ([ 0:"Auto",
    		     1:"On",
    		     2:"Off",
    		     3:"Red-eye reduction" ]) }),
      0x1011:       ({"MN_FlashStrength",           }),
      0x1020:       ({"MN_Macro",                   "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x1021:       ({"MN_FocusMode",               "MAP", ([ 0:"Auto focus", 1:"Manual focus" ]) }),
      0x1030:       ({"MN_SlowSync",                "MAP", ([ 0:"Off", 1:"On" ]) }),
      0x1031:       ({"MN_PictureMode",             "MAP",
    		  ([ 0:"Auto",
    		     1:"Portrait scene",
    		     2:"Landscape scene",
    		     4:"Sports scene",
    		     5:"Night scene",
    		     6:"Program AE",
    		     256:"Aperture prior AE",
    		     512:"Shutter prior AE",
    		     768:"Manual exposure" ]) }),
      0x1300:       ({"MN_BlurWarning",             "MAP", ([ 0:"No", 1:"Yes" ]) }),
      0x1301:       ({"MN_FocusWarning",            "MAP", ([ 0:"No", 1:"Yes" ]) }),
      0x1302:       ({"MN_AEWarning",               "MAP", ([ 0:"No", 1:"Yes" ]) }),
    ]);
    
    static mapping GPS_TAGS = ([
      0x0000: ({"GPSVersionID"}),
      0x0001: ({"GPSLatitudeRef"}),
      0x0002: ({"GPSLatitude"}),
      0x0003: ({"GPSLongitudeRef"}),
      0x0004: ({"GPSLongitude"}),
      0x0005: ({"GPSAltitudeRef"}),
      0x0006: ({"GPSAltitude"}),
      0x0007: ({"GPSTimeStamp"}),
      0x0008: ({"GPSSatellites"}),
      0x0009: ({"GPSStatus"}),
      0x000A: ({"GPSMeasureMode"}),
      0x000B: ({"GPSDOP"}),
      0x000C: ({"GPSSpeedRef"}),
      0x000D: ({"GPSSpeed"}),
      0x000E: ({"GPSTrackRef"}),
      0x000F: ({"GPSTrack"}),
      0x0010: ({"GPSImgDirectionRef"}),
      0x0011: ({"GPSImgDirection"}),
      0x0012: ({"GPSMapDatum"}),
      0x0013: ({"GPSDestLatitudeRef"}),
      0x0014: ({"GPSDestLatitude"}),
      0x0015: ({"GPSDestLongitudeRef"}),
      0x0016: ({"GPSDestLongitude"}),
      0x0017: ({"GPSDestBearingRef"}),
      0x0018: ({"GPSDestBearing"}),
      0x0019: ({"GPSDestDistanceRef"}),
      0x001A: ({"GPSDestDistance"}),
    ]);
    
    static mapping TAG_INFO = ([
      0x0001:       ({"InteroperabilityIndex",      }),
      0x0002:       ({"InteroperabilityVersion",    }),
      0x00fe:	({"NewSubFileType",  	    	}),
      0x0100:	({"ImageWidth",      	    	}),
      0x0101:	({"ImageLength",     	    	}),
      0x0102:	({"BitsPerSample",   	    	}),
      0x0103:	({"Compression",     	    	}),
      0x0106:	({"PhotometricInterpretation",	}),
      0x010a:       ({"FillOrder",                  }),
      0x010d:       ({"DocumentName",               }),
      0x010e:	({"ImageDescription", 	    	}),
      0x010f:	({"Make",    	    	    	}),
      0x0110:	({"Model",   	    	    	}),
      0x0111:	({"StripOffsets",    	    	}),
      0x0112:	({"Orientation",     	    	}),
      0x0115:	({"SamplesPerPixel", 	    	}),
      0x0116:	({"RowsPerStrip",    	    	}),
      0x0117:	({"StripByteCounts", 	    	}),
      0x011a:	({"XResolution",     	    	}),
      0x011b:	({"YResolution",     	    	}),
      0x011c:	({"PlanarConfiguration",     	}),
      0x0128:	({"ResolutionUnit",  	    	"MAP",
    		  ([ 1:	"Not Absolute",
    		     2:	"Inch",
    		     3:	"Centimeter", ]) }),
      0x012d:       ({"TransferFunction",           }),
      0x0131:	({"Software",	    	    	}),
      0x0132:	({"DateTime",	    	    	}),
      0x013b:	({"Artist",  	    	    	}),
      0x013c:       ({"HostComputer",               }),
      0x013e:       ({"WhitePoint",                 }),
      0x013f:       ({"PrimaryChromaticities",      }),
      0x0142:	({"TileWidth",	    	    	}),
      0x0143:	({"TileLength",	    	    	}),
      0x0144:	({"TileOffsets",     	    	}),
      0x0145:	({"TileByteCounts",  	    	}),
      0x014a:	({"SubIFDs", 	    	    	}),
      0x0156:       ({"TransferRange",              }),
      0x015b:	({"JPEGTables",	    	    	}),
      0x0200:       ({"JPEGProc",                   }),
      0x0201:	({"JPEGInterchangeFormat",   	}), // from EXIFread
      0x0202:	({"JPEGInterchangeFormatLength", }), // from EXIFread
      0x0211:	({"YCbCrCoefficients",	    	}),
      0x0212:	({"YCbCrSubSampling",	    	}),
      0x0213:	({"YCbCrPositioning",	    	}),
      0x0214:	({"ReferenceBlackWhite",     	}),
      0x1000:       ({"RelatedImageFileFormat",     }),
      0x1001:       ({"RelatedImageWidth",          }),
      0x1002:       ({"RelatedImageLength",         }),
      0x828d:       ({"CFARepeatPatternDim",        }),
      0x828f:	({"BatteryLevel",    	    	}),
      0x8298:	({"Copyright",	    	    	}),
      0x829a:	({"ExposureTime",    	    	"EXPOSURE" }),
      0x829d:	({"FNumber", 	    	    	"FLOAT" }),
      0x83bb:	({"IPTC_NAA",	    	    	}),
      0x8769:       ({"ExifOffset",                 }),
      0x8773:	({"InterColorProfile",	    	}),
      0x8822:	({"ExposureProgram", 	    	"MAP",
    		    ([ 0:	"Unidentified",
    		       1:	"Manual",
    		       2:	"Program Normal",
    		       3:	"Aperture Priority",
    		       4:	"Shutter Priority",
    		       5:	"Program Creative",
    		       6:	"Program Action",
    		       7:	"Portrait Mode",
    		       8:	"Landscape Mode", ]) }),
      0x8824:	({"SpectralSensitivity",     	}),
      0x8825:	({"GPSInfo", 	    	    	}),
      0x8827:	({"ISOSpeedRating", 	    	}),
      0x8828:	({"OECF",    	    	    	}),
      0x8829:	({"Interlace",	    	    	}),
      0x882a:	({"TimeZoneOffset",  	    	}),
      0x882b:	({"SelfTimerMode",   	    	}),
      0x9000:       ({"ExifVersion",     	    	}), // from EXIFread
      0x9003:       ({"DateTimeOriginal",	    	}),
      0x9004:       ({"DateTimeDigitized",	    	}), // from EXIFread
      0x9101:       ({"ComponentsConfiguration", 	"CUSTOM", components_config }),
      0x9102:	({"CompressedBitsPerPixel",  	}),
      0x9201:	({"ShutterSpeedValue",	    	}),
      0x9202:	({"ApertureValue",   	    	}),
      0x9203:	({"BrightnessValue", 	    	}),
      0x9204:	({"ExposureBiasValue",	    	"BIAS" }),
      0x9205:	({"MaxApertureValue",	    	"FLOAT" }),
      0x9206:	({"SubjectDistance", 	    	}),
      0x9207:	({"MeteringMode",    	    	"MAP",
    		  ([  0:		    "Unidentified",
    		      1:		    "Average",
    		      2:		    "CenterWeightedAverage",
    		      3:		    "Spot",
    		      4:		    "MultiSpot",
    		      5:                    "Pattern",
    		      6:                    "Partial",
    		      255:                  "Other", ]) }),
      0x9208:	({"LightSource",     	    	"MAP",
    		  ([  0:                    "Unknown",
    		      1:                    "Daylight",
    		      2:                    "Fluorescent",
    		      3:                    "Tungsten",
    		      4:                    "Flash",
    		      9:                    "Fine weather",
    		      10:                   "Cloudy weather",
    		      11:                   "Shade",
    		      12:                   "Daylight fluorescent",
    		      13:                   "Day white fluorescent",
    		      14:                   "Cool white fluorescent",
    		      15:                   "White fluorescent",
    		      17:                   "Standard light A",
    		      18:                   "Standard light B",
    		      19:                   "Standard light C",
    		      20:                   "D55",
    		      21:                   "D65",
    		      22:                   "D75",
    		      23:                   "D50",
    		      24:                   "ISO studio tungsten",
    		      255:                  "Other light source", ]) }),
      0x9209:	({"Flash",   	    	    	"MAP",
    		  ([  0:		"no",
    		      1:		"fired",
    		      5:		"fired (?)", // no return sensed
    		      7:		"fired (!)", // return sensed
    		      9:		"fill fired",
    		      13:		"fill fired (?)",
    		      15:		"fill fired (!)",
    		      16:		"fill no",
    		      24:		"auto off",
    		      25:		"auto fired",
    		      29:		"auto fired (?)",
    		      31:		"auto fired (!)",
    		      32:		"not available",
    		      65:               "red-eye fired",
    		      69:               "red-eye fired (?)",
    		      71:               "red-eye fired (!)",
    		      73:               "red-eye fill fired",
    		      77:               "red-eye fill fired (?)",
    		      79:               "red-eye fill fired (!)",
    		      89:               "red-eye auto fired",
    		      93:               "red-eye auto fired (?)",
    		      95:               "red-eye auto fired (!)", ]) }),
      0x920a:	({"FocalLength",     	    	"FLOAT" }),
      0x920b:	({"FlashEnergy",     	    	}),
      0x920c:	({"SpatialFrequencyResponse",	}),
      0x920d:	({"Noise",   	    	    	}),
      0x920e:	({"FocalPlaneXResolution",   	}),
      0x920f:	({"FocalPlaneYResolution",   	}),
      0x9210:	({"FocalPlaneResolutionUnit",	}),
      0x9211:	({"ImageNumber",     	    	}),
      0x9212:	({"SecurityClassification",  	}),
      0x9213:	({"ImageHistory",    	    	}),
      0x9214:	({"SubjectArea", 	    	}),
      0x9215:	({"ExposureIndex",   	    	}),
      0x9216:	({"TIFF_EPStandardID",	    	}),
      0x9217:	({"SensingMethod",   	    	}),
      0x927c:       ({"MakerNote",	    	    	"MAKE_MODEL",
    		    ([
    		      "NIKON_E990":  ({"TAGS", NIKON_990_MAKERNOTE}),
    		      "NIKON":  ({"TAGS", NIKON_990_MAKERNOTE}),
    		      "NIKON CORPORATION": ({"CUSTOM", nikon_D70_makernote}),
    		      "Canon":  ({"TAGS", CANON_D30_MAKERNOTE}),
    		      "FUJIFILM": ({"TAGS", FUJIFILM_MAKERNOTE}),
    		      "OLYMPUS DIGITAL CAMERA": ({"TAGS", OLYMPUS_MAKERNOTE}),
    		      "SANYO DIGITAL CAMERA": ({"TAGS", SANYO_MAKERNOTE}),
    		      "CASIO": ({"TAGS", CASIO_MAKERNOTE}),
    
    		      // FIXME: Reverse engineer these.
    		      "FUJIFILM_FinePix2400Zoom": ({ 0, ([]) }),
    		      "FUJIFILM_FinePix4700 ZOOM": ({ 0, ([]) }),
    
    		      "": ({ 0, ([]) }) ]) }),
      0x9286:       ({"UserComment",                }),
      0x9290:       ({"SubSecTime",                 }),
      0x9291:       ({"SubSecTimeOriginal",         }),
      0x9292:       ({"SubSecTimeDigitized",        }),
      0xa000:       ({"FlashPixVersion",	       	}),
      0xa001:       ({"ColorSpace",	    	    	}),
      0xa002:       ({"PixelXDimension",   	    	}),
      0xa003:       ({"PixelYDimension",   	    	}),
      0xa004:       ({"RelatedSoundFile",           }),
      0xa005:       ({"ExifInterabilityOffset",     }),
      0xa20b:       ({"FlashEnergy",                }),
      0xa20c:       ({"SpatialFrequencyResponse",   }),
      0xa20e:       ({"FocalPlaneXResolution",      }),
      0xa20f:       ({"FocalPlaneYResolution",      }),
      0xa210:       ({"FocalPlaneResolutionUnit",   }),
      0xa214:       ({"SubjectLocation",            }),
      0xa215:       ({"ExposureIndex",              }),
      0xa217:       ({"SensingMethod",              }),
      0xa300:       ({"FileSource",                 }),
      0xa301:       ({"SceneType",                  }),
      0xa302:       ({"CFAPattern",                 }),
      0xa401:       ({"CustomRendered",             }),
      0xa402:       ({"ExposureMode",               "MAP",
    		  ([  0:                "Auto exposure",
    		      1:                "Manual exposure",
    		      2:                "Auto bracket", ]) }),
      0xa403:       ({"WhiteBalance",               "MAP",
    		  ([  0:                "Auto",
    		      1:                "Manual", ]) }),
      0xa404:       ({"DigitalZoomRatio",           "FLOAT" }),
      0xa405:       ({"FocalLengthIn35mmFilm",      }),
      0xa406:       ({"SceneCaptureType",           "MAP",
    		  ([  0:                "Standard",
    		      1:                "Landscape",
    		      2:                "Portrait",
    		      3:                "Night scene", ]) }),
      0xa407:       ({"GainControl",                "MAP",
    		  ([  0:                "None",
    		      1:                "Low gain up",
    		      2:                "High gain up",
    		      3:                "Low gain down",
    		      4:                "High gain down", ]) }),
      0xa408:       ({"Contrast",                   "MAP",
    		  ([  0:                "Normal",
    		      1:                "Soft",
    		      2:                "Hard", ]) }),
      0xa409:       ({"Saturation",                 "MAP",
    		  ([  0:                "Normal",
    		      1:                "Low saturation",
    		      2:                "Hight saturation", ]) }),
      0xa40a:       ({"Sharpness",                  "MAP",
    		  ([  0:                "Normal",
    		      1:                "Soft",
    		      2:                "Hard", ]) }),
      0xa40b:       ({"DeviceSettingDescription",   }),
      0xa40c:       ({"SubjectDistanceRange",       "MAP",
    		  ([  0:                "Unknown",
    		      1:                "Macro",
    		      2:                "Close view",
    		      3:                "Distant view", ]) }),
      0xa420:       ({"ImageUniqueID",              "ASCII" }),
    ]);
    
    static mapping TAG_TYPE_INFO =
                 ([1:	({"BYTE",	1}),
    	       2:	({"ASCII",	1}),
    	       3:	({"SHORT",	2}),
    	       4:	({"LONG",	4}),
    	       5:	({"RATIO",	8}),
    	       6:	({"SBYTE",	1}),
    	       7:	({"UNDEF",	1}),
    	       8:	({"SSHORT",	2}),
    	       9:	({"SLONG",	4}),
    	       10:	({"SRATIO",	8}),
    	       11:	({"FLOAT",	4}),
    	       12:	({"DOUBLE",	8}),
    	     ]);
    
    static int short_value(string str, string order)
    {
      if(order=="MM")
        return (str[0]<<8)|str[1];
      else
        return (str[1]<<8)|str[0];
    }
    
    static int long_value(string str, string order)
    {
      if(order=="MM")
        return (str[0]<<24)|(str[1]<<16)|(str[2]<<8)|str[3];
      else
        return (str[3]<<24)|(str[2]<<16)|(str[1]<<8)|str[0];
    }
    
    static void exif_seek(Stdio.File file, int offset, int exif_offset)
    {
      file->seek(offset+exif_offset);
    }
    
    static string format_bytes(string str)
    {
      return str;
    }
    
    static mapping parse_tag(Stdio.File file, mapping tags, mapping exif_info,
    		  int exif_offset, string order)
    {
      int tag_id=short_value(file->read(2), order);
      int tag_type=short_value(file->read(2), order);
      int tag_count=long_value(file->read(4), order);
      string make,model;
    
      if(!TAG_TYPE_INFO[tag_type]) {
        tag_id = Int.swap_word(tag_id);
        tag_type = Int.swap_word(tag_type);
        tag_count = Int.swap_long(tag_count);
      }
    
      string tag_name, tag_format;
      mapping|function tag_map;
      array temp = exif_info[tag_id];
      if(!temp || !sizeof(temp)) {
        tag_name = sprintf("%s0x%04x", (exif_info->prefix||"Tag"), tag_id);
        tag_format = 0;
        tag_map = ([]);
      }
      else {
        tag_name = temp[0];
        tag_format = sizeof(temp)>1?temp[1]:0;
        tag_map = sizeof(temp)>2?temp[2]:([]);
      }
    
      if(tag_format=="MAKE_MODEL")
      {
        make=tags["Make"];
        model=tags["Model"];
    
        if(tag_map[make+"_"+model])
          [tag_format,tag_map]=tag_map[make+"_"+model];
        else if(tag_map[make])
          [tag_format,tag_map]=tag_map[make];
        else
          [tag_format,tag_map]=tag_map[""];
      }
    
      [string tag_type_name, int tag_type_len] =
         TAG_TYPE_INFO[tag_type];
    
      int tag_len=tag_type_len * tag_count;
      
      int pos=file->tell();
      if(tag_len>4)
        exif_seek(file, long_value(file->read(4), order), exif_offset);
    
      if(tag_type==1 || tag_type==6 || tag_type==7)
      {
        if(tag_count==1)
          tags[tag_name]=(string)file->read(1)[0];
        else if(tag_format == "TAGS")
        {
          int num_entries=short_value(file->read(2), order);
          for(int i=0; i<num_entries; i++) {
    	catch {
    	  tags|=parse_tag(file, tags, tag_map, exif_offset, order);
    	};
          }
        }
        else
        {
          string str=file->read(tag_count);
          if(tag_format=="MAP")
    	if(tag_map[str])
    	  tags[tag_name]=tag_map[str];
    	else
    	{
    	  make = tags["Make"];
    	  model = tags["Model"];
    	  tags[tag_name]= tag_map[make+"_"+model] || tag_map[make] || format_bytes(str);
    	}
          else if(tag_format=="CUSTOM") {
    	mixed res = tag_map(str,tags);
    	if(!zero_type(res))
    	  tags[tag_name]=res;
          }
          else
    	tags[tag_name]=format_bytes(str);
        }
      }
    
      if(tag_type==2) // ASCII
        tags[tag_name]=String.trim_whites(file->read(tag_count-1))-"\0";
      
      if(tag_type==3 || tag_type==8) // (S)SHORT
      {
        array a=allocate(tag_count);
        for(int i=0; i<tag_count; i++)
          a[i]=short_value(file->read(2), order);
        
        if(tag_format=="MAP")
          for(int i=0; i<tag_count; i++)
    	if(tag_map[a[i]])
    	  tags[tag_name]=tag_map[a[i]];
    	else
    	{
    	  make = tags["Make"];
    	  model = tags["Model"];
    	  tags[tag_name]= tag_map[make+"_"+model] || tag_map[make] || (string)a[i];
    	}
        else if(tag_format=="CUSTOM")
          tags|=tag_map(a);
        else
          tags[tag_name]=(array(string))a*", ";
      }
      
      if(tag_type==4 || tag_type==9) // (S)LONG
        for(int i=0;i<tag_count; i++)
          tags[tag_name]=(string)long_value(file->read(4), order);
    
      if(tag_type==5 || tag_type==10) // (S)RATIONAL
      {
        for(int i=0;i<tag_count; i++)
        {
          int long1=long_value(file->read(4), order);
          int long2=long_value(file->read(4), order);
          string val;
          switch(tag_format)
          {
      	case "BIAS":
      	  if(long1>0)
      	    val=sprintf("+%3.1f", long1*1.0/long2);
      	  else if(long1 < 0)
      	    val=sprintf("-%3.1f", -long1*1.0/long2);
      	  else
      	    val = "0.0";
      	  break;
      	  
      	case "FLOAT":
      	  if(long2==0)
      	    tags[tag_name]="0";
      	  else
      	    tags[tag_name]=sprintf("%g", long1*1.0/long2);
      	  break;
      	  
      	case "EXPOSURE":
      	  tags[tag_name] = sprintf("%d/%d", long1,long2);
    	  break;
    //  	  break;
    //    	  if(long1>=long2)
    //    	    tags[tag_name]=sprintf("%g", long1*1.0/long2);
    //    	  else
    //    	    if(long1 > 1)
    //    	      tags[tag_name]=sprintf("1/%d",  ((2*long2+long1)/(2*long1)));
    //    	    else
    //    	      tags[tag_name]=sprintf("1/%d", long1);
    //    	  break;
      	  
      	default:
      	  tags[tag_name] = sprintf("%.2f", (float)long1/(float)long2);
      	  break;
          }
        }
      }
    
      file->seek(pos+4);
      return tags;
    }
    
    //! Retrieve the EXIF properties of the given file.
    //! @param file
    //!   The Stdio.File object containing wanted EXIF properties.
    //! @returns
    //!   A mapping with all found EXIF properties.
    mapping get_properties(Stdio.File file)
    {
      int exif_offset=12;
    
      string skip=file->read(12);  // skip the jpeg header
    
      if (skip[sizeof(skip)-6..]!="Exif\0\0")
      {
         skip=file->read(100);
         int z=search(skip,"Exif\0\0");
         if (z==-1) return ([]); // no exif header?
         exif_offset=z+18;
         file->seek(z+18);
      }
    
      string order=file->read(2);  // tiff order magic
      string code=file->read(2);   // tiff magic 42
      mapping tags=([]);
    
      if(sizeof(code)==2 && short_value(code, order)==42)
      {
        int offset=long_value(file->read(4), order);
        while(offset>0)
        {
          exif_seek(file,offset,exif_offset);
          int num_entries=short_value(file->read(2), order);
          for(int i=0; i<num_entries; i++)
    	tags|=parse_tag(file, tags, TAG_INFO, exif_offset, order);
    
          offset=long_value(file->read(4), order);
    
          if(offset == 0)
    	if(tags["ExifOffset"])
    	{
    	  offset=(int)tags["ExifOffset"];
    	  m_delete(tags, "ExifOffset");
    	}
        }
      }
    
      return tags;
    }