📄 id3v2.pas
字号:
{ *************************************************************************** }
{ }
{ Audio Tools Library (Freeware) }
{ Class TID3v2 - for manipulating with ID3v2 tags }
{ }
{ Copyright (c) 2001,2002 by Jurgen Faul }
{ E-mail: jfaul@gmx.de }
{ http://jfaul.de/atl }
{ }
{ Version 1.4 (24 March 2002) }
{ - Reading support for ID3v2.2.x & ID3v2.4.x tags }
{ }
{ Version 1.3 (16 February 2002) }
{ - Fixed bug with property Comment }
{ - Added info: composer, encoder, copyright, language, link }
{ }
{ Version 1.2 (17 October 2001) }
{ - Writing support for ID3v2.3.x tags }
{ - Fixed bug with track number detection }
{ - Fixed bug with tag reading }
{ }
{ Version 1.1 (31 August 2001) }
{ - Added public procedure ResetData }
{ }
{ Version 1.0 (14 August 2001) }
{ - Reading support for ID3v2.3.x tags }
{ - Tag info: title, artist, album, track, year, genre, comment }
{ }
{ *************************************************************************** }
unit ID3v2;
interface
uses
Classes, SysUtils;
const
TAG_VERSION_2_2 = 2; { Code for ID3v2.2.x tag }
TAG_VERSION_2_3 = 3; { Code for ID3v2.3.x tag }
TAG_VERSION_2_4 = 4; { Code for ID3v2.4.x tag }
type
{ Class TID3v2 }
TID3v2 = class(TObject)
private
{ Private declarations }
FExists: Boolean;
FVersionID: Byte;
FSize: Integer;
FTitle: string;
FArtist: string;
FAlbum: string;
FTrack: Byte;
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: Byte);
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
{ Public declarations }
constructor Create; { Create object }
procedure ResetData; { Reset all data }
function ReadFromFile(const FileName: string): Boolean; { Load tag }
function SaveToFile(const FileName: string): Boolean; { Save tag }
function RemoveFromFile(const FileName: string): Boolean; { Delete tag }
property Exists: Boolean read FExists; { True if tag found }
property VersionID: Byte read FVersionID; { Version code }
property Size: Integer read FSize; { Total tag size }
property Title: string read FTitle write FSetTitle; { Song title }
property Artist: string read FArtist write FSetArtist; { Artist name }
property Album: string read FAlbum write FSetAlbum; { Album title }
property Track: Byte read FTrack write FSetTrack; { Track number }
property Year: string read FYear write FSetYear; { Release year }
property Genre: string read FGenre write FSetGenre; { Genre name }
property Comment: string read FComment write FSetComment; { Comment }
property Composer: string read FComposer write FSetComposer; { Composer }
property Encoder: string read FEncoder write FSetEncoder; { Encoder }
property Copyright: string read FCopyright write FSetCopyright; { (c) }
property Language: string read FLanguage write FSetLanguage; { Language }
property Link: string read FLink write FSetLink; { URL link }
end;
implementation
const
{ ID3v2 tag ID }
ID3V2_ID = 'ID3';
{ Max. number of supported tag frames }
ID3V2_FRAME_COUNT = 16;
{ Names of supported tag frames (ID3v2.3.x & ID3v2.4.x) }
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');
{ Names of supported tag frames (ID3v2.2.x) }
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');
type
{ Frame header (ID3v2.3.x & ID3v2.4.x) }
FrameHeaderNew = record
ID: array [1..4] of Char; { Frame ID }
Size: Integer; { Size excluding header }
Flags: Word; { Flags }
end;
{ Frame header (ID3v2.2.x) }
FrameHeaderOld = record
ID: array [1..3] of Char; { Frame ID }
Size: array [1..3] of Byte; { Size excluding header }
end;
{ ID3v2 header data - for internal use }
TagInfo = record
{ Real structure of ID3v2 header }
ID: array [1..3] of Char; { Always "ID3" }
Version: Byte; { Version number }
Revision: Byte; { Revision number }
Flags: Byte; { Flags of tag }
Size: array [1..4] of Byte; { Tag size excluding header }
{ Extended data }
FileSize: Integer; { File size (bytes) }
Frame: array [1..ID3V2_FRAME_COUNT] of string; { Information from frames }
end;
{ ********************* Auxiliary functions & procedures ******************** }
function ReadHeader(const FileName: string; var Tag: TagInfo): Boolean;
var
SourceFile: file;
Transferred: Integer;
begin
try
Result := true;
{ Set read-access and open file }
AssignFile(SourceFile, FileName);
FileMode := 0;
Reset(SourceFile, 1);
{ Read header and get file size }
BlockRead(SourceFile, Tag, 10, Transferred);
Tag.FileSize := FileSize(SourceFile);
CloseFile(SourceFile);
{ if transfer is not complete }
if Transferred < 10 then Result := false;
except
{ Error }
Result := false;
end;
end;
{ --------------------------------------------------------------------------- }
function GetTagSize(const Tag: TagInfo): Integer;
begin
{ Get total tag size }
Result :=
Tag.Size[1] * $200000 +
Tag.Size[2] * $4000 +
Tag.Size[3] * $80 +
Tag.Size[4] + 10;
if Tag.Flags and $10 > 0 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
{ Set tag item if supported frame found }
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 then Tag.Frame[Iterator] := Data;
end;
end;
{ --------------------------------------------------------------------------- }
function Swap32(const Figure: Integer): Integer;
var
ByteArray: array [1..4] of Byte absolute Figure;
begin
{ Swap 4 bytes }
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..250] of Char;
DataPosition, DataSize: Integer;
begin
{ Get information from frames (ID3v2.3.x & ID3v2.4.x) }
try
{ Set read-access, open file }
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);
{ Read frame header and check frame ID }
BlockRead(SourceFile, Frame, 10);
if not (Frame.ID[1] in ['A'..'Z']) then break;
{ Note data position and determine significant data size }
DataPosition := FilePos(SourceFile);
if Swap32(Frame.Size) > SizeOf(Data) then DataSize := SizeOf(Data)
else DataSize := Swap32(Frame.Size);
{ Read frame data and set tag item if frame supported }
BlockRead(SourceFile, Data, DataSize);
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..250] of Char;
DataPosition, FrameSize, DataSize: Integer;
begin
{ Get information from frames (ID3v2.2.x) }
try
{ Set read-access, open file }
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);
{ Read frame header and check frame ID }
BlockRead(SourceFile, Frame, 6);
if not (Frame.ID[1] in ['A'..'Z']) then break;
{ Note data position and determine significant data size }
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;
{ Read frame data and set tag item if frame supported }
BlockRead(SourceFile, Data, DataSize);
SetTagItem(Frame.ID, Data, Tag);
Seek(SourceFile, DataPosition + FrameSize);
end;
CloseFile(SourceFile);
except
end;
end;
{ --------------------------------------------------------------------------- }
function GetContent(const Content1, Content2: string): string;
begin
{ Get content preferring the first content }
Result := Trim(Content1);
if Result = '' then Result := Trim(Content2);
end;
{ --------------------------------------------------------------------------- }
function ExtractTrack(const TrackString: string): Byte;
var
Index, Value, Code: Integer;
begin
{ Extract track from string }
Index := Pos('/', Trim(TrackString));
if Index = 0 then Val(Trim(TrackString), Value, Code)
else Val(Copy(Trim(TrackString), 1, Index - 1), Value, Code);
if Code = 0 then Result := Value
else Result := 0;
end;
{ --------------------------------------------------------------------------- }
function ExtractYear(const YearString, DateString: string): string;
begin
{ Extract year from strings }
Result := Trim(YearString);
if Result = '' then Result := Copy(Trim(DateString), 1, 4);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -