I have taken extensivly from others here so here is something back however poor.
I write software (up until now only on windows) for pro photogs. I have had my own exif reader for some years now on windows. I just started looking at porting one of my windows apps to the mac so I fired up the mac I purchased 3 or 4 years ago, bought Tiger and looked around at what was there. I was pretty impressed with where Tiger has taken the mac and not a little impressed with the suite of programming tools available for no extra charge. What a difference. Instead of needing to invest $3K to $5K for development tools they were all there free for the use when purchasing Tiger.
So here is a (not finished) image exif reader I did yesterday and today. It is not done as I suggest but does chase the exif ifd entries and their associated tags. I nearly gave up on this for a time but finally got to a point it actually works. I think it has an issue if there is no exif data and I’ll correct that shortly.
The real point of this is to show that applescript can read and use binary data files given a known structure and known values to look for. This reader works for both II and MM byte orders and should work for any tags defined in the main tags list.
This has also not undergone any relook to clean it up. I just got it working. Hope this helps somebody as so much from here has helped me in the last 3 weeks. My powerbook shows up on Tuesday next week - yoohoo. Finally things should perk up in speed. This 800mghz flat panel imac with 512kb of ram is pretty much a dog. I’m used to much faster.
Note that in the mytaglist the four elements are 1) the description of the tag 2) the standard MM order of the tag id 3) my special routine id - you can change this to whatever you like 4) the decimal equivalent of the standard MM order of the tag id - ie 010f in hex = 271 in decimal.
-- KSSCEXIFReader.applescript
-- KSSCEXIFReader
-- Created by gesmith on 3/23/06.
-- Copyright 2006 __MyCompanyName__. All rights reserved.
property myTagList : {¬
{"Make", "010F", 100, 271}, ¬
{"Model", "0110", 101, 272}, ¬
{"Date-Time", "0132", 140, 306}, ¬
{"Maker Note", "927C", 109, 37500}, ¬
{"Sub IFD", "8769", 109, 34665}, ¬
{"ISO", "8827", 13, 34855}, ¬
{"Jpg Offset", "0201", 18, 513}, ¬
{"Jpg Byte Count", "0202", 19, 514}, ¬
{"Compression", "0103", 20, 259}, ¬
{"Camera Serial Number", "000C", 24, 12}, ¬
{"Custom Functions", "000F", 25, 15}, ¬
{"IPTC/NAA", "83BB", 26, 33723}, ¬
{"Inter Color Profile", "8773", 27, 34675}, ¬
{"Firmware Version", "0007", 33, 7}, ¬
{"Camera Owner", "0009", 34, 9}, ¬
{"Orientation", "0112", 6, 274}, ¬
{"XRes", "A20E", 14, 41486}, ¬
{"YRes", "A20F", 15, 41487}, ¬
{"Width", "0100", 36, 256}, ¬
{"Height", "0101", 37, 257}, ¬
{"Main Image Width", "A002", 38, 40962}, ¬
{"Main Image Height", "A003", 39, 40963}, ¬
{"Date-Time Digitized", "9003", 141, 36867}, ¬
{"Aperture Value", "9202", 1, 37378}, ¬
{"Shutter Speed Value", "9201", 10, 37377}, ¬
{"Exposure Time", "829A", 3, 33434}, ¬
{"FNumber", "829D", 4, 33437}, ¬
{"Subject Distance", "9206", 5, 37382}, ¬
{"Flash", "9209", 7, 37385}, ¬
{"Focal Length", "920A", 12, 37386}, ¬
{"Rows Per Strip", "0116", 28, 278}, ¬
{"Strip Offset", "0111", 21, 273}, ¬
{"Strip Byte Count", "0117", 22, 279}, ¬
{"Photometric Interpretation", "0106", 23, 262}, ¬
{"Planar Configuration", "011C", 32, 284}, ¬
{"YRes", "011B", 41, 283}, ¬
{"XRes", "011A", 42, 282}, ¬
{"Bits Per Sample", "0102", 43, 258}, ¬
{"Metering Mode", "9207", 11, 37383}, ¬
{"Exposure Index", "A215", 0, 41493}, ¬
{"Exposure Mode", "8822", 16, 34850}, ¬
{"Exposure Comp", "9204", 17, 37380}, ¬
{"Interoperability IFD/DCF", "A005", 27, 40965} ¬
}
property returnTags : {JFIF:false, APP0LengthWithThumb:0, EXIFOffset:0, Motorola:false, theIFDOffsets:{}, DigitizedDateTime:"", CameraMaker:"", CameraModel:"", DateTime:""}
--65498 = ffd9
--65497 = ffd8
property sExt : ""
property theFileToUse : ""
property inFile : missing value
property wFile : missing value
property bMotorola : false
property bIntel : true
property addEntry : {}
property baddEntry : false
property curIFDCount : 0
property myFoundCount : 0
on clicked theObject
(*Add your script here.*)
set myFoundCount to 0
set theFile to (choose file) as string
DoTheReader(theFile)
display dialog myFoundCount
end clicked
on DoTheReader(theFile)
set JFIF of returnTags to false
set CameraModel of returnTags to ""
set CameraMaker of returnTags to ""
set DateTime of returnTags to ""
set APP0LengthWithThumb of returnTags to 0
set EXIFOffset of returnTags to 0
set Motorola of returnTags to false
set theIFDOffsets of returnTags to {} --initial list of offset and entries
set DigitizedDateTime of returnTags to ""
set filePathElements to get_file_path_elements(theFile)
set sExt to item 4 of filePathElements
if (sExt ≠"JPG") and (sExt ≠"THM") and (sExt ≠"TIF") and (sExt ≠"RAF") ¬
and (sExt ≠"DCR") and (sExt ≠"CR2") and (sExt ≠"NEF") and (sExt ≠"CRW") then
display dialog "This file type either is not recognized or does not contain exif information"
return
end if
set theFileToUse to theFile
if sExt = "CRW" then
set theFileToUse to (item 1 of filePathElements) & (item 3 of filePathElements) & ".THM"
end if
try
set inFile to open for access file theFileToUse
on error msg
display dialog "Could Not Open: " & theFile
return
end try
set myJFIFsearch to read inFile for 1024 as string --read first 1024 to see if it is a JFIF
if sExt = "JPG" then
if "JFIF" is in myJFIFsearch then
set JFIF of returnTags to true
end if
set APP0LengthWithThumb of returnTags to read inFile from 5 to 6 as small integer
end if
set theExifOffset to offset of "II" in myJFIFsearch
if theExifOffset = 0 then set theExifOffset to offset of "MM" in myJFIFsearch
if theExifOffset = 0 then
display dialog "No exif markers found"
return
end if
set bMotorola to false
set bIntel to true
set sWork to text from character theExifOffset to character (theExifOffset + 1) of myJFIFsearch
set EXIFOffset of returnTags to theExifOffset
--motorola of returntags = false means the data is in intel order
if sWork = "MM" then
set Motorola of returnTags to true --set to not intel order which is ho first
set bMotorola to true
set bIntel to false
end if
set myJFIFsearch to "" --give the string memory back
if bIntel then
set lowAddress to fixIntel(theExifOffset + 4, 4)
else
set lowAddress to read inFile from theExifOffset + 4 to theExifOffset + 7 as integer --address of first
end if
set lowAddress to lowAddress + theExifOffset
--*
--* get inital set of IFD entry offset and number of tag entries in each
--* this list is added to later if we find other ifd pointers in the tags
--* such as a maker note for example
--*
repeat while lowAddress ≠0
if bIntel then
set numEntries to fixIntel(lowAddress, 2)
else
set numEntries to read inFile from lowAddress to lowAddress + 1 as small integer
end if
set thisOne to {lowAddress + 2, numEntries}
set end of theIFDOffsets of returnTags to thisOne
set lowAddress to lowAddress + (numEntries * 12) + 2
if bIntel then
set lowAddress to fixIntel(lowAddress, 4)
else
set lowAddress to read inFile from lowAddress to lowAddress + 3 as integer --get address offset of next ifd
end if
if lowAddress ≠0 then
set lowAddress to lowAddress + theExifOffset --must add original offset for real file offset
end if --display dialog lowAddress
end repeat
if (count of theIFDOffsets of returnTags) < 1 then
close access inFile
if bIntel then
close access wFile
end if
return returnTags
end if
set bQuit to false
set curIFDCount to count of theIFDOffsets of returnTags
set x to 0
repeat until bQuit --with x from 1 to count of theIFDOffsets of returnTags
set x to x + 1
if x ≤ curIFDCount then
WalkTheTags(item x of theIFDOffsets of returnTags)
else
set bQuit to true
end if
end repeat
close access inFile
end DoTheReader
--*
--* go through all the tags for this ifd
--*
on WalkTheTags(Offsetlist)
repeat with thisTagEntry from 1 to item 2 of Offsetlist
set mytagoffset to (thisTagEntry - 1) * 12 --get offset of the next tag
set currentOffset to (mytagoffset + (item 1 of Offsetlist))
if bIntel then
set theTagID to fixIntel(currentOffset, 2)
else
set theTagID to read inFile from currentOffset to currentOffset + 1 as small integer
end if
--display dialog theTagID
repeat with tagTblEntry from 1 to count of myTagList
if item 4 of item tagTblEntry of myTagList = theTagID then
--display dialog item 1 of item tagTblEntry of myTagList
set myFoundCount to myFoundCount + 1
WorkThisTag(tagTblEntry, currentOffset)
exit repeat
end if
end repeat
end repeat
end WalkTheTags
--*
--* have a recognized tag so get the data from it
--*
on WorkThisTag(tagIndex, tagOffset)
set tagDesc to item 1 of item tagIndex of myTagList
set tagRout to item 3 of item tagIndex of myTagList
set tagID to item 4 of item tagIndex of myTagList
set theDataAddress01 to 0
set theDataAddress02 to 0
set theDataValue to 0
set theDataValueString to ""
if bIntel then
set tagDataType to fixIntel(tagOffset + 2, 2)
else
set tagDataType to read inFile from tagOffset + 2 for 2 as small integer
end if
if bIntel then
set theDataLength to fixIntel(tagOffset + 4, 4)
else
set theDataLength to read inFile from tagOffset + 4 for 4 as integer
end if
if tagDataType = 1 then
--unsigned byte
else if tagDataType = 2 then
--ascii string
if bIntel then
set theDataAddress01 to fixIntel(tagOffset + 8, 4)
else
set theDataAddress01 to read inFile from tagOffset + 8 for 4 as integer
end if
set theDataAddress01 to theDataAddress01 + (EXIFOffset of returnTags)
else if (tagDataType = 3) or (tagDataType = 8) then
--unsigned short
if bIntel then
set theDataValue to fixIntel(tagOffset + 8, 2)
else
set theDataValue to read inFile from tagOffset + 8 for 2 as small integer
end if
set theDataValueString to theDataValue as string
else if (tagDataType = 4) or (tagDataType = 9) then
--unsigned long (offset address)
if bIntel then
set theDataAddress01 to fixIntel(tagOffset + 8, 4)
else
set theDataAddress01 to read inFile from tagOffset + 8 for 4 as integer
end if
set theDataAddress01 to theDataAddress01 + (EXIFOffset of returnTags)
else if (tagDataType = 5) or (tagDataType = 10) then
--unsigned rational
if bIntel then
set theDataAddress02 to fixIntel(tagOffset + 8, 4)
else
set theDataAddress02 to read inFile from tagOffset + 8 for 4 as integer
end if
set theDataAddress02 to theDataAddress02 + (EXIFOffset of returnTags)
if bIntel then
set theDataAddress01 to fixIntel(tagOffset + 4, 4)
else
set theDataAddress01 to read inFile from tagOffset + 4 for 4 as integer
end if
set theDataAddress01 to theDataAddress01 + (EXIFOffset of returnTags)
else if tagDataType = 6 then
--signed byte
else if tagDataType = 7 then
--address and length
if bIntel then
set theDataAddress01 to fixIntel(tagOffset + 8, 4)
set theDataAddress02 to fixIntel(tagOffset + 4, 4)
else
set theDataAddress01 to read inFile from tagOffset + 8 for 4 as integer
set theDataAddress02 to read inFile from tagOffset + 4 as integer
end if
set theDataAddress01 to theDataAddress01 + (EXIFOffset of returnTags)
end if
if tagRout = 140 then
set mystring to read inFile from theDataAddress01 to theDataAddress01 + theDataLength - 2 as string
set DateTime of returnTags to mystring
else if tagRout = 100 then
set mystring to read inFile from theDataAddress01 to theDataAddress01 + theDataLength - 2 as string
set CameraMaker of returnTags to mystring
else if tagRout = 101 then
set mystring to read inFile from theDataAddress01 to theDataAddress01 + theDataLength - 2 as string
set CameraModel of returnTags to mystring
else if tagRout = 109 then
--maker note ifd pointer
--sub ifd
if bIntel then
set numEntries to fixIntel(theDataAddress01, 2)
else
set numEntries to read inFile from theDataAddress01 for 2 as small integer
end if
set addEntry to {theDataAddress01 + 2, numEntries}
set end of theIFDOffsets of returnTags to addEntry
set curIFDCount to curIFDCount + 1
end if
end WorkThisTag
--*
--* II (intel) values are stored backwards from MM so must turn them around
--*
on fixIntel(myOffset, num)
set byt0 to read inFile from myOffset for 1
set byt1 to read inFile from myOffset + 1 for 1
set val0 to (ASCII number byt0)
set val1 to (ASCII number byt1)
if num = 4 then
set byt2 to read inFile from myOffset + 2 for 1
set byt3 to read inFile from myOffset + 3 for 1
set val2 to (ASCII number byt2)
set val3 to (ASCII number byt3)
return ((val3 * 16777216) + (val2 * 65536) + (val1 * 256) + val0) as integer
else
return ((val1 * 256) + val0) as integer
end if
return 0
end fixIntel
--*
--* common routines that have nothing to do with the exif data
--*
on get_file_path_elements(fName)
set thePathName to fName as string
if thePathName ends with ":" then
--no file name present, it's all path
return {thePathName, "", "", "", (fName as string)} --path,fullfilename(with extension),filenamenoextension,extension
end if
if thePathName ends with "/" then
--no file name present, it's all path
return {thePathName, "", "", "", (fName as string)} --path,fullfilename(with extension),filenamenoextension,extension
end if
set thePathName to strReverse(thePathName)
set thePath to ""
set theFullFileName to ""
set theFileNameNoExtension to ""
set theExtension to ""
repeat with x from 1 to (length of thePathName)
if item x of thePathName = ":" then
set thePath to strReverse(text from character x to end of thePathName)
set theFileName to (text from character 1 to character (x - 1) of thePathName)
exit repeat
else if item x of thePathName = "/" then --work --for either
set thePath to strReverse(text from character x to end of thePathName)
set theFileName to (text from character 1 to character (x - 1) of thePathName)
exit repeat
end if
end repeat
if theFileName ≠"" then set theFullFileName to strReverse(theFileName)
if length of theFileName < 2 then
return {thePath, theFullFileName, theFullFileName, "", (fName as string)} --no extension to return
end if
set baseExt to ""
repeat with idx from 1 to (length of theFileName)
if (text item idx of theFileName = ".") then
set baseExt to strReverse(text from character 1 to character (idx - 1) of theFileName)
set theFileNameNoExtension to strReverse(text from character (idx + 1) to end of theFileName)
return {thePath, theFullFileName, theFileNameNoExtension, baseExt, (fName as string)}
end if
end repeat
return {thePath, theFullFileName, theFullFileName, "", (fName as string)}
--set dirStr to "dirname " & fName
--return do shell script dirStr
end get_file_path_elements
on strReverse(iText)
if length of iText < 2 then
return iText
end if
set reversedText to ""
repeat with x from (length of iText) to 1 by -1
set reversedText to reversedText & item x of iText
end repeat
return reversedText
--return reverse of (iText's text items)
end strReverse
on get_f_as_alias(f) --this shoud work for strings (in both HFS & POSIX paths), file specs, aliases
try
return (f as alias)
on error
try
return (f as POSIX file) as alias
on error
return false
end try
end try
end get_f_as_alias
--if bIntel then
-- set wFileName to ((path to temporary items folder as string) & "wFile.bin")
-- set wFile to open for access file wFileName with write permission
--end if