Applescript exif reader

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


Many thanks for sharing!

I love myself binary work in AS (I’ve tried with AETE structures and SWF files) :smiley:

Just for the records, someone else called David Lloyd wrote this same code (that is: similar code) and much more things image-related, and compiled it in a shareware library called “Image Info” (maybe still in ScriptBuilders), and ported it later to a scriptable RB app (see Kanzu).

And we have also, for this specific task: http://osaxen.com/files/exifinfox2.2.html

I have updated this to include more of the code though still not done. There were also some problems in the original particularly with Motorola order files that were quite large.

-- 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}, ¬
	{"Camera Serial Number", "000C", 24, 12}, ¬
	{"1D/1DS Embedded Jpg Offset", "0081", 35, 129}, ¬
	{"Orientation", "0112", 6, 274}, ¬
	{"Width", "0100", 136, 256}, ¬
	{"Height", "0101", 137, 257}, ¬
	{"Main Image Width", "A002", 138, 40962}, ¬
	{"Main Image Height", "A003", 139, 40963}, ¬
	{"Date-Time Digitized", "9003", 141, 36867}, ¬
	{"Rows Per Strip", "0116", 28, 278}, ¬
	{"Strip Offset", "0111", 21, 273}, ¬
	{"Strip Byte Count", "0117", 22, 279}, ¬
	{"YRes", "011B", 41, 283}, ¬
	{"XRes", "011A", 42, 282}, ¬
	{"IPTC/NAA", "83BB", 26, 33723}, ¬
	{"Bits Per Sample", "0102", 43, 258}}

--{"Custom Functions", "000F", 25, 15}, ¬
--{"Inter Color Profile", "8773", 27, 34675}, ¬
--{"1D/1DS Custom Functions", "0090", 25, 144}, ¬
--{"Firmware Version", "0007", 33, 7}, ¬
--{"Camera Owner", "0009", 34, 9}, ¬
--{"XRes", "A20E", 14, 41486}, ¬
--{"YRes", "A20F", 15, 41487}, ¬
--{"Compression", "0103", 20, 259}, ¬
--{"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} ¬
--{"Photometric Interpretation", "0106", 23, 262}, ¬
--{"Planar Configuration", "011C", 32, 284}, ¬
--{"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}, ¬

property returnTags : {ErrorRaised:false, errormsg:"", ExifPresent:false, JFIF:false, APP0LengthWithThumb:0, EXIFOffset:0, Motorola:false, theIFDOffsets:{}, DigitizedDateTime:"", CameraMaker:"", CameraModel:"", DateTime:"", BigJPGOffset:0, BigJPGLength:0, BigJPGPresent:false, Orientation:0, OrientationTagOffset:0, OrientationTagSetting:0, MainImageWidth:0, MainImageHeight:0, ISO:0, theWidth:0, theHeight:0, RowsPerStrip:0, StripOffset:0, StripByteCount:0, BitsPerSample:0, IPTCOffset:0, IPTCLength:0, JPGOffset:0, JPGLength:0, MakerNotePresent:false, rawtif:false}

--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)
	if ExifPresent of returnTags then
		display dialog (count of theIFDOffsets of returnTags)
		display dialog "makernotepresent: " & MakerNotePresent of returnTags
		display dialog "Camera Maker: " & CameraMaker of returnTags & " Raw TIF: " & rawtif of returnTags & return ¬
			& "Model: " & CameraModel of returnTags & return & "Digitized Date and Time: " & DigitizedDateTime of returnTags & return & "Main Width/Width: " & MainImageWidth of returnTags & ":" & theWidth of returnTags & " Main Height/Height: " & MainImageHeight of returnTags & ":" & theHeight of returnTags
	else
		display dialog "No exif data present"
	end if
end clicked

on DoTheReader(theFile)
	clear_returnTags()
	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
			--set ExifPresent of returnTags to false
			--return
		end if
		set APP0LengthWithThumb of returnTags to fixMMHW(5)
	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 ExifPresent of returnTags to true
	set myJFIFsearch to "" --give the string memory back
	try
		if bIntel then
			set lowAddress to fixIntel(theExifOffset + 4, 4)
		else
			set lowAddress to read inFile from theExifOffset + 4 for 4 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 fixMMHW(lowAddress)
			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 for 4 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
	on error msg
		set ErrorRaised of returnTags to true
		set errormsg of returnTags to msg
	end try
	if (CameraMaker of returnTags = "Canon") and ¬
		(sExt = "TIF") and ¬
		(MakerNotePresent of returnTags) and ¬
		(CameraModel of returnTags = "Canon EOS-1DS") ¬
			and (theWidth of returnTags < MainImageWidth of returnTags) then
		set rawtif of returnTags to true
	end if
	close access inFile
end DoTheReader
--*
--* go through all the tags for this ifd
--*
on WalkTheTags(Offsetlist)
	if item 2 of Offsetlist > 100 then
		return --invalid ifd number of elements
	end if
	repeat with thisTagOffset from item 1 of Offsetlist to ((item 1 of Offsetlist) + (12 * (item 2 of Offsetlist))) by 12
		if bIntel then
			set theTagID to fixIntel(thisTagOffset, 2)
		else
			set theTagID to fixMMHW(thisTagOffset)
		end if
		repeat with tagTblEntry from 1 to count of myTagList
			if item 4 of item tagTblEntry of myTagList = theTagID then
				WorkThisTag(tagTblEntry, thisTagOffset)
				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 fixMMHW(tagOffset + 2)
	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 fixMMHW(tagOffset + 8)
		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 for 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 = 141 then
		set mystring to read inFile from theDataAddress01 to theDataAddress01 + theDataLength - 2 as string
		set DigitizedDateTime 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 fixMMHW(theDataAddress01)
		end if
		set addEntry to {theDataAddress01 + 2, numEntries}
		set end of theIFDOffsets of returnTags to addEntry
		set curIFDCount to curIFDCount + 1
		if tagID = 37500 then set MakerNotePresent of returnTags to true
	else if tagRout = 136 then --width
		set theWidth of returnTags to theDataValue
	else if tagRout = 137 then
		set theHeight of returnTags to theDataValue
	else if tagRout = 138 then
		set MainImageWidth of returnTags to theDataValue
	else if tagRout = 139 then
		set MainImageHeight of returnTags to theDataValue
	end if
end WorkThisTag
--*
--* II (intel) values are stored backwards from MM so must turn them around
--*
on fixIntel(myOffset, num)
	if num = 4 then
		--work a full word value
		set myFW to read inFile from myOffset for 4
		return (((ASCII number (item 4 of myFW)) * 16777216) ¬
			+ ((ASCII number (item 3 of myFW)) * 65536) ¬
			+ ((ASCII number (item 2 of myFW)) * 256) ¬
			+ (ASCII number (item 1 of myFW))) as integer
	else
		--work a halfword value
		set myFW to read inFile from myOffset for 2
		return (((ASCII number (item 2 of myFW)) * 256) + (ASCII number (item 1 of myFW))) as integer
	end if
	return 0
end fixIntel
--*
--* read as a small integer even MM going to halfwords have neg sign problems so must be read and converted
--*
on fixMMHW(myOffset)
	set myFW to read inFile from myOffset for 2
	return (((ASCII number (item 1 of myFW)) * 256) + (ASCII number (item 2 of myFW))) as integer
end fixMMHW
on clear_returnTags()
	set RowsPerStrip of returnTags to 0
	set StripOffset of returnTags to 0
	set StripByteCount of returnTags to 0
	set BitsPerSample of returnTags to 0
	set IPTCOffset of returnTags to 0
	set IPTCLength of returnTags to 0
	set JPGOffset of returnTags to 0
	set JPGLength of returnTags to 0
	set MakerNotePresent of returnTags to false
	set theWidth of returnTags to 0
	set theHeight of returnTags to 0
	set ISO of returnTags to 0
	set MainImageWidth of returnTags to 0
	set MainImageHeight of returnTags to 0
	set Orientation of returnTags to 0
	set BigJPGOffset of returnTags to 0
	set BigJPGLength of returnTags to 0
	set BigJPGPresent of returnTags to false
	set JFIF of returnTags to false
	set errormsg of returnTags to ""
	set ErrorRaised of returnTags to false
	set ExifPresent 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 OrientationTagOffset of returnTags to 0
	set OrientationTagSetting of returnTags to 0
	set rawtif of returnTags to false
end clear_returnTags
--*
--* 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

cool :slight_smile:

But for one of my private projects I used a little lazier way:

I implemented (do shell script) ‘jhead’ - a command line EXIF reader/manipulation prog

see here:

http://www.sentex.net/~mwandel/jhead/

I even used it to extract the exif thumbnails and diplay it in NSImageViews …

D.

Yes, jhead is a way to go. But since I write my own software for marketing purposes I need to create everything myself so I can sleep at night. A user that suddenly has trouble because jhead isn’t doing something correctly will blame me, not jhead.

In addition some things you don’t see in my example are routines that extract large JPGS that are always present in many raw files. This is so I can show a thumbnail or large version if desired of any file including raw files and psd files using a simple image view in cocoa. These large embedded jpgs can only be reached by enumerating the exif fields that contain pointers to them. None of them to my knowledge are documented by the camera makers but over time I have found them and use this feature extensively in my windows programs. My issue in moving to OS X was could I still do these things and do them in applescript. The example above is the answer, yes I can.

My next routine will now be an applescript that decodes psd files, gets the layers bounds, name and other properties. This is of course, only possible by doing binary manipulation.

In the overall I was just trying to show people it could be done and one way to approach it.

I’m updating this for a final time. Some routines have been removed which apply to finding and using large embedded JPGs. However the remainder works just fine as far as I have seen. This routine runs about .05 seconds per image on my Macbook Pro including setting up the bigjpg fields for later pulling of those if the ap I’m writing wishes to do so.

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", 113, 34855}, ¬
			{"Jpg Offset", "0201", 118, 513}, ¬
			{"Jpg Byte Count", "0202", 119, 514}, ¬
			{"Camera Serial Number", "000C", 24, 12}, ¬
			{"Orientation", "0112", 106, 274}, ¬
			{"Width", "0100", 136, 256}, ¬
			{"Height", "0101", 137, 257}, ¬
			{"Main Image Width", "A002", 138, 40962}, ¬
			{"Main Image Height", "A003", 139, 40963}, ¬
			{"Date-Time Digitized", "9003", 141, 36867}, ¬
			{"Rows Per Strip", "0116", 28, 278}, ¬
			{"Strip Offset", "0111", 121, 273}, ¬
			{"Strip Byte Count", "0117", 122, 279}, ¬
			{"YRes", "011B", 41, 283}, ¬
			{"XRes", "011A", 42, 282}, ¬
			{"IPTC/NAA", "83BB", 26, 33723}, ¬
			{"XRes", "A20E", 14, 41486}, ¬
			{"YRes", "A20F", 15, 41487}, ¬
			{"Bits Per Sample", "0102", 43, 258}}
		
		--{"Custom Functions", "000F", 25, 15}, ¬
		--{"Inter Color Profile", "8773", 27, 34675}, ¬
		--{"1D/1DS Custom Functions", "0090", 25, 144}, ¬
		--{"Firmware Version", "0007", 33, 7}, ¬
		--{"Camera Owner", "0009", 34, 9}, ¬
		--{"Compression", "0103", 20, 259}, ¬
		--{"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} ¬
		--{"Photometric Interpretation", "0106", 23, 262}, ¬
		--{"Planar Configuration", "011C", 32, 284}, ¬
		--{"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}, ¬
		
		property returnTags : {ErrorRaised:false, errormsg:"", ExifPresent:false, JFIF:false, APP0LengthWithThumb:0, EXIFOffset:0, Motorola:false, theIFDOffsets:{}, DigitizedDateTime:"", CameraMaker:"", CameraModel:"", DateTime:"", BigJPGOffset:0, BigJPGLength:0, BigJPGPresent:false, Orientation:0, OrientationTagOffset:0, OrientationTagSetting:0, MainImageWidth:0, MainImageHeight:0, imageISO:0, theWidth:0, theHeight:0, RowsPerStrip:0, StripOffset:0, StripByteCount:0, BitsPerSample:0, IPTCOffset:0, IPTCLength:0, JPGOffset:0, JPGLength:0, MakerNotePresent:false, rawtif:false, NikonJPGOffset:0, NikonJPGBytes:0} ¬
			
		--65498 = ffd9
		--65497 = ffd8
		property sExt : ""
		property theFileToUse : ""
		property inFile : missing value
		property bIntel : true
		property curIFDCount : 0
		property IFDIndex : 0
		property filePathElements : {}
		--on KSSCExifReader()
		--	script Reader
		on DoTheReader(theFile)
			clear_returnTags()
			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 returnTags
			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
					--set ExifPresent of returnTags to false
					--return
				end if
				set APP0LengthWithThumb of returnTags to fixIntel(5, 2)
			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 returnTags
			end if
			
			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 bIntel to false
			end if
			set ExifPresent of returnTags to true
			set myJFIFsearch to "" --give the string memory back
			try
				if bIntel then
					set lowAddress to fixIntel(theExifOffset + 4, 4)
				else
					set lowAddress to read inFile from theExifOffset + 4 for 4 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
					set numEntries to fixIntel(lowAddress, 2)
					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 for 4 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
				end repeat
				
				if (count of theIFDOffsets of returnTags) < 1 then
					close access inFile
					return returnTags
				end if
				--*
				--* main ifd enum routine
				--*
				set bQuit to false
				set curIFDCount to count of theIFDOffsets of returnTags
				set IFDIndex to 0
				repeat until bQuit
					set IFDIndex to IFDIndex + 1
					if IFDIndex ≤ curIFDCount then
						WalkTheTags(item IFDIndex of theIFDOffsets of returnTags)
					else
						set bQuit to true
					end if
				end repeat
				
			on error msg
				set ErrorRaised of returnTags to true
				set errormsg of returnTags to msg
			end try
		
			close access inFile
			return returnTags
		end DoTheReader
		--end script
		--end KSSCExifReader
		
		--*
		--* go through all the tags for this ifd
		--*
		on WalkTheTags(Offsetlist)
			if item 2 of Offsetlist > 100 then
				return --invalid ifd number of elements
			end if
			repeat with thisTagOffset from item 1 of Offsetlist to ((item 1 of Offsetlist) + (12 * (item 2 of Offsetlist))) by 12
				set theTagID to fixIntel(thisTagOffset, 2)
				repeat with tagTblEntry from 1 to count of myTagList
					if item 4 of item tagTblEntry of myTagList = theTagID then
						WorkThisTag(tagTblEntry, thisTagOffset)
						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 ""
			set tagDataType to fixIntel(tagOffset + 2, 2)
			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
				set theDataValue to fixIntel(tagOffset + 8, 2)
				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 for 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 = 141 then
				set mystring to read inFile from theDataAddress01 to theDataAddress01 + theDataLength - 2 as string
				set DigitizedDateTime of returnTags to mystring
			else if tagRout = 113 then
				set imageISO of returnTags to theDataValue
			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 = 106 then
				--orientation
				set OrientationTagSetting of returnTags to theDataValue
				set OrientationTagOffset of returnTags to tagOffset
				set Orientation of returnTags to setOrientation(theDataValue)
			else if tagRout = 109 then
				--maker note ifd pointer
				--sub ifd
				set numEntries to fixIntel(theDataAddress01, 2)
				set addEntry to {theDataAddress01 + 2, numEntries}
				set end of theIFDOffsets of returnTags to addEntry
				set curIFDCount to curIFDCount + 1
				if tagID = 37500 then set MakerNotePresent of returnTags to true
			else if tagRout = 118 then
				set JPGOffset of returnTags to theDataAddress01
			else if tagRout = 119 then
				set JPGLength of returnTags to theDataAddress01
			else if tagRout = 121 then
				set StripOffset of returnTags to theDataAddress01
				if (sExt = "CR2") and (IFDIndex = 1) then
					set BigJPGOffset of returnTags to theDataAddress01
					set BigJPGPresent of returnTags to true
				end if
			else if tagRout = 122 then
				set StripByteCount of returnTags to theDataAddress01
				if (sExt = "CR2") and (IFDIndex = 1) then
					set BigJPGLength of returnTags to theDataAddress01
				end if
			else if tagRout = 136 then --width
				set theWidth of returnTags to theDataValue
			else if tagRout = 137 then
				set theHeight of returnTags to theDataValue
			else if tagRout = 138 then
				set MainImageWidth of returnTags to theDataValue
			else if tagRout = 139 then
				set MainImageHeight of returnTags to theDataValue
			end if
		end WorkThisTag
		on setOrientation(Orient)
			--Private Sub DoOrientationCase(Orientation As Long)
			--   Select Case Orientation
			--        Case 1
			--            FoundEntry.Data = "Normal"
			--        Case 2
			--            FoundEntry.Data = "Normal"
			--        Case 3
			--            FoundEntry.Data = "Rotate Right 90"
			--        Case 4
			--            FoundEntry.Data = "Rotate Left 90"
			--        Case 5
			--            FoundEntry.Data = "5 left side top"
			--        Case 6
			--            FoundEntry.Data = "Rotate Right 90"
			--        Case 7
			--            FoundEntry.Data = "Normal"
			--        Case 8
			--            FoundEntry.Data = "Rotate Left 90"
			--    End Select
			--End Sub
			if (Orient = 1) or (Orient = 2) or (Orient = 7) then
				return "Normal"
			else if (Orient = 3) or (Orient = 6) then
				return "Rotate Right 90"
			else if (Orient = 4) or (Orient = 8) then
				return "Rotate Left 90"
			else
				return "Unkown"
			end if
		end setOrientation
		
		--*
		--* II (intel) values are stored backwards from MM so must turn them around
		--*
		on fixIntel(myOffset, num)
			if num = 4 then
				--work a full word value
				set myFW to read inFile from myOffset for 4
				return (((ASCII number (item 4 of myFW)) * 16777216) ¬
					+ ((ASCII number (item 3 of myFW)) * 65536) ¬
					+ ((ASCII number (item 2 of myFW)) * 256) ¬
					+ (ASCII number (item 1 of myFW))) as integer
			else
				--work a halfword value
				set myFW to read inFile from myOffset for 2
				if bIntel then
					return (((ASCII number (item 2 of myFW)) * 256) + (ASCII number (item 1 of myFW))) as integer
				else
					return (((ASCII number (item 1 of myFW)) * 256) + (ASCII number (item 2 of myFW))) as integer
				end if
			end if
		end fixIntel
		
		on clear_returnTags()
			set RowsPerStrip of returnTags to 0
			set StripOffset of returnTags to 0
			set StripByteCount of returnTags to 0
			set BitsPerSample of returnTags to 0
			set IPTCOffset of returnTags to 0
			set IPTCLength of returnTags to 0
			set JPGOffset of returnTags to 0
			set JPGLength of returnTags to 0
			set MakerNotePresent of returnTags to false
			set theWidth of returnTags to 0
			set theHeight of returnTags to 0
			set imageISO of returnTags to 0
			set MainImageWidth of returnTags to 0
			set MainImageHeight of returnTags to 0
			set Orientation of returnTags to 0
			set BigJPGOffset of returnTags to 0
			set BigJPGLength of returnTags to 0
			set BigJPGPresent of returnTags to false
			set JFIF of returnTags to false
			set errormsg of returnTags to ""
			set ErrorRaised of returnTags to false
			set ExifPresent 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 OrientationTagOffset of returnTags to 0
			set OrientationTagSetting of returnTags to 0
			set rawtif of returnTags to false
			set NikonJPGOffset of returnTags to 0
			set NikonJPGBytes of returnTags to 0
		end clear_returnTags
		--*
		--* 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