Exploring Photographic EXIF data (using PowerShell, of course)

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.

IdDecimal IDHex TypeCode 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/160PS 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: Microsoft, Powershell, EXIF, Photography