Skip to main navigation Skip to main content Skip to page footer
unit ExifTool;

interface

uses
  Classes, SysUtils, Vcl.Forms, Util, Windows;

function ExecuteExifTool(const Command: WideString; Output, Errors: TStrings): TFunctionResult;

implementation

uses
  MainForm, StatusForm;

function ExecuteExifTool(const Command: WideString; Output, Errors: TStrings): TFunctionResult;
var
  Buffer: array[0..255] of Byte;
  CreationFlags: DWORD;
  NumberOfBytesRead: DWORD;
  PipeErrorsRead: THandle;
  PipeErrorsWrite: THandle;
  PipeOutputRead: THandle;
  PipeOutputWrite: THandle;
  ProcessInfo: TProcessInformation;
  SecurityAttr: TSecurityAttributes;
  StartupInfo: TStartupInfo;
  WaitResult: DWORD;
  FileCounter: integer;
  Line: String;
  OutStream, ErrStream: TMemoryStream;
  Success: BOOL;
begin
  FileCounter := 0;
  FillChar(ProcessInfo, SizeOf(TProcessInformation), 0);
  FillChar(SecurityAttr, SizeOf(TSecurityAttributes), 0);
  SecurityAttr.nLength := SizeOf(TSecurityAttributes);
  SecurityAttr.bInheritHandle := True;
  SecurityAttr.lpSecurityDescriptor := nil;

  CreatePipe(PipeOutputRead, PipeOutputWrite, @SecurityAttr, 0);
  CreatePipe(PipeErrorsRead, PipeErrorsWrite, @SecurityAttr, 0);

  FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
  StartupInfo.cb := SizeOf(TStartupInfo);
  StartupInfo.hStdInput := 0;
  StartupInfo.hStdOutput := PipeOutputWrite;
  StartupInfo.hStdError := PipeErrorsWrite;
  StartupInfo.wShowWindow := SW_HIDE;
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;

  CreationFlags := CREATE_DEFAULT_ERROR_MODE or CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS;

  Success := CreateProcessW(
    nil, PWideChar(Command), nil, nil, True,
    CreationFlags, nil, nil, StartupInfo, ProcessInfo
  );

  if Success then
  begin
    Result := frOk;
    CloseHandle(PipeOutputWrite);
    CloseHandle(PipeErrorsWrite);

    OutStream := TMemoryStream.Create;
    ErrStream := TMemoryStream.Create;
    try
      repeat
        WaitResult := WaitForSingleObject(ProcessInfo.hProcess, 100);

        NumberOfBytesRead := 0;
        if PeekNamedPipe(PipeOutputRead, nil, 0, nil, @NumberOfBytesRead, nil) and (NumberOfBytesRead > 0) then
        begin
          while ReadFile(PipeOutputRead, Buffer, Length(Buffer) - 1, NumberOfBytesRead, nil) and not frmStatus.Cancel do
          begin
            OutStream.Write(Buffer, NumberOfBytesRead);

            for var i := 0 to NumberOfBytesRead - 1 do
            begin
              Line := Line + Char(Buffer[i]);
              if Char(Buffer[i]) = #$0A then
              begin
                if Line.Contains('SourceFile') then
                begin
                  Inc(FileCounter);
                  Line := UTF8ToString(RawByteString(Line));

                  frmStatus.FilesProcessed := FileCounter;

                  Line := Line.Remove(Line.Length - 4, 4).Remove(0, 17);
                  frmStatus.ProcessingFile := Line.Replace('/', '\');
                  frmMain.StatusBar.Panels[3].Text := FileCounter.ToString;
                  frmMain.StatusBar.Panels[9].Text := TruncateFileName(frmStatus.ProcessingFile, (frmMain.StatusBar.Panels[9].Width - 10) div 9);

                  Application.ProcessMessages;
                end;
                Line := '';
              end;
            end;
          end;
        end;

        NumberOfBytesRead := 0;
        if PeekNamedPipe(PipeErrorsRead, nil, 0, nil, @NumberOfBytesRead, nil) and (NumberOfBytesRead > 0) then
        begin
          while ReadFile(PipeErrorsRead, Buffer, Length(Buffer) - 1, NumberOfBytesRead, nil) do
          begin
            ErrStream.Write(Buffer, NumberOfBytesRead);
          end;
        end;

        Application.ProcessMessages;
      until (WaitResult <> WAIT_TIMEOUT) or frmStatus.Cancel;

      if frmStatus.Cancel then
        Result := frCancel
      else
      begin
        frmStatus.FilesProcessed := FileCounter;
        frmStatus.ProcessingFile := Line;
        frmMain.StatusBar.Panels[3].Text := FileCounter.ToString;
        frmMain.StatusBar.Panels[9].Text := TruncateFileName(Line, (frmMain.StatusBar.Panels[9].Width - 10) div 9);
      end;

      OutStream.Position := 0;
      Output.LoadFromStream(OutStream);
      ErrStream.Position := 0;
      Errors.LoadFromStream(ErrStream);
    finally
      OutStream.Free;
      ErrStream.Free;
    end;

    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(PipeOutputRead);
    CloseHandle(PipeErrorsRead);
  end
  else
  begin
    CloseHandle(PipeOutputRead);
    CloseHandle(PipeOutputWrite);
    CloseHandle(PipeErrorsRead);
    CloseHandle(PipeErrorsWrite);
    Result := frError;
  end;
end;

initialization

finalization

end.