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