unit TagTwinVQ;

interface

uses Windows, KOL, ATLCommon;

type
  PTagTwinVQ = ^TTagTwinVQ;
  TTwinVQTag = PTagTwinVQ;
  TTagTwinVQ = object(TTagInfo)
  private
  public
    function ReadFromFile(const AFileName: KOLstring): Boolean; virtual;
    function SaveToFile(const AFileName: KOLstring): Boolean; virtual;
    function RemoveFromFile(const AFileName: KOLstring): Boolean; virtual;

    property OriginalFile: String index etOriginalFile read GetTag write SetTag;
  end;

  function NewTagTwinVQ: TTwinVQTag;

const
  TWIN_ID = 'TWIN';

implementation

uses ATLLib;

type
  TChunkType = (ctNAME, ctCOMT, ctAUTH, ctC , ctFILE, ctALBM);

  TChunkHeader = packed record
    ID: array [1..4] of Char;                                      { Chunk ID }
    Size: Cardinal;                                              { Chunk size }
  end;

  THeaderInfo = packed record
    ID: array [1..4] of Char;                                 { Always "TWIN" }
    Version: array [1..8] of Char;                               { Version ID }
    Size: Cardinal;                                             { Header size }
    Common: TChunkHeader;                                { Common chunk header }
    ChannelMode: Cardinal;               { Channel mode: 0 - mono, 1 - stereo }
    BitRate: Cardinal;                                       { Total bit rate }
    SampleRate: Cardinal;                                 { Sample rate (khz) }
    SecurityLevel: Cardinal;                                       { Always 0 }
  end;

const
  TWIN_CHUNK: array [TChunkType] of string = ('NAME', 'COMT', 'AUTH', '(c) ', 'FILE', 'ALBM');

function NewTagTwinVQ: TTwinVQTag;
begin
    New(Result, Create);
    Result.FExistsTags:= [etTitle, etArtist, etAlbum, etComment, etOriginalFile, etVersion];
    Result.FTagVendor:= tTwinVQ;
end;

function TTagTwinVQ.ReadFromFile(const AFileName: KOLstring): Boolean;
var SourceFile: PStream;
    Chunk: TChunkHeader;
    Data: String;
    Header: THeaderInfo;
    c: TChunkType;
    Find: Boolean;
begin
    Result:= False;
    ResetData;
    try
      SourceFile:= OpenFileRead(AFileName);
      try
        SourceFile.Read(Header, SizeOf(THeaderInfo));
        if Header.ID <> TWIN_ID then Exit;

        inherited SetTag(etVersion, Header.Version);

        SourceFile.Seek(16, spBegin);
        repeat
          SourceFile.Read(Chunk, SizeOf(TChunkHeader));
          Find:= False;
          Chunk.Size:= SwapOrder32(Chunk.Size);
          if Chunk.ID = 'COMM' then begin SourceFile.Seek(Chunk.Size, spCurrent); Continue; end;
          for c:= Low(TChunkType) to High(TChunkType) do
            if Chunk.ID = TWIN_CHUNK[c] then begin
              Find:= True; FExists:= True;
              SetLength(Data, Chunk.Size);
              SourceFile.Read(Data[1], Chunk.Size);
              case c of
                ctNAME: Title:= Data;
                ctCOMT: Comment:= Data;
                ctAUTH: Artist:= Data;
                ctC: Copyright:= Data;
                ctFILE: inherited SetTag(etOriginalFile, Data);
                ctALBM: Album:= Data;
              end;
              Break;
            end;
          if not Find then Break;
          FTagSize:= FTagSize + SizeOf(TChunkHeader) + Chunk.Size;
        until SourceFile.Position>=SourceFile.Size;
        Result:= True;
      except
      end;
    finally
      SourceFile.Free;
    end;
end;

function TTagTwinVQ.SaveToFile(const AFileName: KOLstring): Boolean;
var SourceFile, DestFile: PStream;
    Header: THeaderInfo;
    Sz, l: DWord;
    Chunk: TChunkHeader;
    c: TChunkType;
    Data: String;
begin
    Result := true;
    try
      DestFile:= CreateFile(AFileName+'~');
      SourceFile:= OpenFileRead(AFileName);
      try
        Stream2Stream(DestFile, SourceFile, 16);

        SourceFile.Seek(12, spBegin);
        SourceFile.Read(Header.Size, 4);
        SourceFile.Read(Header.Common, SizeOf(TChunkHeader));

        SourceFile.Seek(16, spBegin);
        Sz:= SwapOrder32(Header.Common.Size);
        Sz:= Sz + SizeOf(TChunkHeader);
        Stream2Stream(DestFile, SourceFile, Sz);

        FExists:= False;
        for c:= Low(TChunkType) to High(TChunkType) do begin
          case c of
            ctNAME: Data:= Title;
            ctCOMT: Data:= Comment;
            ctAUTH: Data:= Artist;
            ctC: Data:= Copyright;
            ctFILE: Data:= OriginalFile;
            ctALBM: Data:= Album;
          end;
          if Data <> '' then begin
            FExists:= True;
            l:= Length(Data);
            Move(TWIN_CHUNK[c][1], Chunk.ID, 4);;
            Chunk.Size:= SwapOrder32(l);
            DestFile.Write(Chunk, SizeOf(TChunkHeader));
            DestFile.Write(Data[1], l);
            Sz:= Sz + SizeOf(TChunkHeader) + l;
          end;
        end;

        SourceFile.Seek(16+SwapOrder32(Header.Size), spBegin);
        Stream2Stream(DestFile, SourceFile, SourceFile.Size - SourceFile.Position);

        Sz:= SwapOrder32(Sz);
        DestFile.Seek(12, spBegin);
        DestFile.Write(Sz, 4);

      finally
        SourceFile.Free;
        DestFile.Free;
      end;
      Result:= DeleteFile(PChar(AFileName)) and MoveFile(PChar(AFileName+'~'), PChar(AFileName));
      if FileExists(AFileName+'~') then DeleteFile(PChar(AFileName+'~'));
    except
    end;
end;

function TTagTwinVQ.RemoveFromFile(const AFileName: KOLstring): Boolean;
begin
    ResetData;
    Result:= SaveToFile(AFileName);
end;

end.
