close

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:

    Image


    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