PureBasic Forum
http://forums.purebasic.com/english/

Module to easy use Exiftool in your application
http://forums.purebasic.com/english/viewtopic.php?f=12&t=72105
Page 1 of 1

Author:  thyphoon [ Thu Jan 17, 2019 9:56 am ]
Post subject:  Module to easy use Exiftool in your application

I am developing an application to replace Google's Picasa.
For that I made a small module to control the fantastic tool that is ExifTool.
ExidTool is a Tool to manipulate metadata from images

ExifTool loads once only... and i send commands to StdIn
You can Download Exiftool: https://www.sno.phy.queensu.ca/~phil/exiftool/
Edit :
-2019-02-02 Version 4.6
Code:
; ********************************************************************
; Program:           ETUE (Easy To use Exiftool)
; Version:           4.6
; Description:       use the verry good Perl Package Exiftool
; Author:            Thyphoon
; Date:              January, 2019
; License:           Free, unrestricted, credit
;                    appreciated but not required.
; Note:              Please share improvement !
;
; Exiftool info:     ExifTool is a platform-independent Perl library plus a command-line application for reading, writing and editing meta information in a wide variety of files
; Exiftool Web:      https://www.sno.phy.queensu.ca/~phil/exiftool/
; Exiftool author:   Phil Harvey
; ********************************************************************

EnableExplicit

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
    #SP = "\"
  CompilerCase #PB_OS_Linux
    #SP = "/"
  CompilerCase #PB_OS_MacOS
    #SP = "/"
CompilerEndSelect

CompilerIf #PB_Compiler_Thread=#False
  CompilerError("You must enable Compiler threadsafe")
  End
CompilerEndIf

DeclareModule Exiftool
  Declare.b SetExecutableFilePath(FilePath.s)
  Declare.s GetExecutableFilePath()
  Declare Start()
  Declare.b IsRun()
  Declare.b Command(cmd.s)
  Declare.i Execute(Event.i=0,WindowEvent.i=0,*ReturnDataProcedure=0)
  Declare.b Stop()
 
  Declare.s GetResultStdout(ExecuteID.i)
  Declare.s GetResultStdErr(ExecuteID.i)
  Declare.i GetResultRawOut(ExecuteID.i)
  Declare.b WaitExecute(ExecuteID.i,TimeOut.i=#False)
  Declare.b WaitReady(TimeOut.i=5000)
  Declare FreeResult(ExecuteID.i)
  Declare Quit()
EndDeclareModule

Module Exiftool
 
  #Verbose=#True   ;TODO #True or #False  if you want a exiftool Log file
 
  Prototype.i ReturnData(ExecuteID.i)
 
  Structure Execute
    ReadyToUse.b          ;#True if Stdout and StdErr is ready to use
    StdErr.s
    *RawOut
    Event.i
    EventWindow.i
    ReturnData.ReturnData
  EndStructure
 
  Structure param
    VerboseFileId.i
    VerboseMutex.i
    NumberCount.i
    Map Execute.Execute()
    ExecuteMutex.i
    Thread.i
    ProgFilePath.s
    ProgHandle.i
  EndStructure
 
  Global param.param
 
  param\ExecuteMutex=CreateMutex()
  param\VerboseMutex=CreateMutex()
 
  Procedure StartVerbose()
    If #Verbose=#True
      LockMutex(param\VerboseMutex)
     
      param\VerboseFileId=CreateFile(#PB_Any,"ExifTool.log")
     
      UnlockMutex(param\VerboseMutex)
    EndIf
  EndProcedure
 
  Procedure Verbose(txt.s)
    If #Verbose=#True
      LockMutex(param\VerboseMutex)
      If IsFile(param\VerboseFileId)
        WriteStringN(param\VerboseFileId,txt)
      EndIf
      UnlockMutex(param\VerboseMutex)
    EndIf
  EndProcedure
 
  Procedure StopVerbose()
    If #Verbose=#True
      LockMutex(param\VerboseMutex)
      CloseFile(param\VerboseFileId)
      UnlockMutex(param\VerboseMutex)
    EndIf
  EndProcedure
 
  Procedure.b SetExecutableFilePath(FilePath.s)
    If FileSize(FilePath)>0
      param\ProgFilePath=FilePath
      ProcedureReturn #True
    Else
      MessageRequester("Exiftools","Exiftool No Found",#PB_MessageRequester_Error)
      ProcedureReturn #False
    EndIf
  EndProcedure
 
  Procedure.s GetExecutableFilePath()
    ProcedureReturn param\ProgFilePath
  EndProcedure
 
  Procedure.s GetResultStdout(ExecuteID.i)
    Protected Stdout.s
    LockMutex(param\ExecuteMutex)
    If param\Execute(Str(ExecuteID))\RawOut>0
      Stdout=PeekS(param\Execute(Str(ExecuteID))\RawOut,MemorySize(param\Execute(Str(ExecuteID))\RawOut),#PB_UTF8)
    Else
      Stdout=""
      Verbose("NO RAWOUT")
    EndIf
    UnlockMutex(param\ExecuteMutex)
    ProcedureReturn Stdout
  EndProcedure
 
  Procedure.s GetResultStdErr(ExecuteID.i)
    Protected StdErr.s
    LockMutex(param\ExecuteMutex)
    StdErr=param\Execute(Str(ExecuteID))\StdErr
    UnlockMutex(param\ExecuteMutex)
    ProcedureReturn StdErr
  EndProcedure
 
  Procedure.i GetResultRawOut(ExecuteID.i)
    Protected *Data
    LockMutex(param\ExecuteMutex)
    If param\Execute(Str(ExecuteID))\RawOut>0
      *Data=param\Execute(Str(ExecuteID))\RawOut
    Else
      *Data=0
    EndIf
    UnlockMutex(param\ExecuteMutex)
    ProcedureReturn *Data
  EndProcedure
 
  Procedure LaunchExifTool(p.l=0)
    Protected Output.s,Stdout.s,StdoutEnd.s,StdErr.s,ExecuteID.s
    Protected result.i
    Protected dataSize.i
    Protected *data
    *buffer = 0
    Protected  ForceEnd.b=#False
    param\ProgHandle=RunProgram(param\ProgFilePath,"-stay_open True -@ -",GetPathPart(param\ProgFilePath),#PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_Hide)
    If IsProgram(param\ProgHandle)
      ItIsEnd=#False
      While ProgramRunning(param\ProgHandle)
        StdErr.s=ReadProgramError(param\ProgHandle)
        If StdErr<>"":Verbose("/!\ ExifTool ERROR :"+StdErr+"<"):Debug "/!\ ExifTool ERROR :"+StdErr+"<":EndIf
        result.i=AvailableProgramOutput(param\ProgHandle)
        If result>0
            *buffer = AllocateMemory(result)
            ReadProgramData(param\ProgHandle,*buffer,result)
            Stdout=PeekS(*buffer,result,#PB_UTF8)
            ;Test only the End to found the {ready
            StdoutEnd=PeekS(*buffer+result-15,15,#PB_Ascii)
            FindStart.i=FindString(StdoutEnd,"{ready",0)
            If FindStart>0
              FindStop.i=FindString(StdoutEnd,"}",FindStart+6)
              Debug("FIND {Ready"+Mid(Stdout,FindStart+6,FindStop-(FindStart+6))+"} Start:"+Str(FindStart)+" Stop:"+Str(FindStop))
              ItIsEnd=#True
            Else
              ItIsEnd=#False
            EndIf
           
            If *data=0
              *data = AllocateMemory(result)
              dataSize=0
              time.i=ElapsedMilliseconds()
            Else
              time.i=ElapsedMilliseconds()
              dataSize=MemorySize(*data)
            EndIf
       
          If ItIsEnd=#True
            ; Delete {readyx} from *buffer
            NoreadySize=Len(Right(StdoutEnd,Len(StdoutEnd)-FindStart+1)) ; {ready1} = 10
            Verbose("NoreadySize="+Str(NoreadySize))
          Else
            NoreadySize=0
          EndIf
          If dataSize+result-NoreadySize>0 ; protect if empty return
            *data = ReAllocateMemory(*data,dataSize+result-NoreadySize)
            Verbose("ReAllocateMemory("+Str(*Data)+","+Str(dataSize)+"+"+Str(result)+"-"+Str(NoreadySize)+")")
            CopyMemory(*buffer,*data+dataSize,MemorySize(*buffer)-NoreadySize)
            Debug "FinalBuffer:"+Str(*Data+dataSize)
          Else
            ;If return is empty No need more memory
            FreeMemory(*data):*data=0
          EndIf
          FreeMemory(*buffer):
         
          ;if it's the end
          If ItIsEnd=#True;FindString(Stdout,"{ready",0);Left(Stdout,6)="{ready"
            ItIsEnd=#False; ready to continue
            ExecuteID=Mid(StdoutEnd,FindStart+6,FindStop-(FindStart+6)) ;at the end chr(13)+chr(10)
            Verbose(">Execute{"+ExecuteID+"}")
            LockMutex(param\ExecuteMutex)
            param\execute(ExecuteID)\StdErr=StdErr
            param\Execute(ExecuteID)\RawOut=*data
            param\Execute(ExecuteID)\ReadyToUse=#True
            ;Send Event to inform the result is available
            If param\execute(ExecuteID)\Event<>0
              PostEvent(param\execute(ExecuteID)\Event,param\execute(ExecuteID)\EventWindow,0,0,Val(ExecuteID))
              Verbose("Post Event:"+Str(param\execute(ExecuteID)\Event)+" from Window:"+Str(param\execute(ExecuteID)\EventWindow))
            EndIf
            ;If Callback use id
            If  param\execute(ExecuteID)\ReturnData<>0
              param\execute(ExecuteID)\ReturnData(Val(ExecuteID)) ; TODO out this function to Call it afer Unlockmutex
            EndIf
            *data=0
            UnlockMutex(param\ExecuteMutex) 
          EndIf
        EndIf
       
      Wend
     
      ;0 on success, or 1 if an error occurred, or 2 if all files failed
      Verbose("Code Fin :"+Str(ProgramExitCode(param\ProgHandle)))
      CloseProgram(param\ProgHandle) ; Ferme la connection vers le programme
    Else
      Debug "No Found"+ param\ProgFilePath
      End
    EndIf
  EndProcedure
 
  Procedure Start()
    StartVerbose()
    param\Thread=CreateThread(@LaunchExifTool(),0)
  EndProcedure
 
  Procedure.b IsRun()
    ProcedureReturn IsThread(param\Thread)
  EndProcedure
 
  Procedure.b Command(cmd.s)
    Verbose("Command>"+cmd)
    If IsProgram(param\ProgHandle) And ProgramRunning(param\ProgHandle)
      WriteProgramStringN(param\ProgHandle,Cmd)
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
 
  Procedure.i Execute(Event.i=0,EventWindow.i=0,*ReturnDataProcedure=0)
    Protected ExecuteID.s
    param\NumberCount=param\NumberCount+1
    ExecuteID=Str(param\NumberCount)
    Command("-execute"+ExecuteID)
    LockMutex(param\ExecuteMutex)
    param\execute(ExecuteID)\Event=Event
    param\execute(ExecuteID)\EventWindow=EventWindow
    param\execute(ExecuteID)\ReturnData=*ReturnDataProcedure
    UnlockMutex(param\ExecuteMutex)
    ProcedureReturn param\NumberCount
  EndProcedure
 
  Procedure.b Stop()
    Protected Ex.i
    Command("-stay_open")
    Command("False")
    Ex=Execute()
    WaitExecute(Ex,-1)
  EndProcedure
 
  Procedure.b WaitReady(TimeOut.i=5000)
    Protected StartTime.i=ElapsedMilliseconds()
    Repeat
     
      If TimeOut>0 And ElapsedMilliseconds()>StarTime+TimeOut
        ProcedureReturn #False
      EndIf
      Delay(100)
    Until  IsProgram(param\ProgHandle)
    Verbose("ExifTool is Ready")
    ProcedureReturn #True
  EndProcedure
 
  Procedure.b WaitExecute(ExecuteID.i,TimeOut.i=#False)
    Protected StartTime.i=ElapsedMilliseconds()
    Repeat
      LockMutex(param\ExecuteMutex)
      If FindMapElement(param\Execute(),Str(ExecuteID)) And param\Execute()\ReadyToUse=#True
        UnlockMutex(param\ExecuteMutex)
        Verbose("Wait Execute"+Str(ExecuteID)+" and it's finish")
        ProcedureReturn #True
      EndIf
      UnlockMutex(param\ExecuteMutex)
      If TimeOut>0 And ElapsedMilliseconds()>StarTime+TimeOut
        Verbose("No Finish Execute"+Str(ExecuteID)+" Timeout ! ")
        ProcedureReturn #False
      EndIf
      Delay(500)
    Until IsProgram(param\ProgHandle)=0
  EndProcedure
 
  Procedure FreeResult(ExecuteID.i)
    LockMutex(param\ExecuteMutex)
    If param\Execute(Str(ExecuteID))\RawOut>0
      FreeMemory(param\Execute(Str(ExecuteID))\RawOut)
    EndIf
    DeleteMapElement(param\Execute(),Str(ExecuteID))
    UnlockMutex(param\ExecuteMutex)
  EndProcedure
 
  Procedure Quit()
    If IsRun()
      Stop()
      Protected time.i=ElapsedMilliseconds()
      Repeat
        Delay(1)
        If ElapsedMilliseconds()>time+5000
          MessageRequester("ExifTool","Can't Stop ExifTool Thread... ",#PB_MessageRequester_Warning|#PB_MessageRequester_Ok)
          Break
        EndIf   
      Until Exiftool::IsRun()=#False
    EndIf
  EndProcedure
 
EndModule

;- MAIN TEST

CompilerIf #PB_Compiler_IsMainFile
  UseJPEGImageDecoder()
 
  Enumeration
    #ModeExtractPreviewImage
    #ModeSimpleLoadImage
  EndEnumeration
  ;In this Demo You can choose the MOde to do a preview image
  #Mode=#ModeExtractPreviewImage ;TODO <- You Canc hange the MODE
 
  Enumeration
    #Win_Main
    #Gdt_LoadImage
    #Gdt_Image
    #Gdt_MetaList
    #Gdt_Description
    #Gdt_State
    #Gdt_OnOff
    #Gdt_Save
  EndEnumeration
 
  Exiftool::SetExecutableFilePath(GetCurrentDirectory()+"exiftool"+#SP+"exiftool.exe") ; TODO Change the path to found the exiftool.exe
  Exiftool::Start()
 
  Enumeration #PB_Event_FirstCustomValue
    #Cust_Event_Exiftools_Read_Metadatas
    #Cust_Event_Exiftools_Write_Metadatas
  EndEnumeration
 
  Procedure ReturnData(ExecuteID)
    Protected Stdout.s,StdError.s
    Stdout=Exiftool::GetResultStdout(ExecuteID)
    StdError=Exiftool::GetResultStdErr(ExecuteID)
    If StdError<>""
      MessageRequester("Exiftool",StdError,#PB_MessageRequester_Info)
    EndIf
    If Stdout<>""
      MessageRequester("Exiftool",Stdout,#PB_MessageRequester_Error)
    EndIf
  EndProcedure
 
  ;test waitexecute
  Exiftool::WaitReady(-1) ;TODO First wait Exiftool is initialized
  Exiftool::Command("-ver")
  Define Ei.i=Exiftool::Execute()
  If Exiftool::WaitExecute(Ei,-1)=#True;TODO Wait the Exiftool data return berfore continue -1 = No wait limit. But you can put a TimeOut
   
    Debug "You Use Exiftool version "+Exiftool::GetResultStdout(Ei)+"You can Thanks Phil Harvey for this great tool"
    If Exiftool::GetResultStdErr(Ei)<>""
      Debug "You Have an Error:"+Exiftool::GetResultStdErr(Ei)
    EndIf
  Else
    Debug "Go out before Execute finish"
  EndIf   
  Exiftool::FreeResult(Ei) ;TODO After take data clean used memory
 
  ;Declare.i Execute(Event.i=0,WindowEvent.i=0,*ReturnDataProcedure=0)
 
  If OpenWindow(#Win_Main, 0, 0, 800, 250,"ExifTool test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ButtonGadget(#Gdt_LoadImage,0,0,320,25,"Load Image")
    CanvasGadget(#Gdt_Image,0,25,320,200)
    ListIconGadget(#Gdt_MetaList,800-400,0,400,250,"Name",200)
    AddGadgetColumn(#Gdt_MetaList,1,"Value",200)
   
    StringGadget(#Gdt_Description,0,225,320,25,""):GadgetToolTip(#Gdt_Description,"Description")
    TextGadget(#Gdt_State,340,10,50,25,"")
    ButtonGadget(#Gdt_OnOff,340,50,50,25,"ON/OFF")
    ButtonGadget(#Gdt_Save,320,225,50,25,"SAVE")
    Define Event.i
    Repeat
     
      Event=WaitWindowEvent()
      Select Event
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #Gdt_LoadImage
              Define FilePath.s
              Define.l Width,Height,x,y
              Define.i Image
              Define.f ImgRatio,ContRatio
              FilePath.s=OpenFileRequester("Choose Image", "", "*.jpg|*.pef", 0 )
              If FilePath<>"" And FileSize(FilePath)>0 And (LCase(GetExtensionPart(FilePath))="jpg" Or LCase(GetExtensionPart(FilePath))="pef")
               
                Select #Mode
                  Case #ModeExtractPreviewImage
                    Exiftool::Command("-b")
                    ;Exiftool::Command("-ThumbnailImage")
                    Exiftool::Command("-JpgFromRaw")
                    ;TODO This Toow Line are very Important to Support Path With Special Character Or You Will Have "File Not Found" Error.
                    Exiftool::Command("-charset")                   
                    Exiftool::Command("FILENAME=utf8")
                    Exiftool::Command(FilePath)
                    Ei.i=Exiftool::Execute()
                    If Exiftool::WaitExecute(Ei,-1)=#True
                     
                      If Exiftool::GetResultRawOut(Ei)>0 ;Always check if your are a Memory pointer or 0
                        Debug MemorySize(Exiftool::GetResultRawOut(Ei))
                        Image.i=CatchImage(#PB_Any,Exiftool::GetResultRawOut(Ei))
                      Else
                        MessageRequester("Oups !","No Preview Image on this file May be use a photo and not juste a jpg image",#PB_MessageRequester_Info|#PB_MessageRequester_Ok)
                      EndIf
                      Exiftool::FreeResult(Ei) ;TODO After take data clean used memory
                    EndIf
                  Case #ModeSimpleLoadImage
                    Image.i=LoadImage(#PB_Any,FilePath)
                   
                EndSelect
               
               
                If Image>0
                  ImgRatio = ImageWidth(Image) / ImageHeight(Image)
                  ContRatio = GadgetWidth(#Gdt_Image) /GadgetHeight(#Gdt_Image)
                  If ImgRatio<ContRatio
                    Width=ImageWidth(Image)*GadgetHeight(#Gdt_Image)/ImageHeight(Image)
                    height=GadgetHeight(#Gdt_Image)
                    x=(GadgetWidth(#Gdt_Image)-Width)/2
                    y=0
                  Else
                    Width=GadgetWidth(#Gdt_Image)
                    height=ImageHeight(Image)*GadgetWidth(#Gdt_Image)/ImageWidth(Image)
                    x=0
                    y=(GadgetHeight(#Gdt_Image)-height)/2
                  EndIf
                  StartDrawing(CanvasOutput(#Gdt_Image))
                  DrawImage(ImageID(Image),x,y,Width,Height)
                  StopDrawing()
                EndIf
                Exiftool::Command("-s")                         ; -s[NUM]     (-short)           Short output format
                Exiftool::Command("-args")                      ; -args       (-argFormat)       Format metadata as exiftool arguments
                Exiftool::Command("-a")                         ; -a          (-duplicates)      Allow duplicate tags to be extracted
                Exiftool::Command("-g")                         ; -g[NUM...]  (-groupHeadings)   Organize output by tag group
                Exiftool::Command("-G")                         ; -G[NUM...]  (-groupNames)      Print group name for each tag
                Exiftool::Command("-n")                         ; -n          (--printConv)      No print conversion (output the coordinates as signed decimal degrees)
                                                                ;TODO This Toow Line are very Important to Support Path With Special Character Or You Will Have "File Not Found" Error.
                Exiftool::Command("-charset")                   
                Exiftool::Command("FILENAME=utf8")
                Exiftool::Command(FilePath)
                Exiftool::Execute(#Cust_Event_Exiftools_Read_Metadatas,#Win_main)  ;TODO Exemple to use Event to return data
              EndIf
            Case #Gdt_OnOff
             
              If Exiftool::IsRun()
                Debug "Debug OFF"
                Exiftool::Stop()
              Else
                Debug "Debug ON"
                Exiftool::Start()
              EndIf
            Case #Gdt_Save
              Exiftool::Command("-Exif:ImageDescription="+GetGadgetText(#Gdt_Description))
              ;TODO This Toow Line are very Important to Support Path With Special Character Or You Will Have "File Not Found" Error.
              Exiftool::Command("-charset")                   
              Exiftool::Command("FILENAME=utf8")
              Exiftool::Command(FilePath)
              Exiftool::Execute(0,0,@ReturnData()) ;TODO Exemple to Use CallBack to return data
             
          EndSelect
         
        Case #Cust_Event_Exiftools_Read_Metadatas
          Define n.l
          Define ExecuteID.i
          Define Backdata.s
          Define line.s
          Define pos.l
          Define name.s
          Define value.s
          ExecuteID.i=EventData() ;TODO Get ExecuteID who send this Event
          Backdata.s=Exiftool::GetResultStdout(ExecuteID) ;Get the Exiftool Return
          Exiftool::FreeResult(Ei)                        ;TODO After take data clean used memory
          ClearGadgetItems(#Gdt_MetaList)
          Backdata=ReplaceString(Backdata,Chr(13)+Chr(10),Chr(13))
          For n=1 To CountString(Backdata,Chr(13))
            line=StringField(Backdata,n,Chr(13))
            pos=FindString(line,"=")
            name=Trim(Mid(line,1,pos-1))
            value=Trim(Mid(line,pos+1,Len(line)-pos+1))
            If name="-EXIF:ImageDescription"
              SetGadgetText(#Gdt_Description,value)
            EndIf
            AddGadgetItem(#Gdt_MetaList,-1,name+Chr(10)+value)
          Next
      EndSelect
      ;Just Check if Exiftool is Ready
      If Exiftool::IsRun()
        SetGadgetText(#Gdt_State,"ON")
      Else
        SetGadgetText(#Gdt_State,"OFF")
      EndIf
     
    Until Event=#PB_Event_CloseWindow
    Exiftool::Stop()
  EndIf
 
CompilerEndIf   


Author:  thyphoon [ Thu Jan 31, 2019 9:14 am ]
Post subject:  Re: Module to easy use Exiftool in your application

Big Update.
You can get binarie return from Exiftool. I find the solution with "No File Found" if path use special characters.
Exemple is more commented !

Page 1 of 1 All times are UTC + 1 hour
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/