(************************************************************************
 *                                                                      *
 *   Ant Movie Catalog 4.x                                              *
 *   (C) 2000-2011 Antoine Potten, Mickaël Vanneufville                 *
 *   http://www.antp.be/software                                        *
 *                                                                      *
 ************************************************************************
 *                                                                      *
 *   This file can be used freely in any program, even if it is not     *
 *   opensource or if it is commercial. It can be used only to provide  *
 *   compatibility with Ant Movie Catalog files, for importation for    *
 *   example. A mention to the origin of this code and eventually       *
 *   a link to Ant Movie Catalog website somewhere in the about box,    *
 *   help file or documentation would be appreciated.                   *
 *   Like for the GPL-licensed files, this file is distributed WITHOUT  *
 *   ANY WARRANTY.                                                      *
 *                                                                      *
 *   To compile fields.pas and movieclass.pas in any project,           *
 *   you have to define "DLLMode" either in project options or          *
 *   directly in these two files.                                       *
 *                                                                      *
 *   Alternatively, this can be used under GPL license:                 *
 *                                                                      *
 *   This program is free software; you can redistribute it and/or      *
 *   modify it under the terms of the GNU General Public License        *
 *   as published by the Free Software Foundation; either version 2     *
 *   of the License, or (at your option) any later version.             *
 *                                                                      *
 *   This program is distributed in the hope that it will be useful,    *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of     *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *
 *   GNU General Public License for more details.                       *
 *                                                                      *
 ************************************************************************)

unit movieclass;

interface

uses
  Classes, Contnrs, SysUtils {$IFNDEF DLLMode},

  JvSimpleXml, Dialogs, Graphics,

  movieclass_old{$ENDIF}, Fields, Interfaces;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

const
  strClipboardHeader = 'AMC40';
  intFileVersion     = 40;

type
  TMovieIncludeOption = (mioAll, mioSelected, mioChecked, mioVisible);
  TMovieDuplicateNumbers = (mdnIgnore, mdnSwap, mdnShift);
  TFieldType = (ftString, ftInteger, ftReal, ftBoolean, ftDate, ftList, ftText, ftUrl);


  TString = class(TObject)
  private
   Fstr: String;
  public
   constructor Create(const Astr: String);
   property str: String read Fstr write Fstr;
  end;

  TCustomFieldProperties = class(TObject)
  public
    FieldTag: string;
    OldFieldTag: string;
    FieldName: string;
    FieldType: TFieldType;
    ListValues: TStrings; // used for ftList type
    DefaultValue: string;
    MultiValues : Boolean; // multiples values with separtor defined in options (for groups)
    ExcludedInScripts: Boolean;
{$IFNDEF DLLMode}
    FieldObject: TObject; // ref on Component Field in FrameMovieCustom
{$ENDIF}
    GUIProperties: string;
    OtherProperties: string;

    constructor Create(Tag: string); reintroduce;
    destructor Destroy; override;
    procedure InitValues;
    procedure Assign(CustomFieldProperties: TCustomFieldProperties);

    procedure WriteData(const OutputFile: TStream; const Version: Integer = 99);
    procedure ReadData(const InputFile: TStream; const Version: Integer = 99);
{$IFNDEF DLLMode}
    procedure SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string = '');
    procedure LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer = 99);
{$ENDIF}
  end;
  TMovieList = class; // Declare identifier for ref
  TCustomFieldsProperties = class(TStringList)
  private
    procedure SetItem(const idx: Integer; Value: TCustomFieldProperties);
    function GetItem(const idx: Integer): TCustomFieldProperties;
  public
    MovieList: TMovieList; // ref
    ColumnSettings: string;
    GUIProperties: string;
    OtherProperties: string;

    constructor Create;
    destructor Destroy; override;

    property Objects[const idx: Integer]: TCustomFieldProperties read GetItem write SetItem; default;

    procedure Clear; override;
    procedure Assign(CustomFieldsProperties: TCustomFieldsProperties;
      CopyMovieListRef: Boolean = True); reintroduce; overload;
    function GetField(Tag: string): TCustomFieldProperties;

    // SpecialAdd: Used to make difference between old field tag
    // and new field tag (even if old tag and new tag is same)
    // If SpecialAdd = True, CheckFieldTag and CheckFieldValue need to be call after all changes!
    function AddField(Tag: string; SpecialAdd: Boolean = False): TCustomFieldProperties;

    // Don't delete field value : call CheckFieldsValue after all changes !
    function DeleteField(Tag: string): Boolean;

    // Don't change tag of fields value : call CheckFieldsTag after all changes !
    function ChangeFieldTag(Tag: string; NewTag: string): Boolean;

    // Don't change fields value : call CheckFieldsValue after all changes !
    function ChangeFieldType(Tag: string; NewType: TFieldType): Boolean;

    // Change tag of fields value according to properties : call it after all changes (BEFORE CheckFieldsValue !) !
    procedure CheckFieldsTag;

    // Convert/Delete old fields value : call it after all changes (AFTER CheckFieldsTag) !
    procedure CheckFieldsValue;

    procedure WriteData(const OutputFile: TStream; const Version: Integer = 99);
    procedure ReadData(const InputFile: TStream; const Version: Integer = 99);
{$IFNDEF DLLMode}
    procedure SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string = '');
    procedure LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer = 99;
      ClearBefore: Boolean = True; SpecialAdd: Boolean = False);

    procedure ExportToXML(const strFileName: string);
    // CheckFieldTag and CheckFieldValue need to be call after all imports!
    procedure ImportFromXML(const strFileName: string);
{$ENDIF}
  end;

  TCustomFields = class(TStringList)
  private
    procedure SetItem(const idx: Integer; Value: TString);
    function GetItem(const idx: Integer): TString;
  public
    Properties: TCustomFieldsProperties; // ref

    constructor Create;
    destructor Destroy; override;

    property Objects[const idx: Integer]: TString read GetItem write SetItem; default;

    procedure Clear; override;
    procedure Assign(CustomFields: TCustomFields;
      CopyPropertiesRef: Boolean = True); reintroduce; overload;

    procedure CheckFieldsValue; // Check fields value according to fields properties
    procedure SetDefaultValues; // Set default field value according to fields properties

    function SetFieldValue(Tag, Value: string;
      AddMissingField: Boolean = True): Boolean;
    function GetFieldValue(Tag: string; ReturnEmptyIfFalse : Boolean = True;
      const LocalFormatSettings: Boolean = False): string;
    function GetIntFieldValue(Tag: string): Integer;
    function DeleteFieldValue(Tag: string): Boolean;
    function GetFieldProperties(Tag: string): TCustomFieldProperties;
    function ChangeTag(Tag: string; NewTag: string): Boolean;

    procedure WriteData(const OutputFile: TStream; const Version: Integer = 99);
    procedure ReadData(const InputFile: TStream; const Version: Integer = 99);
{$IFNDEF DLLMode}
    procedure SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string = '');
    procedure LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer = 99);
{$ENDIF}
  end;

  TMovie = class(TInterfacedObject, IMovie)
  private
{$IFNDEF DLLMode}
    mutex: Cardinal;
{$ENDIF}
  public
    CustomFields: TCustomFields;
    iNumber: Integer;
    strMedia: string;
    strMediaType: string;
    strSource: string;
    iDate: integer;
    strBorrower: string;
    iRating: Integer;
    strOriginalTitle: string;
    strTranslatedTitle: string;
    strDirector: string;
    strProducer: string;
    strCountry: string;
    strCategory: string;
    iYear: Integer;
    iLength: Integer;
    strActors: string;
    strURL: string;
    strDescription: string;
    strComments: string;
    strVideoFormat: string;
    iVideoBitrate: Integer;
    strAudioFormat: string;
    iAudioBitrate: Integer;
    strResolution: string;
    strFramerate: string;
    strLanguages: string;
    strSubtitles: string;
    strSize: string;
    iDisks: Integer;
    bChecked: Boolean;
    strPicture: string;
    Picture: TMemoryStream;
{$IFNDEF DLLMode}
    _listItems: TObjectList; // List of TElTreeItem associated to the movie (Updated in RefreshMovieList)
    _bSelected: Boolean; // Movie selected or not (Updated in StoreStates/LoadStates)
    _bVisible: Boolean; // Movie visible or not(Updated in StoreStates/LoadStates)
    _selectedGroup: string; // Group where movie is selected (Updated in StoreStates/LoadStates)
    _selectedItem: TObject; // TElTreeItem selected (Updated in StoreStates/LoadStates)
    _thumb: TMemoryStream;
    _thumbError: Word;
    _thumbWidth: Word;
    _thumbHeight: Word;
{$ENDIF}

    constructor Create(CustomFieldsProperties: TCustomFieldsProperties);
    destructor  Destroy;override;
{$IFNDEF DLLMode}
    procedure Lock();
    function LockWait(msTime: Integer): boolean;
    procedure Unlock();
    procedure GetFields(MovieFields: PMovie30);
    procedure SetFields(MovieFields: PMovie30);
{$ENDIF}
    procedure WriteData(const OutputFile: TStream; const Version: Integer = 99);
    procedure ReadData(const InputFile: TStream; const Version: Integer = 99);
{$IFDEF MSWINDOWS}
    function  SaveToMemory: THandle;
    class procedure FreeMemory(DataHandle: THandle; OnlyUnlock: Boolean = False);
    procedure LoadFromMemory(DataHandle: THandle);
{$ENDIF}
{$IFNDEF DLLMode}
    procedure SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string = '');
    procedure LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer = 99);
    {function  GetFieldValue(const FieldName: string): string; overload;
    procedure SetFieldValue(const FieldName: string; const Value: string); overload;}
{$ENDIF}
    function  GetFieldValue(const FieldID: TMovieField; const LocalFormatSettings: Boolean = False): string; overload;
    function  GetIntFieldValue(const FieldID: TMovieField): Integer;
    procedure SetFieldValue(const FieldID: TMovieField; const Value: string); overload;
    procedure InitFields;
{$IFNDEF DLLMode}
    function  GetFormattedTitle: string; overload;
    function  GetFormattedTitle(DisplayType: Integer; UsePrefixes: Boolean): string; overload;
{$ENDIF}
    procedure Assign(AMovie: TMovie; IncludeNumber: Boolean = True; IncludePicture: Boolean = True;
      IncludeCustomFields: Boolean = True);
    function CanInclude(const IncOpt: TMovieIncludeOption): Boolean;
    function CalcTotalSize: Int64;
    function ContainsText(const Value: string; const Field: Integer; const WholeField: Boolean): Boolean;
{$IFNDEF DLLMode}
    function GetPictureSize(const CatalogDir: TFileName = ''): Int64;
{$ENDIF}
  end;

  TMovieProperties = class(TObject)
  public
    strEncoding: string;
    strName: string;
    strMail: string;
    strSite: string;
    strDescription: string;
    procedure InitFields;
  end;

{$IFNDEF DLLMode}
  TMovieFilter = class(TObject)
  public
    field: Integer; // -1 = AllFields
    value: string;
    wholeField: Boolean;
  end;
{$ENDIF}

  TMovieList = class(TInterfacedObjectList, IMovieList)
  private
    procedure ReadDatas(InputFile: TFileStream; const version: integer);
{$IFNDEF DLLMode}
    procedure ReadRecords(InputFile: TFileStream; const RecordSize: longint; const version: integer);
    procedure ReadPictures(const strFilename: string);
    procedure ReadBorrowers(const strFilename: string);
{$ENDIF}
    function GetItem(const idx: Integer): TMovie;
    function GetItem2(const idx: Integer): IMovie;
    procedure SetItem(const idx: Integer; const Value: TMovie);
  public
    MovieProperties: TMovieProperties;
    CustomFieldsProperties: TCustomFieldsProperties;
    constructor Create(AOwnsObjects: Boolean = True); reintroduce; overload;
    destructor Destroy; override;
    function Find(const MovieNumber: Integer): TMovie;
    procedure SaveToFile(const strFilename: string; const Version: Integer = 99);
    procedure LoadFromFile(const strFileName: string);
    class function ReadHeader(const AFileName: TFileName; out TheHeader: string): Int64;
{$IFNDEF DLLMode}
    procedure SaveToXML(const strFilename: string; ConvertPictures: Boolean);
    procedure LoadFromXML(const strFilename: string);
    procedure Sort(const FieldsList: TStrings); overload;
    procedure SortReverse(const FieldsList: TStrings); overload;
    function GetMoviesByGroups(const Field: Integer; const IncOpt: TMovieIncludeOption = mioALL;
      MovieFilter : TMovieFilter = nil): TStringList;
{$ENDIF}
    procedure Sort(const Field: Integer); overload;
    procedure SortReverse(const Field: Integer); overload;
    procedure Add(const AMovieList: TMovieList; const AllowDuplicateNumbers: Boolean); overload;
    function Add: TMovie; overload;
    function Add2: IMovie; overload;
    function MaxNumber(ListIsSorted: Boolean = False): Integer;
    function FirstFreeNumber(ListIsSorted: Boolean = False): Integer;
    function HasImages(IncludeStored, IncludeLinksRel, IncludeLinksAbs: Boolean): Boolean;
    procedure GetValues(AList: TStrings; Field: Integer);
    function Count: Integer; overload;
    function Count(const IncOpt: TMovieIncludeOption): Integer; overload;
    procedure Count(out cAll, cSelected, cChecked, cVisible: Integer); overload;
    function Count(const MovieNumber: Integer): Integer; overload;
    procedure MakeFilteredList(AList: TList; const IncOpt: TMovieIncludeOption);
    procedure ShiftNumbers(const StartAt: Integer; const Exclude: TMovie);
    property Items[const idx: Integer]: TMovie read GetItem write SetItem; default;
end;

function GetFieldType(field: Integer): TFieldType;
function ConvertFieldValue(Value: string; FieldType: TFieldType;
  const LocalFormatSettings: Boolean = False;
  const ExtraDateValue: Boolean = False;
  const ReturnEmptyIfFalse : Boolean = True): string;
function ConvertFieldTypeFromString(Value: string): TFieldType;
function ConvertFieldTypeToString(Value: TFieldType): string;
{$IFNDEF DLLMode}
function ConvertFieldTypeFromSQL(Value: string): TFieldType;
function ConvertFieldTypeToSQL(Value: TFieldType): string;
function ConvertColorToHTML(Color: TColor): string;
function ConvertColorFromHTML(Color: string; DefaultColor: TColor = $000000): TColor;
{$ENDIF}
function IsValidTag(const s: string): Boolean;

function CompareStd(Item1, Item2: Pointer): Integer;
function CompareStdReverse(Item1, Item2: Pointer): Integer;
procedure FreeObjects(const strings: TStrings);

procedure WriteString(const str: string; OutputFile: TStream);
function ReadString(InputFile: TStream): string;

{$IFNDEF DLLMode}
function CompareAdv(Item1, Item2: Pointer): Integer;
function CompareAdvReverse(Item1, Item2: Pointer): Integer;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

var
  ClipboardFormat: WORD;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

implementation

uses
  IniFiles{$IFDEF MSWINDOWS}, Windows{$ENDIF}, DateUtils, Math, StrUtils

  {$IFNDEF DLLMode}, functions_str, Global, functions_files,
  functions_xml, ConstValues, ProgramSettings{$ENDIF};

const
  IntSize = SizeOf(Integer);
  BoolSize = SizeOf(Boolean);

const
  strErrorSave = 'Unable to save file "%s": ';
  strErrorLoad = 'Unable to load file "%s": ';
  strErrorHeader = 'Bad header';
  strUnableToIdentify = 'Cannot identify file type from header';
  strNoMovieFound = 'No catalog found in this file';

  strFileHeader   = ' AMC_?.? Ant Movie Catalog         www.buypin.com  www.ant.be.tf ';
  strFileHeader31 = ' AMC_3.1 Ant Movie Catalog 3.1.x   www.buypin.com  www.ant.be.tf ';
  strFileHeader33 = ' AMC_3.3 Ant Movie Catalog 3.3.x   www.buypin.com  www.ant.be.tf ';
  strFileHeader35 = ' AMC_3.5 Ant Movie Catalog 3.5.x   www.buypin.com    www.antp.be ';
  strFileHeader40 = ' AMC_4.0 Ant Movie Catalog 4.0.x   antp/soulsnake    www.antp.be ';

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

var
{$IFNDEF DLLMode}
  SortFieldsList: TStringList;
  SortFieldsIndex: Integer;
{$ENDIF}
  SortField: Integer;

{-------------------------------------------------------------------------------
  TString
-------------------------------------------------------------------------------}

constructor TString.Create(const Astr: String) ;
begin
   inherited Create;
   Fstr := Astr;
end;

{-------------------------------------------------------------------------------
  TCustomFieldProperties
-------------------------------------------------------------------------------}

constructor TCustomFieldProperties.Create(Tag: string);
begin
  inherited Create;
  ListValues := TStringList.Create;
  FieldTag := Tag;
  OldFieldTag := '';
  InitValues;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

destructor TCustomFieldProperties.Destroy;
begin
  ListValues.Free;
  inherited;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldProperties.InitValues;
begin
  FieldName := FieldTag;
  FieldType := ftString;
  DefaultValue := '';
  ListValues.Clear;
  MultiValues := False;
  ExcludedInScripts := False;
{$IFNDEF DLLMode}
  FieldObject := nil;
{$ENDIF}
  GUIProperties := '';
  OtherProperties := '';
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldProperties.Assign(
  CustomFieldProperties: TCustomFieldProperties);
begin
  FieldTag := CustomFieldProperties.FieldTag;
  OldFieldTag := CustomFieldProperties.OldFieldTag;
  FieldName := CustomFieldProperties.FieldName;
  FieldType := CustomFieldProperties.FieldType;
  ListValues.Assign(CustomFieldProperties.ListValues);
  DefaultValue := CustomFieldProperties.DefaultValue;
  MultiValues := CustomFieldProperties.MultiValues;
  ExcludedInScripts := CustomFieldProperties.ExcludedInScripts;
{$IFNDEF DLLMode}
  FieldObject := CustomFieldProperties.FieldObject;
{$ENDIF}
  GUIProperties := CustomFieldProperties.GUIProperties;
  OtherProperties := CustomFieldProperties.OtherProperties;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldProperties.WriteData(const OutputFile: TStream;
  const Version: Integer);
var
  i,j: Integer;
begin
  WriteString(FieldName, OutputFile);
  WriteString(ConvertFieldTypeToString(FieldType), OutputFile);
  WriteString(ConvertFieldValue(DefaultValue, FieldType, False, True), OutputFile);
  OutputFile.WriteBuffer(MultiValues, boolsize);
  OutputFile.WriteBuffer(ExcludedInScripts, boolsize);
  WriteString(GUIProperties, OutputFile);
  //WriteString(OtherProperties, OutputFile);

  // ListValues
  if FieldType = ftList then
  begin
    j := ListValues.Count;
    OutputFile.WriteBuffer(j, intsize);
    for i := 0 to ListValues.Count-1 do
      WriteString(ListValues.Strings[i], OutputFile);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldProperties.ReadData(const InputFile: TStream;
  const Version: Integer);
var
  i,j: Integer;
begin
  FieldName := ReadString(InputFile);
  FieldType := ConvertFieldTypeFromString(ReadString(InputFile));
  DefaultValue := ConvertFieldValue(ReadString(InputFile), FieldType, False, True);
  InputFile.ReadBuffer(MultiValues, boolsize);
  InputFile.ReadBuffer(ExcludedInScripts, boolsize);
  GUIProperties := ReadString(InputFile);
  //OtherProperties := ReadString(InputFile);

  // ListValues
  if FieldType = ftList then
  begin
    InputFile.ReadBuffer(j, intsize);
    for i := 0 to j-1 do
      ListValues.Add(ReadString(InputFile));
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}
{$IFNDEF DLLMode}
procedure TCustomFieldProperties.SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string);
var
  i: Integer;
  Elem: TJvSimpleXmlElem;
begin
  if (Root = nil) then
    Exit;

  AddNotEmpty(Root.Properties, 'Name', FieldName);
  AddNotEmpty(Root.Properties, 'Type', ConvertFieldTypeToString(FieldType));
  AddNotEmpty(Root.Properties, 'DefaultValue', ConvertFieldValue(DefaultValue, FieldType, False, True));
  AddNotEmpty(Root.Properties, 'MultiValues', IfThen(MultiValues, 'True', ''));
  AddNotEmpty(Root.Properties, 'ExcludedInScripts', IfThen(ExcludedInScripts, 'True', ''));
  AddNotEmpty(Root.Properties, 'GUIProperties', GUIProperties);
  AddNotEmpty(Root.Properties, 'OtherProperties', OtherProperties);

  // ListValues;
  if FieldType = ftList then
    for i := 0 to ListValues.Count-1 do
    begin
      Elem := Root.Items.Add('ListValue');
      AddNotEmpty(Elem.Properties, 'Text', ListValues.Strings[i])
    end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldProperties.LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer);
var
  str: string;
  i: Integer;
begin
  if (Root = nil) then
    Exit;
  //InitValues;
  FieldName := ReadTag(Root.Properties, 'Name', FieldName);
  FieldType := ConvertFieldTypeFromString(ReadTag(Root.Properties, 'Type',
     ConvertFieldTypeToString(FieldType)));
  DefaultValue := ConvertFieldValue(ReadTag(Root.Properties, 'DefaultValue', DefaultValue), FieldType, False, True);
  str := ReadTag(Root.Properties, 'MultiValues', IfThen(MultiValues, 'True', ''));
  MultiValues := ConvertFieldValue(str, ftBoolean) = 'True';
  str := ReadTag(Root.Properties, 'ExcludedInScripts', IfThen(ExcludedInScripts, 'True', ''));
  ExcludedInScripts := ConvertFieldValue(str, ftBoolean) = 'True';
  GUIProperties := ReadTag(Root.Properties, 'GUIProperties', GUIProperties);
  OtherProperties := ReadTag(Root.Properties, 'OtherProperties', OtherProperties);
  ListValues.Clear;
  if FieldType = ftList then
    with Root.Items do
      for i := 0 to Count-1 do
        if (Item[i].Name = 'ListValue') then
        begin
          str := ReadTag(Item[i].Properties, 'Text', '');
          if (str <> '') and (ListValues.IndexOf(str) = -1) then
            ListValues.Add(str);
        end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
  TCustomFieldsProperties
-------------------------------------------------------------------------------}

constructor TCustomFieldsProperties.Create;
begin
  inherited Create;
  Sorted := True;
  CaseSensitive := False;
  Duplicates := dupIgnore;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

destructor TCustomFieldsProperties.Destroy;
begin
  FreeObjects(Self);
  inherited;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.SetItem(const idx: Integer; Value: TCustomFieldProperties);
begin
  inherited Objects[idx] := Value;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.GetItem(const idx: Integer): TCustomFieldProperties;
begin
  Result := TCustomFieldProperties(inherited Objects[idx]);
end;
{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.Clear;
begin
  FreeObjects(Self);
  inherited Clear;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.Assign(CustomFieldsProperties: TCustomFieldsProperties;
  CopyMovieListRef: Boolean);
var
  i: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  // Free and clear objects
  Clear();

  inherited Assign(CustomFieldsProperties);

  ColumnSettings := CustomFieldsProperties.ColumnSettings;
  GUIProperties := CustomFieldsProperties.GUIProperties;
  OtherProperties := CustomFieldsProperties.OtherProperties;

  if CopyMovieListRef then
    MovieList := CustomFieldsProperties.MovieList;

  // Make a copy of objects!
  for i:=0 to Count-1 do
  begin
    FieldProperties := TCustomFieldProperties.Create(Objects[i].FieldTag);
    FieldProperties.Assign(Objects[i]);
    Objects[i] := FieldProperties;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.GetField(Tag: string): TCustomFieldProperties;
var
  Idx: Integer;
begin
  Result := nil;
  Idx := IndexOf(Tag);
  if Idx = -1 then
    Exit;
  Result := Objects[idx];
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.AddField(Tag: string; SpecialAdd: Boolean = False): TCustomFieldProperties;
var
  Idx: Integer;
begin
  Result := nil;
  Idx := Add(Tag);
  if(Idx <> -1) then
  begin
    if (not Assigned(Objects[Idx])) then
    begin
      Objects[Idx] := TCustomFieldProperties.Create(Tag);
      if SpecialAdd then
        Objects[Idx].OldFieldTag := '#';
    end;
    Result := Objects[Idx];
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.DeleteField(Tag: string): Boolean;
var
  Idx: Integer;
begin
  Result := Find(Tag, Idx);
  if Result = False then
    Exit;
  Objects[Idx].Free;
  Objects[Idx] := nil;
  Delete(Idx);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.ChangeFieldTag(Tag, NewTag: string): Boolean;
var
  Idx: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  Result := False;
  Idx := IndexOf(Tag);
  if (Idx = -1) then
    Exit;
  if (IndexOf(NewTag) <> -1) and (AnsiCompareText(Tag, NewTag) <> 0) then
    Exit;
  FieldProperties := nil;
  if (Objects[Idx] <> nil) then
  begin
    FieldProperties := Objects[Idx];
    if FieldProperties.OldFieldTag = '' then
      FieldProperties.OldFieldTag := FieldProperties.FieldTag;
    FieldProperties.FieldTag := NewTag;
  end;
  Delete(Idx);
  Idx := Add(NewTag);
  Objects[Idx] := FieldProperties;
  Result := True;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFieldsProperties.ChangeFieldType(Tag: string;
  NewType: TFieldType): Boolean;
var
  FieldProperties: TCustomFieldProperties;
begin
  Result := False;
  FieldProperties := GetField(Tag);
  if (FieldProperties = nil) then
    Exit;
  if (NewType <> FieldProperties.FieldType) then
  begin
    FieldProperties.ListValues.Clear;
    FieldProperties.FieldType := NewType;
    FieldProperties.DefaultValue := ConvertFieldValue(FieldProperties.DefaultValue, NewType, False, True);
  end;
  Result := True;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.CheckFieldsTag;
var
  i, n: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  if (MovieList = nil) then
    Exit;
  // First pass !
  for i:=0 to Count-1 do
  begin
    FieldProperties := Objects[i];
    if (FieldProperties.OldFieldTag <> '') and (FieldProperties.OldFieldTag <> '#') then
    begin
      for n:=0 to MovieList.Count-1 do
        MovieList.GetItem(n).CustomFields.ChangeTag(FieldProperties.OldFieldTag, '*'+FieldProperties.FieldTag);
      FieldProperties.OldFieldTag := '*'+FieldProperties.FieldTag;
    end
    else if (FieldProperties.OldFieldTag = '#') then
    begin // SpecialAdd (delete old values if exists)
      for n:=0 to MovieList.Count-1 do
        MovieList.GetItem(n).CustomFields.DeleteFieldValue(FieldProperties.FieldTag);
      FieldProperties.OldFieldTag := '';
    end;
  end;
  // Second pass !
  for i:=0 to Count-1 do
  begin
    FieldProperties := Objects[i];
    if (FieldProperties.OldFieldTag <> '') then
    begin
      for n:=0 to MovieList.Count-1 do
        MovieList.GetItem(n).CustomFields.ChangeTag(FieldProperties.OldFieldTag, FieldProperties.FieldTag);
      FieldProperties.OldFieldTag := '';
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.CheckFieldsValue;
var
  i: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  if (MovieList = nil) then
    Exit;
  for i:=0 to Count-1 do
  begin
    FieldProperties := Objects[i];
    FieldProperties.DefaultValue := ConvertFieldValue(
      FieldProperties.DefaultValue, FieldProperties.FieldType, False, True);
  end;
  for i:=0 to MovieList.Count-1 do
    MovieList.GetItem(i).CustomFields.CheckFieldsValue;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.WriteData(const OutputFile: TStream;
  const Version: Integer);
var
  i,j: Integer;
  Tag: string;
begin
  WriteString(ColumnSettings, OutputFile);
  WriteString(GUIProperties, OutputFile);
  //WriteString(OtherProperties, OutputFile);
  j := Count;
  OutputFile.WriteBuffer(j, intsize);
  for i := 0 to j-1 do
  begin
    Tag := Strings[i];
    WriteString(Tag, OutputFile);
    Objects[i].WriteData(OutputFile, Version);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.ReadData(const InputFile: TStream;
  const Version: Integer);
var
  i,j: Integer;
  Tag: string;
begin
  ColumnSettings := ReadString(InputFile);
  GUIProperties := ReadString(InputFile);
  //OtherProperties := ReadString(InputFile);
  InputFile.ReadBuffer(j, intsize);
  for i := 0 to j-1 do
  begin
    Tag := ReadString(InputFile);
    AddField(Tag).ReadData(InputFile, Version);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}
{$IFNDEF DLLMode}
procedure TCustomFieldsProperties.SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string);
var
  i: Integer;
  Elem: TJvSimpleXmlElem;
begin
  if (Root = nil) then
    Exit;
  AddNotEmpty(Root.Properties, 'ColumnSettings', ColumnSettings);
  AddNotEmpty(Root.Properties, 'GUIProperties', GUIProperties);
  AddNotEmpty(Root.Properties, 'OtherProperties', OtherProperties);
  for i := 0 to Count-1 do
  begin
    if IsValidTag(Strings[i]) then
    begin
      Elem := Root.Items.Add('CustomField');
      AddNotEmpty(Elem.Properties, 'Tag', Strings[i]);
      Objects[i].SaveToXML(Elem);
    end
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.LoadFromXML(Root: TJvSimpleXmlElem;
  const Version: Integer; ClearBefore, SpecialAdd: Boolean);
var
  i: Integer;
  Tag: string;
  CustomFieldProperties: TCustomFieldProperties;
begin
  if ClearBefore then
    Clear;
  if (Root = nil) then
    Exit;
  ColumnSettings := ReadTag(Root.Properties, 'ColumnSettings', '');
  GUIProperties := ReadTag(Root.Properties, 'GUIProperties', '');
  OtherProperties := ReadTag(Root.Properties, 'OtherProperties', '');
  with Root.Items do
    for i := 0 to Count-1 do
      if(Item[i].Name = 'CustomField') then
      begin
        Tag := ReadTag(Item[i].Properties, 'Tag', '');
        if IsValidTag(Tag) then
        begin
          if ClearBefore then
            CustomFieldProperties := AddField(Tag, SpecialAdd)
          else
          begin
            CustomFieldProperties := GetField(Tag);
            if CustomFieldProperties = nil then
              CustomFieldProperties := AddField(Tag, SpecialAdd);
            // else Replace It
          end;
          if (CustomFieldProperties <> nil) then
            CustomFieldProperties.LoadFromXML(Item[i]);
        end
      end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.ExportToXML(const strFileName: string);
var
  i: Integer;
  HeaderItem: TJvSimpleXmlElemHeader;
begin
  if MovieList = nil then
    Exit;
  with TJvSimpleXml.Create(nil) do
    try
      WriteXMLHeader(Root, intFileVersion, 'AntMovieCatalog', strVersion, strDate);
      with Prolog do
      begin
        HeaderItem := nil;
        for i := 0 to Count-1 do
          if Item[i] is TJvSimpleXmlElemHeader then
          begin
            HeaderItem := TJvSimpleXmlElemHeader(Item[i]);
            Break;
          end;
        if HeaderItem = nil then
        begin
          HeaderItem := TJvSimpleXmlElemHeader.Create;
          Add(HeaderItem);
        end;
        HeaderItem.Encoding := MovieList.MovieProperties.strEncoding;
      end;
      Root.Items.Delete('Catalog');
      with Root.Items.Add('Catalog') do
        if Self.Count > 0 then
          Self.SaveToXML(Items.Add('CustomFieldsProperties'), strFilename);
      SaveToFile(strFilename);
    finally
      Free;
    end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFieldsProperties.ImportFromXML(const strFileName: string);
var
  CurElem: TJvSimpleXmlElem;
begin
  if (MovieList = nil) then
    Exit;
  with TJvSimpleXml.Create(nil) do
    try
      LoadFromFile(strFilename);
      if Root.Name <> 'AntMovieCatalog' then
        raise Exception.Create(Format(strErrorLoad + strUnableToIdentify, [strFileName]));
      CurElem := Root.Items.ItemNamed['Catalog'];
      if CurElem = nil then
        raise Exception.Create(Format(strErrorLoad + strNoMovieFound, [strFileName]));
      CurElem := Root.Items.ItemNamed['Catalog'].Items.ItemNamed['CustomFieldsProperties'];
      if CurElem <> nil then
      begin
        LoadFromXml(CurElem, 99, False, True);
      end;
    finally
      Free;
    end;
end;
{$ENDIF}
{-------------------------------------------------------------------------------
  TCustomFields
-------------------------------------------------------------------------------}

constructor TCustomFields.Create;
begin
  inherited;
  Sorted := True;
  CaseSensitive := False;
  Duplicates := dupIgnore;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

destructor TCustomFields.Destroy;
begin
  FreeObjects(Self);
  inherited;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.SetItem(const idx: Integer; Value: TString);
begin
  inherited Objects[idx] := Value;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.GetItem(const idx: Integer): TString;
begin
  Result := TString(inherited Objects[idx]);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.Clear;
begin
  FreeObjects(Self);
  inherited Clear;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.Assign(CustomFields: TCustomFields; CopyPropertiesRef: Boolean);
var
  i: Integer;
begin
  // Free and clear objects
  Clear();

  if CopyPropertiesRef then
    Properties := CustomFields.Properties;

  inherited Assign(CustomFields);

  // Make a copy of objects!
  for i:=0 to Count-1 do
    Objects[i] := TString.Create(Objects[i].str);

  CheckFieldsValue;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.CheckFieldsValue;
var
  i: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  if Properties = nil then
    Exit;

  for i:=Count-1 downto 0 do
  begin
    FieldProperties := Properties.GetField(Strings[i]);
    if(FieldProperties = nil) then
      DeleteFieldValue(Strings[i])
    else
      with Objects[i] do
      begin
        str := ConvertFieldValue(str, FieldProperties.FieldType);
        if str = '' then
          DeleteFieldValue(Strings[i])
      end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.SetDefaultValues;
var
  i: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  if Properties = nil then
    Exit;

  for i:=0 to Properties.Count-1 do
  begin
    if (Properties.Objects[i] <> nil) then
    begin
      FieldProperties := Properties.Objects[i];
      if (FieldProperties.FieldType = ftDate) and (AnsiSameText(FieldProperties.DefaultValue, 'Today')) then
        {$IFNDEF DLLMode}
        SetFieldValue(Properties.Strings[i], DateToStr(Date, FormatSettings))
        {$ELSE}
        SetFieldValue(Properties.Strings[i], DateToStr(Date))
        {$ENDIF}
      else
        SetFieldValue(Properties.Strings[i], FieldProperties.DefaultValue);
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.SetFieldValue(Tag, Value: string;
  AddMissingField: Boolean): Boolean;
var
  Idx: Integer;
  Prop: TCustomFieldProperties;
begin
  Result := False;
  if Properties = nil then
    Exit;

  Prop := Properties.GetField(Tag);
  if Prop = nil then
    if AddMissingField then // Auto insert missing custom field properties
      Prop := Properties.AddField(Tag)
    else
      Exit;

  if Prop = nil then
    Exit;

  Value := ConvertFieldValue(Value, Prop.FieldType);

  if Value = '' then
  begin
    DeleteFieldValue(Tag);
  end else
  begin
    Idx := Add(Tag);
    if Idx <> -1 then
    begin
      if(not Assigned(Objects[Idx])) then
        Objects[Idx] := TString.Create('');
      with Objects[Idx] do
        str := Value;
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.GetFieldValue(Tag: string; ReturnEmptyIfFalse : Boolean; const LocalFormatSettings: Boolean): string;
var
  Idx: Integer;
  Prop: TCustomFieldProperties;
begin
  Result := '';
  if Properties = nil then
    Exit;

  Prop := Properties.GetField(Tag);
  if Prop = nil then
    Exit;

  Idx := IndexOf(Tag);
  if Idx <> -1 then
    Result := Objects[idx].str;
  Result := ConvertFieldValue(Result, Prop.FieldType, LocalFormatSettings, False, ReturnEmptyIfFalse);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.GetIntFieldValue(Tag: string): Integer;
var
  Idx: Integer;
  Value: string;
  Prop: TCustomFieldProperties;
begin
  Result := -1;
  if Properties = nil then
    Exit;

  Prop := Properties.GetField(Tag);
  if Prop = nil then
    Exit;

  Idx := IndexOf(Tag);
  if Idx <> -1 then
    Value := Objects[idx].str
  else
    Exit;
  try
    if (Prop.FieldType = ftInteger) then
      Result := StrToIntDef(Value, -1)
    else if (Prop.FieldType = ftReal) then
    begin
      {$IFNDEF DLLMode}
      Result := Trunc(StrToFloatDef(Value, -0.01, FormatSettings) * 100);
      {$ELSE}
      Result := Trunc(StrToFloatDef(Value, -0.01) * 100);
      {$ENDIF}
    end
    else if (Prop.FieldType = ftDate) then
      {$IFNDEF DLLMode}
      Result := Trunc(StrToDateDef(Value, 0, FormatSettings));
      {$ELSE}
      Result := Trunc(StrToDateDef(Value, 0));
      {$ENDIF}
  except
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.ChangeTag(Tag: string; NewTag: string): Boolean;
var
  Obj: TString;
  Idx: Integer;
begin
  Result := False;
  if (IndexOf(NewTag) <> -1) then // Need to be done here to delete old value !
    DeleteFieldValue(NewTag);
  Idx := IndexOf(Tag);
  if (Idx = -1) then
    Exit;
  Obj := Objects[Idx];
  Delete(Idx);
  Idx := Add(NewTag);
  Objects[Idx] := Obj;
  Result := True;
end;


{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.DeleteFieldValue(Tag: string): Boolean;
var
  Idx: Integer;
begin
  Result := False;
  if Properties = nil then
    Exit;

  Result := Find(Tag, Idx);
  if Result = False then
    Exit;
  Objects[idx].Free;
  Objects[idx] := nil;
  Delete(Idx);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TCustomFields.GetFieldProperties(Tag: string): TCustomFieldProperties;
begin
  Result := nil;
  if Properties <> nil then
    Result := Properties.GetField(Tag);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.WriteData(const OutputFile: TStream;
  const Version: Integer);
var
  i: Integer;
begin
  for i := 0 to Properties.Count-1 do
    WriteString(GetFieldValue(Properties.Strings[i]), OutputFile);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.ReadData(const InputFile: TStream;
  const Version: Integer);
var
  i: Integer;
begin
  for i := 0 to Properties.Count-1 do
    SetFieldValue(Properties.Strings[i], ReadString(InputFile));
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}
{$IFNDEF DLLMode}
procedure TCustomFields.SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string);
var
  i: Integer;
begin
  if (Root = nil) or (Properties = nil) then
    Exit;
  for i := 0 to Count-1 do
    if IsValidTag(Strings[i]) then
      AddNotEmpty(Root.Properties, Strings[i], Objects[i].str);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TCustomFields.LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer);
var
  i: Integer;
begin
  Clear;
  if (Root = nil) or (Properties = nil) then
    Exit;
  with Root.Properties do
    for i := 0 to Count-1 do
      if IsValidTag(Item[i].Name) then
        SetFieldValue(Item[i].Name, InsertLineBreaks(Item[i].Value));
end;
{$ENDIF}
{-------------------------------------------------------------------------------
  TMovie
-------------------------------------------------------------------------------}

constructor TMovie.Create(CustomFieldsProperties: TCustomFieldsProperties);
begin
  inherited create;
{$IFNDEF DLLMode}
  mutex := CreateMutex(nil, False, Pchar('TMovieMutex'+IntToStr(Integer(Self))));
{$ENDIF}
  CustomFields := TCustomFields.Create;
  CustomFields.Properties := CustomFieldsProperties;
  Picture := nil;
{$IFNDEF DLLMode}
  _listItems := TObjectList.Create(False);
  _thumb := nil;
  _thumbError := 0;
{$ENDIF}
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

destructor TMovie.Destroy;
begin
  CustomFields.Free;
  if (Picture <> nil) then
    Picture.Free;
{$IFNDEF DLLMode}
  if (_thumb <> nil) then
    _thumb.Free;
  _listItems.Free;
  CloseHandle(mutex);
{$ENDIF}
  inherited destroy;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovie.Lock();
begin
  if WaitForSingleObject(mutex, INFINITE) <> WAIT_OBJECT_0 then
    RaiseLastOSError;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.LockWait(msTime: Integer): boolean;
begin
  if WaitForSingleObject(mutex, msTime) <> WAIT_OBJECT_0 then
    Result := false
  else
    Result := true;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.Unlock();
begin
  ReleaseMutex(Mutex);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.GetFields(MovieFields: PMovie30);
begin
  if MovieFields <> nil then
  begin
    FillChar(MovieFields^,sizeof(MovieFields^),' ');
    with MovieFields^ do
    begin
      iNumber := self.iNumber;
      strOriginalTitle := self.strOriginalTitle;
      strTranslatedTitle := self.strTranslatedTitle;
      strDirector := self.strDirector;
      strProducer := self.strProducer;
      strCountry := self.strCountry;
      iYear := self.iYear;
      strCategory := self.strCategory;
      iLength := self.iLength;
      strActors := self.strActors;
      strURL := self.strURL;
      StrLCopy(@(strDescription)[1],PChar(self.strDescription),sizeof(strDescription));
      strComments := self.strComments;
      strVideoFormat := self.strVideoFormat;
      strSize := self.strSize;
      strResolution := self.strResolution;
      strLanguages := self.strLanguages;
      strSubtitles := self.strSubtitles;
      iRating := self.iRating;
      bChecked := self.bChecked;
      iDate := self.iDate;
      strPicture := self.strPicture;
      if Picture = nil then
        iPictureSize := 0
      else
        iPictureSize := Picture.Size;
      strBorrower := self.strBorrower;
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.SetFields(MovieFields: PMovie30);
begin
  self.InitFields;
  if MovieFields <> nil then
  begin
    with MovieFields^ do
    begin
      self.iNumber := iNumber;
      self.strOriginalTitle := strOriginalTitle;
      self.strTranslatedTitle := strTranslatedTitle;
      self.strDirector := strDirector;
      self.strProducer := strProducer;
      self.strCountry := strCountry;
      if iYear = 1 then
        self.iYear := -1
      else
        self.iYear := iYear;
      self.strCategory := strCategory;
      if iLength = 1 then
        self.iLength := -1
      else
        self.iLength := iLength;
      self.strActors := strActors;
      if strURL = 'http://' then
        self.strURL := ''
      else
        self.strURL := strURL;
      self.strDescription := strDescription;
      self.strComments := strComments;
      self.strVideoFormat := strVideoFormat;
      self.strSize := strSize;
      self.strResolution := strResolution;
      self.strLanguages := strLanguages;
      self.strSubtitles := strSubtitles;
      self.iRating := iRating;
      self.bChecked := bChecked;
      self.iDate := iDate;
      self.strPicture := strPicture;
      self.strBorrower := strBorrower;
    end;
  end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.WriteData(const OutputFile: TStream; const Version: Integer);
var
  recsize: integer;
begin
  with OutputFile do
  begin
    WriteBuffer(iNumber, intsize);
    WriteBuffer(iDate, intsize);
    WriteBuffer(iRating, intsize);
    WriteBuffer(iYear, intsize);
    WriteBuffer(iLength, intsize);
    WriteBuffer(iVideoBitrate, intsize);
    WriteBuffer(iAudioBitrate, intsize);
    WriteBuffer(iDisks, intsize);
    WriteBuffer(bChecked, boolsize);
    WriteString(strMedia, OutputFile);
    WriteString(strMediaType, OutputFile);
    WriteString(strSource, OutputFile);
    WriteString(strBorrower, OutputFile);
    WriteString(strOriginalTitle, OutputFile);
    WriteString(strTranslatedTitle, OutputFile);
    WriteString(strDirector, OutputFile);
    WriteString(strProducer, OutputFile);
    WriteString(strCountry, OutputFile);
    WriteString(strCategory, OutputFile);
    WriteString(strActors, OutputFile);
    WriteString(strURL, OutputFile);
    WriteString(strDescription, OutputFile);
    WriteString(strComments, OutputFile);
    WriteString(strVideoFormat, OutputFile);
    WriteString(strAudioFormat, OutputFile);
    WriteString(strResolution, OutputFile);
    WriteString(strFramerate, OutputFile);
    WriteString(strLanguages, OutputFile);
    WriteString(strSubtitles, OutputFile);
    WriteString(strSize, OutputFile);
    WriteString(strPicture, OutputFile);
    if (Picture = nil) then
      recsize := 0
    else
      recsize := Picture.Size;
    WriteBuffer(recsize, intsize);
    if recsize > 0 then
    begin
      Picture.Seek(0, soFromBeginning);
      CopyFrom(Picture, recsize)
    end;
    if Version >= 40 then
      CustomFields.WriteData(OutputFile, Version);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.ReadData(const InputFile: TStream; const Version: Integer = 99);
var
  recsize: integer;
begin
  with InputFile do
  begin
    ReadBuffer(iNumber, intsize);
    ReadBuffer(iDate, intsize);
    ReadBuffer(iRating, intsize);
    if (Version < 35) and (iRating <> -1) then
      iRating := iRating * 10;
    ReadBuffer(iYear, intsize);
    ReadBuffer(iLength, intsize);
    ReadBuffer(iVideoBitrate, intsize);
    ReadBuffer(iAudioBitrate, intsize);
    ReadBuffer(iDisks, intsize);
    ReadBuffer(bChecked, boolsize);
    strMedia := ReadString(InputFile);
    if Version >= 33 then
    begin
      strMediaType := ReadString(InputFile);
      strSource := ReadString(InputFile);
    end else
    begin
      strMediaType := '';
      strSource := '';
    end;
    strBorrower := ReadString(InputFile);
    strOriginalTitle := ReadString(InputFile);
    strTranslatedTitle := ReadString(InputFile);
    strDirector := ReadString(InputFile);
    strProducer := ReadString(InputFile);
    strCountry := ReadString(InputFile);
    strCategory := ReadString(InputFile);
    strActors := ReadString(InputFile);
    strURL := ReadString(InputFile);
    strDescription := ReadString(InputFile);
    strComments := ReadString(InputFile);
    strVideoFormat := ReadString(InputFile);
    strAudioFormat := ReadString(InputFile);
    strResolution := ReadString(InputFile);
    strFramerate := ReadString(InputFile);
    strLanguages := ReadString(InputFile);
    strSubtitles := ReadString(InputFile);
    strSize := ReadString(InputFile);
    strPicture := ReadString(InputFile);
    ReadBuffer(recsize, intsize);
    if recsize > 0 then
    begin
      Picture := TMemoryStream.Create;
      Picture.CopyFrom(InputFile, recsize)
    end;
    if Version >= 40 then
      CustomFields.ReadData(InputFile, Version);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFDEF MSWINDOWS}
function TMovie.SaveToMemory: THandle;
var
  Stream: TMemoryStream;
  DataPtr: Pointer;
begin
  Stream := TMemoryStream.Create;
  try
    WriteData(Stream);
    Stream.Seek(0, soFromBeginning);
    Result := GlobalAlloc(GMEM_MOVEABLE, Stream.Size);
    if Result <> 0 then
    begin
      DataPtr := GlobalLock(Result);
      if DataPtr <> nil then
      begin
        Move(Stream.Memory^, DataPtr^, Stream.Size);
      end;
    end;
  finally
    Stream.Free;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

class procedure TMovie.FreeMemory(DataHandle: THandle; OnlyUnlock: Boolean = False);
begin
  if DataHandle <> 0 then
  begin
    GlobalUnlock(DataHandle);
    if not OnlyUnlock then
      GlobalFree(DataHandle);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.LoadFromMemory(DataHandle: THandle);
var
  DataPtr: Pointer;
  DataSize: Integer;
  Stream: TMemoryStream;
begin
  if DataHandle <> 0 then
  begin
    DataPtr := GlobalLock(DataHandle);
    if DataPtr <> nil then
    begin
      Stream := TMemoryStream.Create;
      try
        DataSize := GlobalSize(DataHandle);
        Stream.SetSize(DataSize);
        Move(DataPtr^, stream.Memory^, DataSize);
        Stream.Seek(0, soFromBeginning);
        ReadData(Stream);
      finally
        Stream.Free;
        GlobalUnlock(DataHandle);
      end;
    end;
  end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
{function TMovie.GetFieldValue(const FieldName: string):string;
begin
  Result := GetFieldValue(strFields.IndexOf(FieldName));
end;}
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.GetFieldValue(const FieldID: TMovieField; const LocalFormatSettings: Boolean = False): string;
begin
  case FieldID of
    fieldNumber:          Result := IntToStr(iNumber);
    fieldChecked:         Result := BoolToStr(bChecked, True);
    fieldMedia:           Result := strMedia;
    fieldMediaType:       Result := strMediaType;
    fieldSource:          Result := strSource;
    fieldDate:
        if iDate > 0 then
        begin
          {$IFNDEF DLLMode}
          if not LocalFormatSettings then
            Result := DateToStr(iDate, FormatSettings)
          else
          {$ENDIF}
            Result := DateToStr(iDate);
        end else
          Result := '';
    fieldBorrower:        Result := strBorrower;
    fieldRating:
      if iRating = -1 then
        Result := ''
      else
      begin
        {$IFNDEF DLLMode}
        if Settings.rOptions.rMovieInformation.RatingTrunc then
          Result := IntToStr(iRating div 10)
        else
          if not LocalFormatSettings then
            Result := FormatFloat('#0.0', iRating / 10, FormatSettings)
          else
        {$ENDIF}
            Result := FormatFloat('#0.0', iRating / 10);
      end;
    fieldOriginalTitle:   Result := strOriginalTitle;
    fieldTranslatedTitle: Result := strTranslatedTitle;
    {$IFNDEF DLLMode}
    fieldFormattedTitle:  Result := GetFormattedTitle(Settings.rOptions.rMovieList.TitleColumn, Settings.rOptions.rMovieList.UsePrefixes);
    {$ENDIF}
    fieldDirector:        Result := strDirector;
    fieldProducer:        Result := strProducer;
    fieldCountry:         Result := strCountry;
    fieldCategory:        Result := strCategory;
    fieldYear:
        if iYear = -1 then
          Result := ''
        else
          Result := IntToStr(iYear);
    fieldLength:
        if iLength = -1 then
          Result := ''
        else
          Result := IntToStr(iLength);
    fieldActors:          Result := strActors;
    fieldURL:             Result := strURL;
    fieldDescription:     Result := strDescription;
    fieldComments:        Result := strComments;
    fieldVideoFormat:     Result := strVideoFormat;
    fieldAudioFormat:     Result := strAudioFormat;
    fieldVideoBitrate:
        if iVideoBitrate = -1 then
          Result := ''
        else
          Result := IntToStr(iVideoBitrate);
    fieldAudioBitrate:
        if iAudioBitrate = -1 then
          Result := ''
        else
          Result := IntToStr(iAudioBitrate);
    fieldResolution:      Result := strResolution;
    fieldFramerate:       Result := strFramerate;
    fieldLanguages:       Result := strLanguages;
    fieldSubtitles:       Result := strSubtitles;
    fieldSize:            Result := strSize;
    fieldDisks:
        if iDisks = -1 then
          Result := ''
        else
          Result := IntToStr(iDisks);
  else
    Result := '';
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.GetIntFieldValue(const FieldID: TMovieField): Integer;
begin
  case FieldID of
    fieldNumber:          Result := iNumber;
    fieldDate:            Result := iDate;
    fieldRating:          Result := iRating;
    fieldYear:            Result := iYear;
    fieldLength:          Result := iLength;
    fieldVideoBitrate:    Result := iVideoBitrate;
    fieldAudioBitrate:    Result := iAudioBitrate;
    fieldDisks:           Result := iDisks;
  else
    Result := -1;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
{procedure TMovie.SetFieldValue(const FieldName: string; const Value: string);
var
  i: Integer;
begin
  if FieldName = strFieldPicture then
  begin
    if strPicture = '' then
      strPicture := Value;
  end
  else
  begin
    i := strFields.IndexOf(FieldName);
    if i in AllFields then
      SetFieldValue(i, Value);
  end;
end;}
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.SetFieldValue(const FieldID: TMovieField; const Value: string);
begin
  case FieldID of
    fieldNumber:          iNumber := StrToIntDef(Value, 0);
    fieldChecked:         bChecked := not ((Value = '') or (Value = '0') or (Value = '-1') or SameText(Value, 'False'));
    fieldMedia:           strMedia := Value;
    fieldMediaType:       strMediaType := Value;
    fieldSource:          strSource := Value;
    fieldBorrower:        strBorrower := value;
    {$IFNDEF DLLMode}
    fieldDate:            iDate := Trunc(StrToDateDef(ConvertFieldValue(Value, ftDate), 0, FormatSettings));
    fieldRating:          iRating := Trunc(StrToFloatDef(ConvertFieldValue(Value, ftReal), - 0.1, FormatSettings) * 10);
    {$ELSE}
    fieldDate:            iDate := Trunc(StrToDateDef(Value, 0));
    fieldRating:          iRating := StrToIntDef(Value, -1);
    {$ENDIF}
    fieldOriginalTitle:   strOriginalTitle := Value;
    fieldTranslatedTitle: strTranslatedTitle := Value;
    fieldDirector:        strDirector := Value;
    fieldProducer:        strProducer := Value;
    fieldCountry:         strCountry := Value;
    fieldCategory:        strCategory := Value;
    fieldYear:            iYear := StrToIntDef(Value, -1);
    fieldLength:          iLength := StrToIntDef(Value, -1);
    fieldActors:          strActors := Value;
    fieldURL:             strURL := Value;
    fieldDescription:     strDescription := Value;
    fieldComments:        strComments := Value;
    fieldVideoFormat:     strVideoFormat := Value;
    fieldAudioFormat:     strAudioFormat := Value;
    fieldVideoBitrate:    iVideoBitrate := StrToIntDef(Value, -1);
    fieldAudioBitrate:    iAudioBitrate := StrToIntDef(Value, -1);
    fieldResolution:      strResolution := Value;
    fieldFrameRate:       strFramerate := Value;
    fieldLanguages:       strLanguages := Value;
    fieldSubtitles:       strSubtitles := Value;
    fieldSize:            strSize := Value;
    fieldDisks:           iDisks := StrToIntDef(Value, -1);
  end; // case
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.InitFields;
begin
  iNumber := 0;
  bChecked := True;
  strMedia := '';
  strMediaType := '';
  strSource := '';
  iDate := 0;
  strBorrower := '';
  iRating := -1;
  strOriginalTitle := '';
  strTranslatedTitle := '';
  strDirector := '';
  strProducer := '';
  strCountry := '';
  strCategory := '';
  iYear := -1;
  iLength := -1;
  strActors := '';
  strURL := '';
  strDescription := '';
  strComments := '';
  strVideoFormat := '';
  iVideoBitrate := -1;
  strAudioFormat := '';
  iAudioBitrate := -1;
  strResolution := '';
  strFramerate := '';
  strLanguages := '';
  strSubtitles := '';
  strSize := '';
  iDisks := -1;
  strPicture := '';
  Picture := nil;
{$IFNDEF DLLMode}
  _bSelected := True;
  _bVisible := True;
  _thumb := nil;
  _thumbError := 0;
  _thumbWidth := 0;
  _thumbHeight := 0;
{$ENDIF}
  CustomFields.Clear;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}
{$IFNDEF DLLMode}

function TMovie.GetFormattedTitle: string;
begin
  with Settings.rOptions.rMovieList do
    Result := Global.GetFormattedTitle(strOriginalTitle, strTranslatedTitle, strMedia, TitleColumn, UsePrefixes);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.GetFormattedTitle(DisplayType: Integer; UsePrefixes: Boolean): string;
begin
  Result := Global.GetFormattedTitle(strOriginalTitle, strTranslatedTitle, strMedia, DisplayType, UsePrefixes);
end;

{$ENDIF}
{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.Assign(AMovie: TMovie; IncludeNumber: Boolean; IncludePicture: Boolean;
      IncludeCustomFields: Boolean);
begin
  if IncludeNumber then
    iNumber := AMovie.iNumber;
  strMedia := AMovie.strMedia;
  strMediaType := AMovie.strMediaType;
  strSource := AMovie.strSource;
  iDate := AMovie.iDate;
  strBorrower := AMovie.strBorrower;
  iRating := AMovie.iRating;
  strOriginalTitle := AMovie.strOriginalTitle;
  strTranslatedTitle := AMovie.strTranslatedTitle;
  strDirector := AMovie.strDirector;
  strProducer := AMovie.strProducer;
  strCountry := AMovie.strCountry;
  strCategory := AMovie.strCategory;
  iYear := AMovie.iYear;
  iLength := AMovie.iLength;
  strActors := AMovie.strActors;
  strURL := AMovie.strURL;
  strDescription := AMovie.strDescription;
  strComments := AMovie.strComments;
  strVideoFormat := AMovie.strVideoFormat;
  iVideoBitrate := AMovie.iVideoBitrate;
  strAudioFormat := AMovie.strAudioFormat;
  iAudioBitrate := AMovie.iAudioBitrate;
  strResolution := AMovie.strResolution;
  strFramerate := AMovie.strFramerate;
  strLanguages := AMovie.strLanguages;
  strSubtitles := AMovie.strSubtitles;
  strSize := AMovie.strSize;
  iDisks := AMovie.iDisks;
  bChecked := AMovie.bChecked;

  if IncludePicture then
  begin
    strPicture := AMovie.strPicture;
    if (Picture <> nil) then
      FreeAndNil(Picture);
    if AMovie.Picture <> nil then
    begin
      Picture := TMemoryStream.Create;
      AMovie.Picture.Seek(0, soFromBeginning);
      Picture.CopyFrom(AMovie.Picture, AMovie.Picture.Size);
    end;
{$IFNDEF DLLMode}
    if _thumb <> nil then
      FreeAndNil(_thumb);
    _thumbError := 0;
    _thumbWidth := 0;
    _thumbHeight := 0;
{$ENDIF}
  end;

  if IncludeCustomFields then
    CustomFields.Assign(AMovie.CustomFields, False);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovie.SaveToXML(Root: TJvSimpleXmlElem; const strFileName: string = '');
var
  i: Integer;
  PicName: string;
begin
  if Root = nil then
    raise Exception.Create('TMovie.SaveToXML : nil element');
  for i := 0 to fieldCount-1 do
    AddNotEmpty(Root.Properties, strTagFields[i], GetFieldValue(i));
  if (strPicture <> '') and (Picture <> nil) then
  begin
    if (strFileName <> '') then
    begin
      if Settings.rOptions.rMovieInformation.rPicImport.Naming = 2 then
        PicName := ValidateFileName(GetFormattedTitle) + strPicture
      else
        PicName := IntToStr(iNumber) + strPicture;
      if Settings.rOptions.rMovieInformation.rPicImport.CatalogPrefix then
        PicName := Format('%s_%s', [ChangeFileExt(ExtractFileName(strFileName), ''), PicName]);
      PicName := MakeUniqueFileName(PicName, '_');
      Picture.SaveToFile(PicName);
      PicName := ExtractFileName(PicName);
      strPicture := PicName;
    end
    else
      strPicture := '';
    Picture.Free;
    Picture := nil;
  end
  else
    PicName := strPicture;
  AddNotEmpty(Root.Properties, 'Picture', PicName);
  if CustomFields.Count > 0 then
    CustomFields.SaveToXML(Root.Items.Add('CustomFields'), strFileName);
  // AdditionalPictures.SaveToXML(Root.Items.Add('AdditionalPictures'), strFileName); // For future
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovie.LoadFromXML(Root: TJvSimpleXmlElem; const Version: Integer = 99);
var
  i: Integer;
begin
  if Root = nil then
    raise Exception.Create('TMovie.LoadFromXML : nil element');
  for i := 0 to fieldCount-1 do
    SetFieldValue(i, ReadTag(Root.Properties, strTagFields[i], GetFieldValue(i)));
  strPicture := ReadTag(Root.Properties, 'Picture', '');
//  if (Version < 35) and (iRating <> -1) then
//    iRating := iRating * 10;
  with Root.Items do
    for i := 0 to Count-1 do
      if Item[i].Name = 'CustomFields' then
        CustomFields.LoadFromXML(Item[i]);
      // else if Item[i].Name = 'AdditionalPictures' then // For future !!!
      //  AdditionalPictures.LoadFromXML(Root.Item[i]);
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.CanInclude(const IncOpt: TMovieIncludeOption): Boolean;
begin
  case IncOpt of
    mioChecked:     Result := Self.bChecked;
{$IFNDEF DLLMode}
    mioSelected:    Result := Self._bSelected;
    mioVisible:     Result := Self._bVisible;
{$ENDIF}
  else
    Result := True;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.CalcTotalSize: Int64;
var
  i: Integer;
  s: string;
begin
  i := 1;
  Result := 0;
  s := strSize;
  while i <= Length(s) do
  begin
    if s[i] in ['0'..'9'] then
      Inc(i)
    else
    begin
      if i > 1 then
        Inc(Result, StrToIntDef(Copy(s, 1, i - 1), 0));
      Delete(s, 1, i);
      i := 1;
    end;
  end;
  if i > 1 then
    Inc(Result, StrToIntDef(Copy(s, 1, i - 1), 0));
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovie.ContainsText(const Value: string; const Field: Integer; const WholeField: Boolean): Boolean;
var
  i: Integer;
  FieldProperties: TCustomFieldProperties;
begin
  Result := False;
  if Field in AllFields then
  begin
    Result := ((WholeField) and AnsiSameText(GetFieldValue(Field), Value))
      or ((not WholeField) and AnsiContainsText(GetFieldValue(Field), Value));
  end
  else
  if Field in AllCustomFields then
  begin
    if (CustomFields.Properties <> nil) and
      ((Field - customFieldLow) < CustomFields.Properties.Count) then
    begin
      FieldProperties := CustomFields.Properties.Objects[Field - customFieldLow];
      Result := ((WholeField) and AnsiSameText(CustomFields.GetFieldValue(FieldProperties.FieldTag, False), Value))
        or ((not WholeField) and AnsiContainsText(CustomFields.GetFieldValue(FieldProperties.FieldTag, False), Value))
    end;
  end
  else
  if Field = -1 then
  begin
    for i := fieldLow to fieldCount-1 do
      if ContainsText(Value, i, WholeField) then
      begin
        Result := True;
        Exit;
      end;
    if (CustomFields.Properties <> nil) then
      with CustomFields.Properties do
        for i := 0 to Count-1 do
          if ContainsText(Value, customFieldLow+i, WholeField) then
          begin
            Result := True;
            Exit;
          end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
function TMovie.GetPictureSize(const CatalogDir: TFileName = ''): Int64;
begin
  Result := 0;
  if strPicture <> '' then
  begin
    if Picture <> nil then
      Result := Picture.Size
    else
    begin
      if CatalogDir <> '' then
        SetCurrentDir(CatalogDir);
      Result := GetFileSize(ExpandFileName(strPicture));
    end;
  end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
  TMovieList
-------------------------------------------------------------------------------}

constructor TMovieList.Create(AOwnsObjects: Boolean);
begin
  inherited Create(AOwnsObjects);
  MovieProperties := TMovieProperties.Create;
  MovieProperties.InitFields;
  CustomFieldsProperties := TCustomFieldsProperties.Create;
  CustomFieldsProperties.MovieList := Self;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

destructor TMovieList.Destroy;
begin
  MovieProperties.Free;
  CustomFieldsProperties.Free;
  inherited;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.LoadFromFile(const strFileName: string);
var
  InputFile: TFileStream;
  Header: array[0..Length(strFileHeader)] of Char;
  iHeader{$IFNDEF DLLMode}, iRecSize{$ENDIF}: integer;
begin
  Header := '';
  iHeader := 0;
{$IFNDEF DLLMode}
  iRecSize := 0;
{$ENDIF}
  try
    InputFile:=TFileStream.Create(strFileName,fmOpenRead);
    try
      Inputfile.Seek(0,soFrombeginning);
      InputFile.Read(Header,Length(strFileHeader));
      if Header = strFileHeader40 then
      begin
        iHeader := 40;
      end else
      if Header = strFileHeader35 then
      begin
        iHeader := 35;
      end else
      if Header = strFileHeader33 then
      begin
        iHeader := 33;
      end else
      if Header = strFileHeader31 then
      begin
        iHeader := 31;
{$IFNDEF DLLMode}
      end else
      if Header = strFileHeader30 then
      begin
        iHeader := 30;
        iRecSize := sizeof(RMovie30);
      end else
      if Header = strFileHeader21 then
      begin
        iHeader := 21;
        iRecSize := sizeof(RMovie21);
      end else
      if Header = strFileHeader11 then
      begin
        iHeader := 11;
        iRecSize := sizeof(RMovie11);
      end else
      if Header = strFileHeader10 then
      begin
        iHeader := 10;
        iRecSize := sizeof(RMovie10);
{$ENDIF}
      end;
      Clear;
      case iHeader of
{$IFNDEF DLLMode}
        10,11,21,30:
          begin
            ReadRecords(InputFile, iRecSize, iHeader);
            if iHeader < 30 then
            begin
              ReadPictures(ChangeFileExt(strFileName, ''));
              ReadBorrowers(ChangeFileExt(strFileName, '.amcl'));
            end;
          end;
{$ENDIF}
        31,33,35,40:
          begin
            ReadDatas(InputFile, iHeader);
          end;
      else
        raise Exception.Create(strUnableToIdentify);
      end; // case
    finally
      InputFile.Free;
    end; // try
  except
    on E: Exception do
      raise Exception.Create(Format(strErrorLoad,[strFilename])+E.Message);
  end; // try
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

class function TMovieList.ReadHeader(const AFileName: TFileName; out TheHeader: string): Int64;
var
  i: Integer;
begin
  with TFileStream.Create(AFileName, fmOpenRead) do
    try
      Seek(0, soFrombeginning);
      SetLength(TheHeader, Length(strFileHeader));
      Read(TheHeader[1], Length(strFileHeader));
      i := Pos(sLineBreak, TheHeader);
      if i > 0 then
        SetLength(TheHeader, i - 1);
      Result := Size;
    finally
      Free;
    end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovieList.LoadFromXML(const strFilename: string);
var
  i, iHeader: Integer;
  CurElem: TJvSimpleXmlElem;
  NewMovie: TMovie;
  HeaderItem: TJvSimpleXmlElemHeader;
begin
  Clear;
  with TJvSimpleXml.Create(nil) do
    try
      if Assigned(ProgressWin) then
        ProgressWin.Update;
      LoadFromFile(strFilename);
      if Root.Name <> 'AntMovieCatalog' then
        raise Exception.Create(Format(strErrorLoad + strUnableToIdentify, [strFileName]));
      with Prolog do
      begin
        HeaderItem := nil;
        for i := 0 to Count-1 do
          if Item[i] is TJvSimpleXmlElemHeader then
          begin
            HeaderItem := TJvSimpleXmlElemHeader(Item[i]);
            Break;
          end;
        if HeaderItem <> nil then
          MovieProperties.strEncoding := HeaderItem.Encoding;
      end;
      iHeader := Root.Properties.IntValue('Format', intFileVersion);
      CurElem := Root.Items.ItemNamed['Catalog'];
      if CurElem = nil then
        raise Exception.Create(Format(strErrorLoad + strNoMovieFound, [strFileName]));
      CurElem := CurElem.Items.ItemNamed['Properties'];
      if CurElem <> nil then
      begin
        with CurElem, MovieProperties do
        begin
          strName := ReadTag(Properties, 'Owner', strName);
          strMail := ReadTag(Properties, 'Mail', strMail);
          strSite := ReadTag(Properties, 'Site', strSite);
          strDescription := ReadTag(Properties, 'Description', strDescription);
        end;
      end;
      CurElem := Root.Items.ItemNamed['Catalog'].Items.ItemNamed['CustomFieldsProperties'];
      if CurElem <> nil then
        CustomFieldsProperties.LoadFromXml(CurElem);
      CurElem := Root.Items.ItemNamed['Catalog'].Items.ItemNamed['Contents'];
      if CurElem <> nil then
        with CurElem do
        begin
          if Assigned(ProgressWin) then
          begin
            ProgressWin.Maximum := ProgressWin.Maximum + Ceil(Items.Count / 50) + 1;
            ProgressWin.StepIt;
          end;
          for i := 0 to Items.Count-1 do
          begin
            if (i mod 50 = 0) and Assigned(ProgressWin) then
              ProgressWin.StepIt;
            if Items.Item[i].Name = 'Movie' then
            begin
              NewMovie := TMovie.Create(CustomFieldsProperties);
              with NewMovie do
              begin
                InitFields;;
                NewMovie.LoadFromXML(Items.Item[i], iHeader);
              end;
              Self.Add(NewMovie);
            end;
          end;
        end;
    finally
      Free;
    end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.ReadDatas(InputFile: TFileStream; const version: Integer);
var
  newMovie: TMovie;
  {$IFNDEF DLLMode}
  c: Integer;
  {$ENDIF}
begin
  {$IFNDEF DLLMode}
  c := 0;
  if Assigned(ProgressWin) then
  begin
    ProgressWin.Maximum := Ceil(InputFile.Size / 1024) + 2;
    ProgressWin.IntProgress := 1;
  end;
  {$ENDIF}
  InputFile.Seek(Length(strFileHeader),soFrombeginning);
  with MovieProperties do
  begin
    strName := ReadString(InputFile);
    strMail := ReadString(InputFile);
    if version < 35 then
      ReadString(InputFile); // ICQ field that was deleted
    strSite := ReadString(InputFile);
    strDescription := ReadString(InputFile);
  end;
  if version >= 40 then
    CustomFieldsProperties.ReadData(InputFile, Version);
  newMovie := nil;
  while InputFile.Position < InputFile.Size do
  begin
    {$IFNDEF DLLMode}
    Inc(c);
    if (c mod 50 = 0) and Assigned(ProgressWin) then
      ProgressWin.IntProgress := InputFile.Position div 1024;
    {$ENDIF}
    try
      newMovie := TMovie.Create(CustomFieldsProperties);
      newMovie.ReadData(InputFile, version);
      Add(newMovie);
    except
      if IndexOf(NewMovie) = -1 then
        newMovie.Free;
      Break;
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovieList.ReadRecords(InputFile: TFileStream; const RecordSize: longint; const version: integer);
var
  CurrentMovie: PMovie30;
  addedMovie: TMovie;
  ReadBytes: Longint;
  Header: array[0..Length(strFileHeader)] of Char;
  MovieProperties: RMovieProperties;
  ts: TTimeStamp;
begin
  Header := '';
  Inputfile.Seek(0,soFrombeginning);
  ReadBytes := InputFile.Read(Header,Length(strFileHeader));
  if (version >= 21) and (ReadBytes>0) then
  begin
    ReadBytes := InputFile.Read(MovieProperties,sizeof(RMovieProperties));
  end else
  begin
    with MovieProperties do
    begin
      strOwnerName := '';
      strOwnerMail := '';
      strOwnerICQ := '';
      strOwnerSite := '';
    end;
  end;
  with MovieProperties do
  begin
    Self.MovieProperties.strName := strOwnerName;
    Self.MovieProperties.strMail := strOwnerMail;
    Self.MovieProperties.strSite := strOwnerSite;
    Self.MovieProperties.strDescription := '';
  end;
  CurrentMovie := new(PMovie30);
  while(ReadBytes>0) do
  begin
    FillChar(CurrentMovie^,sizeof(CurrentMovie^),' ');
    ReadBytes := InputFile.Read(CurrentMovie^,RecordSize);
    with CurrentMovie^ do
    begin
      if(ReadBytes>0) then
      begin
        if version < 11 then
        begin
          iRating := 0;
        end;
        if version < 21 then
        begin
          bChecked := true;
          iDate := 0;
        end;
        if version < 30 then
        begin
          strPicture := '';
          iPictureSize := 0;
          strBorrower := '';
          if iDate = DefaultDate then
            iDate := 0;
        end;
        if version < 31 then
        begin
          case iRating of
            0: iRating := -1;
            1: iRating := 2;
            2: iRating := 4;
            3: iRating := 6;
            4: iRating := 8;
            5: iRating := 9;
          else
            iRating := -1;
          end;
          if iDate <> 0 then
          begin
            ts.Time := 0;
            ts.Date := iDate;
            iDate := trunc(TimeStampToDateTime(ts));
          end;
        end;
        addedMovie := TMovie.Create(CustomFieldsProperties);
        addedMovie.SetFields(CurrentMovie);
        if iPictureSize > 0 then
        begin
          addedMovie.Picture := TMemoryStream.Create;
          addedMovie.Picture.CopyFrom(InputFile, iPictureSize);
        end;
        Add(addedMovie);
      end else
      begin
      end;
    end;
  end;
  Dispose(CurrentMovie);
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovieList.ReadPictures(const strFilename: string);
var
  i: integer;
  currentFile, ext: string;
begin
  for i := 0 to Count-1 do
  begin
    if Items[i] <> nil then
    begin
      with TMovie(Items[i]) do
      begin
        currentFile := Format('%s_%d', [strFileName, iNumber]);
        ext := '.jpg';
        if not FileExists(currentFile + ext) then
        begin
          ext := '.gif';
          if not FileExists(currentFile + ext) then
          begin
            ext := '.png';
            if not FileExists(currentFile + ext) then
            begin
              strPicture := '';
              continue;
            end; // not png
          end; // not gif
        end; // not jpg
        strPicture := ExtractFileName(currentFile) + ext;
      end;
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.ReadBorrowers(const strFilename: string);
var
  Names: TStringList;
  Movies: TStringList;
  i,j: integer;
  Movie: TMovie;
begin
  with TMemIniFile.Create(strFileName) do
    try
      Names := TStringList.Create;
      Movies := TStringList.Create;
      try
        ReadSections(Names);
        for i := 0 to Names.Count-1 do
        begin
          ReadSection(Names.Strings[i], Movies);
          for j := 0 to Movies.Count-1 do
          begin
            Movie := Find(StrToInt(Movies.Strings[j]));
            if Movie <> nil then
            begin
              Movie.strBorrower := Names.Strings[i];
            end;
          end;
        end;
      finally
        Movies.Free;
        Names.Free;
      end;
    finally
      Free;
    end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.SaveToFile(const strFilename: string; const Version: Integer);
var
  OutputFile: TFileStream;
  i: integer;
begin
  {$IFNDEF DLLMode}
  if Assigned(ProgressWin) then
  begin
    ProgressWin.Maximum := ProgressWin.Maximum + Ceil(Count / 25) + 1;
    ProgressWin.StepIt;
  end;
  {$ENDIF}
  OutputFile := TFileStream.Create(strFileName,fmCreate);
  try
    OutputFile.Size := 0;
    try
      if Version >= 40 then
        OutputFile.Write(strFileHeader40,Length(strFileHeader))
      else
        OutputFile.Write(strFileHeader35,Length(strFileHeader));

      with MovieProperties do
      begin
        WriteString(strName, OutputFile);
        WriteString(strMail, OutputFile);
        WriteString(strSite, OutputFile);
        WriteString(strDescription, OutputFile);
      end;
      if Version >= 40 then
        CustomFieldsProperties.WriteData(OutputFile, Version);
      for i := 0 to Count-1 do
      begin
        {$IFNDEF DLLMode}
        if (i mod 25 = 0) and Assigned(ProgressWin) then
          ProgressWin.StepIt;
        {$ENDIF}
        TMovie(Items[i]).WriteData(OutputFile, Version);
      end;
    except
      on E: Exception do
        raise Exception.Create(Format(strErrorSave,[strFilename])+E.Message);
    end;
  finally
    OutputFile.Free;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovieList.SaveToXML(const strFilename: string; ConvertPictures: Boolean{; const Header: string});
var
  i: Integer;
  s: string;
  HeaderItem: TJvSimpleXmlElemHeader;
begin
  if Assigned(ProgressWin) then
  begin
    ProgressWin.Maximum := ProgressWin.Maximum + Ceil(Count / 25);
    ProgressWin.StepIt;
  end;
  if ConvertPictures then
    s := strFilename
  else
    s := '';
  with TJvSimpleXml.Create(nil) do
    try
      WriteXMLHeader(Root, intFileVersion, 'AntMovieCatalog', strVersion, strDate);
      with Prolog do
      begin
        HeaderItem := nil;
        for i := 0 to Count-1 do
          if Item[i] is TJvSimpleXmlElemHeader then
          begin
            HeaderItem := TJvSimpleXmlElemHeader(Item[i]);
            Break;
          end;
        if HeaderItem = nil then
        begin
          HeaderItem := TJvSimpleXmlElemHeader.Create;
          Add(HeaderItem);
        end;
        HeaderItem.Encoding := MovieProperties.strEncoding;
      end;
      Root.Items.Delete('Catalog');
      with Root.Items.Add('Catalog') do
      begin
        with Items.Add('Properties'), MovieProperties do
        begin
          AddNotEmpty(Properties, 'Owner', strName);
          AddNotEmpty(Properties, 'Mail', strMail);
          AddNotEmpty(Properties, 'Site', strSite);
          AddNotEmpty(Properties, 'Description', strDescription);
        end;

        if CustomFieldsProperties.Count > 0 then
          CustomFieldsProperties.SaveToXml(Items.Add('CustomFieldsProperties'), strFilename);

        with Items.Add('Contents') do
        begin
          SetCurrentDir(ExtractFilePath(strFilename)); // for images
          for i := 0 to Count-1 do
          begin
            if (i mod 25 = 0) and Assigned(ProgressWin) then
              ProgressWin.StepIt;
            TMovie(Self.Items[i]).SaveToXML(Items.Add('Movie'), s);
          end;
        end;
      end;
      SaveToFile(strFilename);
      if Assigned(ProgressWin) then
        ProgressWin.StepIt;
    finally
      Free;
    end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.Sort(const Field: Integer);
begin
  SortField := Field;
  inherited Sort(CompareStd);
end;

procedure TMovieList.SortReverse(const Field: Integer);
begin
  SortField := Field;
  inherited Sort(CompareStdReverse);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
procedure TMovieList.Sort(const FieldsList: TStrings);
begin
  SortFieldsList.Assign(FieldsList);
  inherited Sort(CompareAdv);
end;

procedure TMovieList.SortReverse(const FieldsList: TStrings);
begin
  SortFieldsList.Assign(FieldsList);
  inherited Sort(CompareAdvReverse);
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Find(const MovieNumber: Integer): TMovie;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to Count-1 do
  begin
    if TMovie(Items[i]).iNumber = MovieNumber then
    begin
      Result := Items[i];
      Break;
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.Add(const AMovieList: TMovieList; const AllowDuplicateNumbers: Boolean);
var
  i: Integer;
  NewMovie, CurrentMovie: TMovie;
begin
  if (AMovieList <> nil) and (AMovieList.Count > 0) then
  begin
    AMovieList.Sort(fieldNumber);
    for i := 0 to AMovieList.Count-1 do
    begin
      CurrentMovie := TMovie(AMovieList.Items[i]);
      NewMovie := TMovie.Create(CustomFieldsProperties);
      if (CurrentMovie.iNumber < 1) or ((not AllowDuplicateNumbers) and (Find(CurrentMovie.iNumber) <> nil)) then
        NewMovie.iNumber := MaxNumber(False) + 1
      else
        NewMovie.iNumber := CurrentMovie.iNumber;
      NewMovie.Assign(CurrentMovie, False);
      inherited Add(NewMovie);
    end;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Add: TMovie;
begin
  Result := TMovie.Create(CustomFieldsProperties);
  inherited Add(Result);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Add2: IMovie;
begin
  Result := Add;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.MaxNumber(ListIsSorted: Boolean = False): Integer;
begin
  if not ListIsSorted then
    Sort(fieldNumber);
  if Count = 0 then
    Result := 0
  else
    Result := TMovie(Items[Count-1]).iNumber;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.FirstFreeNumber(ListIsSorted: Boolean = False): Integer;
var
  i, Prev: Integer;
begin
  if not ListIsSorted then
    Sort(fieldNumber);
  Prev := 0;
  for i := 0 to Count-1 do
  begin
    if TMovie(Items[i]).iNumber > (Prev + 1) then
      Break
    else
      Prev := TMovie(Items[i]).iNumber;
  end;
  Result := Prev + 1;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.HasImages(IncludeStored, IncludeLinksRel, IncludeLinksAbs: Boolean): Boolean;
var
  i: Integer;
  PicStr, PicDrive: Boolean;
begin
  Result := False;
  for i := 0 to Count-1 do
  begin
    with TMovie(Items[i]) do
    begin
      PicStr := strPicture <> '';
      PicDrive := ExtractFileDrive(strPicture) <> '';
      Result := (IncludeStored and (Picture <> nil) and PicStr) or
                ((Picture = nil)
                         and PicStr
                         and ((IncludeLinksRel and not PicDrive) or
                              (IncludeLinksAbs and PicDrive)));
    end;
    if Result then
      Break;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.GetValues(AList: TStrings; Field: Integer);
var
  i: Integer;
  value: string;
begin
  AList.BeginUpdate;
  AList.Clear;
  try
    for i := 0 to Count-1 do
    begin
      value := TMovie(Items[i]).GetFieldValue(Field);
      if (value <> '') and (AList.IndexOf(value) = -1) then
        AList.Add(value);
    end;
  finally
    AList.EndUpdate;
  end;
end;

{-------------------------------------------------------------------------------
  TMovieProperties
-------------------------------------------------------------------------------}

procedure TMovieProperties.InitFields;
begin
  strEncoding := 'iso-8859-1';
  strName := '';
  strMail := '';
  strSite := '';
  strDescription := '';
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Count: Integer;
begin
  Result := inherited Count;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Count(const IncOpt: TMovieIncludeOption): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to Count-1 do
    if TMovie(Items[i]).CanInclude(IncOpt) then
      Inc(Result);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.Count(out cAll, cSelected, cChecked, cVisible: Integer);
var
  i: Integer;
begin
  cAll := 0;
  cSelected := 0;
  cChecked := 0;
  cVisible := 0;
  for i := 0 to Count-1 do
    with TMovie(Items[i]) do
    begin
      Inc(cAll);
      if bChecked then
        Inc(cChecked);
{$IFNDEF DLLMode}
      if _bSelected then
        Inc(cSelected);
      if _bVisible then
        Inc(cVisible);
{$ENDIF}
    end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.Count(const MovieNumber: Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to Count-1 do
    with TMovie(Items[i]) do
      if iNumber = MovieNumber then
        Inc(Result);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.MakeFilteredList(AList: TList; const IncOpt: TMovieIncludeOption);
var
  i: Integer;
begin
  AList.Clear;
  for i := 0 to Count-1 do
    if TMovie(Items[i]).CanInclude(IncOpt) then
      AList.Add(Items[i]);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.ShiftNumbers(const StartAt: Integer; const Exclude: TMovie);
var
  i: Integer;
begin
  Sort(fieldNumber);
  for i := 0 to Count-1 do
    if Exclude <> Items[i] then
      with TMovie(Items[i]) do
        if iNumber >= StartAt then
        begin
          Inc(iNumber);
          if (i < Count-1) and ((TMovie(Items[i+1]).iNumber > iNumber) or (TMovie(Items[i+1]) = Exclude)) then
            Exit;
        end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.GetItem(const idx: Integer): TMovie;
begin
  Result := TMovie(inherited Items[idx]);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function TMovieList.GetItem2(const idx: Integer): IMovie;
begin
  Result := GetItem(idx);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure TMovieList.SetItem(const idx: Integer; const Value: TMovie);
begin
  inherited Items[idx] := Value;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
function TMovieList.GetMoviesByGroups(const Field: Integer; const IncOpt: TMovieIncludeOption = mioALL;
      MovieFilter : TMovieFilter = nil): TStringList;
var
  Groups: TStringList;
  GroupMovies: TObjectList;
  Movie: TMovie;
  FieldValue, GroupName: string;
  Sep : Char;
  RmPFields : TMovieFields;
  RmAllPFields : Boolean;
  i, len, idx, n, initPos: Integer;
  nbPOpen, firstPOpen, lastPClose: Integer;
  bGroupMulti, bPatch : Boolean;
  Prop : TCustomFieldProperties;
  // Args: IN [GroupName, Movie]
  procedure AddMovieToMap;
  begin
    if Length(GroupName) > 128 then
      GroupName := Copy(GroupName, 1, 128);
    GroupName := Trim(GroupName);
    if GroupName <> '' then
    begin
      idx := Groups.IndexOf(GroupName);
      if idx = -1 then
      begin
        GroupMovies := TObjectList.Create(False);
        GroupMovies.Add(Movie);
        Groups.AddObject(GroupName, GroupMovies);
      end else
      begin
        GroupMovies := TObjectList(Groups.Objects[idx]);
        if(GroupMovies.IndexOf(Movie) = -1) then
          GroupMovies.Add(Movie);
      end;
    end;
  end;
begin
  with Settings.rOptions.rMovieList do
  begin
    bGroupMulti := GroupMulti;
    Sep := GroupMultiSep;
    bPatch := GroupMultiAddPatch;
    if GroupMultiRmAllP then
    begin
      RmAllPFields := True;
      RmPFields := []
    end
    else
    begin
      RmAllPFields := False;
      RmPFields := GroupByFieldsMultiDefaultRmP;
    end;
  end;
  Groups := TStringList.Create;
  Groups.Sorted := True;
  Groups.CaseSensitive := False;
  
  for i := 0 to Count-1 do
  begin
    Movie := TMovie(Items[i]);
    Prop := nil;
    if not Movie.CanInclude(IncOpt) then
      continue;
    if MovieFilter <> nil then
      with MovieFilter do
      begin
        if not Movie.ContainsText(value, field, wholeField) then
          continue;
      end;
    with Movie do
    begin
      if Field in AllFields then
      begin
        FieldValue := GetFieldValue(Field, True);
        len := Length(FieldValue);
        if (len > 0) and (FieldValue[len] = '.') then
          FieldValue := Copy(FieldValue, 1, len-1);
        if (FieldValue = '') then
          FieldValue := '$$$EMPTY$$$';
      end else
      if (Field in AllCustomFields) and (Field - customFieldLow < CustomFields.Properties.Count) then
      begin
        Prop := CustomFields.Properties.Objects[Field - customFieldLow];
        FieldValue := CustomFields.GetFieldValue(Prop.FieldTag, False, True);
        len := Length(FieldValue);
        if (len > 0) and (FieldValue[len] = '.') then
          FieldValue := Copy(FieldValue, 1, len-1);
        if (FieldValue = '') then
          FieldValue := '$$$EMPTY$$$';
      end else
      begin
        FieldValue := '$$$NOGROUP$$$';
      end;
      if (not bGroupMulti) or ((not (Field in GroupByFieldsMulti)) and ((Prop = nil) or (not Prop.MultiValues))) then
      begin
        GroupName := FieldValue;
        AddMovieToMap;
      end else
      begin
        initPos := 1;
        n := 1;
        nbPOpen := 0;
        firstPOpen := 0;
        lastPClose := 0;
        while True do
        begin
          if (FieldValue[n] = ')') then
          begin
            Dec(nbPOpen);
            if nbPOpen = 0 then
              lastPClose := n;
          end else if (FieldValue[n] = '(') then
          begin
            if (not bPatch) or (not
              (((FieldValue[n+1] = 'I') or (FieldValue[n+1] = 'V') or (FieldValue[n+1] = 'X')) and
               ((FieldValue[n+2] = 'I') or (FieldValue[n+2] = 'V') or (FieldValue[n+2] = 'X') or
                (FieldValue[n+2] = Sep) or (FieldValue[n+2] = #10) or
                ((FieldValue[n+2] = ' ') and (FieldValue[n+3] = '('))) and
               ((FieldValue[n+2] = Sep) or (FieldValue[n+2] = #10) or
                ((FieldValue[n+2] = ' ') and (FieldValue[n+3] = '(')) or
                (FieldValue[n+3] = 'I') or (FieldValue[n+3] = 'V') or (FieldValue[n+3] = 'X') or
                (FieldValue[n+3] = Sep) or (FieldValue[n+3] = #10) or
                ((FieldValue[n+3] = ' ') and (FieldValue[n+4] = '('))) and
               ((FieldValue[n+2] = Sep) or (FieldValue[n+2] = #10) or
                ((FieldValue[n+2] = ' ') and (FieldValue[n+3] = '(')) or (FieldValue[n+3] = Sep) or
                (FieldValue[n+3] = #10) or ((FieldValue[n+3] = ' ') and (FieldValue[n+4] = '(')) or
                (FieldValue[n+4] = Sep) or (FieldValue[n+4] = #10) or
                ((FieldValue[n+4] = ' ') and (FieldValue[n+5] = '('))))) then
            begin
              if (nbPOpen = 0) then
                firstPOpen := n;
              Inc(nbPOpen);
            end;
          end else if (FieldValue[n] = Sep) or (FieldValue[n] = #0) then
          begin
            if nbPOpen = 0 then // Good parentheses
            begin
              if(firstPOpen > 0) and ((Field in RmPFields) or RmAllPFields) then // There are Parentheses
              begin
                GroupName := Trim(Copy(FieldValue, initPos, firstPOpen - initPos)) +' '+
                             Trim(Copy(FieldValue, lastPClose + 1, n - lastPClose - 1));
              end else
              begin
                GroupName := Copy(FieldValue, initPos, n - initPos);
              end;
              AddMovieToMap;
              initPos := n + 1;
              firstPOpen := 0;
              lastPClose := 0;
            end else if(FieldValue[n] = #0) then // Bad parentheses !
            begin
              GroupName := strErrorParenthesis; // Copy(FieldValue, initPos, n - initPos);
              AddMovieToMap;
            end;
            if(FieldValue[n] = #0) then // Finish
              break;
          end;
          Inc(n);
        end; // while True
      end; // else GroupByFieldsMulti
    end; // end with Movie
  end; // end for
  Result := Groups;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
    External functions
-------------------------------------------------------------------------------}

function GetFieldType(field: Integer): TFieldType;
begin
  case field of
    fieldNumber,
    fieldYear,            fieldLength,          fieldVideoBitrate,
    fieldAudioBitrate,    fieldDisks:
      Result := ftInteger;
    fieldRating:
      {$IFNDEF DLLMode}
      if Settings.rOptions.rMovieInformation.RatingTrunc then
        Result := ftInteger
      else
      {$ENDIF}
        Result := ftReal;
    fieldDate:
      Result := ftDate;
    fieldChecked:
      Result := ftBoolean;
    fieldMedia,           fieldMediaType,       fieldSource,
    fieldBorrower,        fieldOriginalTitle,
    fieldTranslatedTitle, fieldFormattedTitle,
    fieldDirector,        fieldProducer,
    fieldCountry,         fieldCategory,        fieldURL,
    fieldVideoFormat,     fieldAudioFormat,     fieldResolution,
    fieldFrameRate,       fieldLanguages,       fieldSubtitles,
    fieldSize:
      Result := ftString;
    fieldActors,          fieldDescription,     fieldComments:
      Result := ftText;
  else
    Result := ftString;
  end; // case
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertFieldValue(Value: string; FieldType: TFieldType; const LocalFormatSettings: Boolean;
  const ExtraDateValue: Boolean; const ReturnEmptyIfFalse : Boolean): string;
begin
  Result := '';
  if (Value = '') and (FieldType <> ftBoolean) then
    Exit;
  case FieldType of
    ftInteger:
      try
        {$IFNDEF DLLMode}
        Result := IntToStr(Trunc(StrToFloat(CharReplace(Value, DecimalSeparator, FormatSettings.DecimalSeparator), FormatSettings)))
        {$ELSE}
        Result := IntToStr(Trunc(StrToFloat(Value)));
        {$ENDIF}
      except
        Result := '';
      end;
    ftReal:
      try
        {$IFNDEF DLLMode}
        if not LocalFormatSettings then
          Result := FormatFloat('#0.00', StrToFloat(CharReplace(Value, DecimalSeparator, FormatSettings.DecimalSeparator), FormatSettings), FormatSettings)
        else
          Result := FormatFloat('#0.00', StrToFloat(CharReplace(Value, DecimalSeparator, FormatSettings.DecimalSeparator), FormatSettings))
        {$ELSE}
        Result := FormatFloat('#0.00', StrToFloat(Value));
        {$ENDIF}
      except
        Result := '';
      end;
    ftBoolean:
      begin
        UpperCase(Value);
        if (Value = '') or (Value = '0') or (Value = '-1') or SameText(Value, 'False') then
          if ReturnEmptyIfFalse then
            Result := ''
          else
            Result := 'False'
        else
          Result := 'True';
      end;
    ftDate:
      try
        if ExtraDateValue and (AnsiSameText(Value, 'Today')) then
          Result := 'Today'
        else
        begin
          {$IFNDEF DLLMode}
          if not LocalFormatSettings then
            Result := DateToStr(StrToDate(Value, FormatSettings), FormatSettings)
          else
            Result := DateToStr(StrToDate(Value, FormatSettings));
          {$ELSE}
          Result := DateToStr(StrToDate(Value));
          {$ENDIF}
        end;
      except
        {$IFNDEF DLLMode}
        try
          if not LocalFormatSettings then
            Result := DateToStr(StrToDate(Value, OldFormatSettings), FormatSettings)
          else
            Result := DateToStr(StrToDate(Value, OldFormatSettings));
        except
          try
            if not LocalFormatSettings then
              Result := DateToStr(StrToDate(Value))
            else
              Result := DateToStr(StrToDate(Value), FormatSettings);
          except
          end;
        end;
        {$ENDIF}
      end;
    else
      Result := Value;
    end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertFieldTypeFromString(Value: string): TFieldType;
begin
  Result := ftString;
  Value := UpperCase(Value);
  if Value = 'FTINTEGER' then
    Result := ftInteger
  else if Value = 'FTREAL' then
    Result := ftReal
  else if Value = 'FTBOOLEAN' then
    Result := ftBoolean
  else if Value = 'FTDATE' then
    Result := ftDate
  else if Value = 'FTLIST' then
    Result := ftList
  else if Value = 'FTTEXT' then
    Result := ftText
  else if Value = 'FTURL' then
    Result := ftUrl;

end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertFieldTypeToString(Value: TFieldType): string;
begin
  Result := 'ftString';
  case Value of
    ftInteger:
      Result := 'ftInteger';
    ftReal:
      Result := 'ftReal';
    ftBoolean:
      Result := 'ftBoolean';
    ftDate:
      Result := 'ftDate';
    ftList:
      Result := 'ftList';
    ftText:
      Result := 'ftText';
    ftUrl:
      Result := 'ftUrl'
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
function ConvertFieldTypeFromSQL(Value: string): TFieldType;
begin
  Result := ftString;
  Value := UpperCase(Value);
  if Value = 'INT' then
    Result := ftInteger
  else if Value = 'FLOAT' then
    Result := ftReal
  else if Value = 'DATE' then
    Result := ftDate;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertFieldTypeToSQL(Value: TFieldType): string;
begin
  Result := 'TEXT';
  case Value of
    ftInteger:
      Result := 'INT';
    ftReal:
      Result := 'FLOAT';
    ftDate:
      Result := 'DATE';
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertColorToHTML(Color: TColor): string;
var
  tmpRGB : TColorRef;
begin
  tmpRGB := ColorToRGB(color) ;
  Result := Format('#%.2x%.2x%.2x',[GetRValue(tmpRGB), GetGValue(tmpRGB), GetBValue(tmpRGB)]) ;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ConvertColorFromHTML(Color: string; DefaultColor: TColor): TColor;
var
  i: Integer;
  Bad: Boolean;
begin
  Result := DefaultColor;
  if (Length(Color) = 7) and (Color[1] = '#') then
  begin
    Bad := False;
    for i := 2 to 7 do
      if not ((Color[i] in ['0'..'9']) or (Color[i] in ['a'..'f']) or (Color[i] in ['A'..'F'])) then
      begin
        Bad := true;
        Break;
      end;
    if not Bad then
      Result := StringToColor('$' + Copy(Color, 6, 2) + Copy(Color, 4, 2) + Copy(Color, 2, 2));
  end;
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function IsValidTag(const s: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  if (s = '') or ( not((s[1] in ['a'..'z']) or (s[1] in ['A'..'Z']) or (s[1] = '_')) ) then
    Exit;
  for i:=1 to Length(s) do
    if not( (s[i] in ['a'..'z']) or (s[i] in ['A'..'Z']) or (s[i] in ['0'..'9']) or (s[i] = '_') ) then
      Exit;
  {$IFNDEF DLLMode}
  if (IndexText(s, strTagFields) = -1) and (AnsiCompareText(s, strTagFieldPicture) <> 0) then
    Result := True;
  {$ELSE}
  Result := True;
  for i := Low(strTagFields) to High(strTagFields) do
    if SameText(s, strTagFields[i]) then
    begin
      Result := False;
      Break;
    end;
  if (Result = True) and (AnsiCompareText(s, strTagFieldPicture) = 0) then
    Result := False;
  {$ENDIF}
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function CompareStd(Item1, Item2: Pointer): Integer;
var
  Movie1, Movie2: TMovie;
  FieldsProperties: TCustomFieldsProperties;
  FieldProperties: TCustomFieldProperties;
begin
  Result := 0;
  Movie1 := Item1;
  Movie2 := Item2;
  FieldsProperties := Movie1.CustomFields.Properties;
  if SortField < fieldCount then // movie field
  begin
    case GetFieldType(SortField) of
      ftInteger, ftReal, ftDate:
        Result := Movie1.GetIntFieldValue(SortField) - Movie2.GetIntFieldValue(SortField);
      else
        Result := AnsiCompareText(Movie1.GetFieldValue(SortField), Movie2.GetFieldValue(SortField));
    end; // case
  end else if (FieldsProperties <> nil) and (SortField - customFieldLow < FieldsProperties.Count) then // custom field
  begin
    FieldProperties := FieldsProperties.Objects[SortField - customFieldLow];
    case FieldProperties.FieldType of
      ftInteger, ftReal, ftDate:
        Result := Movie1.CustomFields.GetIntFieldValue(FieldProperties.FieldTag) -
          Movie2.CustomFields.GetIntFieldValue(FieldProperties.FieldTag);
      else
        Result := AnsiCompareText(Movie1.CustomFields.GetFieldValue(FieldProperties.FieldTag),
          Movie2.CustomFields.GetFieldValue(FieldProperties.FieldTag));
    end; // case
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function CompareStdReverse(Item1, Item2: Pointer): Integer;
begin
  Result := -CompareStd(Item1, Item2);
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

{$IFNDEF DLLMode}
function CompareAdv(Item1, Item2: Pointer): Integer;
var
  Movie1, Movie2: TMovie;
  field: Integer;
  FieldsProperties: TCustomFieldsProperties;
  FieldProperties: TCustomFieldProperties;
begin
  Result := 0;
  try
    Movie1 := Item1;
    Movie2 := Item2;
    Inc(SortFieldsIndex);
    if SortFieldsIndex < SortFieldsList.Count then
    begin
      field := -1;
      FieldsProperties := Movie1.CustomFields.Properties;
      FieldProperties := nil;
      if (SortFieldsList[SortFieldsIndex][1] in ['0'..'9']) then // movie field (Field Number)
        field := StrToIntDef(SortFieldsList[SortFieldsIndex], -1)
      else if FieldsProperties <> nil then // custom field (Custom Field Tag)
      begin
        field := FieldsProperties.IndexOf(SortFieldsList[SortFieldsIndex]);
        if field <> -1 then
          FieldProperties := FieldsProperties.Objects[field];
      end;
      if (field > -1) then // field found
      begin
        if (FieldProperties = nil) then // movie field
          case GetFieldType(field) of
            ftInteger, ftReal, ftDate:
              Result := Movie1.GetIntFieldValue(field) - Movie2.GetIntFieldValue(field);
            else
              Result := AnsiCompareText(Movie1.GetFieldValue(field), Movie2.GetFieldValue(field));
            end // case
        else // custom field
          case FieldProperties.FieldType of
            ftInteger, ftReal, ftDate:
              Result := Movie1.CustomFields.GetIntFieldValue(FieldProperties.FieldTag) -
                Movie2.CustomFields.GetIntFieldValue(FieldProperties.FieldTag);
            else
              Result := AnsiCompareText(Movie1.CustomFields.GetFieldValue(FieldProperties.FieldTag),
                Movie2.CustomFields.GetFieldValue(FieldProperties.FieldTag));
            end; // case
      end;
      if Result = 0 then
        Result := CompareAdv(Item1, Item2);
    end; // if
  finally
    SortFieldsIndex := -1;
  end; // try
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function CompareAdvReverse(Item1, Item2: Pointer): Integer;
var
  Movie1, Movie2: TMovie;
  field: Integer;
  FieldsProperties: TCustomFieldsProperties;
  FieldProperties: TCustomFieldProperties;
begin
  Result := 0;
  try
    Movie1 := Item2;
    Movie2 := Item1;
    Inc(SortFieldsIndex);
    if SortFieldsIndex < SortFieldsList.Count then
    begin
      field := -1;
      FieldsProperties := Movie1.CustomFields.Properties;
      FieldProperties := nil;
      if (SortFieldsList[SortFieldsIndex][1] in ['0'..'9']) then // movie field (Field Number)
        field := StrToIntDef(SortFieldsList[SortFieldsIndex], -1)
      else if FieldsProperties <> nil then // custom field (Custom Field Tag)
      begin
        field := FieldsProperties.IndexOf(SortFieldsList[SortFieldsIndex]);
        if field <> -1 then
          FieldProperties := FieldsProperties.Objects[field];
      end;
      if (field > -1) then // field found
      begin
        if (FieldProperties = nil) then // movie field
          case GetFieldType(field) of
            ftInteger, ftReal, ftDate:
              Result := Movie1.GetIntFieldValue(field) - Movie2.GetIntFieldValue(field);
            else
              Result := AnsiCompareText(Movie1.GetFieldValue(field), Movie2.GetFieldValue(field));
            end // case
        else // custom field
          case FieldProperties.FieldType of
            ftInteger, ftReal, ftDate:
              Result := Movie1.CustomFields.GetIntFieldValue(FieldProperties.FieldTag) -
                Movie2.CustomFields.GetIntFieldValue(FieldProperties.FieldTag);
            else
              Result := AnsiCompareText(Movie1.CustomFields.GetFieldValue(FieldProperties.FieldTag),
                Movie2.CustomFields.GetFieldValue(FieldProperties.FieldTag));
            end; // case
      end;
      if Result = 0 then
        Result := CompareAdv(Item1, Item2);
    end; // if
  finally
    SortFieldsIndex := -1;
  end; // try
end;
{$ENDIF}

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure FreeObjects(const strings: TStrings);
var
  idx : integer;
begin
  for idx := 0 to strings.Count-1 do
  begin
    strings.Objects[idx].Free;
    strings.Objects[idx] := nil;
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

procedure WriteString(const str: string; OutputFile: TStream);
var
  recsize: Integer;
begin
  with OutputFile do
  begin
    recsize := Length(str);
    WriteBuffer(recsize, intsize);
    if recsize > 0 then
      WriteBuffer(str[1], recsize);
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

function ReadString(InputFile: TStream): string;
var
  recsize: Integer;
begin
  with InputFile do
  begin
    ReadBuffer(recsize, intsize);
    if recsize > 0 then
    begin
      SetLength(Result, recsize);
      ReadBuffer(Result[1], recsize);
    end else
      Result := '';
  end;
end;

{-------------------------------------------------------------------------------
-------------------------------------------------------------------------------}

initialization
{$IFDEF MSWINDOWS}
  ClipboardFormat := RegisterClipBoardFormat(strClipboardHeader);
{$ENDIF}
{$IFNDEF DLLMode}
  SortFieldsList := TStringList.Create;
  SortFieldsIndex := -1;
{$ENDIF}
finalization
{$IFNDEF DLLMode}
  SortFieldsList.Free;
{$ENDIF}
end.

