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

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

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

implementation
uses Windows, 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;
  tmpWaitR : DWORD;
  c: integer;
  Str: String;
  OutStream, ErrStream: TMemoryStream;
  Ok: bool;

begin
  c:= 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;

  { Start the child process. }
  Ok:=      CreateProcessW(nil,          // No module name (use command line)
                   (PWideChar(Command)), // Command line
                   nil,                  // Process handle not inheritable
                   nil,                  // Thread handle not inheritable
                   True,                 // handles are inherited
                   CreationFlags,        // creation flags
                   nil,                  // Use parent's environment block
                   nil,                  // Use parent's starting directory
                   StartupInfo,          // Pointer to STARTUPINFO structure
                   ProcessInfo);         // Pointer to PROCESS_INFORMATION structure

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

    OutStream:= TMemoryStream.Create; ErrStream:= TmemoryStream.Create;
    try
      repeat // until (tmpWaitR <> WAIT_TIMEOUT)
        tmpWaitR := 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);

            { Show where we are in the process }
            for var i:= 0 to NumberOfBytesRead -1 do
            begin
              Str:= Str + Char(Buffer[i]);
              if Char(Buffer[i]) = #$0A then
              begin
                if Str.Contains('SourceFile') then
                begin
                  Inc(c);
                  //if c mod 10 = 0 then // restrict screen update for improving performance
                  //begin
                    Str:= UTF8ToString(RawByteString(Str));

                    frmStatus.FilesProcessed:= c;

                    // Remove '",#0A#0D' at the end of the jsonstring and '  "SourceFile": "' at the beginning
                    Str:= Str.Remove(Str.Length-4,4).Remove(0,17);

                    frmStatus.ProcessingFile:= Str.Replace('/','\');
                    frmMain.StatusBar.Panels[3].Text:= c.ToString;
                    frmMain.StatusBar.Panels[9].Text:= TruncateFileName(frmStatus.ProcessingFile, (frmMain.StatusBar.Panels[9].Width-10) div 9);

                    Application.ProcessMessages;
                  //end;
                end;
                Str:= '';
              end;
            end;

          end; // while ReadFile
        end; // if PeekNamedPipe

        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 (tmpWaitR <> WAIT_TIMEOUT) or frmStatus.Cancel;
      { Show final values }
      if frmStatus.Cancel then
        Result:= frCancel
      else
        begin
          frmStatus.FilesProcessed:= c;
          frmStatus.ProcessingFile:= Str;
          frmMain.StatusBar.Panels[3].Text:= c.ToString;
          frmMain.StatusBar.Panels[9].Text:= TruncateFileName(Str, (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; // if result
end;

initialization

finalization

end.