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
|