EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

04 Dec 2018, 16:36

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     
}     

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Usage examples:

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)
EXIFX() will handle only tags in the range of 0x0100 and 0xEA1C
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
My Scripts and Functions: V1  V2
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

21 Sep 2020, 14:39

It is a very nice work. It is lovely! Congratulations Skan!

I wonder if one could change it to remove the exif metadata? Eg. where you do all those NumGets, one could overwrite them using NumPut() with zeros?

Thank you .

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

21 Sep 2020, 19:46

robodesign wrote: It is a very nice work. It is lovely! Congratulations Skan!
 
Thanks. :)
 
robodesign wrote:I wonder if one could change it to remove the exif metadata? Eg. where you do all those NumGets, one could overwrite them using NumPut() with zeros?
Any specific reason to do this?. There are tools which can remove complete EXIF from JPEG.
I think, I could manage to write one.
I use this function with CR2, ARW raw files which shouldn't be modified in any way.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

22 Sep 2020, 02:47

Hello, Skan.

I'd like to implement an option to remove EXIFF metadata in my image viewer. I have a friend who wants to ditch IrfanView for my image viewer, but would miss this option from IrfanView....

Would you be able to do this? I'd credit your code...

Thank you.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

22 Sep 2020, 03:01

robodesign wrote: I'd like to implement an option to remove EXIFF metadata in my image viewer. I have a friend who wants to ditch IrfanView for my image viewer, but would miss this option from IrfanView....
I need some time. I'm familiar with JPEG format but not with TIFF.
:thumbup:
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: EXIFX() : Exif tags as object. For JPEG,TIFF and TIFF styled RAW

22 Sep 2020, 05:56

SKAN wrote:
22 Sep 2020, 03:01
robodesign wrote: I'd like to implement an option to remove EXIFF metadata in my image viewer. I have a friend who wants to ditch IrfanView for my image viewer, but would miss this option from IrfanView....
I need some time. I'm familiar with JPEG format but not with TIFF.
:thumbup:
hey, thank you very much ; I will let him know!
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 86 guests