unit TagID3v1Lyrics;
 
interface

uses KOL, ATLCommon;

type
  PTagID3v1Lyrics = ^TTagID3v1Lyrics;
  TID3v1LyricsTag = PTagID3v1Lyrics;
  TTagID3v1Lyrics = object(TTagInfo)
  private
    function ReadLyrics(const FileName: KOLString; ASetFields: Boolean = True): Boolean;
    function SaveLyrics(const FileName: KOLString): Boolean;

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

    property Lyrics: String index etLyrics read GetTag write SetTag;
    property IMG: String index etImageLink read GetTag write SetTag;
  end;

  function NewTagID3v1Lyrics: TID3v1LyricsTag;

implementation

uses ATLLib;

type
  TLyr2Mark = record
    Size: array [1..6] of Char;
    Mark: array [1..9] of Char;
  end;
  TLyr2Field = record
    ID: array [1..3] of Char;
    Size: array [1..5] of Char;
  end;

function NewTagID3v1Lyrics: TID3v1LyricsTag;
begin
    New(Result, Create);
    Result.FExistsTags:= [etTitle, etArtist, etAlbum, etComment, etComposer, etLyrics, etImageLink];
    Result.FTagVendor:= tID3v1Lyrics;
end;

function TTagID3v1Lyrics.ReadFromFile(const AFileName: KOLString): Boolean;
begin
    ResetData;
    Result:= ReadLyrics(AFileName);
end;

function TTagID3v1Lyrics.ReadLyrics(const FileName: KOLString; ASetFields: Boolean = True): Boolean;
var SourceFile: PStream;
    Mark: TLyr2Mark;
    Field: TLyr2Field;
    iOffSet, iFieldSize, LyrEnd: Cardinal;
    Buff: String;
begin
    try
      Result:= True;
      SourceFile:= OpenFileRead(FileName);
      try

        Setlength(Buff, 3);
        SourceFile.Seek(SourceFile.Size - 128, spBegin);
        SourceFile.Read(Buff[1], 3);
        if Buff = 'TAG' then iOffSet:= 128 else iOffSet:= 0;

        // Read LYRICS2 tag
        iOffSet:= iOffSet + 15;
        SourceFile.Seek(SourceFile.Size - iOffSet, spBegin);
        SourceFile.Read(Mark, 15);
        LyrEnd:= SourceFile.Position;

        if Mark.Mark = 'LYRICS200' then begin
          FTagSize:= Str2IntDef(Trim(Mark.Size), 0);
          if FTagSize > 0 then begin
            SourceFile.Seek(SourceFile.Size - (FTagSize + iOffSet), spBegin);
            SetLength(Buff, 11); // LYRICSBEGIN
            SourceFile.Read(Buff[1], 11);

            if Buff = 'LYRICSBEGIN' then begin  // is it ok ?
              FExists:= True;
              FTagSize:= FTagSize + 15;
              if ASetFields then
                while SourceFile.Position < LyrEnd do begin // read all fields
                  SourceFile.Read(Field, SizeOf(Field));
                  iFieldSize:= Str2IntDef(Field.Size, 0);
                  if iFieldSize = 0 then Continue;

                  SetLength(Buff, iFieldSize);
                  SourceFile.Read(Buff[1], iFieldSize);

                  if Field.ID = 'IND' then
                  else
                    if Field.ID = 'LYR' then begin
                      while StrReplace(Buff, #13, #13#10) do;
                      while StrReplace(Buff, #13#10#10, #13#10) do;
                      SetTag(etLyrics, Trim(Buff));
                    end
                    else
                      if Field.ID = 'INF' then Comment:= Trim(Buff)
                      else
                        if Field.ID = 'AUT' then Composer:= Trim(Buff)
                        else
                          if Field.ID = 'EAL' then Album:= Trim(Buff)
                          else
                            if Field.ID = 'EAR' then Artist:= Trim(Buff)
                            else
                              if Field.ID = 'ETT' then Title:= Trim(Buff)
                              else
                                if Field.ID = 'IMG' then SetTag(etImageLink, Buff)
                                else Break;
                end;
            end
            else begin
               FExists:= False;
               FTagSize:= 0;
            end;

          end;
        end;
      finally
        SourceFile.Free;
      end;
    except
      Result:= False;
    end;
end;

function TTagID3v1Lyrics.SaveToFile(const AFileName: KOLString): Boolean;
begin
    Result:= RemoveFromFile(AFileName) and SaveLyrics(AFileName);
end;

function TTagID3v1Lyrics.SaveLyrics(const FileName: KOLString): Boolean;
var SourceFile: PStream;
    iFilePos: Cardinal;
    S: string;
    Buff: PChar;
    TagExists: Boolean;
  procedure WriteField(sID, sValue: string);
  var iLen: integer;
      S: String;
  begin
      if Trim(sValue) <> '' then begin
        iLen:= Length(sValue);
        S:= sID + StrRepeat('0', 5 - Length(Int2Str(iLen))) + Int2Str(iLen) + sValue;
        SourceFile.Write(S[1], Length(S));
      end;
  end;
begin
    Result:= True;
    try
      try
        SourceFile:= OpenFileWrite(FileName);
        Buff:= AllocMem(128);


        // Write lyrics2
        if (Lyrics <> '') or (Composer <> '') or (Artist <> '') or (Album <> '') or (Comment <> '') or (Title <> '') or (IMG <> '') then begin

          SourceFile.Seek(SourceFile.Size - 128, spBegin);
          SourceFile.Read(Buff^, 128);
          TagExists:= Copy(Buff, 1, 3) = 'TAG';
          if TagExists then SourceFile.Size:= SourceFile.Size - 128;

          SourceFile.Seek(SourceFile.Size, spBegin);
          iFilePos:= SourceFile.Position;

          S:= 'LYRICSBEGIN';
          SourceFile.Write(S[1], Length(S));

          if Lyrics <> '' then S:= 'IND0000210' else S:= 'IND0000200';
          SourceFile.Write(S[1], Length(S));

          WriteField('EAL', Album);
          WriteField('EAR', Artist);
          WriteField('ETT', Title);
          WriteField('INF', Comment);
          WriteField('AUT', Composer);
          WriteField('LYR', Lyrics);
          WriteField('IMG', IMG);

          iFilepos:= SourceFile.Position - iFilePos;

          S:= StrRepeat('0', 6 - Length(Int2Str(iFilepos))) + Int2Str(iFilepos) + 'LYRICS200';
          SourceFile.Write(S[1], Length(S));
          FExists:= True;

          if TagExists then begin
            SourceFile.Seek(0, spEnd);
            SourceFile.Write(Buff^, 128);
          end;
        end;


      finally
        SourceFile.Free;
        DisposeMem(Pointer(Buff));
      end;
    except
      Result:= False;
    end;
end;

function TTagID3v1Lyrics.RemoveFromFile(const AFileName: KOLString): Boolean;
var SourceFile: PStream;
    Buff: PChar;
    TagExists: Boolean;
begin
    try
      Result:= ReadLyrics(AFileName, False);
      if FExists then begin
        try
          SourceFile:= OpenFileWrite(AFileName);
          Buff:= AllocMem(128);
          SourceFile.Seek(SourceFile.Size - 128, spBegin);
          SourceFile.Read(Buff^, 128);
          TagExists:= Copy(Buff, 1, 3) = 'TAG';
          if TagExists then SourceFile.Size:= SourceFile.Size - 128;

          SourceFile.Size:= SourceFile.Size - FTagSize;
          FExists:= False;

          if TagExists then begin
            SourceFile.Seek(0, spEnd);
            SourceFile.Write(Buff^, 128);
          end;
        finally
           SourceFile.Free;
           DisposeMem(Pointer(Buff));
        end;
      end;
    except
      Result:= False;
    end;
end;

end.
