Source of Demo Main Form

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, {Variants,} Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, Menus, FFmpeg, FFmpegVCL;

type
  TfrmMain = class(TForm)
    btnAdd: TButton;
    mmoLog: TMemo;
    OpenDialog1: TOpenDialog;
    btnStop: TButton;
    chkThreadMode: TCheckBox;
    chkVerNum: TCheckBox;
    btnPause: TButton;
    FFVCL: TFFmpegVCL;
    btnStart: TButton;
    btnResume: TButton;
    edtThreadCount: TEdit;
    Label7: TLabel;
    btnRemove: TButton;
    btnClear: TButton;
    grpOption: TGroupBox;
    lvFiles: TListView;
    btnExit: TButton;
    grpLogLevel: TRadioGroup;
    grpPriority: TRadioGroup;
    btnWebSite: TButton;
    PopupMenu1: TPopupMenu;
    mnuAdd: TMenuItem;
    mnuRemove: TMenuItem;
    mnuClear: TMenuItem;
    N1: TMenuItem;
    mnuOpenFolder: TMenuItem;
    mnuPlay: TMenuItem;
    procedure btnAddClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure chkVerNumClick(Sender: TObject);
    procedure btnPauseClick(Sender: TObject);
    procedure btnResumeClick(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FFVCLBeforeHook(const AIndex: Integer; const APTS: Int64;
      var AHookAction: THookAction);
    procedure FFVCLCustomHook(const AIndex: Integer; ABitmap: TBitmap;
      const AFrameNumber: Integer; const APTS: Int64; var AUpdate, AStopHook: Boolean);
    procedure FFVCLLog(const AIndex: Integer; const ALogLevel: TLogLevel; const AMsg: string);
    procedure FFVCLProgress(const AIndex, AFrameNumber, AFPS,
      ACurrentDuration: Integer; const AQuality, ABitRate: Single;
      const ACurrentSize: Int64; const ATotalOutputDuration: Integer);
    procedure FFVCLTerminate(const AIndex: Integer; const AFinished,
      AException: Boolean; const AMessage: string);
    procedure btnClearClick(Sender: TObject);
    procedure btnRemoveClick(Sender: TObject);
    procedure btnExitClick(Sender: TObject);
    procedure btnWebSiteClick(Sender: TObject);
    procedure mnuOpenFolderClick(Sender: TObject);
    procedure PopupMenu1Popup(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure mnuPlayClick(Sender: TObject);
  private
    FFolderList: TStringList;
    FOutFileList: TStringList;
    procedure DoException(Sender: TObject; E: Exception);
    procedure DoAddFile(const AIO: TInputOptions; const AOO: TOutputOptions);
  public
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

uses
{$IF NOT (DEFINED(VER140) OR DEFINED(VER185) OR DEFINED(VER200))}
  XPMan,
{$IFEND}
  ShellAPI,
  OptionFrm;

var
  GfrmOption: TfrmOption = nil;

const
  CLibAVPath = 'LibAV';

  SAppTitle = 'Demo of FFVCL %s';
  SCaption = 'Demo of FFVCL - Delphi FFmpeg VCL Component %s';
  SWebSiteC = 'http://www.CCAVC.com';
  SWebSiteE = 'http://www.DelphiFFmpeg.com';
  DEMO_INFO =
    '* The Demo Version FFVCL only works in Delphi IDE' +
      #13#10 +
    '* Load VerNum DLL means "avcodec-51.dll" rather than "avcodec.dll"' +
      #13#10 +
    '* Thread Mode means creating new threads to do converting jobs, ' +
      'it dose not using the main thread so you can Stop/Pause/Resume ' +
      'converting without block.' +
      #13#10 +
    '  * Thread Count means the quantity of converting jobs in the same time.' +
      #13#10 +
    '  * WARNING: if you want to debug your code in IDE such as Breakpoints, ' +
      'please do not check Thread Mode, or choose Thread Priority below "Normal", ' +
      'because Delphi Debugger maybe fall into trouble in Thread Mode.' +
      #13#10 +
    '* Log Level means the log messages level' +
      #13#10 +
    '* Thread Priority only works in Thread Mode' +
      #13#10 +
      #13#10;

  // License sample (this is a fake license key)
//BOMB: you should update the seed and the key with your own ones.
  LICENSE_SEED = $D24E9E33;
  LICENSE_KEY =
    '39EA968465F26B6CDCA1E51EC8FAC6392100A838813BD0E0F5575E31AC38AEE4' +
    '34E3AF85FDC4B84FBC8BC88078E83D482D8CD226F013CF85BA90F88765D91977' +
    'A8C2E0E345B052AFC342FF244A8D7A95306623716DDBB1B512A1F44F32D6731C' +
    '49308768679B36FC8F6AEF3207ED6CC8EA5EF8F4D2F2AA0F2DC5F13654B78322';

  CDialogOptions = [ofHideReadOnly, ofFileMustExist, ofEnableSizing];
  CPictureFiles = '*.BMP;*.GIF;*.JPEG;*.JPG;*.PNG;';
  CAudioFiles = '*.MP3;*.AAC;*.WAV;*.WMA;*.CDA;*.FLAC;*.M4A;*.MID;*.MKA;' +
      '*.MP2;*.MPA;*.MPC;*.APE;*.OFR;*.OGG;*.RA;*.WV;*.TTA;*.AC3;*.DTS;';
  CVideoFiles = '*.AVI;*.AVM;*.ASF;*.WMV;*.AVS;*.FLV;*.MKV;*.MOV;*.3GP;' +
      '*.MP4;*.MPG;*.MPEG;*.DAT;*.OGM;*.VOB;*.RM;*.RMVB;*.TS;*.TP;*.IFO;*.NSV;';
  CDialogFilter =
      'Video/Audio/Picture Files|' + CVideoFiles + CAudioFiles + CPictureFiles +
      '|Video Files|' + CVideoFiles +
      '|Audio Files|' + CAudioFiles +
      '|Picture Files|' + CPictureFiles +
      '|All Files|*.*';

  SHookFrameNumber = 'FFVCL - Frame Number: %d';
  SHookTimeStamp = 'FFVCL - Time Stamp: %d';
  SHookTextImage = 'FFVCL';
  SHookReverseVertical = 'Reverse Picture Vertically';
  SHookReverseHorizontal = 'Reverse Picture Horizontally';

var
  SWebSite: string = SWebSiteE;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Application.Title := Format(SAppTitle, [FFVCL.Version]);
  Self.Caption := Format(SCaption, [FFVCL.Version]);
  if SysUtils.SysLocale.PriLangID = LANG_CHINESE then
    SWebSite := SWebSiteC
  else
    SWebSite := SWebSiteE;
  btnWebSite.Caption := SWebSite;
  mmoLog.Text := DEMO_INFO;
  FFolderList := TStringList.Create;
  FOutFileList := TStringList.Create;

  // open dialog setting
  OpenDialog1.Options := CDialogOptions;
  OpenDialog1.Filter := CDialogFilter;

  // exception handler
  Application.OnException := DoException;

  // Set License Key
  FFVCL.SetLicenseKey(LICENSE_KEY, LICENSE_SEED);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FFolderList);
  FreeAndNil(FOutFileList);
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  with FFVCL do
  begin
    // Clear the event handlers
    OnBeforeHook := nil;
    OnCustomHook := nil;
    OnLog := nil;
    OnProgress := nil;
    OnTerminate := nil;

    // Break converting
    BreakConverting;
  end;
end;

procedure TfrmMain.DoException(Sender: TObject; E: Exception);
var
  LMsg: string;
begin
  LMsg := E.Message;
  if (LMsg <> '') and (AnsiLastChar(LMsg) > '.') then
    LMsg := LMsg + '.';
  if E is FFmpegException then
    // FFmpeg exception: show icon as warning
    Application.MessageBox(PChar(LMsg), PChar(Application.Title), MB_OK + MB_ICONWARNING)
  else
    // Other exception: show icon as error
    Application.MessageBox(PChar(LMsg), PChar(Application.Title), MB_OK + MB_ICONERROR);
end;

procedure TfrmMain.chkVerNumClick(Sender: TObject);
begin
  // Normally, call LoadAVLib only once is enough, UnloadAVLib is not necessary.
  // In this demo, LoadAVLib and UnloadAVLib maybe called more than once,
  //    that only shows you the LoadAVLib parameter AWithVerNum
  FFVCL.UnloadAVLib;
end;

procedure TfrmMain.btnAddClick(Sender: TObject);
begin
  // ensure FFmpeg DLLs loaded
  // FFVCL.LoadAVLib(const APath: string; const AWithVerNum): Boolean;
  // APath: Full path indicates location of FFmpeg DLLs.
  //        It can be empty, let Windows search DLLs in current dir or environment <PATH>
  // AWithVerNum: True means "avcodec-51.dll" rather than "avcodec.dll"
  // Normally, call LoadAVLib only once is enough, UnloadAVLib is not necessary.
  // In this demo, LoadAVLib and UnloadAVLib maybe called more than once,
  //    that only shows you the LoadAVLib parameter AWithVerNum
  if not FFVCL.LoadAVLib(ExtractFilePath(Application.ExeName) + CLibAVPath, chkVerNum.Checked) then
  begin
    // cannot load FFmpeg DLLs, show error message
    mmoLog.Lines.Add(FFVCL.LastErrMsg);
    Exit;
  end;

  // TLogLevel = (llQuiet, llError, llInfo, llDebug, llAll);
  FFVCL.LogLevel := TLogLevel(grpLogLevel.ItemIndex);

  if not OpenDialog1.Execute then
    // cancel open file
    Exit;

  // now, try to load the input file selected to get AVFileInfo,
  // and then generate input options and output options for converting

  // FFVCL.AVProbe is an instance of TAVProbe class defined in unit AVProbe.pas
  // you can create your own AVProbe instance as your purpose
  if FFVCL.AVProbe.LoadFile(OpenDialog1.FileName) then
  begin // load av file success
    if not Assigned(GfrmOption) then
      // need to create option form
      GfrmOption := TfrmOption.Create(Self);

    // init option form with AVProbe
    GfrmOption.AVProbe := FFVCL.AVProbe;

    if GfrmOption.ShowModal = mrOk then
      // do add file to convert list
      DoAddFile(GfrmOption.InputOptions, GfrmOption.OutputOptions);

    // close the file handle in AVProbe
    FFVCL.AVProbe.CloseFile;
  end
  else
  begin // load av file failed
    mmoLog.Lines.Add('');
    mmoLog.Lines.Add('***File load error: ' + FFVCL.AVProbe.LastErrMsg);
    mmoLog.Lines.Add('');
  end;
end;

procedure TfrmMain.DoAddFile(const AIO: TInputOptions; const AOO: TOutputOptions);
var
  LIndex: Integer;
begin
  // try to open input file with input options
  LIndex := FFVCL.AddInputFile(AIO.FileName, @AIO);
  if LIndex < 0 then
  begin // open failed
    mmoLog.Lines.Add('');
    mmoLog.Lines.Add('***File open error: ' + FFVCL.LastErrMsg);
    mmoLog.Lines.Add('');
  end
  else
  begin // open success
    // try to set output file with output options
    if FFVCL.SetOutputFile(LIndex, AOO.FileName, @AOO) then
    begin // can do output file with output options
      with lvFiles.Items.Add do
      begin
        // display some information of input file and output file
        Caption := ExtractFileName(AIO.FileName);
        // microseconds to seconds
        SubItems.Add(IntToStr(FFVCL.AVProbe.FileStreamInfo.Duration div 1000000));
        SubItems.Add(IntToStr(FFVCL.AVProbe.FileSize));
        SubItems.Add(ExtractFileName(AOO.FileName));
        SubItems.Add('');
        SubItems.Add('');
        SubItems.Add('');
        SubItems.Add('');
      end;

      // add output file path to folder list
      FFolderList.Add(ExtractFilePath(AOO.FileName));
      FOutFileList.AddObject(AOO.FileName, nil);

      mmoLog.Lines.Add('');
      mmoLog.Lines.Add('***File has been added to convert list.');
      mmoLog.Lines.Add('');

      btnRemove.Enabled := True;
      btnClear.Enabled := True;
      btnStart.Enabled := True;
    end
    else
    begin // cannot do output file, do not forget to remove input file!!!
      FFVCL.RemoveInputFile(LIndex);
      mmoLog.Lines.Add('');
      mmoLog.Lines.Add('***Cannot do convert, error: ' + FFVCL.LastErrMsg);
      mmoLog.Lines.Add('');
    end;
  end;
end;

procedure TfrmMain.btnRemoveClick(Sender: TObject);
begin
  if lvFiles.ItemIndex >= 0 then
  begin
    // remove input file
    FFVCL.RemoveInputFile(lvFiles.ItemIndex);
    FFolderList.Delete(lvFiles.ItemIndex);
    FOutFileList.Delete(lvFiles.ItemIndex);
    lvFiles.Items.Delete(lvFiles.ItemIndex);
    btnRemove.Enabled := lvFiles.Items.Count > 0;
    btnClear.Enabled := lvFiles.Items.Count > 0;
    btnStart.Enabled := lvFiles.Items.Count > 0;
  end;
end;

procedure TfrmMain.btnClearClick(Sender: TObject);
begin
  // clear all input files
  FFVCL.ClearInputFiles;
  FFolderList.Clear;
  FOutFileList.Clear;
  lvFiles.Items.Clear;
  btnAdd.Enabled := True;
  btnRemove.Enabled := False;
  btnClear.Enabled := False;
  btnStart.Enabled := False;
end;

procedure TfrmMain.btnStartClick(Sender: TObject);
begin
  // TLogLevel = (llQuiet, llError, llInfo, llDebug, llAll);
  FFVCL.LogLevel := TLogLevel(grpLogLevel.ItemIndex);
  // TConvertPriority = tpIdle..tpNormal; //tpIdle, tpLowest, tpLower, tpNormal
  FFVCL.ThreadPriority := TThreadPriority(grpPriority.ItemIndex);

  btnAdd.Enabled := False;
  btnRemove.Enabled := False;
  btnClear.Enabled := False;

  btnStart.Enabled := False;
  btnStop.Enabled := chkThreadMode.Checked;
  btnPause.Enabled := chkThreadMode.Checked;
  btnResume.Enabled := False;

  // procedure StartConvert(const AThreadCount: Integer);
  // AThreadCount: >  0, means do converting task in thread mode
  //               >  1, means do converting task with multiple files in the same time
  //               <= 0, means do converting task in main thread
  if chkThreadMode.Checked then
    // do converting task in thread mode
    FFVCL.StartConvert(StrToIntDef(edtThreadCount.Text, 1))
  else
    // do converting task in main thread
    FFVCL.StartConvert(0);
end;

procedure TfrmMain.btnStopClick(Sender: TObject);
begin
  btnStop.Enabled := False;
  FFVCL.BreakConverting; // only works in thread mode
end;

procedure TfrmMain.btnPauseClick(Sender: TObject);
begin
  btnPause.Enabled := False;
  btnResume.Enabled := True;
  FFVCL.PauseConverting; // only works in thread mode
end;

procedure TfrmMain.btnResumeClick(Sender: TObject);
begin
  btnPause.Enabled := True;
  btnResume.Enabled := False;
  FFVCL.ResumeConverting; // only works in thread mode
end;

procedure TfrmMain.btnWebSiteClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'Open',   {Do not Localize}
    PChar(LowerCase(SWebSite)), '',
    PChar(ExtractFilePath(Application.ExeName)), 1);
end;

procedure TfrmMain.btnExitClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmMain.mnuOpenFolderClick(Sender: TObject);
var
  LPath: string;
begin
  if (lvFiles.ItemIndex >= 0) and (lvFiles.ItemIndex < FFolderList.Count) then
    LPath := FFolderList.Strings[lvFiles.ItemIndex]
  else if FFolderList.Count > 0 then
    LPath := FFolderList.Strings[FFolderList.Count - 1]
  else
    LPath := '';

  if (LPath <> '') and DirectoryExists(LPath) then
    ShellExecute(Application.Handle, 'Open', // {Do not Localize}
      PChar(LPath), nil,
      PChar(LPath), SW_SHOWDEFAULT);
end;

procedure TfrmMain.mnuPlayClick(Sender: TObject);
var
  I: Integer;
begin
  if (lvFiles.ItemIndex >= 0) and (lvFiles.ItemIndex < FFolderList.Count) then
    I := lvFiles.ItemIndex
  else
    I := FOutFileList.Count - 1;
  if (I >= 0) and Assigned(FOutFileList.Objects[I]) and FileExists(FOutFileList.Strings[I]) then
    ShellExecute(Application.Handle, 'Open',   {Do not Localize}
      PChar(FOutFileList.Strings[I]), '',
      PChar(ExtractFilePath(FOutFileList.Strings[I])), SW_SHOWNORMAL);
end;

procedure TfrmMain.PopupMenu1Popup(Sender: TObject);
var
  I: Integer;
begin
  mnuAdd.Enabled := btnAdd.Enabled;
  mnuRemove.Enabled := btnRemove.Enabled;
  mnuClear.Enabled := btnClear.Enabled;
  mnuOpenFolder.Enabled := lvFiles.Items.Count > 0;
  if (lvFiles.ItemIndex >= 0) and (lvFiles.ItemIndex < FFolderList.Count) then
    I := lvFiles.ItemIndex
  else
    I := FOutFileList.Count - 1;
  mnuPlay.Enabled := (I >= 0) and Assigned(FOutFileList.Objects[I]);
end;

procedure TfrmMain.FFVCLBeforeHook(const AIndex: Integer; const APTS: Int64;
  var AHookAction: THookAction);
begin
  // OnBeforeHook event handler, only triggered with OutputOptions.BeforeHook = True
  // NOTICE: not thread safety
  // AIndex: index of input files
  // APTS: presentation time stamp of current picture, in microseconds
  // AHookAction: THookAction = (haContinue, haIgnore, haStop)
  //    haContinue: continue processing video hook
  //    haIgnore: ignore processing video hook
  //    haStop: stop video hook, and BeforeHook event will not be triggered with this file
  //    initial value is haContinue, and next time it will keep the previous value
  // this demo shows you that processing video hook only in pts periods 0-5,10-15 (seconds)
  if APTS < 5 * 1000 * 1000 then
    AHookAction := haContinue
  else if APTS < 10 * 1000 * 1000 then
    AHookAction := haIgnore
  else if APTS < 15 * 1000 * 1000 then
    AHookAction := haContinue
  else
    AHookAction := haStop;
end;

procedure TfrmMain.FFVCLCustomHook(const AIndex: Integer; ABitmap: TBitmap;
  const AFrameNumber: Integer; const APTS: Int64; var AUpdate, AStopHook: Boolean);
const
  CBytes = 3; // ABitmap.PiexlFormat is pf24bit
var
  H, W: Integer;
  I, J, K: Integer;
  P1, P2: PChar;
  B: Char;
begin
  // OnCustomHook event handler, only triggered with OutputOptions.CustomHook = True
  // NOTICE: not thread safety
  // AIndex: index of input files
  // ABitmap: bitmap filled with the original video picture, you can save it,
  //          or change it by drawing text or image on bitmap.Canvas,
  //          but you must NOT change the size and the PixelFormat of the bitmap!
  // AFrameNumber: frame index number, first is 1 not 0
  // APTS: presentation time stamp of current picture, in microseconds
  // AUpdate: whether update the bitmap back to original video picture, default true
  // AStopHook: whether stop video hook, if true then CustomHook event will not
  //          be triggered with this file, default false
  if AFrameNumber = 1 then
  begin // first frame number
    with ABitmap.Canvas.Font do
    begin // setup font example
      Color := clWhite;
      Name := 'Tahoma';
      Size := 12;
      Style := [fsBold, fsUnderline];
    end;
    // capture video frame example
    //ABitmap.SaveToFile('C:\CustomHook_' + FloatToStr(Now) + '.bmp');
  end;
  if AFrameNumber < 100 then
    // text out with frame number example
    ABitmap.Canvas.TextOut(10, 10, Format(SHookFrameNumber, [AFrameNumber]))
  else if AFrameNumber < 150 then
    // do not change the original video picture example
    AUpdate := False
  else if AFrameNumber < 250 then
    // text out with Presentation Time Stamp example
    ABitmap.Canvas.TextOut(10, 10, Format(SHookTimeStamp, [APTS]))
  else if AFrameNumber < 350 then
  begin
    // draw text and graphic example
    if AFrameNumber = 250 then
      with ABitmap.Canvas.Font do
      begin // change font example
        Color := clRed;
        Size := 16;
      end;
    ABitmap.Canvas.TextOut(10, 10, SHookTextImage);
    ABitmap.Canvas.Draw(ABitmap.Width - Application.Icon.Width - 10,
                        ABitmap.Height - Application.Icon.Height - 10,
                        Application.Icon);
  end
  else if AFrameNumber < 450 then
  begin
    if AFrameNumber = 350 then
      with ABitmap.Canvas.Font do
      begin // change font example
        Color := clWhite;
        Style := [fsBold];
      end;
    ABitmap.Canvas.TextOut(10, 10, SHookReverseVertical);
    // reverse picture vertically example
    H := ABitmap.Height;
    W := ABitmap.Width;
    for I := 0 to H div 2 - 1 do
    begin
      P1 := PChar(ABitmap.ScanLine[I]);
      P2 := PChar(ABitmap.ScanLine[H - 1 - I]);
      for J := 0 to CBytes * W - 1 do
      begin
        B := P1^;
        P1^ := P2^;
        P2^ := B;
        Inc(P1);
        Inc(P2);
      end;
    end;
  end
  else if AFrameNumber < 550 then
  begin
    if AFrameNumber = 350 then
      with ABitmap.Canvas.Font do
      begin // change font example
        Style := [];
      end;
    ABitmap.Canvas.TextOut(10, 10, SHookReverseHorizontal);
    // reverse picture horizontally example
    H := ABitmap.Height;
    W := ABitmap.Width;
    for I := 0 to H - 1 do
    begin
      P1 := PChar(ABitmap.ScanLine[I]);
      P2 := P1 + CBytes * (W - 1);
      for J := 0 to W div 2 - 1 do
      begin
        for K := 0 to CBytes - 1 do
        begin
          B := (P1 + K)^;
          (P1 + K)^ := (P2 + K)^;
          (P2 + K)^ := B;
        end;
        Inc(P1, CBytes);
        Dec(P2, CBytes);
      end;
    end;
  end
  else
    // stop video hook exmaple, the converting will speedup
    AStopHook := True;
end;

procedure TfrmMain.FFVCLLog(const AIndex: Integer; const ALogLevel: TLogLevel; const AMsg: string);
begin
  // OnLog event handler
  // AIndex: index of input files, (-1) means index cannot be determined
  // TLogLevel = (llQuiet, llError, llInfo, llDebug, llAll);
  mmoLog.Lines.Add('#' + IntToStr(AIndex) + '.' + IntToStr(Ord(ALogLevel)) + ': ' + AMsg);
end;

procedure TfrmMain.FFVCLProgress(const AIndex, AFrameNumber, AFPS,
  ACurrentDuration: Integer; const AQuality, ABitRate: Single;
  const ACurrentSize: Int64; const ATotalOutputDuration: Integer);
begin
  // OnProgress event handler
  // AIndex: index of input files
  // AFrameNumber: current frame number
  // AFPS: video converting speed, frames per second, not valid when only audio output
  // ACurrentDuration: current duration time in millisecond
  // AQuality: <i don't know...>???
  // ABitRate: ...
  // ACurrentSize: current output file size in bytes
  // ATotalOutputDuration: total output duration time in millisecond
  with lvFiles.Items.Item[AIndex].SubItems do
  begin
    Strings[3] := IntToStr(ACurrentDuration * 100 div ATotalOutputDuration) + '%';
    if AFPS > 0 then
      Strings[4] := IntToStr(AFPS);
    Strings[5] := IntToStr(ACurrentDuration div 1000); // display as seconds
    Strings[6] := IntToStr(ACurrentSize);
  end;
  if not chkThreadMode.Checked then
  begin // make sure update converting status when converting without thread mode
    lvFiles.Repaint;
  end;
end;

procedure TfrmMain.FFVCLTerminate(const AIndex: Integer; const AFinished,
  AException: Boolean; const AMessage: string);
begin
  // OnTerminate event handler
  // AIndex: index of input files, (-1) means all files are converted
  // AFinished: true means converted success, false means converting breaked
  // AException: true means Exception occured
  // AMessage: exception message
  if AIndex < 0 then
  begin
    btnAdd.Enabled := False;
    btnRemove.Enabled := False;
    btnClear.Enabled := True;

    btnStart.Enabled := False;
    btnStop.Enabled := False;
    btnPause.Enabled := False;
    btnResume.Enabled := False;

    // do not forget to clear all input files!!!
    FFVCL.ClearInputFiles;
  end
  else if AFinished then
  begin // AIndex file converted success
    lvFiles.Items.Item[AIndex].SubItems.Strings[3] := '100%';
    if not chkThreadMode.Checked then
    begin // make sure update converting status when converting without thread mode
      lvFiles.Repaint;
    end;
  end;
  if (AIndex >= 0) and not AException then // indicate the output file is ready to play
    FOutFileList.Objects[AIndex] := TObject(1);
  if AException then // Exception occured, show exception message
    Application.MessageBox(PChar(AMessage), PChar(Application.Title), MB_OK + MB_ICONWARNING);
end;

end.