Well, the unit you present me seems to only support ID3v1.
I said: "It works only under the ID3v1 pattern!" No. Its not correct.
What I want to say is that only the same tags filled under the ID3v1
pattern is filled when I use the TID3v2 class. So the attached unit
and the mentioned example behaves as the ID3v1 was the active
supported pattern.
In my example, "composer" is the single tag present in v2, not present in v1.
Attached is the unit sent to me.
ID3v1 is a very simple pattern. It's enough to read the last 128 byte
of the mp3 file, assigning it to a record. So with simple blockreads
and blockwrites, the work is done without difficulties. ID3v2 is much
more complex.
After visiting http://www.id3.org, I got some information for
developers. Hum.. The ID3v2 is so complex that I think I was not able
to implement it if it is already available.


2013/5/9, leledumbo <leledumbo_c...@yahoo.co.id>:
> As wikipedia explains, ID3v1 and ID3v2 are completely unrelated and have
> different structure. Without seeing what your friend unit contains, it's
> hard to tell whether he implements both versions correctly or not. But
> since
> you said the version 1 works, and I don't see anything discriminating the
> two version in your sample code (again, this can't be concluded without
> seeing the actual unit code), I guess he only implements version 1.
>
> Apparently, Cactus Jukebox supports ID3, both version 1 and 2.
> The related unit is here:
> https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/applications/cactusjukebox/source/tagreader/mp3file.pas
> You can see how it's used here:
> https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/applications/cactusjukebox/source/mediacol.pas
>
>
>
>
> --
> View this message in context:
> http://free-pascal-general.1045716.n5.nabble.com/ID3v2-Reading-and-writing-MP3-metadata-tp5714707p5714708.html
> Sent from the Free Pascal - General mailing list archive at Nabble.com.
> _______________________________________________
> fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
> http://lists.freepascal.org/mailman/listinfo/fpc-pascal
>


-- 
Luciano de Souza
unit ID3v2;
{$mode objfpc}{$H+}

interface 

uses 
  Classes, SysUtils; 

const 
  TAG_VERSION_2_2 = 2; 
  TAG_VERSION_2_3 = 3; 
  TAG_VERSION_2_4 = 4; 

type 
  TID3v2 = class(TObject) 
  private 
    FExists: Boolean; 
    FVersionID: Byte; 
    FSize: Integer; 
    FTitle: string; 
    FArtist: string; 
    FAlbum: string; 
    FTrack: Word; 
    FTrackString: string; 
    FYear: string; 
    FGenre: string; 
    FComment: string; 
    FComposer: string; 
    FEncoder: string; 
    FCopyright: string; 
    FLanguage: string; 
    FLink: string; 
    procedure FSetTitle(const NewTitle: string); 
    procedure FSetArtist(const NewArtist: string); 
    procedure FSetAlbum(const NewAlbum: string); 
    procedure FSetTrack(const NewTrack: Word); 
    procedure FSetYear(const NewYear: string); 
    procedure FSetGenre(const NewGenre: string); 
    procedure FSetComment(const NewComment: string); 
    procedure FSetComposer(const NewComposer: string); 
    procedure FSetEncoder(const NewEncoder: string); 
    procedure FSetCopyright(const NewCopyright: string); 
    procedure FSetLanguage(const NewLanguage: string); 
    procedure FSetLink(const NewLink: string);

  public 
    constructor Create;
    procedure ResetData; 
    function ReadFromFile  (const FileName: string): Boolean; 
    function SaveToFile    (const FileName: string): Boolean; 
    function RemoveFromFile(const FileName: string): Boolean; 
    property Exists      : Boolean read FExists; 
    property VersionID   : Byte    read FVersionID; 
    property Size        : Integer read FSize; 
    property Title       : String  read FTitle     write FSetTitle; 
    property Artist      : String  read FArtist    write FSetArtist; 
    property Album       : String  read FAlbum     write FSetAlbum; 
    property Track       : Word    read FTrack     write FSetTrack; 
    property TrackString : String  read FTrackString; 
    property Year        : String  read FYear      write FSetYear; 
    property Genre       : String  read FGenre     write FSetGenre; 
    property Comment     : String  read FComment   write FSetComment; 
    property Composer    : String  read FComposer  write FSetComposer; 
    property Encoder     : String  read FEncoder   write FSetEncoder; 
    property Copyright   : String  read FCopyright write FSetCopyright;
    property Language    : String  read FLanguage  write FSetLanguage;
    property Link        : String  read FLink      write FSetLink;
  end;

implementation

const
  ID3V2_ID = 'ID3';
  ID3V2_FRAME_COUNT = 16;
  ID3V2_FRAME_DESC: array [1..ID3V2_FRAME_COUNT] of string =
    ('Title/songname/content description',
     'Lead performer(s)/Soloist(s)',
     'Album/Movie/Show title',
     'Track number/Position in set',
     'Year',
     'Content type',
     'Comments',
     'Composer',
     'Encoded by',
     'Copyright message',
     'Language(s)',
     'User defined URL link frame',
     'Recording time',
     'Original artist(s)/performer(s)',
     'Content group description',
     'Original album/movie/show title');
  ID3V2_FRAME_NEW: array [1..ID3V2_FRAME_COUNT] of string =
    ('TIT2',
     'TPE1',
     'TALB',
     'TRCK',
     'TYER',
     'TCON',
     'COMM',
     'TCOM',
     'TENC',
     'TCOP',
     'TLAN',
     'WXXX',
     'TDRC',
     'TOPE',
     'TIT1',
     'TOAL');
  ID3V2_FRAME_OLD: array [1..ID3V2_FRAME_COUNT] of string =
    ('TT2',
     'TP1',
     'TAL',
     'TRK',
     'TYE',
     'TCO',
     'COM',
     'TCM',
     'TEN',
     'TCR',
     'TLA',
     'WXX',
     'TOR',
     'TOA',
     'TT1',
     'TOT');
  ID3V2_MAX_SIZE = 4096;
  UNICODE_ID = #1;

// %artist% = %title% = %album% = %year% = %genre% = %track% = %_filename_ext% = %comment%

{ --------------------------------------------------------------------------- }
type
  FrameHeaderNew = record
    ID    : array [1..4] of Char;
    Size  : Integer;
    Flags : Word;
  end;

  FrameHeaderOld = record
    ID   : array [1..3] of Char; 
    Size : array [1..3] of Byte; 
  end; 

  TagInfo = record 
    ID          : array [1..3] of Char; 
    Version     : Byte; 
    Revision    : Byte; 
    Flags       : Byte; 
    Size        : array [1..4] of Byte; 
    FileSize    : Integer; 
    Frame       : array [1..ID3V2_FRAME_COUNT] of string; 
    NeedRewrite : Boolean; 
    PaddingSize : Integer; 
  end; 
{ --------------------------------------------------------------------------- } 
function ReadHeader(const FileName: string; var Tag: TagInfo): Boolean; 
var 
  SourceFile: file; 
  Transferred: Integer; 
begin 
  try 
    Result := true; 
    AssignFile(SourceFile, FileName); 
    FileMode := 0; 
    Reset(SourceFile, 1); 
    BlockRead(SourceFile, Tag, 10, Transferred);
    Tag.FileSize := FileSize(SourceFile); 
    CloseFile(SourceFile); 
    if Transferred < 10 then Result := false; 
  except 
    Result := false; 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function GetTagSize(const Tag: TagInfo): Integer; 
begin
  Result := 
    Tag.Size[1] * $200000 + 
    Tag.Size[2] * $4000 + 
    Tag.Size[3] * $80 + 
    Tag.Size[4] + 10; 
  if Tag.Flags and $10 = $10 then Inc(Result, 10); 
  if Result > Tag.FileSize then Result := 0; 
end; 
{ --------------------------------------------------------------------------- } 
procedure SetTagItem(const ID, Data: string; var Tag: TagInfo); 
var 
  Iterator: Byte; 
  FrameID: string; 
begin 
  for Iterator := 1 to ID3V2_FRAME_COUNT do 
  begin 
    if Tag.Version > TAG_VERSION_2_2 then 
      FrameID := ID3V2_FRAME_NEW[Iterator] 
    else 
      FrameID := ID3V2_FRAME_OLD[Iterator]; 
    if (FrameID = ID) and (Data[1] <= UNICODE_ID) then 
      Tag.Frame[Iterator] := Data; 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function Swap32(const Figure: Integer): Integer; 
var 
  ByteArray: array [1..4] of Byte absolute Figure; 
begin 
  Result := 
    ByteArray[1] * $1000000 + 
    ByteArray[2] * $10000 + 
    ByteArray[3] * $100 + 
    ByteArray[4]; 
end; 
{ --------------------------------------------------------------------------- }
procedure ReadFramesNew(const FileName: string; var Tag: TagInfo); 
var 
  SourceFile: file; 
  Frame: FrameHeaderNew; 
  Data: array [1..500] of Char; 
  DataPosition, DataSize: Integer; 
begin 
  try 
    AssignFile(SourceFile, FileName); 
    FileMode := 0;
    Reset(SourceFile, 1); 
    Seek(SourceFile, 10); 
    while (FilePos(SourceFile) < GetTagSize(Tag)) and (not EOF(SourceFile)) do 
    begin 
      FillChar(Data, SizeOf(Data), 0); 
      BlockRead(SourceFile, Frame, 10); 
      if not (Frame.ID[1] in ['A'..'Z']) then break; 
      DataPosition := FilePos(SourceFile); 
      if Swap32(Frame.Size) > SizeOf(Data) then DataSize := SizeOf(Data) 
      else DataSize := Swap32(Frame.Size); 
      BlockRead(SourceFile, Data, DataSize); 
      if Frame.Flags and $8000 <> $8000 then SetTagItem(Frame.ID, Data, Tag); 
      Seek(SourceFile, DataPosition + Swap32(Frame.Size)); 
    end; 
    CloseFile(SourceFile); 
  except 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
procedure ReadFramesOld(const FileName: string; var Tag: TagInfo); 
var 
  SourceFile: file; 
  Frame: FrameHeaderOld; 
  Data: array [1..500] of Char; 
  DataPosition, FrameSize, DataSize: Integer; 
begin 
  try 
    AssignFile(SourceFile, FileName); 
    FileMode := 0; 
    Reset(SourceFile, 1); 
    Seek(SourceFile, 10); 
    while (FilePos(SourceFile) < GetTagSize(Tag)) and (not EOF(SourceFile)) do 
    begin 
      FillChar(Data, SizeOf(Data), 0); 
      BlockRead(SourceFile, Frame, 6); 
      if not (Frame.ID[1] in ['A'..'Z']) then break;
      DataPosition := FilePos(SourceFile); 
      FrameSize := Frame.Size[1] shl 16 + Frame.Size[2] shl 8 + Frame.Size[3]; 
      if FrameSize > SizeOf(Data) then DataSize := SizeOf(Data) 
      else DataSize := FrameSize; 
      BlockRead(SourceFile, Data, DataSize); 
      SetTagItem(Frame.ID, Data, Tag); 
      Seek(SourceFile, DataPosition + FrameSize); 
    end; 
    CloseFile(SourceFile); 
  except
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function GetANSI(const Source: string): string; 
var 
  Index: Integer; 
  FirstByte, SecondByte: Byte; 
  UnicodeChar: WideChar; 
begin 
  if (Length(Source) > 0) and (Source[1] = UNICODE_ID) then 
  begin 
    Result := ''; 
    for Index := 1 to ((Length(Source) - 1) div 2) do 
    begin 
      FirstByte := Ord(Source[Index * 2]); 
      SecondByte := Ord(Source[Index * 2 + 1]); 
      UnicodeChar := WideChar(FirstByte or (SecondByte shl 8)); 
      if UnicodeChar = #0 then break; 
      if FirstByte < $FF then Result := Result + UnicodeChar; 
    end; 
    Result := Trim(Result); 
  end 
  else 
    Result := Trim(Source); 
end; 
{ --------------------------------------------------------------------------- } 
function GetContent(const Content1, Content2: string): string; 
begin 
  Result := GetANSI(Content1); 
  if Result = '' then Result := GetANSI(Content2); 
end; 
{ --------------------------------------------------------------------------- } 
function ExtractTrack(const TrackString: string): Word; 
var 
  Track: string; 
  Index, Value, Code: Integer;
begin 
  Track := GetANSI(TrackString); 
  Index := Pos('/', Track); 
  if Index = 0 then Val(Track, Value, Code) 
  else Val(Copy(Track, 1, Index - 1), Value, Code); 
  if Code = 0 then Result := Value 
  else Result := 0; 
end; 
{ --------------------------------------------------------------------------- } 
function ExtractYear(const YearString, DateString: string): string;
begin 
  Result := GetANSI(YearString); 
  if Result = '' then Result := Copy(GetANSI(DateString), 1, 4); 
end; 
{ --------------------------------------------------------------------------- } 
function ExtractGenre(const GenreString: string): string; 
begin 
  Result := GetANSI(GenreString); 
  if Pos(')', Result) > 0 then Delete(Result, 1, LastDelimiter(')', Result)); 
end; 
{ --------------------------------------------------------------------------- } 
function ExtractText(const SourceString: string; LanguageID: Boolean): string; 
var 
  Source, Separator: string; 
  EncodingID: Char; 
begin 
  Source := SourceString; 
  Result := ''; 
  if Length(Source) > 0 then 
  begin 
    EncodingID := Source[1]; 
    if EncodingID = UNICODE_ID then Separator := #0#0 
    else Separator := #0; 
    if LanguageID then  Delete(Source, 1, 4) 
    else Delete(Source, 1, 1); 
    Delete(Source, 1, Pos(Separator, Source) + Length(Separator) - 1); 
    Result := GetANSI(EncodingID + Source); 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
procedure BuildHeader(var Tag: TagInfo); 
var 
  Iterator, TagSize: Integer; 
begin 
  TagSize := 10; 
  for Iterator := 1 to ID3V2_FRAME_COUNT do
    if Tag.Frame[Iterator] <> '' then 
      Inc(TagSize, Length(Tag.Frame[Iterator]) + 11); 
  Tag.NeedRewrite := 
    (Tag.ID <> ID3V2_ID) or 
    (GetTagSize(Tag) < TagSize) or 
    (GetTagSize(Tag) > ID3V2_MAX_SIZE); 
  if Tag.NeedRewrite then Tag.PaddingSize := ID3V2_MAX_SIZE - TagSize 
  else Tag.PaddingSize := GetTagSize(Tag) - TagSize; 
  if Tag.PaddingSize > 0 then Inc(TagSize, Tag.PaddingSize); 
  Tag.ID := ID3V2_ID;
  Tag.Version := TAG_VERSION_2_3; 
  Tag.Revision := 0; 
  Tag.Flags := 0; 
  for Iterator := 1 to 4 do 
    Tag.Size[Iterator] := ((TagSize - 10) shr ((4 - Iterator) * 7)) and $7F; 
end; 
{ --------------------------------------------------------------------------- } 
function ReplaceTag(const FileName: string; TagData: TStream): Boolean; 
var 
  Destination: TFileStream; 
begin 
  Result := false; 
  if (not FileExists(FileName)) or (FileSetAttr(FileName, 0) <> 0) then exit; 
  try 
    TagData.Position := 0; 
    Destination := TFileStream.Create(FileName, fmOpenReadWrite); 
    Destination.CopyFrom(TagData, TagData.Size); 
    Destination.Free; 
    Result := true; 
  except 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function RebuildFile(const FileName: string; TagData: TStream): Boolean; 
var 
  Tag: TagInfo; 
  Source, Destination: TFileStream; 
  BufferName: string; 
begin 
  Result := false; 
  if (not FileExists(FileName)) or (FileSetAttr(FileName, 0) <> 0) then exit; 
  if not ReadHeader(FileName, Tag) then exit; 
  if (TagData = nil) and (Tag.ID <> ID3V2_ID) then exit; 
  try 
    BufferName := FileName + '~'; 
    Source := TFileStream.Create(FileName, fmOpenRead);
    Destination := TFileStream.Create(BufferName, fmCreate); 
    if Tag.ID = ID3V2_ID then Source.Seek(GetTagSize(Tag), soFromBeginning); 
    if TagData <> nil then Destination.CopyFrom(TagData, 0); 
    Destination.CopyFrom(Source, Source.Size - Source.Position); 
    Source.Free; 
    Destination.Free; 
    if (DeleteFile(FileName)) and (RenameFile(BufferName, FileName)) then 
      Result := true 
    else 
      raise Exception.Create('');
  except 
    if FileExists(BufferName) then DeleteFile(BufferName); 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function SaveTag(const FileName: string; Tag: TagInfo): Boolean; 
var 
  TagData: TStringStream; 
  Iterator, FrameSize: Integer; 
  Padding: array [1..ID3V2_MAX_SIZE] of Byte; 
begin 
  TagData := TStringStream.Create(''); 
  BuildHeader(Tag); 
  TagData.Write(Tag, 10); 
  for Iterator := 1 to ID3V2_FRAME_COUNT do 
    if Tag.Frame[Iterator] <> '' then 
    begin 
      TagData.WriteString(ID3V2_FRAME_NEW[Iterator]); 
      FrameSize := Swap32(Length(Tag.Frame[Iterator]) + 1); 
      TagData.Write(FrameSize, SizeOf(FrameSize)); 
      TagData.WriteString(#0#0#0 + Tag.Frame[Iterator]); 
    end; 
  FillChar(Padding, SizeOf(Padding), 0); 
  if Tag.PaddingSize > 0 then TagData.Write(Padding, Tag.PaddingSize); 
  if Tag.NeedRewrite then Result := RebuildFile(FileName, TagData) 
  else Result := ReplaceTag(FileName, TagData); 
  TagData.Free; 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetTitle(const NewTitle: string); 
begin 
  FTitle := Trim(NewTitle); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetArtist(const NewArtist: string); 
begin
  FArtist := Trim(NewArtist); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetAlbum(const NewAlbum: string); 
begin 
  FAlbum := Trim(NewAlbum); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetTrack(const NewTrack: Word); 
begin
  FTrack := NewTrack; 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetYear(const NewYear: string); 
begin 
  FYear := Trim(NewYear); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetGenre(const NewGenre: string); 
begin 
  FGenre := Trim(NewGenre); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetComment(const NewComment: string); 
begin 
  FComment := Trim(NewComment); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetComposer(const NewComposer: string); 
begin 
  FComposer := Trim(NewComposer); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetEncoder(const NewEncoder: string); 
begin 
  FEncoder := Trim(NewEncoder); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetCopyright(const NewCopyright: string); 
begin 
  FCopyright := Trim(NewCopyright); 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetLanguage(const NewLanguage: string); 
begin 
  FLanguage := Trim(NewLanguage);
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.FSetLink(const NewLink: string); 
begin 
  FLink := Trim(NewLink); 
end; 
{ --------------------------------------------------------------------------- } 
constructor TID3v2.Create; 
begin 
  inherited;
  ResetData; 
end; 
{ --------------------------------------------------------------------------- } 
procedure TID3v2.ResetData; 
begin 
  FExists      := false; 
  FVersionID   := 0; 
  FSize        := 0; 
  FTitle       := ''; 
  FArtist      := ''; 
  FAlbum       := ''; 
  FTrack       := 0; 
  FTrackString := ''; 
  FYear        := ''; 
  FGenre       := ''; 
  FComment     := ''; 
  FComposer    := ''; 
  FEncoder     := ''; 
  FCopyright   := ''; 
  FLanguage    := ''; 
  FLink        := ''; 
end; 
{ --------------------------------------------------------------------------- } 
function TID3v2.ReadFromFile(const FileName: string): Boolean; 
var 
  Tag: TagInfo; 
begin 
  ResetData; 
  Result := ReadHeader(FileName, Tag); 
  if (Result) and (Tag.ID = ID3V2_ID) then 
  begin 
    FExists := true; 
    FVersionID := Tag.Version; 
    FSize := GetTagSize(Tag); 
    if (FVersionID in [TAG_VERSION_2_2..TAG_VERSION_2_4]) and (FSize > 0) then 
    begin
      if FVersionID > TAG_VERSION_2_2 then ReadFramesNew(FileName, Tag) 
      else ReadFramesOld(FileName, Tag); 
      FTitle := GetContent(Tag.Frame[1], Tag.Frame[15]); 
      FArtist := GetContent(Tag.Frame[2], Tag.Frame[14]); 
      FAlbum := GetContent(Tag.Frame[3], Tag.Frame[16]); 
      FTrack := ExtractTrack(Tag.Frame[4]); 
      FTrackString := GetANSI(Tag.Frame[4]); 
      FYear := ExtractYear(Tag.Frame[5], Tag.Frame[13]); 
      FGenre := ExtractGenre(Tag.Frame[6]); 
      FComment := ExtractText(Tag.Frame[7], true);
      FComposer := GetANSI(Tag.Frame[8]); 
      FEncoder := GetANSI(Tag.Frame[9]); 
      FCopyright := GetANSI(Tag.Frame[10]); 
      FLanguage := GetANSI(Tag.Frame[11]); 
      FLink := ExtractText(Tag.Frame[12], false); 
    end; 
  end; 
end; 
{ --------------------------------------------------------------------------- } 
function TID3v2.SaveToFile(const FileName: string): Boolean; 
var 
  Tag: TagInfo; 
begin 
  FillChar(Tag, SizeOf(Tag), 0); 
  ReadHeader(FileName, Tag); 
  Tag.Frame[1] := FTitle; 
  Tag.Frame[2] := FArtist; 
  Tag.Frame[3] := FAlbum; 
  if FTrack > 0 then Tag.Frame[4] := IntToStr(FTrack); 
  Tag.Frame[5] := FYear; 
  Tag.Frame[6] := FGenre; 
  if FComment <> '' then Tag.Frame[7] := 'eng' + #0 + FComment; 
  Tag.Frame[8] := FComposer; 
  Tag.Frame[9] := FEncoder; 
  Tag.Frame[10] := FCopyright; 
  Tag.Frame[11] := FLanguage; 
  if FLink <> '' then Tag.Frame[12] := #0 + FLink; 
  Result := SaveTag(FileName, Tag); 
end; 
{ --------------------------------------------------------------------------- } 
function TID3v2.RemoveFromFile(const FileName: string): Boolean; 
begin 
  Result := RebuildFile(FileName, nil); 
end; 
{ --------------------------------------------------------------------------- } 
end.
_______________________________________________
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-pascal

Reply via email to