How to call ExifTool from Delphi*


*Delphi v7

Introduction

Some have asked for ExifToolGUI source code. The main reason why I can't do that, is:
  • In GUI, some 3rd party code is used, which is not open source.
  • And the reason why I probably wouldn't do that (even if possible), is:
  • If at all, something entirely different than GUI should be made.

  • I made GUI, because there was no similar free solution at that time (and I desperately wished to use ExifTool). In my opinion, making GUI just "different" isn't a challenge, nor is worth to spend time doing this -at the end, it would be just another GUI.

    Calling ExifTool from Delphi

    Exiftool unit

    Here's a unit, which can be used as a basis for further enhancements:

    unit ExifTool;
    
    interface
    uses Classes;
    
    var
      ETout, ETerr: TStringList; //data from ExifTool will be here
    
    function ExecuteET(const ETcmd,WorkDir: string): Boolean;
    
    implementation
    uses Windows;
    
    function ExecuteET(const ETcmd,WorkDir: string): Boolean;
    const
      szBuffer=255;
    var
      StartupInfo: TStartupInfo;
      ProcessInfo: TProcessInformation;
      PWorkDir: PChar;
      SecurityAttr: TSecurityAttributes;
      PipeOutputRead: THandle;
      PipeOutputWrite: THandle;
      PipeErrorsRead: THandle;
      PipeErrorsWrite: THandle;
      Succeed: Boolean;
      Buffer: array [0..szBuffer] of Char;
      BytesRead: DWORD;
      Stream: TMemoryStream;
    begin
      //=== Usual steps to initialize data for CreateProcess:
      FillChar(Buffer,SizeOf(Buffer),0);
      FillChar(ProcessInfo, SizeOf(TProcessInformation), 0);
      FillChar(SecurityAttr, SizeOf(TSecurityAttributes), 0);
      SecurityAttr.nLength := SizeOf(SecurityAttr);
      SecurityAttr.bInheritHandle := true;
      SecurityAttr.lpSecurityDescriptor := nil;
      CreatePipe(PipeOutputRead, PipeOutputWrite, @SecurityAttr, 0);
      CreatePipe(PipeErrorsRead, PipeErrorsWrite, @SecurityAttr, 0);
      FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
      StartupInfo.cb:=SizeOf(StartupInfo);
      with StartupInfo do begin
        hStdInput:=0; hStdOutput:=PipeOutputWrite; hStdError:=PipeErrorsWrite;
        wShowWindow:=SW_HIDE;
        dwFlags:=STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      end;
      if WorkDir='' then PWorkDir:=nil else PWorkDir:=PChar(WorkDir);
      ETout.Clear; ETerr.Clear;
    
      //=== Here is where ExifTool is called:
      if  CreateProcess(nil, PChar(ETcmd), nil, nil, true,
        CREATE_DEFAULT_ERROR_MODE or CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
        nil, PWorkDir, StartupInfo, ProcessInfo)
      then begin      //=ExifTool started successfully:
        result:=true;
        CloseHandle(PipeOutputWrite);
        CloseHandle(PipeErrorsWrite);
      end else begin  //=ExifTool not started (because, i.e. not found):
        result:=false;
        CloseHandle(PipeOutputRead); CloseHandle(PipeOutputWrite);
        CloseHandle(PipeErrorsRead); CloseHandle(PipeErrorsWrite);
      end;
    
      if result then begin
        //= Get output written by ExifTool(tag names/values):
        Stream:=TMemoryStream.Create;
        try
          repeat
            succeed:=ReadFile(PipeOutputRead,Buffer,szBuffer,BytesRead,nil);
            if not succeed then break;
            Stream.Write(Buffer,BytesRead)
          until (BytesRead=0);
          Stream.Position:=0; ETout.LoadFromStream(Stream);
        finally Stream.Free; end;
        CloseHandle(PipeOutputRead);
        //= Get errors written by ExifTool (if any):
        Stream:=TMemoryStream.Create;
        try
          repeat
            succeed:=ReadFile(PipeErrorsRead,Buffer,szBuffer,BytesRead,nil);
            if not succeed then break;
            Stream.Write(Buffer,BytesRead);
          until (BytesRead=0);
          Stream.Position:=0; ETerr.LoadFromStream(Stream);
        finally Stream.Free; end;
        CloseHandle(PipeErrorsRead);
    
        WaitForSingleObject(ProcessInfo.hProcess,5000); //=5sec
        CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess);
      end;
    end;
    
    initialization
    begin
      ETout:=TStringList.Create;
      ETerr:=TStringList.Create;
    end;
    
    finalization
    begin
      ETerr.Free;
      ETout.Free;
    end;
    
    end.

    Main program

    Here's example of how we call above function from "main" program:

    unit Main;
    
    interface
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ExtCtrls;
    
    type
      TForm1 = class(TForm)
        LabeledEdit1: TLabeledEdit;
        Button1: TButton;
        Memo1: TMemo;
        Label1: TLabel;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    uses ExifTool;
    {$R *.dfm}
    
    var ETcmd:string;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var i: smallint;
    begin
      Memo1.Clear;
      ETcmd:=LabeledEdit1.Text;
      if ExecuteET('exiftool '+ETcmd,'') then with Memo1.Lines do begin
        Append('*** ExifTool output:');
        if ETout.Count>0 then
          for i:=0 to ETout.Count-1 do Append(ETout[i]);
        Append('*** ExifTool errors:');
        if ETerr.Count>0 then
          for i:=0 to ETerr.Count-1 do Append(ETerr[i]);
        Append('^^^ END');
      end else ShowMessage('exiftool.exe not found!?');
    end;
    
    end.

    -which reflects something like this:




    In above example, because second ExecuteET parameter (WorkDir) is empty, command must include path to image file which we wish to read or modify. So ExifTool command must look like:
    -Exif:all c:\myPhotos\test.jpg
    
    or
    -Exif:Artist="My name" "c:\Photo album\Family\*.jpg"
    
    -here we also needed quotes, because tag value (and path) contains spaces.

    However, from our "main" code, we can call ExecuteET function by specifying "working directory". By doing something like:

    ...
    var ETcmd, WorkDir: string;
    ...
      WorkDir:='c:\Photo album\Family\';     // -no quotes needed now
      ETcmd:='-Exif:Artist="My name" *.jpg'; // -path not needed now
      if ExecuteET('exiftool '+ETcmd, WorkDir) then ...
    ...
    

    That's it.

    Bogdan Hrastnik
    September, 2011