It is currently Mon Dec 09, 2019 5:20 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 2 posts ] 
Author Message
 Post subject: Module to easy use Exiftool in your application
PostPosted: Thu Jan 17, 2019 9:56 am 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Sat Dec 25, 2004 2:37 pm
Posts: 233
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   



Last edited by thyphoon on Sat Feb 02, 2019 5:26 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: Module to easy use Exiftool in your application
PostPosted: Thu Jan 31, 2019 9:14 am 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Sat Dec 25, 2004 2:37 pm
Posts: 233
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 !


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 2 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye