Code: Select all
EXIFX( File, p* ) { ; v0.34 - By SKAN on D1BJ/D1C5 @ goo.gl/JDLQs7
Local
Static y := [1,1,2,4,8,1,1,2,4,8,4,8]
Static OmitList := {0x02BC:"", 0x927C:"",0xA302:""}
Global EXIFX_ErrN := 0, EXIFX_ErrM := ""
VarSetCapacity(v,16,0)
If ( (f:=FileOpen(File,"r"))=0 or f.Length<4096 )
{
f<>0 ? f.Close() : 0
EXIFX_ErrN := 1, EXIFX_ErrM := "File open error/File too small"
Return
}
TOJ := 0 ; TIFF or JPEG?
ot := 0 ; Offset to TIFF header
II := "" ; Is encoding Intel endian?s
f.RawRead(v,16)
; TIFF / TIFF based RAW
If ( ( be:=NumGet(v,0,"UShort") ) ; Byte encoding
&& ( be=0x4949 || be=0x4D4D ) ) ; 'II' or 'MM'
{
II := (be=0x4949) ; Is encoding little endian?
n := NumGet(v,2,"UShort") ; Magic number
mn := ( II ? n : (n & 0xFF)<<8 | n>>8 ) ; converted to Little endian if required
If ! ( mn=0x002A ; Regular TIFF/RAW
|| mn=0x0055 ; Panasonic RAW
|| mn=0x4F52 ) ; Olympic RAW
{
EXIFX_ErrN := 3, EXIFX_ErrM := "Unknown magic number"
}
Else
{
TOJ := 1
}
}
Else ; JPEG / EXIF
If ( ( NumGet(v,0,"UInt") & 0xFFFFFF )=0xFFD8FF )
{
l := 0 ; Length of data
m := 0xFFE0 ; APP marker range = 0xFFE0-0xFFEF
x := 0
f.Pos := 4
; 0x66697845 = AStr:'Exif'
While ( (m >> 4)=0x0FFE && (x & 0xFFFFFFFF)<>0x66697845 )
{
f.Seek(l-2,1)
If ( f.AtEOF )
{
Break
}
n := f.ReadUInt() ; Marker + data length
n := (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24
m := n >> 16 ; Marker
l := n & 0xFFFF ; Length of data
If ( m=0xFFE1 )
{
x := f.ReadInt64() ; APP1 data
f.Pos -= 8
}
}
If ( (x & 0xFFFFFFFF)=0x66697845 ) ; 0x66697845 = AStr:'Exif'
{
II := ( (X>>48)=0x4949 )
f.Pos += 6
ot := f.Pos
VarSetCapacity(V,8,0)
f.RawRead(v,8)
TOJ := 2
}
Else
{
EXIFX_ErrN := 2, EXIFX_ErrM := "EXIF not found in JPEG"
}
}
Else
If ( NumGet(v,0,"UInt") = 0x4D524D00 ) ; AStr:'.\0MRM'
{
b := 0
l := 0
f.Pos := 8
While ( b <> 0x57545400 )
{
f.Pos += l
If ( f.AtEOF )
{
break
}
b := f.ReadUInt()
n := f.ReadUInt()
l := (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24
}
If ( b = 0x57545400 )
{
ot := f.Pos
VarSetCapacity(v,16,0)
f.RawRead(v,8)
II := (NumGet(v,0,"UShort") = 0x4949)
TOJ := 1
}
Else
{
ErrorLevel := 6, EXIFX_ErrM := "Invalid Minolta RAW"
}
}
Else
{
EXIFX_ErrN := 4, EXIFX_ErrM := "Unknown file type"
}
If ( EXIFX_ErrN = 0 )
{
n := NumGet(v,4,"UInt") ; Offset to IFD
oi := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
If ( oi > f.Length )
{
EXIFX_ErrN := 5, EXIFX_ErrM := "Invalid offset in IFD"
}
}
If ( EXIFX_ErrN || p.1="Test" )
{
f.Close()
Return ( EXIFX_ErrN ? "" : II ? 1 : 2 )
}
f.Pos := ot + oi
VarSetCapacity(d,4096,0)
dsz := 0
Dir := 0
Chk := 0
If ( p.Count() > 1 )
{
chk := p.RemoveAt(1)
chk := ( chk="==" ? 1 : (chk="<>" || chk="!=") ? 2 : (chk=1 || chk=2) ? chk : 0 )
If ( chk )
{
chkMode := chk-1
If IsObject(p.1)
{
chkList := p.1
}
Else
{
chkList := []
For i,k in p
{
chkList[k] := ""
}
}
}
p := ""
}
; Flags for File/Thumbnail properties. -3 will exclude Thumbnail propes, -15 will exclude File props
PropFlags := (chk ? chkList.HasKey(-3) | chkList.HasKey(-15)<<1 : 0)
XIF := ( PropFlags=0 ? {-15:"",-14:"",-13:"",-12:"",-11:"",-10:"",-9:"",-8:"",-3:"",-2:"",-1:""}
: PropFlags=1 ? {-15:"",-14:"",-13:"",-12:"",-11:"",-10:"",-9:"",-8:""}
: PropFlags=2 ? {-3:"",-2:"",-1:""}
: {} )
; ------------------------------------------------------------------------------------------------------
While ( True ) ; MASTER LOOP BEGINS
{
f.RawRead(v,2)
n := NumGet(v,"UShort")
nEnt := ( II ? n : (n & 0xFF)<<8 | n>>8 ) ; Number of entries (tags) in IFD
ReqZ := (nEnt*12) +4 ; Each tag is 12 bytes + 4 bytes for next IFD
VarSetCapacity(V, Reqz,0)
f.RawRead(v,Reqz)
XIF.SetCapacity(XIF.GetCapacity() + nEnt) ; Increase capacity of XIF to hold new entries
pIFDE := &v-12
While ( A_Index <= nEnt && (pIFDE += 12) ) ; WORKER LOOP BEGINS
{
n := NumGet(pIFDE+0,"UShort")
tag := ( II ? n : (n & 0xFF)<<8 | n>>8 )
If ( tag>0xEA1B || (tag<0x0100 && Dir<>0x8825) || OmitList.HasKey(tag) )
{
Continue
}
If ( Chk && (tag=0x014A || tag=0x8769 || tag=0x8825)=0 )
{
If ( ChkList.HasKey(tag)=ChkMode )
{
Continue
}
}
n := NumGet(pIFDE+2,"UShort")
typ := ( II ? n : (n & 0xFF)<<8 | n>>8 )
If ( typ>13 ) ; Something's strange
{
Break ; Abort parsing this IFD
}
n := NumGet(pIFDE+4,"UInt")
cnt := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
dsz := y[typ] * cnt
n := NumGet(pIFDE+8,"UInt")
If ( dSz <= 4 )
{
VarSetCapacity(d,6,0)
NumPut(n,d,0,"UInt")
}
Else
{
VarSetCapacity(d,dsz+2,0)
n := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
f.Pos := ot + n
f.RawRead(d,dsz)
}
If ( tag>0x9C99 && tag<0x9CA0 ) ; XP Tags
{
XIF[tag] := StrGet(&d, "utf-16")
Continue
}
If ( (typ=1 || typ=7) && dsz<5 ) ; String
{
x := StrGet(&d,"cp0")
If x is not Integer
{
x := ""
Loop % ( dsz )
{
x .= ( A_Index>1 ? A_Space : "" ) . NumGet(d,A_Index-1, "Uchar" )
}
}
XIF[tag] := x
Continue
}
If ( typ=1 || typ=2 || typ=7 ) ; String
{
XIF[tag] := StrGet(&d, "utf-8")
Continue
}
If ( typ=3 || typ=8 ) ; unsigned Short / signed Short
{
x := ""
Loop % (dsz/2 )
{
n := NumGet(d,2*(A_Index-1), typ=3 ? "UShort" : "Short" )
n := ( II ? n : (n & 0xFF)<<8 | n>>8 )
x .= ( A_Index>1 ? A_Space : "" ) . n
}
XIF[tag] := x
Continue
}
If ( typ=4 || typ=9 ) ; unsigned Long / signed Long
{
x := ""
Loop % (dsz/4 )
{
n := NumGet(d,4*(A_Index-1), typ=4 ? "UInt" : "Int" )
n := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
x .= ( A_Index>1 ? A_Space : "" ) . n
}
XIF[tag] := x
Continue
}
If ( typ=5 || typ=10 ) ; unsigned Rational / signed Rational
{
x := ""
q := 0
Loop % ( dsz/8 )
{
n := NumGet(d,q, typ=5 ? "UInt" : "Int")
n1 := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
n := NumGet(d,q+4, typ=5 ? "UInt" : "Int")
n2 := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
x .= ( A_Index>1 ? A_Space : "" ) . ( n1 . "/" . n2 )
q += 8
}
XIF[tag] := x
Continue
}
If ( typ=11 || typ=12 ) ; single Float / double float
{
XIF[tag] := "Float" ; Too rare to handle
Continue
}
XIF[tag] := "Unknown data type. Please report to Dev."
} ; WORKER LOOP ENDS
If ( Dir=0 ) ; IFD0
{
n := NumGet( pIFDE+12, "UInt" )
n := ( II ? n : (n & 255)<<24 | (n>>8 & 255)<<16 | (n>>16 & 255)<<8 | n>>24 )
If ( n )
{
Dir := 1 ; IFD1
f.SeeK(ot+n,0)
Continue
}
}
If XIF.HasKey(0x014A) && (Dir:=0x014A) ; SubIFD
{
n := XIF[Dir]
XIF.Delete(Dir)
If (n+0)
{
f.SeeK(ot+n,0)
Continue
}
}
If XIF.HasKey(0x8769) && (Dir:=0x8769) ; ExifIFD
{
n := XIF[Dir]
XIF.Delete(Dir)
If (n+0)
{
f.SeeK(ot+n,0)
Continue
}
}
If XIF.HasKey(0xA005) && (Dir:=0xA005) ; InteropIFD
{
n := XIF[Dir]
XIF.Delete(Dir)
If (n+0)
{
f.SeeK(ot+n,0)
Continue
}
}
If XIF.HasKey(0x8825) && (Dir:=0x8825) ; GpsIFD
{
n := XIF[Dir]
XIF.Delete(Dir)
If (n+0)
{
f.SeeK(ot+n,0)
Continue
}
}
Break ; Break MASTER LOOP
} ; MASTER LOOP ENDS
; ------------------------------------------------------------------------------------------------------
If ! ( PropFlags & 2 ) ; Exclude File properties = 2
{
XIF[-15] := file
VarSetCapacity(d,84,0) ; 52 (+32 for 2 SYSTEMTIME structures)
DllCall( "GetFileInformationByHandle", "Ptr",f.__Handle, "Ptr",&d )
XIF[-14] := Format( "{1:08X}-{2:08X}{3:08X}"
, Numget(d,28,"UInt") ; dwVolumeSerialNumber
, Numget(d,44,"UInt") ; nFileIndexHigh
, Numget(d,48,"UInt") ) ; nFileIndexLow
XIF[-13] := f.Length ; File size
DllCall( "FileTimeToLocalFileTime", "Ptr",&d+4, "Ptr",&d+20 )
DllCall( "FileTimeToSystemTime", "Ptr",&d+20, "Ptr",&d+52 )
DllCall( "FileTimeToLocalFileTime", "Ptr",&d+12, "Ptr",&d+20 )
DllCall( "FileTimeToSystemTime", "Ptr",&d+20, "Ptr",&d+68 )
XIF[-12] := Format( "{:04}:{:02}:{:02} {:02}:{:02}:{:02}"
, NumGet(d,52,"UShort"), NumGet(d,54,"UShort"), NumGet(d,58,"UShort")
, NumGet(d,60,"UShort"), NumGet(d,62,"UShort"), NumGet(d,64,"UShort") )
XIF[-11] := Format( "{:04}:{:02}:{:02} {:02}:{:02}:{:02}"
, NumGet(d,68,"UShort"), NumGet(d,70,"UShort"), NumGet(d,74,"UShort")
, NumGet(d,76,"UShort"), NumGet(d,78,"UShort"), NumGet(d,80,"UShort") )
XIF[-10] := Format("0x{:08X}", NumGet(d,0,"UInt"))
XIF[ -9] := ot
XIF[ -8] := (II ? "II" : "MM" )
}
If ! ( PropFlags & 1 ) ; Exclude Thumbnail properties = 1
{
If ( ( tno := ( XIF[0x0201] +0 ) )
&& ( tsz := ( XIF[0x0202] +0 ) )
&& ( tno+tsz < f.Length ) )
{
f.Seek(tno+(TOJ=2 ? ot : 0),0)
n := f.ReadUInt() ; Read 24bit from header
If ( (n & 0xFFFFFF)=0xFFD8FF ) ; 0xFFD8FF is JPEG magic number in header
{
XIF.SetCapacity(-1,tsz)
f.Seek(-4,1)
p := XIF.GetAddress(-1)
f.RawRead(p+0,tsz)
XIF[-2] := tsz
pEnd := p+tsz
err := 0
p += 2 ; Ascertain thumbnail dimensions
While ( (Segm := NumGet(p+0,"UShort")) > 0x00FF )
{
Segm >>= 8
If ( Segm>=0xC0 && Segm<=0xCF && Segm<>0xCC && Segm<>0xC4 )
{
Break
}
n := NumGet(p+2,"UShort" )
sLen := ( (n & 0xFF)<<8 | n>>8 ) + 2
p += sLen
If (err := p > pEnd)
{
Break
}
}
If (err=0)
{
n := NumGet(p+7,"UShort")
W := ( (n & 0xFF)<<8 | n>>8 )
n := NumGet(p+5,"UShort")
H := ( (n & 0xFF)<<8 | n>>8 )
xIF[ -3] := W "x" H
}
}
}
}
f.Close()
XIF.SetCapacity(-1)
Return XIF
}
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Code: Select all
XT := EXIFX( "somefile.jpg") ; Returns all (possible) tags as an object.
XT := EXIFX( "somefile.jpg", "<>",0x0132) ; Excludes tag 0x0132 from resulting object (if present in source image)
XT := EXIFX( "somefile.jpg", "==",0x9003,0x9004 ) ; Will extract only those two tags (if present in source image)
The returned object is a sparsely populated (integer keys only) associated array. No nested values.
The function was written in AHK 1.1.30.00 Unicode and requires newer functionalities like Force local mode.
Tag reference and utils:
Standard Exif Tags
Extensive list of tags.
It wouldn't have been possible to write this function without the help from
HTML dump feature of ExifTool by Phil Harvey