| 1 | # EXIF parser in Tcl |
|---|
| 2 | # Author: Darren New <dnew@san.rr.com> |
|---|
| 3 | # Translated directly from the Perl version |
|---|
| 4 | # by Chris Breeze <chris@breezesys.com> |
|---|
| 5 | # http://www.breezesys.com |
|---|
| 6 | # See the original comment block, reproduced |
|---|
| 7 | # at the bottom. |
|---|
| 8 | # Most of the inline comments about the meanings of fields |
|---|
| 9 | # are copied verbatim and without understanding from the |
|---|
| 10 | # original, unless "DNew" is there. |
|---|
| 11 | # Much of the structure is preserved, except in |
|---|
| 12 | # makerNote, where I got tired of typing as verbosely |
|---|
| 13 | # as the original Perl. But thanks for making it so |
|---|
| 14 | # readable that even someone who doesn't know Perl |
|---|
| 15 | # could translate it, Chris! ;-) |
|---|
| 16 | # PLEASE read and understand exif::fieldnames |
|---|
| 17 | # BEFORE making any changes here! Thanks! |
|---|
| 18 | |
|---|
| 19 | # Usage of this version: |
|---|
| 20 | # exif::analyze $stream ?$thumbnail? |
|---|
| 21 | # Stream should be an open file handle |
|---|
| 22 | # rewound to the start. It gets set to |
|---|
| 23 | # binary mode and is left at EOF or |
|---|
| 24 | # possibly pointing at image data. |
|---|
| 25 | # You have to open and close the |
|---|
| 26 | # stream yourself. |
|---|
| 27 | # The return is a serialized array |
|---|
| 28 | # (a la [array get]) with informative |
|---|
| 29 | # english text about what was found. |
|---|
| 30 | # Errors in parsing or I/O or whatever |
|---|
| 31 | # throw errors. |
|---|
| 32 | # exif::allfields |
|---|
| 33 | # returns a list of all possible field names. |
|---|
| 34 | # Added by DNew. Funky implementation. |
|---|
| 35 | # |
|---|
| 36 | # New |
|---|
| 37 | # exif::analyzeFile $filename ?$thumbnail? |
|---|
| 38 | # |
|---|
| 39 | # If you find any mistakes here, feel free to correct them |
|---|
| 40 | # and/or send them to me. I just cribbed this - I don't even |
|---|
| 41 | # have a camera that puts this kind of info into the file. |
|---|
| 42 | |
|---|
| 43 | # LICENSE: Standard BSD License. |
|---|
| 44 | |
|---|
| 45 | # There's probably something here I'm using without knowing it. |
|---|
| 46 | package require Tcl 8.3 |
|---|
| 47 | |
|---|
| 48 | package provide exif 1.1.2 ; # first release |
|---|
| 49 | |
|---|
| 50 | namespace eval ::exif { |
|---|
| 51 | namespace export analyze analyzeFile fieldnames |
|---|
| 52 | variable debug 0 ; # set to 1 for puts of debug trace |
|---|
| 53 | variable cameraModel ; # used internally to understand options |
|---|
| 54 | variable jpeg_markers ; # so we only have to do it once |
|---|
| 55 | variable intel ; # byte order - so we don't have to pass to every read |
|---|
| 56 | variable cached_fieldnames ; # just what it says |
|---|
| 57 | array set jpeg_markers { |
|---|
| 58 | SOF0 \xC0 |
|---|
| 59 | DHT \xC4 |
|---|
| 60 | SOI \xD8 |
|---|
| 61 | EOI \xD9 |
|---|
| 62 | SOS \xDA |
|---|
| 63 | DQT \xDB |
|---|
| 64 | DRI \xDD |
|---|
| 65 | APP1 \xE1 |
|---|
| 66 | } |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | proc ::exif::debug {str} { |
|---|
| 70 | variable debug |
|---|
| 71 | if {$debug} {puts $str} |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | proc ::exif::streq {s1 s2} { |
|---|
| 75 | return [string equal $s1 $s2] |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | proc ::exif::analyzeFile {file {thumbnail {}}} { |
|---|
| 79 | set stream [open $file] |
|---|
| 80 | set res [analyze $stream $thumbnail] |
|---|
| 81 | close $stream |
|---|
| 82 | return $res |
|---|
| 83 | } |
|---|
| 84 | |
|---|
| 85 | proc ::exif::analyze {stream {thumbnail {}}} { |
|---|
| 86 | variable jpeg_markers |
|---|
| 87 | array set result {} |
|---|
| 88 | fconfigure $stream -translation binary -encoding binary |
|---|
| 89 | while {![eof $stream]} { |
|---|
| 90 | set ch [read $stream 1] |
|---|
| 91 | if {1 != [string length $ch]} {error "End of file reached @1"} |
|---|
| 92 | if {![streq "\xFF" $ch]} {break} ; # skip image data |
|---|
| 93 | set marker [read $stream 1] |
|---|
| 94 | if {1 != [string length $marker]} {error "End of file reached @2"} |
|---|
| 95 | if {[streq $marker $jpeg_markers(SOI)]} { |
|---|
| 96 | debug "SOI" |
|---|
| 97 | } elseif {[streq $marker $jpeg_markers(EOI)]} { |
|---|
| 98 | debug "EOI" |
|---|
| 99 | } else { |
|---|
| 100 | set msb [read $stream 1] |
|---|
| 101 | set lsb [read $stream 1] |
|---|
| 102 | if {1 != [string length $msb] || 1 != [string length $lsb]} { |
|---|
| 103 | error "File truncated @1" |
|---|
| 104 | } |
|---|
| 105 | scan $msb %c msb ; scan $lsb %c lsb |
|---|
| 106 | set size [expr {256 * $msb + $lsb}] |
|---|
| 107 | set data [read $stream [expr {$size-2}]] |
|---|
| 108 | debug "read [expr {$size - 2}] bytes of data" |
|---|
| 109 | if {[expr {$size-2}] != [string length $data]} { |
|---|
| 110 | error "File truncated @2" |
|---|
| 111 | } |
|---|
| 112 | if {[streq $marker $jpeg_markers(APP1)]} { |
|---|
| 113 | debug "APP1\t$size" |
|---|
| 114 | array set result [app1 $data $thumbnail] |
|---|
| 115 | } elseif {[streq $marker $jpeg_markers(DQT)]} { |
|---|
| 116 | debug "DQT\t$size" |
|---|
| 117 | } elseif {[streq $marker $jpeg_markers(SOF0)]} { |
|---|
| 118 | debug "SOF0\t$size" |
|---|
| 119 | } elseif {[streq $marker $jpeg_markers(DHT)]} { |
|---|
| 120 | debug "DHT\t$size" |
|---|
| 121 | } elseif {[streq $marker $jpeg_markers(SOS)]} { |
|---|
| 122 | debug "SOS\t$size" |
|---|
| 123 | } else { |
|---|
| 124 | binary scan $marker H* x |
|---|
| 125 | debug "UNKNOWN MARKER $x" |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | } |
|---|
| 129 | return [array get result] |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | proc ::exif::app1 {data thumbnail} { |
|---|
| 133 | variable intel |
|---|
| 134 | variable cameraModel |
|---|
| 135 | array set result {} |
|---|
| 136 | if {![string equal [string range $data 0 5] "Exif\0\0"]} { |
|---|
| 137 | error "APP1 does not contain EXIF" |
|---|
| 138 | } |
|---|
| 139 | debug "Reading EXIF data" |
|---|
| 140 | set data [string range $data 6 end] |
|---|
| 141 | set t [string range $data 0 1] |
|---|
| 142 | if {[streq $t "II"]} { |
|---|
| 143 | set intel 1 |
|---|
| 144 | debug "Intel byte alignment" |
|---|
| 145 | } elseif {[streq $t "MM"]} { |
|---|
| 146 | set intel 0 |
|---|
| 147 | debug "Motorola byte alignment" |
|---|
| 148 | } else { |
|---|
| 149 | error "Invalid byte alignment: $t" |
|---|
| 150 | } |
|---|
| 151 | if {[readShort $data 2]!=0x002A} {error "Invalid tag mark"} |
|---|
| 152 | set curoffset [readLong $data 4] ; # just called "offset" in the Perl - DNew |
|---|
| 153 | debug "Offset to first IFD: $curoffset" |
|---|
| 154 | set numEntries [readShort $data $curoffset] |
|---|
| 155 | incr curoffset 2 |
|---|
| 156 | debug "Number of directory entries: $numEntries" |
|---|
| 157 | for {set i 0} {$i < $numEntries} {incr i} { |
|---|
| 158 | set head [expr {$curoffset + 12 * $i}] |
|---|
| 159 | set entry [string range $data $head [expr {$head+11}]] |
|---|
| 160 | set tag [readShort $entry 0] |
|---|
| 161 | set format [readShort $entry 2] |
|---|
| 162 | set components [readLong $entry 4] |
|---|
| 163 | set offset [readLong $entry 8] |
|---|
| 164 | set value [readIFDEntry $data $format $components $offset] |
|---|
| 165 | if {$tag==0x010e} { |
|---|
| 166 | set result(ImageDescription) $value |
|---|
| 167 | } elseif {$tag==0x010f} { |
|---|
| 168 | set result(CameraMake) $value |
|---|
| 169 | } elseif {$tag==0x0110} { |
|---|
| 170 | set result(CameraModel) $value |
|---|
| 171 | set cameraModel $value |
|---|
| 172 | } elseif {$tag==0x0112} { |
|---|
| 173 | set result(Orientation) $value |
|---|
| 174 | } elseif {$tag == 0x011A} { |
|---|
| 175 | set result(XResolution) $value |
|---|
| 176 | } elseif {$tag == 0x011B} { |
|---|
| 177 | set result(YResolution) $value |
|---|
| 178 | } elseif {$tag == 0x0128} { |
|---|
| 179 | set result(ResolutionUnit) "unknown" |
|---|
| 180 | if {$value==2} {set result(ResolutionUnit) "inch"} |
|---|
| 181 | if {$value==3} {set result(ResolutionUnit) "centimeter"} |
|---|
| 182 | } elseif {$tag==0x0131} { |
|---|
| 183 | set result(Software) $value |
|---|
| 184 | } elseif {$tag==0x0132} { |
|---|
| 185 | set result(DateTime) $value |
|---|
| 186 | } elseif {$tag==0x0213} { |
|---|
| 187 | set result(YCbCrPositioning) "unknown" |
|---|
| 188 | if {$value==1} {set result(YCbCrPositioning) "Center of pixel array"} |
|---|
| 189 | if {$value==2} {set result(YCbCrPositioning) "Datum point"} |
|---|
| 190 | } elseif {$tag==0x8769} { |
|---|
| 191 | # EXIF sub IFD |
|---|
| 192 | debug "==CALLING exifSubIFD==" |
|---|
| 193 | array set result [exifSubIFD $data $offset] |
|---|
| 194 | } else { |
|---|
| 195 | debug "Unrecognized entry: Tag=$tag, value=$value" |
|---|
| 196 | } |
|---|
| 197 | } |
|---|
| 198 | set offset [readLong $data [expr {$curoffset + 12 * $numEntries}]] |
|---|
| 199 | debug "Offset to next IFD: $offset" |
|---|
| 200 | array set thumb_result [exifSubIFD $data $offset] |
|---|
| 201 | |
|---|
| 202 | if {$thumbnail != {}} { |
|---|
| 203 | set jpg [string range $data \ |
|---|
| 204 | $thumb_result(JpegIFOffset) \ |
|---|
| 205 | [expr {$thumb_result(JpegIFOffset) + $thumb_result(JpegIFByteCount) - 1}]] |
|---|
| 206 | |
|---|
| 207 | set to [open $thumbnail w] |
|---|
| 208 | fconfigure $to -translation binary -encoding binary |
|---|
| 209 | puts $to $jpg |
|---|
| 210 | close $to |
|---|
| 211 | |
|---|
| 212 | #can be used (with a JPG-aware TK) to add the image to the result array |
|---|
| 213 | #set result(THUMB) [image create photo -file $thumbnail] |
|---|
| 214 | } |
|---|
| 215 | |
|---|
| 216 | return [array get result] |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | # Extract EXIF sub IFD info |
|---|
| 220 | proc ::exif::exifSubIFD {data curoffset} { |
|---|
| 221 | debug "EXIF: offset=$curoffset" |
|---|
| 222 | set numEntries [readShort $data $curoffset] |
|---|
| 223 | incr curoffset 2 |
|---|
| 224 | debug "Number of directory entries: $numEntries" |
|---|
| 225 | for {set i 0} {$i < $numEntries} {incr i} { |
|---|
| 226 | set head [expr {$curoffset + 12 * $i}] |
|---|
| 227 | set entry [string range $data $head [expr {$head+11}]] |
|---|
| 228 | set tag [readShort $entry 0] |
|---|
| 229 | set format [readShort $entry 2] |
|---|
| 230 | set components [readLong $entry 4] |
|---|
| 231 | set offset [readLong $entry 8] |
|---|
| 232 | if {$tag==0x9000} { |
|---|
| 233 | set result(ExifVersion) [string range $entry 8 11] |
|---|
| 234 | } elseif {$tag==0x9101} { |
|---|
| 235 | set result(ComponentsConfigured) [format 0x%08x $offset] |
|---|
| 236 | } elseif {$tag == 0x927C} { |
|---|
| 237 | array set result [makerNote $data $offset] |
|---|
| 238 | } elseif {$tag == 0x9286} { |
|---|
| 239 | # Apparently, this doesn't usually work. |
|---|
| 240 | set result(UserComment) "$offset - [string range $data $offset [expr {$offset+8}]]" |
|---|
| 241 | set result(UserComment) [string trim $result(UserComment) "\0"] |
|---|
| 242 | } elseif {$tag==0xA000} { |
|---|
| 243 | set result(FlashPixVersion) [string range $entry 8 11] |
|---|
| 244 | } elseif {$tag==0xA300} { |
|---|
| 245 | # 3 means digital camera |
|---|
| 246 | if {$offset == 3} { |
|---|
| 247 | set result(FileSource) "3 - Digital camera" |
|---|
| 248 | } else { |
|---|
| 249 | set result(FileSource) $offset |
|---|
| 250 | } |
|---|
| 251 | } else { |
|---|
| 252 | set value [readIFDEntry $data $format $components $offset] |
|---|
| 253 | if {$tag==0x829A} { |
|---|
| 254 | if {0.3 <= $value} { |
|---|
| 255 | # In seconds... |
|---|
| 256 | set result(ExposureTime) "$value seconds" |
|---|
| 257 | } else { |
|---|
| 258 | set result(ExposureTime) "1/[expr {1.0/$value}] seconds" |
|---|
| 259 | } |
|---|
| 260 | } elseif {$tag == 0x829D} { |
|---|
| 261 | set result(FNumber) $value |
|---|
| 262 | } elseif {$tag == 0x8827} { |
|---|
| 263 | # D30 stores ISO here, G1 uses MakerNote Tag 1 field 16 |
|---|
| 264 | set result(ISOSpeedRatings) $value |
|---|
| 265 | } elseif {$tag == 0x9003} { |
|---|
| 266 | set result(DateTimeOriginal) $value |
|---|
| 267 | } elseif {$tag == 0x9004} { |
|---|
| 268 | set result(DateTimeDigitized) $value |
|---|
| 269 | } elseif {$tag == 0x9102} { |
|---|
| 270 | if {$value == 5} { |
|---|
| 271 | set result(ImageQuality) "super fine" |
|---|
| 272 | } elseif {$value == 3} { |
|---|
| 273 | set result(ImageQuality) "fine" |
|---|
| 274 | } elseif {$value == 2} { |
|---|
| 275 | set result(ImageQuality) "normal" |
|---|
| 276 | } else { |
|---|
| 277 | set result(CompressedBitsPerPixel) $value |
|---|
| 278 | } |
|---|
| 279 | } elseif {$tag == 0x9201} { |
|---|
| 280 | # Not very accurate, use Exposure time instead. |
|---|
| 281 | # (That's Chris' comment. I don't know what it means.) |
|---|
| 282 | set value [expr {pow(2,$value)}] |
|---|
| 283 | if {$value < 4} { |
|---|
| 284 | set value [expr {1.0 / $value}] |
|---|
| 285 | set value [expr {int($value * 10 + 0.5) / 10.0}] |
|---|
| 286 | } else { |
|---|
| 287 | set value [expr {int($value + 0.49)}] |
|---|
| 288 | } |
|---|
| 289 | set result(ShutterSpeedValue) "$value Hz" |
|---|
| 290 | } elseif {$tag == 0x9202} { |
|---|
| 291 | set value [expr {int(pow(sqrt(2.0), $value) * 10 + 0.5) / 10.0}] |
|---|
| 292 | set result(AperatureValue) $value |
|---|
| 293 | } elseif {$tag == 0x9204} { |
|---|
| 294 | set value [compensationFraction $value] |
|---|
| 295 | set result(ExposureBiasValue) $value |
|---|
| 296 | } elseif {$tag == 0x9205} { |
|---|
| 297 | set value [expr {int(pow(sqrt(2.0), $value) * 10 + 0.5) / 10.0}] |
|---|
| 298 | } elseif {$tag == 0x9206} { |
|---|
| 299 | # May need calibration |
|---|
| 300 | set result(SubjectDistance) "$value m" |
|---|
| 301 | } elseif {$tag == 0x9207} { |
|---|
| 302 | set result(MeteringMode) "other" |
|---|
| 303 | if {$value == 0} {set result(MeteringMode) "unknown"} |
|---|
| 304 | if {$value == 1} {set result(MeteringMode) "average"} |
|---|
| 305 | if {$value == 2} {set result(MeteringMode) "center weighted average"} |
|---|
| 306 | if {$value == 3} {set result(MeteringMode) "spot"} |
|---|
| 307 | if {$value == 4} {set result(MeteringMode) "multi-spot"} |
|---|
| 308 | if {$value == 5} {set result(MeteringMode) "multi-segment"} |
|---|
| 309 | if {$value == 6} {set result(MeteringMode) "partial"} |
|---|
| 310 | } elseif {$tag == 0x9209} { |
|---|
| 311 | if {$value == 0} { |
|---|
| 312 | set result(Flash) no |
|---|
| 313 | } elseif {$value == 1} { |
|---|
| 314 | set result(Flash) yes |
|---|
| 315 | } else { |
|---|
| 316 | set result(Flash) "unknown: $value" |
|---|
| 317 | } |
|---|
| 318 | } elseif {$tag == 0x920a} { |
|---|
| 319 | set result(FocalLength) "$value mm" |
|---|
| 320 | } elseif {$tag == 0xA001} { |
|---|
| 321 | set result(ColorSpace) $value |
|---|
| 322 | } elseif {$tag == 0xA002} { |
|---|
| 323 | set result(ExifImageWidth) $value |
|---|
| 324 | } elseif {$tag == 0xA003} { |
|---|
| 325 | set result(ExifImageHeight) $value |
|---|
| 326 | } elseif {$tag == 0xA005} { |
|---|
| 327 | set result(ExifInteroperabilityOffset) $value |
|---|
| 328 | } elseif {$tag == 0xA20E} { |
|---|
| 329 | set result(FocalPlaneXResolution) $value |
|---|
| 330 | } elseif {$tag == 0xA20F} { |
|---|
| 331 | set result(FocalPlaneYResolution) $value |
|---|
| 332 | } elseif {$tag == 0xA210} { |
|---|
| 333 | set result(FocalPlaneResolutionUnit) "none" |
|---|
| 334 | if {$value == 2} {set result(FocalPlaneResolutionUnit) "inch"} |
|---|
| 335 | if {$value == 3} {set result(FocalPlaneResolutionUnit) "centimeter"} |
|---|
| 336 | } elseif {$tag == 0xA217} { |
|---|
| 337 | # 2 = 1 chip color area sensor |
|---|
| 338 | set result(SensingMethod) $value |
|---|
| 339 | } elseif {$tag == 0xA401} { |
|---|
| 340 | #TJE |
|---|
| 341 | set result(SensingMethod) "normal" |
|---|
| 342 | if {$value == 1} {set result(SensingMethod) "custom"} |
|---|
| 343 | } elseif {$tag == 0xA402} { |
|---|
| 344 | #TJE |
|---|
| 345 | set result(ExposureMode) "auto" |
|---|
| 346 | if {$value == 1} {set result(ExposureMode) "manual"} |
|---|
| 347 | if {$value == 2} {set result(ExposureMode) "auto bracket"} |
|---|
| 348 | } elseif {$tag == 0xA403} { |
|---|
| 349 | #TJE |
|---|
| 350 | set result(WhiteBalance) "auto" |
|---|
| 351 | if {$value == 1} {set result(WhiteBalance) "manual"} |
|---|
| 352 | } elseif {$tag == 0xA404} { |
|---|
| 353 | # digital zoom not used if number is zero |
|---|
| 354 | set result(DigitalZoomRatio) "not used" |
|---|
| 355 | if {$value != 0} {set result(DigitalZoomRatio) $value} |
|---|
| 356 | } elseif {$tag == 0xA405} { |
|---|
| 357 | set result(FocalLengthIn35mmFilm) "unknown" |
|---|
| 358 | if {$value != 0} {set result(FocalLengthIn35mmFilm) $value} |
|---|
| 359 | } elseif {$tag == 0xA406} { |
|---|
| 360 | set result(SceneCaptureType) "Standard" |
|---|
| 361 | if {$value == 1} {set result(SceneCaptureType) "Landscape"} |
|---|
| 362 | if {$value == 2} {set result(SceneCaptureType) "Portrait"} |
|---|
| 363 | if {$value == 3} {set result(SceneCaptureType) "Night scene"} |
|---|
| 364 | } elseif {$tag == 0xA407} { |
|---|
| 365 | set result(GainControl) "none" |
|---|
| 366 | if {$value == 1} {set result(GainControl) "Low gain up"} |
|---|
| 367 | if {$value == 2} {set result(GainControl) "High gain up"} |
|---|
| 368 | if {$value == 3} {set result(GainControl) "Low gain down"} |
|---|
| 369 | if {$value == 4} {set result(GainControl) "High gain down"} |
|---|
| 370 | } elseif {$tag == 0x0103} { |
|---|
| 371 | #TJE |
|---|
| 372 | set result(Compression) "unknown" |
|---|
| 373 | if {$value == 1} {set result(Compression) "none"} |
|---|
| 374 | if {$value == 6} {set result(Compression) "JPEG"} |
|---|
| 375 | } elseif {$tag == 0x011A} { |
|---|
| 376 | #TJE |
|---|
| 377 | set result(XResolution) $value |
|---|
| 378 | } elseif {$tag == 0x011B} { |
|---|
| 379 | #TJE |
|---|
| 380 | set result(YResolution) $value |
|---|
| 381 | } elseif {$tag == 0x0128} { |
|---|
| 382 | #TJE |
|---|
| 383 | set result(ResolutionUnit) "unknown" |
|---|
| 384 | if {$value == 1} {set result(ResolutionUnit) "inch"} |
|---|
| 385 | if {$value == 6} {set result(ResolutionUnit) "cm"} |
|---|
| 386 | } elseif {$tag == 0x0201} { |
|---|
| 387 | #TJE |
|---|
| 388 | set result(JpegIFOffset) $value |
|---|
| 389 | debug "offset = $value" |
|---|
| 390 | } elseif {$tag == 0x0202} { |
|---|
| 391 | #TJE |
|---|
| 392 | set result(JpegIFByteCount) $value |
|---|
| 393 | debug "bytecount = $value" |
|---|
| 394 | } else { |
|---|
| 395 | error "Unrecognized EXIF Tag: $tag (0x[string toupper [format %x $tag]])" |
|---|
| 396 | } |
|---|
| 397 | } |
|---|
| 398 | } |
|---|
| 399 | return [array get result] |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | # Canon proprietary data that I didn't feel like translating to Tcl yet. |
|---|
| 403 | proc ::exif::makerNote {data curoffset} { |
|---|
| 404 | variable cameraModel |
|---|
| 405 | debug "MakerNote: offset=$curoffset" |
|---|
| 406 | |
|---|
| 407 | array set result {} |
|---|
| 408 | set numEntries [readShort $data $curoffset] |
|---|
| 409 | incr curoffset 2 |
|---|
| 410 | debug "Number of directory entries: $numEntries" |
|---|
| 411 | for {set i 0} {$i < $numEntries} {incr i} { |
|---|
| 412 | set head [expr {$curoffset + 12 * $i}] |
|---|
| 413 | set entry [string range $data $head [expr {$head+11}]] |
|---|
| 414 | set tag [readShort $entry 0] |
|---|
| 415 | set format [readShort $entry 2] |
|---|
| 416 | set components [readLong $entry 4] |
|---|
| 417 | set offset [readLong $entry 8] |
|---|
| 418 | debug "$i)\tTag: $tag, format: $format, components: $components" |
|---|
| 419 | |
|---|
| 420 | if {$tag==6} { |
|---|
| 421 | set value [readIFDEntry $data $format $components $offset] |
|---|
| 422 | set result(ImageFormat) $value |
|---|
| 423 | } elseif {$tag==7} { |
|---|
| 424 | set value [readIFDEntry $data $format $components $offset] |
|---|
| 425 | set result(FirmwareVersion) $value |
|---|
| 426 | } elseif {$tag==8} { |
|---|
| 427 | set value [string range $offset 0 2]-[string range $offset 3 end] |
|---|
| 428 | set result(ImageNumber) $value |
|---|
| 429 | } elseif {$tag==9} { |
|---|
| 430 | set value [readIFDEntry $data $format $components $offset] |
|---|
| 431 | set result(Owner) $value |
|---|
| 432 | } elseif {$tag==0x0C} { |
|---|
| 433 | # camera serial number |
|---|
| 434 | set msw [expr {($offset >> 16) & 0xFFFF}] |
|---|
| 435 | set lsw [expr {$offset & 0xFFFF}] |
|---|
| 436 | set result(CameraSerialNumber) [format %04X%05d $msw $lsw] |
|---|
| 437 | } elseif {$tag==0x10} { |
|---|
| 438 | set result(UnknownTag-0x10) $offset |
|---|
| 439 | } else { |
|---|
| 440 | if {$format == 3 && 1 < $components} { |
|---|
| 441 | debug "MakerNote $i: TAG=$tag" |
|---|
| 442 | catch {unset field} |
|---|
| 443 | array set field {} |
|---|
| 444 | for {set j 0} {$j < $components} {incr j} { |
|---|
| 445 | set field($j) [readShort $data [expr {$offset+2*$j}]] |
|---|
| 446 | debug "$j : $field($j)" |
|---|
| 447 | } |
|---|
| 448 | if {$tag == 1} { |
|---|
| 449 | if {![string match -nocase "*Pro90*" $cameraModel]} { |
|---|
| 450 | if {$field(1)==1} { |
|---|
| 451 | set result(MacroMode) macro |
|---|
| 452 | } else { |
|---|
| 453 | set result(MacroMode) normal |
|---|
| 454 | } |
|---|
| 455 | } |
|---|
| 456 | if {0 < $field(2)} { |
|---|
| 457 | set result(SelfTimer) "[expr {$field(2)/10.0}] seconds" |
|---|
| 458 | } |
|---|
| 459 | set result(ImageQuality) [switch $field(3) { |
|---|
| 460 | 2 {format Normal} |
|---|
| 461 | 3 {format Fine} |
|---|
| 462 | 4 {format "CCD Raw"} |
|---|
| 463 | 5 {format "Super fine"} |
|---|
| 464 | default {format ""} |
|---|
| 465 | }] |
|---|
| 466 | set result(FlashMode) [switch $field(4) { |
|---|
| 467 | 0 {format off} |
|---|
| 468 | 1 {format auto} |
|---|
| 469 | 2 {format on} |
|---|
| 470 | 3 {format "red eye reduction"} |
|---|
| 471 | 4 {format "slow synchro"} |
|---|
| 472 | 5 {format "auto + red eye reduction"} |
|---|
| 473 | 6 {format "on + red eye reduction"} |
|---|
| 474 | default {format ""} |
|---|
| 475 | }] |
|---|
| 476 | if {$field(5)} { |
|---|
| 477 | set result(ShootingMode) "Continuous" |
|---|
| 478 | } else { |
|---|
| 479 | set result(ShootingMode) "Single frame" |
|---|
| 480 | } |
|---|
| 481 | # Field 6 - don't know what it is. |
|---|
| 482 | set result(AutoFocusMode) [switch $field(7) { |
|---|
| 483 | 0 {format "One-shot"} |
|---|
| 484 | 1 {format "AI servo"} |
|---|
| 485 | 2 {format "AI focus"} |
|---|
| 486 | 3 - 6 {format "MF"} |
|---|
| 487 | 5 {format "Continuous"} |
|---|
| 488 | 4 { |
|---|
| 489 | # G1: uses field 32 to store single/continuous, |
|---|
| 490 | # and always sets 7 to 4. |
|---|
| 491 | if {[info exists field(32)] && $field(32)} { |
|---|
| 492 | format "Continuous" |
|---|
| 493 | } else { |
|---|
| 494 | format "Single" |
|---|
| 495 | } |
|---|
| 496 | } |
|---|
| 497 | default {format unknown} |
|---|
| 498 | }] |
|---|
| 499 | # Field 8 and 9 are unknown |
|---|
| 500 | set result(ImageSize) [switch $field(10) { |
|---|
| 501 | 0 {format "large"} |
|---|
| 502 | 1 {format "medium"} |
|---|
| 503 | 2 {format "small"} |
|---|
| 504 | default {format "unknown"} |
|---|
| 505 | }] |
|---|
| 506 | # Field 11 - easy shooting - see field 20 |
|---|
| 507 | # Field 12 - unknown |
|---|
| 508 | set NHL { |
|---|
| 509 | 0 {format "Normal"} |
|---|
| 510 | 1 {format "High"} |
|---|
| 511 | 65536 {format "Low"} |
|---|
| 512 | default {format "Unknown"} |
|---|
| 513 | } |
|---|
| 514 | set result(Contrast) [switch $field(13) $NHL] |
|---|
| 515 | set result(Saturation) [switch $field(14) $NHL] |
|---|
| 516 | set result(Sharpness) [switch $field(15) $NHL] |
|---|
| 517 | set result(ISO) [switch $field(16) { |
|---|
| 518 | 15 {format Auto} |
|---|
| 519 | 16 {format 50} |
|---|
| 520 | 17 {format 100} |
|---|
| 521 | 18 {format 200} |
|---|
| 522 | 19 {format 400} |
|---|
| 523 | default {format "unknown"} |
|---|
| 524 | }] |
|---|
| 525 | set result(MeteringMode) [switch $field(17) { |
|---|
| 526 | 3 {format evaluative} |
|---|
| 527 | 4 {format partial} |
|---|
| 528 | 5 {format center-weighted} |
|---|
| 529 | default {format unknown} |
|---|
| 530 | }] |
|---|
| 531 | # Field 18 - unknown |
|---|
| 532 | if {[info exists field(19)]} { |
|---|
| 533 | set result(AFPoint) [switch -- [expr {$field(19)-0x3000}] { |
|---|
| 534 | 0 {format none} |
|---|
| 535 | 1 {format auto-selected} |
|---|
| 536 | 2 {format right} |
|---|
| 537 | 3 {format center} |
|---|
| 538 | 4 {format left} |
|---|
| 539 | default {format unknown} |
|---|
| 540 | }] ; # {} |
|---|
| 541 | } |
|---|
| 542 | if {[info exists field(20)]} { |
|---|
| 543 | if {$field(20) == 0} { |
|---|
| 544 | set result(ExposureMode) [switch $field(11) { |
|---|
| 545 | 0 {format auto} |
|---|
| 546 | 1 {format manual} |
|---|
| 547 | 2 {format landscape} |
|---|
| 548 | 3 {format "fast shutter"} |
|---|
| 549 | 4 {format "slow shutter"} |
|---|
| 550 | 5 {format "night scene"} |
|---|
| 551 | 6 {format "black and white"} |
|---|
| 552 | 7 {format sepia} |
|---|
| 553 | 8 {format portrait} |
|---|
| 554 | 9 {format sports} |
|---|
| 555 | 10 {format close-up} |
|---|
| 556 | 11 {format "pan focus"} |
|---|
| 557 | default {format unknown} |
|---|
| 558 | }] ; # {} |
|---|
| 559 | } elseif {$field(20) == 1} { |
|---|
| 560 | set result(ExposureMode) program |
|---|
| 561 | } elseif {$field(20) == 2} { |
|---|
| 562 | set result(ExposureMode) Tv |
|---|
| 563 | } elseif {$field(20) == 3} { |
|---|
| 564 | set result(ExposureMode) Av |
|---|
| 565 | } elseif {$field(20) == 4} { |
|---|
| 566 | set result(ExposureMode) manual |
|---|
| 567 | } elseif {$field(20) == 5} { |
|---|
| 568 | set result(ExposureMode) A-DEP |
|---|
| 569 | } else { |
|---|
| 570 | set result(ExposureMode) unknown |
|---|
| 571 | } |
|---|
| 572 | } |
|---|
| 573 | # Field 21 and 22 are unknown |
|---|
| 574 | # Field 23: max focal len, 24 min focal len, 25 units per mm |
|---|
| 575 | if {[info exists field(23)] && [info exists field(25)]} { |
|---|
| 576 | set result(MaxFocalLength) \ |
|---|
| 577 | "[expr {1.0 * $field(23) / $field(25)}] mm" |
|---|
| 578 | } |
|---|
| 579 | if {[info exists field(24)] && [info exists field(25)]} { |
|---|
| 580 | set result(MinFocalLength) \ |
|---|
| 581 | "[expr {1.0 * $field(24) / $field(25)}] mm" |
|---|
| 582 | } |
|---|
| 583 | # Field 26-28 are unknown. |
|---|
| 584 | if {[info exists field(29)]} { |
|---|
| 585 | if {$field(29) & 0x0010} { |
|---|
| 586 | lappend result(FlashMode) "FP_sync_enabled" |
|---|
| 587 | } |
|---|
| 588 | if {$field(29) & 0x0800} { |
|---|
| 589 | lappend result(FlashMode) "FP_sync_used" |
|---|
| 590 | } |
|---|
| 591 | if {$field(29) & 0x2000} { |
|---|
| 592 | lappend result(FlashMode) "internal_flash" |
|---|
| 593 | } |
|---|
| 594 | if {$field(29) & 0x4000} { |
|---|
| 595 | lappend result(FlashMode) "external_E-TTL" |
|---|
| 596 | } |
|---|
| 597 | } |
|---|
| 598 | if {[info exists field(34)] && \ |
|---|
| 599 | [string match -nocase "*pro90*" $cameraModel]} { |
|---|
| 600 | if {$field(34)} { |
|---|
| 601 | set result(ImageStabilisation) on |
|---|
| 602 | } else { |
|---|
| 603 | set result(ImageStabilisation) off |
|---|
| 604 | } |
|---|
| 605 | } |
|---|
| 606 | } elseif {$tag == 4} { |
|---|
| 607 | set result(WhiteBalance) [switch $field(7) { |
|---|
| 608 | 0 {format Auto} |
|---|
| 609 | 1 {format Daylight} |
|---|
| 610 | 2 {format Cloudy} |
|---|
| 611 | 3 {format Tungsten} |
|---|
| 612 | 4 {format Fluorescent} |
|---|
| 613 | 5 {format Flash} |
|---|
| 614 | 6 {format Custom} |
|---|
| 615 | default {format Unknown} |
|---|
| 616 | }] |
|---|
| 617 | if {$field(14) & 0x07} { |
|---|
| 618 | set result(AFPointsUsed) \ |
|---|
| 619 | [expr {($field(14)>>12) & 0x0F}] |
|---|
| 620 | if {$field(14)&0x04} { |
|---|
| 621 | append result(AFPointsUsed) " left" |
|---|
| 622 | } |
|---|
| 623 | if {$field(14)&0x02} { |
|---|
| 624 | append result(AFPointsUsed) " center" |
|---|
| 625 | } |
|---|
| 626 | if {$field(14)&0x01} { |
|---|
| 627 | append result(AFPointsUsed) " right" |
|---|
| 628 | } |
|---|
| 629 | } |
|---|
| 630 | if {[info exists field(15)]} { |
|---|
| 631 | set v $field(15) |
|---|
| 632 | if {32768 < $v} {incr v -65536} |
|---|
| 633 | set v [compensationFraction [expr {$v / 32.0}]] |
|---|
| 634 | set result(FlashExposureCompensation) $v |
|---|
| 635 | } |
|---|
| 636 | if {[info exists field(19)]} { |
|---|
| 637 | set result(SubjectDistance) "$field(19) m" |
|---|
| 638 | } |
|---|
| 639 | } elseif {$tag == 15} { |
|---|
| 640 | foreach k [array names field] { |
|---|
| 641 | set func [expr {($field($k) >> 8) & 0xFF}] |
|---|
| 642 | set v [expr {$field($k) & 0xFF}] |
|---|
| 643 | if {$func==1 && $v} { |
|---|
| 644 | set result(LongExposureNoiseReduction) on |
|---|
| 645 | } elseif {$func==1 && !$v} { |
|---|
| 646 | set result(LongExposureNoiseReduction) off |
|---|
| 647 | } elseif {$func==2} { |
|---|
| 648 | set result(Shutter/AE-Lock) [switch $v { |
|---|
| 649 | 0 {format "AF/AE lock"} |
|---|
| 650 | 1 {format "AE lock/AF"} |
|---|
| 651 | 2 {format "AF/AF lock"} |
|---|
| 652 | 3 {format "AE+release/AE+AF"} |
|---|
| 653 | default {format "Unknown"} |
|---|
| 654 | }] |
|---|
| 655 | } elseif {$func==3} { |
|---|
| 656 | if {$v} { |
|---|
| 657 | set result(MirrorLockup) enable |
|---|
| 658 | } else { |
|---|
| 659 | set result(MirrorLockup) disable |
|---|
| 660 | } |
|---|
| 661 | } elseif {$func==4} { |
|---|
| 662 | if {$v} { |
|---|
| 663 | set result(Tv/AvExposureLevel) "1/3 stop" |
|---|
| 664 | } else { |
|---|
| 665 | set result(Tv/AvExposureLevel) "1/2 stop" |
|---|
| 666 | } |
|---|
| 667 | } elseif {$func==5} { |
|---|
| 668 | if {$v} { |
|---|
| 669 | set result(AFAssistLight) off |
|---|
| 670 | } else { |
|---|
| 671 | set result(AFAssistLight) on |
|---|
| 672 | } |
|---|
| 673 | } elseif {$func==6} { |
|---|
| 674 | if {$v} { |
|---|
| 675 | set result(ShutterSpeedInAVMode) "Fixed 1/200" |
|---|
| 676 | } else { |
|---|
| 677 | set result(ShutterSpeedInAVMode) "Auto" |
|---|
| 678 | } |
|---|
| 679 | } elseif {$func==7} { |
|---|
| 680 | set result(AEBSeq/AutoCancel) [switch $v { |
|---|
| 681 | 0 {format "0, -, + enabled"} |
|---|
| 682 | 1 {format "0, -, + disabled"} |
|---|
| 683 | 2 {format "-, 0, + enabled"} |
|---|
| 684 | 3 {format "-, 0, + disabled"} |
|---|
| 685 | default {format unknown} |
|---|
| 686 | }] |
|---|
| 687 | } elseif {$func==8} { |
|---|
| 688 | if {$v} { |
|---|
| 689 | set result(ShutterCurtainSync) "2nd curtain sync" |
|---|
| 690 | } else { |
|---|
| 691 | set result(ShutterCurtainSync) "1st curtain sync" |
|---|
| 692 | } |
|---|
| 693 | } elseif {$func==9} { |
|---|
| 694 | set result(LensAFStopButtonFnSwitch) [switch $v { |
|---|
| 695 | 0 {format "AF stop"} |
|---|
| 696 | 1 {format "operate AF"} |
|---|
| 697 | 2 {format "lock AE and start timer"} |
|---|
| 698 | default {format unknown} |
|---|
| 699 | }] |
|---|
| 700 | } elseif {$func==10} { |
|---|
| 701 | if {$v} { |
|---|
| 702 | set result(AutoReductionOfFillFlash) disable |
|---|
| 703 | } else { |
|---|
| 704 | set result(AutoReductionOfFillFlash) enable |
|---|
| 705 | } |
|---|
| 706 | } elseif {$func==11} { |
|---|
| 707 | if {$v} { |
|---|
| 708 | set result(MenuButtonReturnPosition) previous |
|---|
| 709 | } else { |
|---|
| 710 | set result(MenuButtonReturnPosition) top |
|---|
| 711 | } |
|---|
| 712 | } elseif {$func==12} { |
|---|
| 713 | set result(SetButtonFuncWhenShooting) [switch $v { |
|---|
| 714 | 0 {format "not assigned"} |
|---|
| 715 | 1 {format "change quality"} |
|---|
| 716 | 2 {format "change ISO speed"} |
|---|
| 717 | 3 {format "select parameters"} |
|---|
| 718 | default {format unknown} |
|---|
| 719 | }] |
|---|
| 720 | } elseif {$func==13} { |
|---|
| 721 | if {$v} { |
|---|
| 722 | set result(SensorCleaning) enable |
|---|
| 723 | } else { |
|---|
| 724 | set result(SensorCleaning) disable |
|---|
| 725 | } |
|---|
| 726 | } elseif {$func==0} { |
|---|
| 727 | # Discovered by DNew? |
|---|
| 728 | set result(CameraOwner) $v |
|---|
| 729 | } else { |
|---|
| 730 | append result(UnknownCustomFunc) "$func=$v " |
|---|
| 731 | } |
|---|
| 732 | } |
|---|
| 733 | } |
|---|
| 734 | } else { |
|---|
| 735 | debug [format "makerNote: Unrecognized TAG: 0x%x" $tag] |
|---|
| 736 | } |
|---|
| 737 | } |
|---|
| 738 | } |
|---|
| 739 | return [array get result] |
|---|
| 740 | } |
|---|
| 741 | |
|---|
| 742 | proc ::exif::readShort {data offset} { |
|---|
| 743 | variable intel |
|---|
| 744 | if {[string length $data] < [expr {$offset+2}]} { |
|---|
| 745 | error "readShort: end of string reached" |
|---|
| 746 | } |
|---|
| 747 | set ch1 [string index $data $offset] |
|---|
| 748 | set ch2 [string index $data [expr {$offset+1}]] |
|---|
| 749 | scan $ch1 %c ch1 ; scan $ch2 %c ch2 |
|---|
| 750 | if {$intel} { |
|---|
| 751 | return [expr {$ch1 + 256 * $ch2}] |
|---|
| 752 | } else { |
|---|
| 753 | return [expr {$ch2 + 256 * $ch1}] |
|---|
| 754 | } |
|---|
| 755 | } |
|---|
| 756 | |
|---|
| 757 | proc ::exif::readLong {data offset} { |
|---|
| 758 | variable intel |
|---|
| 759 | if {[string length $data] < [expr {$offset+4}]} { |
|---|
| 760 | error "readLong: end of string reached" |
|---|
| 761 | } |
|---|
| 762 | set ch1 [string index $data $offset] |
|---|
| 763 | set ch2 [string index $data [expr {$offset+1}]] |
|---|
| 764 | set ch3 [string index $data [expr {$offset+2}]] |
|---|
| 765 | set ch4 [string index $data [expr {$offset+3}]] |
|---|
| 766 | scan $ch1 %c ch1 ; scan $ch2 %c ch2 |
|---|
| 767 | scan $ch3 %c ch3 ; scan $ch4 %c ch4 |
|---|
| 768 | if {$intel} { |
|---|
| 769 | return [expr {(((($ch4 * 256) + $ch3) * 256) + $ch2) * 256 + $ch1}] |
|---|
| 770 | } else { |
|---|
| 771 | return [expr {(((($ch1 * 256) + $ch2) * 256) + $ch3) * 256 + $ch4}] |
|---|
| 772 | } |
|---|
| 773 | } |
|---|
| 774 | |
|---|
| 775 | proc ::exif::readIFDEntry {data format components offset} { |
|---|
| 776 | variable intel |
|---|
| 777 | if {$format == 2} { |
|---|
| 778 | # ASCII string |
|---|
| 779 | set value [string range $data $offset [expr {$offset+$components-1}]] |
|---|
| 780 | return [string trimright $value "\0"] |
|---|
| 781 | } elseif {$format == 3} { |
|---|
| 782 | # unsigned short |
|---|
| 783 | if {!$intel} { |
|---|
| 784 | set offset [expr {0xFFFF & ($offset >> 16)}] |
|---|
| 785 | } |
|---|
| 786 | return $offset |
|---|
| 787 | } elseif {$format == 4} { |
|---|
| 788 | # unsigned long |
|---|
| 789 | return $offset |
|---|
| 790 | } elseif {$format == 5} { |
|---|
| 791 | # unsigned rational |
|---|
| 792 | # This could be messy, if either is >2**31 |
|---|
| 793 | set numerator [readLong $data $offset] |
|---|
| 794 | set denominator [readLong $data [expr {$offset + 4}]] |
|---|
| 795 | return [expr {(1.0*$numerator)/$denominator}] |
|---|
| 796 | } elseif {$format == 10} { |
|---|
| 797 | # signed rational |
|---|
| 798 | # Should work normally, since everything in Tcl is signed |
|---|
| 799 | set numerator [readLong $data $offset] |
|---|
| 800 | set denominator [readLong $data [expr {$offset + 4}]] |
|---|
| 801 | return [expr {(1.0*$numerator)/$denominator}] |
|---|
| 802 | } else { |
|---|
| 803 | set x [format %08x $format] |
|---|
| 804 | error "Invalid IFD entry format: $x" |
|---|
| 805 | } |
|---|
| 806 | } |
|---|
| 807 | |
|---|
| 808 | proc ::exif::compensationFraction {value} { |
|---|
| 809 | if {$value==0} {return 0} |
|---|
| 810 | if {$value < 0} { |
|---|
| 811 | set result "-" |
|---|
| 812 | set value [expr {0-$value}] |
|---|
| 813 | } else { |
|---|
| 814 | set result "+" |
|---|
| 815 | } |
|---|
| 816 | set value [expr {int(0.5 + $value * 6)}] |
|---|
| 817 | set integer [expr {int($value / 6)}] |
|---|
| 818 | set sixths [expr {$value % 6}] |
|---|
| 819 | if {$integer != 0} { |
|---|
| 820 | append result $integer |
|---|
| 821 | if {$sixths != 0} { |
|---|
| 822 | append result " " |
|---|
| 823 | } |
|---|
| 824 | } |
|---|
| 825 | if {$sixths == 2} { |
|---|
| 826 | append result "1/3" |
|---|
| 827 | } elseif {$sixths == 3} { |
|---|
| 828 | append result "1/2" |
|---|
| 829 | } elseif {$sixths == 4} { |
|---|
| 830 | append result "2/3" |
|---|
| 831 | } else { |
|---|
| 832 | # Added by DNew |
|---|
| 833 | append result "$sixths/6" |
|---|
| 834 | } |
|---|
| 835 | return $result |
|---|
| 836 | } |
|---|
| 837 | |
|---|
| 838 | # This returns the list of all possible fieldnames |
|---|
| 839 | # that analyze might return. |
|---|
| 840 | proc ::exif::fieldnames {} { |
|---|
| 841 | variable cached_fieldnames |
|---|
| 842 | if {[info exists cached_fieldnames]} { |
|---|
| 843 | return $cached_fieldnames |
|---|
| 844 | } |
|---|
| 845 | # Otherwise, parse the source to find the fieldnames. |
|---|
| 846 | # Cool, huh? Don'tcha just love Tcl? |
|---|
| 847 | # Because of this, "result(...)" should only appear |
|---|
| 848 | # in these functions when "..." is the literal name |
|---|
| 849 | # of a field to be returned. |
|---|
| 850 | array set namelist {} |
|---|
| 851 | foreach proc {analyze app1 exifSubIFD makerNote} { |
|---|
| 852 | set body [info body ::exif::$proc] |
|---|
| 853 | foreach line [split $body \n] { |
|---|
| 854 | if {[regexp {result\(([^)]+)\)} $line junk name]} { |
|---|
| 855 | set namelist($name) {} |
|---|
| 856 | } |
|---|
| 857 | } |
|---|
| 858 | } |
|---|
| 859 | set cached_fieldnames [lsort -dictionary [array names namelist]] |
|---|
| 860 | return $cached_fieldnames |
|---|
| 861 | } |
|---|
| 862 | |
|---|
| 863 | |
|---|
| 864 | |
|---|
| 865 | # # # # # # # # # # # # # # |
|---|
| 866 | # What follows is the original header comments |
|---|
| 867 | # from the Perl code from which this is |
|---|
| 868 | # translated. Any changes I made directly |
|---|
| 869 | # are marked by "DNew". |
|---|
| 870 | |
|---|
| 871 | # PERL script to extract EXIF information from JPEGs generated by Canon |
|---|
| 872 | # digital cameras. |
|---|
| 873 | # This software is free and you may do anything like with it except sell it. |
|---|
| 874 | # |
|---|
| 875 | # Current version: 1.3 |
|---|
| 876 | # Author: Chris Breeze |
|---|
| 877 | # email: chris@breezesys.com |
|---|
| 878 | # Web: http://www.breezesys.com |
|---|
| 879 | # |
|---|
| 880 | # Based on experimenting with my G1 and information from: |
|---|
| 881 | # http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html |
|---|
| 882 | # |
|---|
| 883 | # Also Canon MakerNote from David Burren's page: |
|---|
| 884 | # http://www.burren.cx/david/canon.html |
|---|
| 885 | # |
|---|
| 886 | # More EXIF info and specs: |
|---|
| 887 | # http://exif.org |
|---|
| 888 | # |
|---|
| 889 | # Warnings: |
|---|
| 890 | # 1) The Subject distance is unreliable. It seems reasonably accurate |
|---|
| 891 | # for the G1 but on the D30 it is highly dependent on the lens fitted. |
|---|
| 892 | # |
|---|
| 893 | # Perl for Windows is available for free from: |
|---|
| 894 | # http://www.activestate.com |
|---|
| 895 | # |
|---|
| 896 | # History |
|---|
| 897 | # 11 Jan 2001 |
|---|
| 898 | # v0.1: Initial version |
|---|
| 899 | # |
|---|
| 900 | # 14 Jan 2001 |
|---|
| 901 | # v0.2: Updated with data from David Burren's page |
|---|
| 902 | # |
|---|
| 903 | # 15 Jan 2001 |
|---|
| 904 | # v0.3: Added more info for D30 (supplied by David Burren) |
|---|
| 905 | # 1) D30 stores ISO in EXIF tag 0x8827, G1 uses MakerNote 0x1/16 |
|---|
| 906 | # 2) MakerNote 0x1/10, ImageSize appears to be large, medium, small |
|---|
| 907 | # 3) D30 allows 1/2 or 1/3 stop exposure compensation |
|---|
| 908 | # 4) Added D30 custom function details, but can't test them |
|---|
| 909 | # |
|---|
| 910 | # 17 Jan 2001 |
|---|
| 911 | # v1.0 Tidied up AutoFocusMode for G1 vs D30 + added manual auto focus point (D30) |
|---|
| 912 | # |
|---|
| 913 | # 18 Jan 2001 |
|---|
| 914 | # v1.1 Removed some debug code left in by mistake |
|---|
| 915 | # |
|---|
| 916 | # 29 Jan 2001 |
|---|
| 917 | # v1.2 Added flash mode (MakerNote Tag 1, field 4) |
|---|
| 918 | # |
|---|
| 919 | # 7 Mar 2001 |
|---|
| 920 | # v1.3 Added ImageQuality (MakerNote Tag 1, field 3) |
|---|
| 921 | # |
|---|
| 922 | # 21 Apr 2001 |
|---|
| 923 | # v1.4 added ImageStabilisation for Pro90 IS |
|---|
| 924 | # |
|---|
| 925 | # 17 Sep 2001 |
|---|
| 926 | # v1.5 Incorporated D30 improvements from Jim Leonard |
|---|
| 927 | |
|---|
| 928 | if {0} { |
|---|
| 929 | # Trivial usage example |
|---|
| 930 | set x [exif::fieldnames] |
|---|
| 931 | puts "fieldnames = $x" |
|---|
| 932 | set f [open [lindex $argv 0]] |
|---|
| 933 | array set v [exif::analyze $f] |
|---|
| 934 | close $f |
|---|
| 935 | parray v |
|---|
| 936 | } |
|---|
| 937 | |
|---|