exif.tcl

Go to the documentation of this file.
00001 /*  EXIF parser in Tcl*/
00002 /*  Author: Darren New <dnew@san.rr.com>*/
00003 /*  Translated directly from the Perl version*/
00004 /*  by Chris Breeze <chris@breezesys.com>*/
00005 /*  http://www.breezesys.com*/
00006 /*  See the original comment block, reproduced*/
00007 /*  at the bottom.*/
00008 /*  Most of the inline comments about the meanings of fields*/
00009 /*  are copied verbatim and without understanding from the*/
00010 /*  original, unless "DNew" is there.*/
00011 /*  Much of the structure is preserved, except in*/
00012 /*  makerNote, where I got tired of typing as verbosely*/
00013 /*  as the original Perl. But thanks for making it so*/
00014 /*  readable that even someone who doesn't know Perl*/
00015 /*  could translate it, Chris! ;-)*/
00016 /*  PLEASE read and understand exif::fieldnames*/
00017 /*  BEFORE making any changes here! Thanks!*/
00018 
00019 /*  Usage of this version:*/
00020 /*      exif::analyze $stream ?$thumbnail?*/
00021 /*  Stream should be an open file handle*/
00022 /*  rewound to the start. It gets set to*/
00023 /*  binary mode and is left at EOF or */
00024 /*  possibly pointing at image data.*/
00025 /*  You have to open and close the*/
00026 /*  stream yourself.*/
00027 /*  The return is a serialized array*/
00028 /*  (a la [array get]) with informative*/
00029 /*  english text about what was found.*/
00030 /*  Errors in parsing or I/O or whatever*/
00031 /*  throw errors.*/
00032 /*      exif::allfields*/
00033 /*  returns a list of all possible field names.*/
00034 /*  Added by DNew. Funky implementation.*/
00035 /* */
00036 /*  New*/
00037 /*      exif::analyzeFile $filename ?$thumbnail?*/
00038 /* */
00039 /*  If you find any mistakes here, feel free to correct them*/
00040 /*  and/or send them to me. I just cribbed this - I don't even*/
00041 /*  have a camera that puts this kind of info into the file.*/
00042 
00043 /*  LICENSE: Standard BSD License.*/
00044 
00045 /*  There's probably something here I'm using without knowing it.*/
00046 package require Tcl 8.3
00047 
00048 package provide exif 1.1.2 ; /*  first release*/
00049 
00050 namespace ::exif {
00051     namespace export analyze analyzeFile fieldnames
00052     variable debug 0 ; /*  set to 1 for puts of debug trace*/
00053     variable cameraModel ; /*  used internally to understand options*/
00054     variable jpeg_markers ; /*  so we only have to do it once*/
00055     variable intel ; /*  byte order - so we don't have to pass to every read*/
00056     variable cached_fieldnames ; /*  just what it says*/
00057     array  jpeg = _markers {
00058         SOF0  \xC0
00059         DHT   \xC4
00060         SOI   \xD8
00061         EOI   \xD9
00062         SOS   \xDA
00063         DQT   \xDB
00064         DRI   \xDD
00065         APP1  \xE1
00066     }
00067 }
00068 
00069 ret  ::exif::debug (type str) {
00070     variable debug
00071     if {$debug} {puts $str}
00072 }
00073 
00074 ret  ::exif::streq (type s1 , type s2) {
00075     return [string equal $s1 $s2]
00076 }
00077 
00078 ret  ::exif::analyzeFile (type file , optional thumbnail ={)} {
00079     set stream [open $file]
00080     set res [analyze $stream $thumbnail]
00081     close $stream
00082     return $res
00083 }
00084 
00085 proc ::exif::analyze {stream {thumbnail {}}} {
00086     variable jpeg_markers
00087     array  result =  {}
00088     fconfigure $stream -translation binary -encoding binary
00089     while {![eof $stream]} {
00090          ch =  [read $stream 1]
00091         if {1 != [string length $ch]} {error "End of file reached @1"}
00092         if {![streq "\xFF" $ch]} {break} ; /*  skip image data*/
00093          marker =  [read $stream 1]
00094         if {1 != [string length $marker]} {error "End of file reached @2"}
00095         if {[streq $marker $jpeg_markers(SOI)]} {
00096             debug "SOI"
00097         } elseif {[streq $marker $jpeg_markers(EOI)]} {
00098             debug "EOI"
00099         } else {
00100              msb =  [read $stream 1]
00101              lsb =  [read $stream 1]
00102             if {1 != [string length $msb] || 1 != [string length $lsb]} {
00103                 error "File truncated @1"
00104             }
00105             scan $msb %c msb ; scan $lsb %c lsb
00106              size =  [expr {256 * $msb + $lsb}]
00107              data =  [read $stream [expr {$size-2}]]
00108         debug "read [expr {$size - 2}] bytes of data"
00109             if {[expr {$size-2}] != [string length $data]} {
00110                 error "File truncated @2"
00111             }
00112             if {[streq $marker $jpeg_markers(APP1)]} {
00113                 debug "APP1\t$size"
00114                 array  result =  [app1 $data $thumbnail]
00115             } elseif {[streq $marker $jpeg_markers(DQT)]} {
00116                 debug "DQT\t$size"
00117             } elseif {[streq $marker $jpeg_markers(SOF0)]} {
00118                 debug "SOF0\t$size"
00119             } elseif {[streq $marker $jpeg_markers(DHT)]} {
00120                 debug "DHT\t$size"
00121             } elseif {[streq $marker $jpeg_markers(SOS)]} {
00122                 debug "SOS\t$size"
00123             } else {
00124                 binary scan $marker H* x
00125                 debug "UNKNOWN MARKER $x"
00126             }
00127         }
00128     }
00129     return [array get result]
00130 }
00131 
00132 ret  ::exif::app1 (type data , type thumbnail) {
00133     variable intel
00134     variable cameraModel
00135     array set result {}
00136     if {![string equal [string range $data 0 5] "Exif\0\0"]} {
00137         error "APP1 does not contain EXIF"
00138     }
00139     debug "Reading EXIF data"
00140     set data [string range $data 6 end]
00141     set t [string range $data 0 1]
00142     if {[streq $t "II"]} {
00143         set intel 1
00144         debug "Intel byte alignment"
00145     } elseif {[streq $t "MM"]} {
00146         set intel 0
00147         debug "Motorola byte alignment"
00148     } else {
00149         error "Invalid byte alignment: $t"
00150     }
00151     if {[readShort $data 2]!=0x002A} {error "Invalid tag mark"}
00152     set curoffset [readLong $data 4] ; # just called "offset" in the Perl - DNew
00153     debug "Offset to first IFD: $curoffset"
00154     set numEntries [readShort $data $curoffset]
00155     incr curoffset 2
00156     debug "Number of directory entries: $numEntries"
00157     for {set i 0} {$i < $numEntries} {incr i} {
00158         set head [expr {$curoffset + 12 * $i}]
00159         set entry [string range $data $head [expr {$head+11}]]
00160         set tag [readShort $entry 0]
00161         set format [readShort $entry 2]
00162         set components [readLong $entry 4]
00163         set offset [readLong $entry 8]
00164         set value [readIFDEntry $data $format $components $offset]
00165         if {$tag==0x010e} {
00166             set result(ImageDescription) $value
00167         } elseif {$tag==0x010f} {
00168             set result(CameraMake) $value
00169         } elseif {$tag==0x0110} {
00170             set result(CameraModel) $value
00171             set cameraModel $value
00172         } elseif {$tag==0x0112} {
00173             set result(Orientation) $value
00174         } elseif {$tag == 0x011A} {
00175             set result(XResolution) $value
00176         } elseif {$tag == 0x011B} {
00177             set result(YResolution) $value
00178         } elseif {$tag == 0x0128} {
00179             set result(ResolutionUnit) "unknown"
00180             if {$value==2} {set result(ResolutionUnit) "inch"}
00181             if {$value==3} {set result(ResolutionUnit) "centimeter"}
00182         } elseif {$tag==0x0131} {
00183             set result(Software) $value
00184         } elseif {$tag==0x0132} {
00185             set result(DateTime) $value
00186         } elseif {$tag==0x0213} {
00187             set result(YCbCrPositioning) "unknown"
00188             if {$value==1} {set result(YCbCrPositioning) "Center of pixel array"}
00189             if {$value==2} {set result(YCbCrPositioning) "Datum point"}
00190         } elseif {$tag==0x8769} {
00191             # EXIF sub IFD
00192         debug "==CALLING exifSubIFD=="
00193             array set result [exifSubIFD $data $offset]
00194         } else {
00195             debug "Unrecognized entry: Tag=$tag, value=$value"
00196         }
00197     }
00198     set offset [readLong $data [expr {$curoffset + 12 * $numEntries}]]
00199     debug "Offset to next IFD: $offset"
00200     array set thumb_result [exifSubIFD $data $offset]
00201 
00202     if {$thumbnail != {}} {
00203     set jpg [string range $data \
00204         $thumb_result(JpegIFOffset) \
00205         [expr {$thumb_result(JpegIFOffset) + $thumb_result(JpegIFByteCount) - 1}]]
00206 
00207         set         to [open $thumbnail w]
00208         fconfigure $to -translation binary -encoding binary
00209     puts       $to $jpg
00210         close      $to
00211 
00212         #can be used (with a JPG-aware TK) to add the image to the result array
00213     #set result(THUMB) [image create photo -file $thumbnail]
00214     }
00215 
00216     return [array get result]
00217 }
00218 
00219 /*  Extract EXIF sub IFD info*/
00220 ret  ::exif::exifSubIFD (type data , type curoffset) {
00221     debug "EXIF: offset=$curoffset"
00222     set numEntries [readShort $data $curoffset]
00223     incr curoffset 2
00224     debug "Number of directory entries: $numEntries"
00225     for {set i 0} {$i < $numEntries} {incr i} {
00226         set head [expr {$curoffset + 12 * $i}]
00227         set entry [string range $data $head [expr {$head+11}]]
00228         set tag [readShort $entry 0]
00229         set format [readShort $entry 2]
00230         set components [readLong $entry 4]
00231         set offset [readLong $entry 8]
00232         if {$tag==0x9000} {
00233             set result(ExifVersion) [string range $entry 8 11]
00234         } elseif {$tag==0x9101} {
00235             set result(ComponentsConfigured) [format 0x%08x $offset]
00236         } elseif {$tag == 0x927C} {
00237             array set result [makerNote $data $offset]
00238         } elseif {$tag == 0x9286} {
00239             # Apparently, this doesn't usually work.
00240             set result(UserComment) "$offset - [string range $data $offset [expr {$offset+8}]]"
00241             set result(UserComment) [string trim $result(UserComment) "\0"]
00242         } elseif {$tag==0xA000} {
00243             set result(FlashPixVersion) [string range $entry 8 11]
00244         } elseif {$tag==0xA300} {
00245             # 3 means digital camera
00246             if {$offset == 3} {
00247                 set result(FileSource) "3 - Digital camera"
00248             } else {
00249                 set result(FileSource) $offset
00250             }
00251         } else {
00252             set value [readIFDEntry $data $format $components $offset]
00253             if {$tag==0x829A} {
00254                 if {0.3 <= $value} {
00255                     # In seconds...
00256                     set result(ExposureTime) "$value seconds"
00257                 } else {
00258                     set result(ExposureTime) "1/[expr {1.0/$value}] seconds"
00259                 }
00260             } elseif {$tag == 0x829D} {
00261                 set result(FNumber) $value
00262             } elseif {$tag == 0x8827} {
00263                 # D30 stores ISO here, G1 uses MakerNote Tag 1 field 16
00264                 set result(ISOSpeedRatings) $value
00265             } elseif {$tag == 0x9003} {
00266                 set result(DateTimeOriginal) $value
00267             } elseif {$tag == 0x9004} {
00268                 set result(DateTimeDigitized) $value
00269             } elseif {$tag == 0x9102} {
00270                 if {$value == 5} {
00271                     set result(ImageQuality) "super fine"
00272                 } elseif {$value == 3} {
00273                     set result(ImageQuality) "fine"
00274                 } elseif {$value == 2} {
00275                     set result(ImageQuality) "normal"
00276                 } else {
00277                     set result(CompressedBitsPerPixel) $value
00278                 }
00279             } elseif {$tag == 0x9201} {
00280                 # Not very accurate, use Exposure time instead.
00281                 #  (That's Chris' comment. I don't know what it means.)
00282                 set value [expr {pow(2,$value)}]
00283                 if {$value < 4} {
00284                     set value [expr {1.0 / $value}]
00285                     set value [expr {int($value * 10 + 0.5) / 10.0}]
00286                 } else {
00287                     set value [expr {int($value + 0.49)}]
00288                 }
00289                 set result(ShutterSpeedValue) "$value Hz"
00290             } elseif {$tag == 0x9202} {
00291                 set value [expr {int(pow(sqrt(2.0), $value) * 10 + 0.5) / 10.0}]
00292                 set result(AperatureValue) $value
00293             } elseif {$tag == 0x9204} {
00294                 set value [compensationFraction $value]
00295                 set result(ExposureBiasValue) $value
00296             } elseif {$tag == 0x9205} {
00297                 set value [expr {int(pow(sqrt(2.0), $value) * 10 + 0.5) / 10.0}]
00298             } elseif {$tag == 0x9206} {
00299                 # May need calibration
00300                 set result(SubjectDistance) "$value m"
00301             } elseif {$tag == 0x9207} {
00302                 set result(MeteringMode) "other"
00303                 if {$value == 0} {set result(MeteringMode) "unknown"} 
00304                 if {$value == 1} {set result(MeteringMode) "average"} 
00305                 if {$value == 2} {set result(MeteringMode) "center weighted average"} 
00306                 if {$value == 3} {set result(MeteringMode) "spot"} 
00307                 if {$value == 4} {set result(MeteringMode) "multi-spot"} 
00308                 if {$value == 5} {set result(MeteringMode) "multi-segment"} 
00309                 if {$value == 6} {set result(MeteringMode) "partial"} 
00310             } elseif {$tag == 0x9209} {
00311                 if {$value == 0} {
00312                     set result(Flash) no
00313                 } elseif {$value == 1} {
00314                     set result(Flash) yes
00315                 } else {
00316                     set result(Flash) "unknown: $value"
00317                 }
00318             } elseif {$tag == 0x920a} {
00319                 set result(FocalLength) "$value mm"
00320             } elseif {$tag == 0xA001} {
00321                 set result(ColorSpace) $value
00322             } elseif {$tag == 0xA002} {
00323                 set result(ExifImageWidth) $value
00324             } elseif {$tag == 0xA003} {
00325                 set result(ExifImageHeight) $value
00326             } elseif {$tag == 0xA005} {
00327                 set result(ExifInteroperabilityOffset) $value
00328             } elseif {$tag == 0xA20E} {
00329                 set result(FocalPlaneXResolution) $value
00330             } elseif {$tag == 0xA20F} {
00331                 set result(FocalPlaneYResolution) $value
00332             } elseif {$tag == 0xA210} {
00333                 set result(FocalPlaneResolutionUnit) "none"
00334                 if {$value == 2} {set result(FocalPlaneResolutionUnit) "inch"}
00335                 if {$value == 3} {set result(FocalPlaneResolutionUnit) "centimeter"} 
00336             } elseif {$tag == 0xA217} {
00337                 # 2 = 1 chip color area sensor
00338                 set result(SensingMethod) $value
00339             } elseif {$tag == 0xA401} {
00340         #TJE
00341         set result(SensingMethod) "normal"
00342                 if {$value == 1} {set result(SensingMethod) "custom"}
00343             } elseif {$tag == 0xA402} {
00344         #TJE
00345                 set result(ExposureMode) "auto"
00346                 if {$value == 1} {set result(ExposureMode) "manual"}
00347                 if {$value == 2} {set result(ExposureMode) "auto bracket"}
00348             } elseif {$tag == 0xA403} {
00349         #TJE
00350                 set result(WhiteBalance) "auto"
00351                 if {$value == 1} {set result(WhiteBalance) "manual"}
00352             } elseif {$tag == 0xA404} {
00353                 # digital zoom not used if number is zero
00354         set result(DigitalZoomRatio) "not used"
00355                 if {$value != 0} {set result(DigitalZoomRatio) $value}
00356             } elseif {$tag == 0xA405} {
00357         set result(FocalLengthIn35mmFilm) "unknown"
00358                 if {$value != 0} {set result(FocalLengthIn35mmFilm) $value}
00359             } elseif {$tag == 0xA406} {
00360                 set result(SceneCaptureType) "Standard"
00361                 if {$value == 1} {set result(SceneCaptureType) "Landscape"} 
00362                 if {$value == 2} {set result(SceneCaptureType) "Portrait"}
00363                 if {$value == 3} {set result(SceneCaptureType) "Night scene"}
00364             } elseif {$tag == 0xA407} {
00365                 set result(GainControl) "none"
00366                 if {$value == 1} {set result(GainControl) "Low gain up"} 
00367                 if {$value == 2} {set result(GainControl) "High gain up"}
00368                 if {$value == 3} {set result(GainControl) "Low gain down"}
00369                 if {$value == 4} {set result(GainControl) "High gain down"}
00370             } elseif {$tag == 0x0103} {
00371         #TJE
00372         set result(Compression) "unknown"
00373         if {$value == 1} {set result(Compression) "none"}
00374         if {$value == 6} {set result(Compression) "JPEG"}
00375             } elseif {$tag == 0x011A} {
00376         #TJE
00377         set result(XResolution) $value
00378             } elseif {$tag == 0x011B} {
00379         #TJE
00380         set result(YResolution) $value
00381             } elseif {$tag == 0x0128} {
00382         #TJE
00383         set result(ResolutionUnit) "unknown"
00384         if {$value == 1} {set result(ResolutionUnit) "inch"}
00385         if {$value == 6} {set result(ResolutionUnit) "cm"}
00386             } elseif {$tag == 0x0201} {
00387         #TJE
00388         set result(JpegIFOffset) $value
00389         debug "offset = $value"
00390             } elseif {$tag == 0x0202} {
00391         #TJE
00392         set result(JpegIFByteCount) $value
00393         debug "bytecount = $value"
00394             } else {
00395                 error "Unrecognized EXIF Tag: $tag (0x[string toupper [format %x $tag]])"
00396             }
00397         }
00398     }
00399     return [array get result]
00400 }
00401 
00402 /*  Canon proprietary data that I didn't feel like translating to Tcl yet.*/
00403 ret  ::exif::makerNote (type data , type curoffset) {
00404     variable cameraModel
00405     debug "MakerNote: offset=$curoffset"
00406 
00407     array set result {}
00408     set numEntries [readShort $data $curoffset]
00409     incr curoffset 2
00410     debug "Number of directory entries: $numEntries"
00411     for {set i 0} {$i < $numEntries} {incr i} {
00412         set head [expr {$curoffset + 12 * $i}]
00413         set entry [string range $data $head [expr {$head+11}]]
00414         set tag [readShort $entry 0]
00415         set format [readShort $entry 2]
00416         set components [readLong $entry 4]
00417         set offset [readLong $entry 8]
00418         debug "$i)\tTag: $tag, format: $format, components: $components"
00419 
00420         if {$tag==6} {
00421             set value [readIFDEntry $data $format $components $offset]
00422             set result(ImageFormat) $value
00423         } elseif {$tag==7} {
00424             set value [readIFDEntry $data $format $components $offset]
00425             set result(FirmwareVersion) $value
00426         } elseif {$tag==8} {
00427             set value [string range $offset 0 2]-[string range $offset 3 end]
00428             set result(ImageNumber) $value
00429         } elseif {$tag==9} {
00430             set value [readIFDEntry $data $format $components $offset]
00431             set result(Owner) $value
00432         } elseif {$tag==0x0C} {
00433             # camera serial number
00434             set msw [expr {($offset >> 16) & 0xFFFF}]
00435             set lsw [expr {$offset & 0xFFFF}]
00436             set result(CameraSerialNumber) [format %04X%05d $msw $lsw]
00437         } elseif {$tag==0x10} {
00438             set result(UnknownTag-0x10) $offset
00439         } else {
00440             if {$format == 3 && 1 < $components} {
00441                 debug "MakerNote $i: TAG=$tag"
00442                 catch {unset field}
00443                 array set field {}
00444                 for {set j 0} {$j < $components} {incr j} {
00445                     set field($j) [readShort $data [expr {$offset+2*$j}]]
00446                     debug "$j : $field($j)"
00447                 }
00448                 if {$tag == 1} {
00449                     if {![string match -nocase "*Pro90*" $cameraModel]} {
00450                         if {$field(1)==1} {
00451                             set result(MacroMode) macro
00452                         } else {
00453                             set result(MacroMode) normal
00454                         }
00455                     }
00456                     if {0 < $field(2)} {
00457                         set result(SelfTimer) "[expr {$field(2)/10.0}] seconds"
00458                     }
00459                     set result(ImageQuality) [switch $field(3) {
00460                         2 {format Normal}
00461                         3 {format Fine}
00462                         4 {format "CCD Raw"}
00463                         5 {format "Super fine"}
00464                         default {format ""}
00465                     }]
00466                     set result(FlashMode) [switch $field(4) {
00467                         0 {format off}
00468                         1 {format auto}
00469                         2 {format on}
00470                         3 {format "red eye reduction"}
00471                         4 {format "slow synchro"}
00472                         5 {format "auto + red eye reduction"}
00473                         6 {format "on + red eye reduction"}
00474                         default {format ""}
00475                     }]
00476                     if {$field(5)} {
00477                         set result(ShootingMode) "Continuous"
00478                     } else {
00479                         set result(ShootingMode) "Single frame"
00480                     }
00481                     # Field 6 - don't know what it is.
00482                     set result(AutoFocusMode) [switch $field(7) {
00483                         0 {format "One-shot"}
00484                         1 {format "AI servo"}
00485                         2 {format "AI focus"}
00486                         3 - 6 {format "MF"}
00487                         5 {format "Continuous"}
00488                         4 {
00489                             # G1: uses field 32 to store single/continuous,
00490                             # and always sets 7 to 4.
00491                             if {[info exists field(32)] && $field(32)} {
00492                                 format "Continuous"
00493                             } else {
00494                                 format "Single"
00495                             }
00496                         }
00497                         default {format unknown}
00498                     }]
00499                     # Field 8 and 9 are unknown
00500                     set result(ImageSize) [switch $field(10) {
00501                         0 {format "large"}
00502                         1 {format "medium"}
00503                         2 {format "small"}
00504                         default {format "unknown"}
00505                     }]
00506                     # Field 11 - easy shooting - see field 20
00507                     # Field 12 - unknown
00508                     set NHL {
00509                         0 {format "Normal"}
00510                         1 {format "High"}
00511                         65536 {format "Low"}
00512                         default {format "Unknown"}
00513                     }
00514                     set result(Contrast) [switch $field(13) $NHL]
00515                     set result(Saturation) [switch $field(14) $NHL]
00516             set result(Sharpness) [switch $field(15) $NHL]
00517                     set result(ISO) [switch $field(16) {
00518                         15 {format Auto}
00519                         16 {format 50}
00520                         17 {format 100}
00521                         18 {format 200}
00522                         19 {format 400}
00523                         default {format "unknown"}
00524                     }]
00525                     set result(MeteringMode) [switch $field(17) {
00526                         3 {format evaluative}
00527                         4 {format partial}
00528                         5 {format center-weighted}
00529                         default {format unknown}
00530                     }]
00531                     # Field 18 - unknown
00532             if {[info exists field(19)]} {
00533             set result(AFPoint) [switch -- [expr {$field(19)-0x3000}] {
00534                 0 {format none}
00535                 1 {format auto-selected}
00536                 2 {format right}
00537                 3 {format center}
00538                 4 {format left}
00539                 default {format unknown}
00540             }] ; # {}
00541             }
00542             if {[info exists field(20)]} {
00543             if {$field(20) == 0} {
00544                 set result(ExposureMode) [switch $field(11) {
00545                 0 {format auto}
00546                 1 {format manual}
00547                 2 {format landscape}
00548                 3 {format "fast shutter"}
00549                 4 {format "slow shutter"}
00550                 5 {format "night scene"}
00551                 6 {format "black and white"}
00552                 7 {format sepia}
00553                 8 {format portrait}
00554                 9 {format sports}
00555                 10 {format close-up}
00556                 11 {format "pan focus"}
00557                 default {format unknown}
00558                 }] ; # {}
00559             } elseif {$field(20) == 1} {
00560                 set result(ExposureMode) program
00561             } elseif {$field(20) == 2} {
00562                 set result(ExposureMode) Tv
00563             } elseif {$field(20) == 3} {
00564                 set result(ExposureMode) Av
00565             } elseif {$field(20) == 4} {
00566                 set result(ExposureMode) manual
00567             } elseif {$field(20) == 5} {
00568                 set result(ExposureMode) A-DEP
00569             } else {
00570                 set result(ExposureMode) unknown
00571             }
00572             }
00573                     # Field 21 and 22 are unknown
00574                     # Field 23: max focal len, 24 min focal len, 25 units per mm
00575             if {[info exists field(23)] && [info exists field(25)]} {
00576             set result(MaxFocalLength) \
00577                 "[expr {1.0 * $field(23) / $field(25)}] mm"
00578             }
00579                     if {[info exists field(24)] && [info exists field(25)]} {
00580             set result(MinFocalLength) \
00581                 "[expr {1.0 * $field(24) / $field(25)}] mm"
00582             }
00583                     # Field 26-28 are unknown.
00584             if {[info exists field(29)]} {
00585             if {$field(29) & 0x0010} {
00586                 lappend result(FlashMode) "FP_sync_enabled"
00587             }
00588             if {$field(29) & 0x0800} {
00589                 lappend result(FlashMode) "FP_sync_used"
00590             }
00591             if {$field(29) & 0x2000} {
00592                 lappend result(FlashMode) "internal_flash"
00593             }
00594             if {$field(29) & 0x4000} {
00595                 lappend result(FlashMode) "external_E-TTL"
00596             }
00597             }
00598                     if {[info exists field(34)] && \
00599                 [string match -nocase "*pro90*" $cameraModel]} {
00600                         if {$field(34)} {
00601                             set result(ImageStabilisation) on
00602                         } else {
00603                             set result(ImageStabilisation) off
00604                         }
00605                     }
00606                 } elseif {$tag == 4} {
00607                     set result(WhiteBalance) [switch $field(7) {
00608                         0 {format Auto}
00609                         1 {format Daylight}
00610                         2 {format Cloudy}
00611                         3 {format Tungsten}
00612                         4 {format Fluorescent}
00613                         5 {format Flash}
00614                         6 {format Custom}
00615                         default {format Unknown}
00616                     }]
00617                     if {$field(14) & 0x07} {
00618                         set result(AFPointsUsed) \
00619                             [expr {($field(14)>>12) & 0x0F}]
00620                         if {$field(14)&0x04} {
00621                             append result(AFPointsUsed) " left"
00622                         }
00623                         if {$field(14)&0x02} {
00624                             append result(AFPointsUsed) " center"
00625                         }
00626                         if {$field(14)&0x01} {
00627                             append result(AFPointsUsed) " right"
00628                         }
00629                     }
00630             if {[info exists field(15)]} {
00631             set v $field(15)
00632             if {32768 < $v} {incr v -65536}
00633             set v [compensationFraction [expr {$v / 32.0}]]
00634             set result(FlashExposureCompensation) $v
00635             }
00636             if {[info exists field(19)]} {
00637             set result(SubjectDistance) "$field(19) m"
00638             }
00639                 } elseif {$tag == 15} {
00640                     foreach k [array names field] {
00641                         set func [expr {($field($k) >> 8) & 0xFF}]
00642                         set v [expr {$field($k) & 0xFF}]
00643                         if {$func==1 && $v} {
00644                             set result(LongExposureNoiseReduction) on
00645                         } elseif {$func==1 && !$v} {
00646                             set result(LongExposureNoiseReduction) off
00647                         } elseif {$func==2} {
00648                             set result(Shutter/AE-Lock) [switch $v {
00649                                 0 {format "AF/AE lock"}
00650                                 1 {format "AE lock/AF"}
00651                                 2 {format "AF/AF lock"}
00652                                 3 {format "AE+release/AE+AF"}
00653                                 default {format "Unknown"}
00654                             }]
00655                         } elseif {$func==3} {
00656                             if {$v} {
00657                                 set result(MirrorLockup) enable
00658                             } else {
00659                                 set result(MirrorLockup) disable
00660                             }
00661                         } elseif {$func==4} {
00662                             if {$v} {
00663                                 set result(Tv/AvExposureLevel) "1/3 stop"
00664                             } else {
00665                                 set result(Tv/AvExposureLevel) "1/2 stop"
00666                             }
00667                         } elseif {$func==5} {
00668                             if {$v} {
00669                                 set result(AFAssistLight) off
00670                             } else {
00671                                 set result(AFAssistLight) on
00672                             }
00673                         } elseif {$func==6} {
00674                             if {$v} {
00675                                 set result(ShutterSpeedInAVMode) "Fixed 1/200"
00676                             } else {
00677                                 set result(ShutterSpeedInAVMode) "Auto"
00678                             }
00679                         } elseif {$func==7} {
00680                             set result(AEBSeq/AutoCancel) [switch $v {
00681                                 0 {format "0, -, + enabled"}
00682                                 1 {format "0, -, + disabled"}
00683                                 2 {format "-, 0, + enabled"}
00684                                 3 {format "-, 0, + disabled"}
00685                                 default {format unknown}
00686                             }]
00687                         } elseif {$func==8} {
00688                             if {$v} {
00689                                 set result(ShutterCurtainSync) "2nd curtain sync"
00690                             } else {
00691                                 set result(ShutterCurtainSync) "1st curtain sync"
00692                             }
00693                         } elseif {$func==9} {
00694                             set result(LensAFStopButtonFnSwitch) [switch $v {
00695                                 0 {format "AF stop"}
00696                                 1 {format "operate AF"}
00697                                 2 {format "lock AE and start timer"}
00698                                 default {format unknown}
00699                             }]
00700                         } elseif {$func==10} {
00701                             if {$v} {
00702                                 set result(AutoReductionOfFillFlash) disable
00703                             } else {
00704                                 set result(AutoReductionOfFillFlash) enable
00705                             }
00706                         } elseif {$func==11} {
00707                             if {$v} {
00708                                 set result(MenuButtonReturnPosition) previous
00709                             } else {
00710                                 set result(MenuButtonReturnPosition) top
00711                             }
00712                         } elseif {$func==12} {
00713                             set result(SetButtonFuncWhenShooting) [switch $v {
00714                                 0 {format "not assigned"}
00715                                 1 {format "change quality"}
00716                                 2 {format "change ISO speed"}
00717                                 3 {format "select parameters"}
00718                                 default {format unknown}
00719                             }]
00720                         } elseif {$func==13} {
00721                             if {$v} {
00722                                 set result(SensorCleaning) enable
00723                             } else {
00724                                 set result(SensorCleaning) disable
00725                             }
00726                         } elseif {$func==0} {
00727                             # Discovered by DNew?
00728                             set result(CameraOwner) $v
00729                         } else {
00730                             append result(UnknownCustomFunc) "$func=$v "
00731                         }
00732                     }
00733                 }
00734             } else {
00735                 debug [format "makerNote: Unrecognized TAG: 0x%x" $tag]
00736             }
00737         }
00738     }
00739     return [array get result]
00740 }
00741 
00742 ret  ::exif::readShort (type data , type offset) {
00743     variable intel
00744     if {[string length $data] < [expr {$offset+2}]} {
00745         error "readShort: end of string reached"
00746     }
00747     set ch1 [string index $data $offset]
00748     set ch2 [string index $data [expr {$offset+1}]]
00749     scan $ch1 %c ch1 ; scan $ch2 %c ch2
00750     if {$intel} {
00751         return [expr {$ch1 + 256 * $ch2}]
00752     } else {
00753         return [expr {$ch2 + 256 * $ch1}]
00754     }
00755 }
00756 
00757 ret  ::exif::readLong (type data , type offset) {
00758     variable intel
00759     if {[string length $data] < [expr {$offset+4}]} {
00760         error "readLong: end of string reached"
00761     }
00762     set ch1 [string index $data $offset]
00763     set ch2 [string index $data [expr {$offset+1}]]
00764     set ch3 [string index $data [expr {$offset+2}]]
00765     set ch4 [string index $data [expr {$offset+3}]]
00766     scan $ch1 %c ch1 ; scan $ch2 %c ch2
00767     scan $ch3 %c ch3 ; scan $ch4 %c ch4
00768     if {$intel} {
00769         return [expr {(((($ch4 * 256) + $ch3) * 256) + $ch2) * 256 + $ch1}]
00770     } else {
00771         return [expr {(((($ch1 * 256) + $ch2) * 256) + $ch3) * 256 + $ch4}]
00772     }
00773 }
00774 
00775 ret  ::exif::readIFDEntry (type data , type format , type components , type offset) {
00776     variable intel
00777     if {$format == 2} {
00778         # ASCII string
00779         set value [string range $data $offset [expr {$offset+$components-1}]]
00780         return [string trimright $value "\0"]
00781     } elseif {$format == 3} {
00782         # unsigned short
00783         if {!$intel} {
00784             set offset [expr {0xFFFF & ($offset >> 16)}]
00785         }
00786         return $offset
00787     } elseif {$format == 4} {
00788         # unsigned long
00789         return $offset
00790     } elseif {$format == 5} {
00791         # unsigned rational
00792         # This could be messy, if either is >2**31
00793         set numerator [readLong $data $offset]
00794         set denominator [readLong $data [expr {$offset + 4}]]
00795         return [expr {(1.0*$numerator)/$denominator}]
00796     } elseif {$format == 10} {
00797         # signed rational
00798         # Should work normally, since everything in Tcl is signed
00799         set numerator [readLong $data $offset]
00800         set denominator [readLong $data [expr {$offset + 4}]]
00801         return [expr {(1.0*$numerator)/$denominator}]
00802     } else {
00803         set x [format %08x $format]
00804         error "Invalid IFD entry format: $x"
00805     }
00806 }
00807 
00808 ret  ::exif::compensationFraction (type value) {
00809     if {$value==0} {return 0}
00810     if {$value < 0} {
00811         set result "-"
00812         set value [expr {0-$value}]
00813     } else {
00814         set result "+"
00815     }
00816     set value [expr {int(0.5 + $value * 6)}]
00817     set integer [expr {int($value / 6)}]
00818     set sixths [expr {$value % 6}]
00819     if {$integer != 0} {
00820         append result $integer
00821         if {$sixths != 0} {
00822             append result " "
00823         }
00824     }
00825     if {$sixths == 2} {
00826         append result "1/3"
00827     } elseif {$sixths == 3} {
00828         append result "1/2" 
00829     } elseif {$sixths == 4} {
00830         append result "2/3"
00831     } else {
00832         # Added by DNew
00833         append result "$sixths/6"
00834     }
00835     return $result
00836 }
00837 
00838 /*  This returns the list of all possible fieldnames*/
00839 /*  that analyze might return.*/
00840 ret  ::exif::fieldnames () {
00841     variable cached_fieldnames 
00842     if {[info exists cached_fieldnames]} {
00843         return $cached_fieldnames
00844     }
00845     # Otherwise, parse the source to find the fieldnames.
00846     # Cool, huh? Don'tcha just love Tcl?
00847     # Because of this, "result(...)" should only appear
00848     # in these functions when "..." is the literal name
00849     # of a field to be returned.
00850     array set namelist {}
00851     foreach proc {analyze app1 exifSubIFD makerNote} {
00852         set body [info body ::exif::$proc]
00853         foreach line [split $body \n] {
00854             if {[regexp {result\(([^)]+)\)} $line junk name]} {
00855                 set namelist($name) {}
00856             }
00857         }
00858     }
00859     set cached_fieldnames [lsort -dictionary [array names namelist]]
00860     return $cached_fieldnames
00861 }
00862 
00863 
00864 
00865 /*  # # # # # # # # # # # # #*/
00866 /*  What follows is the original header comments*/
00867 /*  from the Perl code from which this is */
00868 /*  translated. Any changes I made directly*/
00869 /*  are marked by "DNew".*/
00870 
00871 /*  PERL script to extract EXIF information from JPEGs generated by Canon*/
00872 /*  digital cameras.*/
00873 /*  This software is free and you may do anything like with it except sell it.*/
00874 /* */
00875 /*  Current version: 1.3*/
00876 /*  Author: Chris Breeze*/
00877 /*  email: chris@breezesys.com*/
00878 /*  Web: http://www.breezesys.com*/
00879 /* */
00880 /*  Based on experimenting with my G1 and information from:*/
00881 /*  http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html*/
00882 /* */
00883 /*  Also Canon MakerNote from David Burren's page:*/
00884 /*  http://www.burren.cx/david/canon.html*/
00885 /* */
00886 /*  More EXIF info and specs:*/
00887 /*  http://exif.org*/
00888 /* */
00889 /*  Warnings: */
00890 /*  1) The Subject distance is unreliable. It seems reasonably accurate*/
00891 /*  for the G1 but on the D30 it is highly dependent on the lens fitted.*/
00892 /* */
00893 /*  Perl for Windows is available for free from:*/
00894 /*  http://www.activestate.com*/
00895 /* */
00896 /*  History*/
00897 /*  11 Jan 2001*/
00898 /*  v0.1: Initial version*/
00899 /* */
00900 /*  14 Jan 2001*/
00901 /*  v0.2: Updated with data from David Burren's page*/
00902 /* */
00903 /*  15 Jan 2001*/
00904 /*  v0.3: Added more info for D30 (supplied by David Burren)*/
00905 /*  1) D30 stores ISO in EXIF tag 0x8827, G1 uses MakerNote 0x1/16*/
00906 /*  2) MakerNote 0x1/10, ImageSize appears to be large, medium, small*/
00907 /*  3) D30 allows 1/2 or 1/3 stop exposure compensation*/
00908 /*  4) Added D30 custom function details, but can't test them*/
00909 /* */
00910 /*  17 Jan 2001*/
00911 /*  v1.0 Tidied up AutoFocusMode for G1 vs D30 + added manual auto focus point (D30)*/
00912 /* */
00913 /*  18 Jan 2001*/
00914 /*  v1.1 Removed some debug code left in by mistake*/
00915 /* */
00916 /*  29 Jan 2001*/
00917 /*  v1.2 Added flash mode (MakerNote Tag 1, field 4)*/
00918 /* */
00919 /*  7 Mar 2001*/
00920 /*  v1.3 Added ImageQuality (MakerNote Tag 1, field 3)*/
00921 /* */
00922 /*  21 Apr 2001*/
00923 /*  v1.4 added ImageStabilisation for Pro90 IS*/
00924 /* */
00925 /*  17 Sep 2001*/
00926 /*  v1.5 Incorporated D30 improvements from Jim Leonard*/
00927 
00928 if {0} {
00929     /*  Trivial usage example*/
00930      x =  [exif::fieldnames]
00931     puts "fieldnames = $x"
00932      f =  [open [lindex $argv 0]]
00933     array  v =  [exif::analyze $f]
00934     close $f
00935     parray v
00936 }
00937 
00938 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1