James O'Neill's blog

Windows Platform, Virtualization and PowerShell with a little Photography for good measure.

Exploring Photographic EXIF data (using PowerShell, of course)

Exploring Photographic EXIF data (using PowerShell, of course)

  • Comments 5
  • Likes

My PowerShell project for the last few days has been to get into the EXIF data which is embedded in Pictures. It turns out to be a bit easier than I imagined.

The first thing I found is that the .NET framework has a "BITMAP" object-class which gives access to these properties. Just get a new instance of the object passing it the file name: this works for JPEGs and TIFFs. It doesn't work for RAW files from my camera (presumably because Windows doesn't see those as bit maps)

The Second thing I found is Powershell doesn't load the required assembly by default - which spawned the earlier post about VB complementing PowerShell. So before I can get the object I need to do this 

[reflection.assembly]::loadfile( "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")

Then I can do this

$foo=New-Object -TypeName system.drawing.bitmap -ArgumentList "C:\Dump\Unsorted Pictures\Tour\RAW\TdF23760.JPG"

The object has an array of propertyItems . Each PropertyItem object has an ID, a type, a size, and a value. This is pretty flexible but it is also a darn nuisance for anyone wanting to code for the first time, because you have to know, for example that field ID 271 (or 0x010F in hex) means "Maker name", and type 7 means string.  or ID 41996 (0xA40C hex) means "Subject distance" and type 3 means 16 bit integer; but the value isn't in feet or meters but  a value of 1 Means "Macro", 2 means "Close" and 3 means "Distant". Worst of all 37380 (0x9204) means "Exposure compensation" and type 10 means "Expressed as the Ratio of 2 32 bit signed integers". 

$foo.GetPropertyItem(271) will get the Maker name property (if it exists - it will return an error if the property hasn't been set), but before we can get properties we're interested in we need to find a list of IDs. I've already got EXIF UTILS from HUGSAN and it's documentation covers ID, so does the competing EXIFTOOL and there are others. Here are the main fields - not an exhaustive list.

Id
Decimal
ID
Hex
Type
Code
Type Name Name Notes
40091 9C9B 1 Byte Title (unicode)  
40093 9C9D 1 Byte Author (Unicode)  
271 10F 2 String Make  
272 110 2 String Model  
305 131 2 String Software Version  
36867 9003 2 String Date Time Taken YYYY:MM:DD HH:MM:SS
34850 8822 3 Integer Exposure Program 1=Manual, 2=Program Normal , 3=Aperture Priority, 4=Shutter Priority, 5=Program Creative, 6=Program Action ,7=Portrait Mode , 8=Landscape Mode
37383 9207 3 Integer Metering Mode 1=Av, 2=Centre, 3=Spot, 4=Multi-spot, 5=Multi-Segment, 6=Partial,255=Other
37385 9209 3 Integer Flash 0=Not Fired,1=Fired,5=Strobe return not detected. 7=Strobe return detected, 9=Flash fired; Compulsory flash mode 13=Flash fired; Compulsory flash mode, Return light not detected, 15=Flash fired; Compulsory flash mode; Return light detected, 16=Flash not fired; Compulsory flash mode, 24=Flash not fired; Auto mode
40961 A001 3 Integer Colour Space 0=sRGB,2=Adobe RGB, 65535=Uncalibrated
41986 A402 3 Integer Exposure Mode 0=Auto,1=Manual,2=Auto Bracket
41987 A403 3 Integer White Balance 0=Auto,1=Manual
41990 A406 3 Integer Scene Capture Mode 0=Standard,1=Landscape,2=Portrait,3=NightScene
41992 A408 3 Integer Contrast 0=Normal,1=Soft,2=Hard
41993 A409 3 Integer Saturation 0=Normal,1=Low,2=High
41994 A40A 3 Integer Sharpness 0=Normal,1=Soft,2=Hard
41996 A40C 3 Integer Subject Range 0=Unknown,1=Macro,2=Close,3=Distant
37384 9208 3 Integer Light Source 0 =Auto ,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
34855 8827 3 Long Integer ISO  
40962 A002 4 Long Integer Width  
40963 A003 4 Long Integer Height  
33434 829A 5 Rational:2 Longs Exposure time  
33437 829D 5 Rational:2 Longs F-Number  
37386 920A 5 Rational:2 Longs Focal Len  
37381 9205 5 Rational:2 Longs Max Apperture  
41988 A404 5 Rational:2 Longs Digital Zoom Ratio  
37380 9204 10 Rational:2 Signed longs Exp-bias  
37500 927C 7 Undefined Maker Note {This is vendor specific and needs a whole post of its own}

 For completeness here's a table of type codes.

1 One or Bytes
2 An array of byte objects encoded as ASCII
3 An unsigned integer (16 bits)
4 A unsigned long integer (32 bits)
5 Rational, an array of two long integers that represent a rational number
6 Not used
7 Undefined
8 Not used
9 Signed Long integer
10 Signed Rational

All these types might make you want to scream but we can cheat. Since all the integers seem to be padded with zeros, we can treat them as long integers. In practice no long integers are used to represent numbers greater than 2^31 so we can treat unsigned Long integers (and ordinary integers) as signed. Finally, Rational numbers are sometimes written as 0,0 to mean 0, but this will cause a divide by zero error. To avoid this we can change the second number from 0 to 1. Since the single number like 123 is  padded with zeros to take the same space as a long, if we treat it like a rational well get 123 / 0 which we convert to 123/1. So we can treat ALL numbers as ratios of signed longs.  I'll add one more refinement. If the value is 1/123 then it's probably representing a shutter speed which I want to see in that form, not 0.00813. So I just need to write [fanfare] my first PowerShell function. I can type this in at the prompt (deja Vu for commodore and Applesoft basics which I mentioned recently)

function MakeNumber {
$First =$args[0].value[0] + 256 * $args[0].value[1] + 65536 * $args[0].value[2] + 16777216 * $args[0].value[3] ;
$Second=$args[0].value[4] + 256 * $args[0].value[5] + 65536 * $args[0].value[6] + 16777216 * $args[0].value[7] ;

if ($first -gt 2147483648) {$first = $first  - 4294967296} ;
if ($Second -gt 2147483648) {$Second= $Second - 4294967296} ;
if ($Second -eq 0) {$Second= 1} ;

if (($first –eq 1) -and ($Second -ne 1)) {write-output ("1/" + $Second)}
else {write-output ($first / $second)}
}

The first two lines, smash the two sets of 4 bytes together,  the next two lines convert large-enough numbers to two's compliment negative ones, the 5th line ensures we don't divide by zero but divide by one, and the 6th makes sure if the result is 1/ something it comes back as a fraction.   

PS C:\Users\Jamesone> "Shutter speed= " + (makenumber $foo.GetPropertyItem(33434))
Shutter speed= 1/160

PS C:\Users\Jamesone> "Apperture= f/" + (makenumber $foo.GetPropertyItem(33437))
Apperture= f/3.5

PS C:\Users\Jamesone> "ISO= " + (makenumber $foo.GetPropertyItem(34855))
ISO= 100

PS C:\Users\Jamesone> "Width= " + (makenumber $foo.GetPropertyItem(40962))
Width= 3872

Now, it might be my naivety with Powershell but I couldn't find away to turn an array of bytes into a string, so I had to write my Second powershell function

function MakeString { $s="" ; for ($i=0 ; $i -le $args[0].value.length; $i ++) {$s = $s+ [char]$args[0].value[$i] }; Write-Output $s}

Which is just a for next loop to convert the array to a string; here's the result  

PS C:\Users\Jamesone> "Model= " + (makestring $foo.GetPropertyItem(272))
Model= PENTAX K10D
Technorati tags: , , ,
Comments
  • I'm enjoying your series on Powershell (it's cool that Windows finally has a good shell), but wow, that seems like a lot of work.

    On OS X, the EXIF data is imported by the Spotlight importer for JPEGs.  I can list all the metadata (see example below) and get at individual parts pretty trivially.

    But, if I wanted to fiddle with EXIF data, I'd probably use something like jhead (http://www.sentex.net/~mwandel/jhead/) or exiftags (http://johnst.org/sw/exiftags/).  They're both free and even have Windows binaries.

    Here's the output from mdls ~/Pictures/TestPic.jpg

    /Users/melissa/Pictures/TestPic.jpg -------------

    kMDItemAcquisitionMake         = "Canon"

    kMDItemAcquisitionModel        = "Canon PowerShot SD700 IS"

    kMDItemAperture                = 4.65625

    kMDItemAttributeChangeDate     = 2007-07-14 09:31:24 -0700

    kMDItemBitsPerSample           = 32

    kMDItemColorSpace              = "RGB"

    kMDItemContentCreationDate     = 2007-07-14 09:31:23 -0700

    kMDItemContentModificationDate = 2007-07-14 09:31:23 -0700

    kMDItemContentType             = "public.jpeg"

    kMDItemContentTypeTree         = ("public.jpeg", "public.image", "public.data", "public.item", "public.content")

    kMDItemDisplayName             = "TestPic.jpg"

    kMDItemEXIFVersion             = "2.2"

    kMDItemExposureMode            = 0

    kMDItemExposureTimeSeconds     = 0.0025

    kMDItemFlashOnOff              = 0

    kMDItemFocalLength             = 18.633

    kMDItemFSContentChangeDate     = 2007-07-14 09:31:23 -0700

    kMDItemFSCreationDate          = 2007-07-14 09:31:23 -0700

    kMDItemFSCreatorCode           = 0

    kMDItemFSFinderFlags           = 1024

    kMDItemFSInvisible             = 0

    kMDItemFSIsExtensionHidden     = 0

    kMDItemFSLabel                 = 0

    kMDItemFSName                  = "TestPic.jpg"

    kMDItemFSNodeCount             = 0

    kMDItemFSOwnerGroupID          = 20

    kMDItemFSOwnerUserID           = 501

    kMDItemFSSize                  = 1409646

    kMDItemFSTypeCode              = 0

    kMDItemHasAlphaChannel         = 0

    kMDItemID                      = 2799798

    kMDItemKind                    = "JPEG Image"

    kMDItemLastUsedDate            = 2007-07-14 09:31:23 -0700

    kMDItemOrientation             = 0

    kMDItemPixelHeight             = 2112

    kMDItemPixelWidth              = 2816

    kMDItemProfileName             = "sRGB Profile"

    kMDItemRedEyeOnOff             = 0

    kMDItemResolutionHeightDPI     = 180

    kMDItemResolutionWidthDPI      = 180

    kMDItemUsedDates               = (2007-07-14 09:31:23 -0700)

    kMDItemWhiteBalance            = 0

  • The Equivalent for Windows is the Windows desktop search iFilter for JPEGs, and that puts all the properties into the Windows index (see http://blogs.technet.com/jamesone/archive/2007/07/10/vista-s-desktop-index-and-powershell.aspx for an example about how I get the properties out from there).

    However the are some properties which are there in the exif that are not there in the index (e.g spotlight's filter doesn't put in 35mm equivalent focal length - the windows ifilter doesn't put in ... [memory fails me] some fields.

    Which you'd use ... well it depends on the circumstances : and I haven't quite finished with this one :-)

  • Actually, the focal length is in there:

       kMDItemFocalLength = 18.633

    although admittedly, it's hard to see it in with all the other metadata.  I don't know whether there are obscure EXIF tags that the Spotlight importer ignores, but it's certainly possible.  I can tell you all the metadata tags listed for images in Apple's documentation, which presumably get pulled from EXIF when possible, namely:

    kMDItemAcquisitionMake, kMDItemAcquisitionModel, kMDItemAlbum, kMDItemAperture, kMDItemBitsPerSample, kMDItemColorSpace, kMDItemEXIFVersion, kMDItemExposureMode, kMDItemExposureProgram, kMDItemExposureTimeSeconds, kMDItemExposureTimeString, kMDItemFNumber, kMDItemFlashOnOff, kMDItemFocalLength, kMDItemHasAlphaChannel, kMDItemISOSpeed, kMDItemLayerNames, kMDItemMaxAperture, kMDItemMeteringMode, kMDItemOrientation, kMDItemPixelHeight, kMDItemPixelWidth, kMDItemProfileName, kMDItemRedEyeOnOff, kMDItemResolutionHeightDPI, kMDItemResolutionWidthDPI and kMDItemWhiteBalance

    (taken from the Spotlight Metadata Attributes reference, at http://developer.apple.com/documentation/Carbon/Reference/MetadataAttributesRef/index.html).

    Thanks for the pointer to your earlier article.  It's cool that Windows now has something like Spotlight's Query Expression Syntax (see http://developer.apple.com/documentation/Carbon/Conceptual/SpotlightQuery/Concepts/QueryFormat.html).  

    BTW, in that earlier article, was that all the metadata Windows keeps, or did you cut it down to a subset to reduce clutter?

  • I just played with exiftool (http://www.sno.phy.queensu.ca/~phil/exiftool/), and it pulled out more than 150 separate pieces of information out of the EXIF data for that photo, so clearly there is plenty of stuff that both Spotlight and Windows Desktop Search miss.  I also get the difference between focal length and 35mm focal length now (duh!).

    It's a cool tool. Written in Perl for easy programmatic access, cross-platform too, and so it runs happily in Windows.  It seems like it groks everything you could imagine, and makes it easy to do modifications like fixing times for those occasions when you forgot to adjust the clock on your camera.

  • I nearly answered the first one before I answered the second.

    There's a list of fields that Windows desktop search understands here http://www.microsoft.com/technet/scriptcenter/topics/desktop/wdsprops.mspx

    Exiftool is very cool - Phil seems to be the definitive compiler of vendor specific data. I ended up with Exifutils from http://www.hugsan.com which also works well; but if I had got into Perl I would have go for Exiftool instead.

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment