unit TagVorbis;

interface

uses Windows, KOL, ATLCommon, TagID3v2;

const  OggHeaderSize = 27;
type
  TOggHeader = packed record
    ID: array [1..4] of Char;                                 { Always "OggS" }
    StreamVersion: Byte;                           { Stream structure version }
    TypeFlag: Byte;                                        { Header type flag }
    AbsolutePosition: Int64;                      { Absolute granule position }
    Serial: Integer;                                   { Stream serial number }
    PageNumber: Integer;                               { Page sequence number }
    Checksum: Integer;                                        { Page checksum }
    Segments: Byte;                                 { Number of page segments }
    LacingValues: array [1..$FF] of Byte;     { Lacing values - segment sizes }
  end;

  TCommentType = (ctVorbis, ctSpeex, ctFlac);

  PTagVorbis = ^TTagVorbis;
  TVorbisTag = PTagVorbis;
  TTagVorbis = object(TTagInfo)
  private
    FSecondPage: TOggHeader;                                   { First, second and last page }
    FTagEndPos, FSecondPagePos: Cardinal;         { Position of second Ogg page }
    FVendor: String;
    FCommentType: TCommentType;

    function GetInfo(const AFileName: KOLString; AReadTags: Boolean): Boolean;
    function ReadTags(ASource: PStream): Boolean;

    function WriteFile(const AFileName: KOLString; AIsRemove: Boolean): Boolean;
    function RebuildFile(AFileName: KOLstring; ACommentStream: PStream): Boolean;
    function WriteTags: PStream;

    function GetEncoder: String;
    function GetCount: Integer;
    function GetKey(Index: Integer): String;
    function GetValue(Index: Integer): String;
    function GetValueOfKey(Index: String): String;
    procedure SetKey(Index: Integer; const AValue: String);
    procedure SetValue(Index: Integer; const AValue: String);
    procedure SetValueOfKey(Index: String; const AValue: String);

    function GetTag(const Index: TEasyTags): String; virtual;
    procedure SetTag(const Index: TEasyTags; const AValue: String); virtual;

  public
    destructor Destroy; virtual;
    function ReadFromFile(const AFileName: KOLstring): Boolean; virtual;
    function SaveToFile(const AFileName: KOLstring): Boolean; virtual;
    function RemoveFromFile(const AFileName: KOLstring): Boolean; virtual;

    property Encoder: String read GetEncoder;             { Encoder string }
    property Vendor: String read FVendor write FVendor;

    property Count: Integer read GetCount;
    property Key[Index: Integer]: String read GetKey write SetKey;
    property Value[Index: Integer]: String read GetValue write SetValue;
    property ValueOfKey[Index: String]: String read GetValueOfKey write SetValueOfKey;
    function IndexOfKey(Index: String): Integer;
    procedure DeleteOfKey(Index: String);
  end;

  function NewTagVorbis: TVorbisTag;
  procedure CalculateCRC(var CRC: Cardinal; const Data; Size: Cardinal);

const
  OGG_PAGE_ID = 'OggS';
  VORBIS_PARAMETERS_ID = #1 + 'vorbis';
  SPEEX_PARAMETERS_ID = 'Speex   ';
  fLaC_ID = 'fLaC';

implementation

uses ATLLib;

const
  VORBIS_TAG_ID = #3 + 'vorbis';
  FLAC_VORBIS_COMMENT = 4;

  VORBIS_FIELD: array [TEasyTags] of string =
                ('TRACKNUMBER', 'TITLE', 'ARTIST', 'ALBUM', 'DATE', 'COMMENT', 'GENRE',
                'AUTHOR', '', 'COPYRIGHT', 'CONTACT', 'DESCRIPTION', 'LANGUAGE',
                '', 'PERFORMER', '', '', '', 'VERSION');

function NewTagVorbis: TVorbisTag;
begin
    New(Result, Create);
    Result.FExistsTags:= [etTrack, etTitle, etArtist, etAlbum, etYear, etComment, etGenre, etComposer, etCopyright, etLink, etDescription, etLanguage, etPerformer, etVersion];
    Result.FTagVendor:= tVorbis;
end;

function TTagVorbis.ReadFromFile(const AFileName: KOLstring): Boolean;
begin
    ResetData;
    Result:= GetInfo(AFileName, True);
end;

function TTagVorbis.GetInfo(const AFileName: KOLString; AReadTags: Boolean): Boolean;
  function GetLacingSize(const Buff: array of Byte; Size: Byte; var Last: Boolean): Integer;
  var i : Integer;
  begin
      Result := 0; Last := False;
      for  i := 0 to Size - 1 do begin
        Result := Result + Buff[i];
        if Buff[i] < $FF then begin
          Last := True;
          Break;
        end;
      end;
  end;
var SourceFile, CommentsStream: PStream;
    Last: Boolean;
//    {VorbisPages, } : Integer;
    Page: TOggHeader;
    ID: String;
    Size, SizeID3v2: Cardinal;
    Buff: array [1..4] of Byte;
begin
    Result:= False;
    try
      SourceFile := OpenFileRead(AFileName);
      CommentsStream := NewMemoryStream;

      FillChar(FSecondPage, SizeOf(FSecondPage), 0);
      FSecondPagePos:= 0;
      FTagEndPos:= 0;

      SizeID3v2:= SizeOfID3v2Tag(AFileName);
      SourceFile.Seek(SizeID3v2, spBegin);
      ID:= SourceFile.ReadStrLen(4);

      {FLAC-ФАЙЛ}
      if (ID = fLaC_ID) then begin
        FCommentType:= ctFlac;
        Size:= 0;
        repeat
          SourceFile.Seek(Size, spCurrent);
          SourceFile.Read(Buff, 4);
          Buff[1]:= Buff[1] and $7F;
          Size:= (Buff[2] shl 16) + (Buff[3] shl 8) + Buff[4];
          if (Buff[1] = FLAC_VORBIS_COMMENT) then begin
            FSecondPagePos:= SourceFile.Position-4;
            Stream2Stream(CommentsStream, SourceFile, Size);
            FTagEndPos:= SourceFile.Position;
            Break;
          end
        until (SourceFile.Position >= SourceFile.Size);
      end
      else
      {OGG-ФАЙЛ}
        if (ID = OGG_PAGE_ID) then begin
          { ПЕРВАЯ СТРАНИЦА }
          SourceFile.Seek(SizeID3v2, spBegin);
          if SourceFile.Read(Page, OggHeaderSize) <> OggHeaderSize then Exit; //Читаем Ogg заголовок

          SourceFile.Read(Page.LacingValues, Page.Segments);            // Читаем размеры сегментов
          Size:= GetLacingSize(Page.LacingValues, Page.Segments, Last); // Определяем общий размер сегментов
          Stream2Stream(CommentsStream, SourceFile, Size);                            // и читаем их в буфер

          { Определяем тип заголовка }
          if StrLComp(PChar(VORBIS_PARAMETERS_ID), CommentsStream.Memory, Length(VORBIS_PARAMETERS_ID)) = 0 then FCommentType:= ctVorbis
          else
            if StrLComp(PChar(SPEEX_PARAMETERS_ID), CommentsStream.Memory, Length(SPEEX_PARAMETERS_ID)) = 0  then FCommentType:= ctSpeex
            else Exit;

          CommentsStream.Size:= 0;
          FSecondPagePos:= SourceFile.Position;  // Запоминаем позицию следующей страницы

          { СЛЕДУЮЩАЯ СТРАНИЦА }
          SourceFile.Read(FSecondPage, OggHeaderSize);                                //Читаем Ogg заголовок
          SourceFile.Read(FSecondPage.LacingValues, FSecondPage.Segments);            // Читаем размеры сегментов
          Size:= GetLacingSize(FSecondPage.LacingValues, FSecondPage.Segments, Last); // Определяем общий размер сегментов
          Stream2Stream(CommentsStream, SourceFile, Size);                            // и читаем их в буфер
          //Страниц может быть много, читаем их все (10 хватит)
          //VorbisPages:= 1;
          while (not Last) {and (VorbisPages < 10) }do begin
            SourceFile.Read(Page, OggHeaderSize);                               //Читаем Ogg заголовок
            SourceFile.Read(Page.LacingValues, Page.Segments);             // Читаем размеры сегментов
            Size := GetLacingSize(Page.LacingValues, Page.Segments, Last); // Определяем общий размер сегментов
            Stream2Stream(CommentsStream, SourceFile, Size);                         // и читаем их в буфер
            //Inc(VorbisPages);
          end;

          FTagEndPos:= SourceFile.Position;
          FTagSize:= FTagEndPos;
        end
        else Exit;


      if AReadTags then FExists:= ReadTags(CommentsStream);

      Result := true;
    finally
      CommentsStream.Free;
      SourceFile.Free;
    end;
end;

function TTagVorbis.ReadTags(ASource: PStream): Boolean;
var I, L, C: Cardinal;
    S: String;
//    B: Byte;
begin
    try
      FVendor:= '';
      FTagList.Clear;

      Result:= True;
      ASource.Seek(0, spBegin);
      case FCommentType of
        ctVorbis:begin
          S:= ASource.ReadStrLen(7);
          Result:= S = VORBIS_TAG_ID;
        end;
      end;
      if not Result then Exit;

      Result:= Result and (ASource.Read(L, SizeOf(L)) < 50);  // Длина Vendor
      if not Result then Exit;

      SetLength(FVendor, L);                                        // Читаем Vendor
      Result:= Result and (ASource.Read(FVendor[1], L) = L);
      FVendor:= Utf8ToAnsi(FVendor);

      if not Result then Exit;

      ASource.Read(C, SizeOf(C));                // Кол-во полей
      for I := 1 to C do begin               // Читаем поля
        ASource.Read(L, SizeOf(L));
        SetLength(S, L);
        Result:= Result and (ASource.Read(S[1], L) = L);
        FTagList.Add(S);
        if not Result then Exit;
      end;

//      Result:= (ASource.Read(B, SizeOf(B)) = SizeOf(B));
//      Result:= Result and (B=1);

    except
      Result:= False;
    end;
end;

function TTagVorbis.SaveToFile(const AFileName: KOLstring): Boolean;
begin
    Result:= WriteFile(AFileName, False);
end;

function TTagVorbis.WriteFile(const AFileName: KOLString; AIsRemove: Boolean): Boolean;
  procedure SetLacingValues(const NewTagSize: Cardinal);
  var Index, Position, Value: Cardinal;
      Buffer: array [1..$FF] of Byte;
  begin
      Position := 1;
      Value := 0;
      for Index := FSecondPage.Segments downto 1 do begin
        if FSecondPage.LacingValues[Index] < $FF then begin
          Position := Index;
          Value := 0;
        end;
        Inc(Value, FSecondPage.LacingValues[Index]);
      end;
      Value := Value + NewTagSize - (FTagEndPos - FSecondPagePos - FSecondPage.Segments - OggHeaderSize);
      { Change lacing values at the beginning }
      for Index := 1 to Value div $FF do Buffer[Index] := $FF;
      Buffer[(Value div $FF) + 1] := Value mod $FF;
      if Position < FSecondPage.Segments then
        for Index := Position + 1 to FSecondPage.Segments do
          Buffer[Index - Position + (Value div $FF) + 1]:= FSecondPage.LacingValues[Index];
      FSecondPage.Segments := FSecondPage.Segments - Position + (Value div $FF) + 1;
      for Index := 1 to FSecondPage.Segments do FSecondPage.LacingValues[Index] := Buffer[Index];
  end;
var CommentStream: PStream;
begin
    Result:= False;
    if GetInfo(AFileName, False) then begin
      if AIsRemove and (FCommentType <> ctFlac) then CommentStream:= NewMemoryStream else CommentStream:= WriteTags;

      if FCommentType <> ctFlac then begin
        FSecondPage.Checksum := 0;
        SetLacingValues(CommentStream.Size);
      end;

      Result := RebuildFile(AFileName, CommentStream);
      CommentStream.Free;
    end;
end;

function TTagVorbis.RebuildFile(AFileName: KOLstring; ACommentStream: PStream): Boolean;
var Source, Destination: PStream;
    BufferName: KOLstring;
    Index: Integer;
    Crc, Size: Cardinal;
    Data: array [1..$FF] of Byte;
    Buff: array [1..4] of Byte;
begin
    Result:= false;
    if not FileExists(AFileName) then exit;
    try
      BufferName:= AFileName + '~';
      Source:= OpenFileRead(AFileName);
      Destination:= CreateFile(BufferName);

      Stream2Stream(Destination, Source, FSecondPagePos);                    //Копируем с начала файла до начала блока тега

      if FCommentType = ctFlac then begin
        Size:= ACommentStream.Size;
        Cardinal(Buff):= Size;
        Cardinal(Buff):= SwapOrder32(Size);
        Source.Read(Buff, 1);
        Destination.Write(Buff, 4);
      end
      else //записываем страницу Ogg
        Destination.Write(FSecondPage, FSecondPage.Segments + OggHeaderSize);

      ACommentStream.Position:= 0; Stream2Stream(Destination, ACommentStream, ACommentStream.Size); //Затисывает сам тег

      Source.Seek(FTagEndPos, spBegin);                                     //Копируем с конца тега до конца файла
      Stream2Stream(Destination, Source, Source.Size - FTagEndPos);

      // Обновляем CRC для Ogg
      if FCommentType <> ctFlac then begin
        Crc:= 0;
        CalculateCRC(Crc, FSecondPage, FSecondPage.Segments + OggHeaderSize);
        Destination.Seek(FSecondPagePos + FSecondPage.Segments + OggHeaderSize, spBegin);
        for Index:= 1 to FSecondPage.Segments do
          if FSecondPage.LacingValues[Index] > 0 then begin
            Destination.Read(Data, FSecondPage.LacingValues[Index]);
            CalculateCRC(Crc, Data, FSecondPage.LacingValues[Index]);
          end;
        Destination.Seek(FSecondPagePos + 22, spBegin);
        Destination.Write(Crc, SizeOf(Crc));
      end;

      Source.Free;
      Destination.Free;

      Result:= DeleteFile(PKOLChar(AFileName)) and MoveFile(PKOLChar(BufferName), PKOLChar(AFileName));
      if FileExists(BufferName) then DeleteFile(PKOLChar(BufferName));
    except
    end;
end;

function TTagVorbis.WriteTags: PStream;
var CountFields, Size: Integer;
    FieldData: string;
    i: Integer;
    B: Byte;
begin
    Result:= NewMemoryStream;
    CountFields:= 0;
    for i:= 0 to FTagList.Count-1 do
      if FTagList.Items[i] <> '' then inc(CountFields);

    if FCommentType = ctVorbis then Result.WriteStr(VORBIS_TAG_ID);

    FieldData:= AnsiToUtf8(FVendor);
    Size:= Length(FieldData);
    Result.Write(Size, SizeOf(Size));
    Result.WriteStr(FieldData);
    Result.Write(CountFields, SizeOf(CountFields));

    for i:= 0 to FTagList.Count-1 do
      if FTagList.Items[i] <> '' then begin
        FieldData:= FTagList.Items[i];
        Size:= Length(FieldData);
        Result.Write(Size, SizeOf(Size));
        Result.WriteStr(FieldData);
      end;
    if FCommentType <> ctFlac then begin
      B:= 1;
      Result.Write(B, 1);
    end;
end;

function TTagVorbis.RemoveFromFile(const AFileName: KOLstring): Boolean;
begin
    ResetData;
    FVendor:= ''; FTagList.Clear;
    Result:= WriteFile(AFileName, True);
end;

function TTagVorbis.GetEncoder: String;
begin
    if Vendor = 'Xiphophorus libVorbis I 20000508' then Result:= '1.0 beta 1 or beta 2' else
      if Vendor = 'Xiphophorus libVorbis I 20001031' then Result:= '1.0 beta 3' else
        if Vendor = 'Xiphophorus libVorbis I 20010225' then Result:= '1.0 beta 4' else
          if Vendor = 'Xiphophorus libVorbis I 20010615' then Result:= '1.0 rc1' else
            if Vendor = 'Xiphophorus libVorbis I 20010813' then Result:= '1.0 rc2' else
              if Vendor = 'Xiphophorus libVorbis I 20010816 (gtune 1)' then Result:= '1.0 RC2 GT1' else
                if Vendor = 'Xiphophorus libVorbis I 20011014 (GTune 2)' then Result:= '1.0 RC2 GT2' else
                  if Vendor = 'Xiphophorus libVorbis I 20011217' then Result:= '1.0 rc3' else
                    if Vendor = 'Xiphophorus libVorbis I 20011231' then Result:= '1.0 rc3' else
                      if Vendor = 'Xiph.Org libVorbis I 20020717' then Result:= '1.0' else
                        if Vendor = 'Xiph.Org/Sjeng.Org libVorbis I 20020717 (GTune 3, beta 1)' then Result:= '1.0 GT3b1' else
                          if Vendor = 'Xiph.Org libVorbis I 20030308' then Result:= 'Post 1.0 CVS' else
                            if Vendor = 'Xiph.Org libVorbis I 20030909 (1.0.1)' then Result:= '1.0.1' else
                              if Vendor = 'Xiph.Org libVorbis I 20030909' then Result:= '1.0.1' else
                                if Vendor = 'Xiph.Org/Sjeng.Org libVorbis I 20030909 (GTune 3, beta 2) EXPERIMENTAL' then Result:= 'Experimental GT3b2' else
                                  if Vendor = 'Xiph.Org libVorbis I 20031230 (1.0.1)' then Result:= 'Post 1.0.1 CVS' else
                                    if Vendor = 'Xiph.Org/Sjeng.Org libVorbis I 20031230 (GTune 3, beta 2)' then Result:= 'GT3b2' else
                                      if Vendor = 'AO; aoTuV b2 [20040420]  (based on Xiph.Org''s 1.0.1)' then Result:= '1.0.1 aoTuV beta 2' else
                                        if Vendor = 'Xiph.Org libVorbis I 20040629' then Result:= '1.1' else
                                          if Vendor = 'Xiph.Org libVorbis I 20040920' then Result:= '1.1 with impulse_trigger_profile' else
                                            if Vendor = 'AO; aoTuV b3 [20041120] (based on Xiph.Org''s libVorbis)' then Result:= '1.1 aoTuV beta 3'
                                            else Result:= Vendor;
end;

function TTagVorbis.GetCount: Integer;
begin
    Result:= FTagList.Count;
end;

function TTagVorbis.GetKey(Index: Integer): String;
begin
    Result:= FTagList.Items[Index];
    if Pos('=', Result) > 0 then Result:= Parse(Result, '=');
end;

function TTagVorbis.GetValue(Index: Integer): String;
begin
    Result:= FTagList.Items[Index];
    if Pos('=', Result) > 0 then Parse(Result, '=');
    Result:= Utf8ToAnsi(Result);
end;

function TTagVorbis.GetValueOfKey(Index: String): String;
var i: Integer;
begin
    Result := '';
    i:= IndexOfKey(Index);
    if i < 0 then Exit;
    Result := Value[i];
end;

procedure TTagVorbis.SetKey(Index: Integer; const AValue: String);
var S: String;
begin
    if AValue = '' then FTagList.Delete(Index)
    else begin
       S:= FTagList.Items[Index];
       if Pos('=', S) > 0 then begin
         Parse(S, '=');
         FTagList.Items[Index]:= AnsiUpperCase(AValue) + '=' + S;
       end;
    end;
end;

procedure TTagVorbis.SetValue(Index: Integer; const AValue: String);
var S: String;
begin
    if AValue = '' then
      FTagList.Delete(Index)
    else begin
       S:= FTagList.Items[Index];
       if Pos('=', S) > 0 then S:= Parse(S, '=') + '=' else S:= '';
       FTagList.Items[Index]:= S + AnsiToUtf8(AValue);
    end;;
end;

procedure TTagVorbis.SetValueOfKey(Index: String; const AValue: String);
var i: Integer;
begin
    if Index <> '' then begin
      i:= IndexOfKey(Index);
      if i >= 0 then Value[i]:= AValue
      else
        if AValue <> '' then FTagList.Add(AnsiUpperCase(Index) + '=' + AnsiToUtf8(AValue));
    end;
end;

procedure TTagVorbis.DeleteOfKey(Index: String);
var i: Integer;
begin
    i:= IndexOfKey(Index);
    if (i <> -1) then FTagList.Delete(i);
end;

function TTagVorbis.IndexOfKey(Index: String): Integer;
var S: string;
begin
    S:= AnsiUpperCase(Index);
    for Result := 0 to Count - 1 do
      if AnsiUpperCase(Key[Result]) = S then Exit;
    Result := -1;
end;

function TTagVorbis.GetTag(const Index: TEasyTags): String;
begin
    Result:= ValueOfKey[VORBIS_FIELD[Index]];
end;

procedure TTagVorbis.SetTag(const Index: TEasyTags; const AValue: String);
begin
    ValueOfKey[VORBIS_FIELD[Index]]:= AValue;
end;

const
  { CRC table for checksum calculating }
  CRC_TABLE: array [0..$FF] of Cardinal = (
    $00000000, $04C11DB7, $09823B6E, $0D4326D9, $130476DC, $17C56B6B,
    $1A864DB2, $1E475005, $2608EDB8, $22C9F00F, $2F8AD6D6, $2B4BCB61,
    $350C9B64, $31CD86D3, $3C8EA00A, $384FBDBD, $4C11DB70, $48D0C6C7,
    $4593E01E, $4152FDA9, $5F15ADAC, $5BD4B01B, $569796C2, $52568B75,
    $6A1936C8, $6ED82B7F, $639B0DA6, $675A1011, $791D4014, $7DDC5DA3,
    $709F7B7A, $745E66CD, $9823B6E0, $9CE2AB57, $91A18D8E, $95609039,
    $8B27C03C, $8FE6DD8B, $82A5FB52, $8664E6E5, $BE2B5B58, $BAEA46EF,
    $B7A96036, $B3687D81, $AD2F2D84, $A9EE3033, $A4AD16EA, $A06C0B5D,
    $D4326D90, $D0F37027, $DDB056FE, $D9714B49, $C7361B4C, $C3F706FB,
    $CEB42022, $CA753D95, $F23A8028, $F6FB9D9F, $FBB8BB46, $FF79A6F1,
    $E13EF6F4, $E5FFEB43, $E8BCCD9A, $EC7DD02D, $34867077, $30476DC0,
    $3D044B19, $39C556AE, $278206AB, $23431B1C, $2E003DC5, $2AC12072,
    $128E9DCF, $164F8078, $1B0CA6A1, $1FCDBB16, $018AEB13, $054BF6A4,
    $0808D07D, $0CC9CDCA, $7897AB07, $7C56B6B0, $71159069, $75D48DDE,
    $6B93DDDB, $6F52C06C, $6211E6B5, $66D0FB02, $5E9F46BF, $5A5E5B08,
    $571D7DD1, $53DC6066, $4D9B3063, $495A2DD4, $44190B0D, $40D816BA,
    $ACA5C697, $A864DB20, $A527FDF9, $A1E6E04E, $BFA1B04B, $BB60ADFC,
    $B6238B25, $B2E29692, $8AAD2B2F, $8E6C3698, $832F1041, $87EE0DF6,
    $99A95DF3, $9D684044, $902B669D, $94EA7B2A, $E0B41DE7, $E4750050,
    $E9362689, $EDF73B3E, $F3B06B3B, $F771768C, $FA325055, $FEF34DE2,
    $C6BCF05F, $C27DEDE8, $CF3ECB31, $CBFFD686, $D5B88683, $D1799B34,
    $DC3ABDED, $D8FBA05A, $690CE0EE, $6DCDFD59, $608EDB80, $644FC637,
    $7A089632, $7EC98B85, $738AAD5C, $774BB0EB, $4F040D56, $4BC510E1,
    $46863638, $42472B8F, $5C007B8A, $58C1663D, $558240E4, $51435D53,
    $251D3B9E, $21DC2629, $2C9F00F0, $285E1D47, $36194D42, $32D850F5,
    $3F9B762C, $3B5A6B9B, $0315D626, $07D4CB91, $0A97ED48, $0E56F0FF,
    $1011A0FA, $14D0BD4D, $19939B94, $1D528623, $F12F560E, $F5EE4BB9,
    $F8AD6D60, $FC6C70D7, $E22B20D2, $E6EA3D65, $EBA91BBC, $EF68060B,
    $D727BBB6, $D3E6A601, $DEA580D8, $DA649D6F, $C423CD6A, $C0E2D0DD,
    $CDA1F604, $C960EBB3, $BD3E8D7E, $B9FF90C9, $B4BCB610, $B07DABA7,
    $AE3AFBA2, $AAFBE615, $A7B8C0CC, $A379DD7B, $9B3660C6, $9FF77D71,
    $92B45BA8, $9675461F, $8832161A, $8CF30BAD, $81B02D74, $857130C3,
    $5D8A9099, $594B8D2E, $5408ABF7, $50C9B640, $4E8EE645, $4A4FFBF2,
    $470CDD2B, $43CDC09C, $7B827D21, $7F436096, $7200464F, $76C15BF8,
    $68860BFD, $6C47164A, $61043093, $65C52D24, $119B4BE9, $155A565E,
    $18197087, $1CD86D30, $029F3D35, $065E2082, $0B1D065B, $0FDC1BEC,
    $3793A651, $3352BBE6, $3E119D3F, $3AD08088, $2497D08D, $2056CD3A,
    $2D15EBE3, $29D4F654, $C5A92679, $C1683BCE, $CC2B1D17, $C8EA00A0,
    $D6AD50A5, $D26C4D12, $DF2F6BCB, $DBEE767C, $E3A1CBC1, $E760D676,
    $EA23F0AF, $EEE2ED18, $F0A5BD1D, $F464A0AA, $F9278673, $FDE69BC4,
    $89B8FD09, $8D79E0BE, $803AC667, $84FBDBD0, $9ABC8BD5, $9E7D9662,
    $933EB0BB, $97FFAD0C, $AFB010B1, $AB710D06, $A6322BDF, $A2F33668,
    $BCB4666D, $B8757BDA, $B5365D03, $B1F740B4);

procedure CalculateCRC(var CRC: Cardinal; const Data; Size: Cardinal);
var Buffer: ^Byte;
    Index: Cardinal;
begin
    Buffer := Addr(Data);
    for Index := 1 to Size do begin
      CRC := (CRC shl 8) xor CRC_TABLE[((CRC shr 24) and $FF) xor Buffer^];
      Inc(Buffer);
    end;
end;

destructor TTagVorbis.Destroy;
begin
    FVendor:= '';
    inherited;
end;

end.
