unit WMAfile;
interface
uses
Classes, SysUtils;
const
{ Channel modes }
WMA_CM_UNKNOWN = 0; { Unknown }
WMA_CM_MONO = 1; { Mono }
WMA_CM_STEREO = 2; { Stereo }
{ Channel mode names }
WMA_MODE: array [0..2] of string = ('Unknown', 'Mono', 'Stereo');
type
{ Class TWMAfile }
TWMAfile = class(TObject)
private
{ Private declarations }
FValid: Boolean;
FFileSize: Integer;
FChannelModeID: Byte;
FSampleRate: Integer;
FDuration: Double;
FBitRate: Integer;
FTitle: WideString;
FArtist: WideString;
FAlbum: WideString;
FTrack: Integer;
FYear: WideString;
FGenre: WideString;
FComment: WideString;
procedure FResetData;
function FGetChannelMode: string;
public
{ Public declarations }
constructor Create; { Create object }
function ReadFromFile(const FileName: string): Boolean; { Load data }
property Valid: Boolean read FValid; { True if valid data }
property FileSize: Integer read FFileSize; { File size (bytes) }
property ChannelModeID: Byte read FChannelModeID; { Channel mode code }
property ChannelMode: string read FGetChannelMode; { Channel mode name }
property SampleRate: Integer read FSampleRate; { Sample rate (hz) }
property Duration: Double read FDuration; { Duration (seconds) }
property BitRate: Integer read FBitRate; { Bit rate (kbit) }
property Title: WideString read FTitle; { Song title }
property Artist: WideString read FArtist; { Artist name }
property Album: WideString read FAlbum; { Album name }
property Track: Integer read FTrack; { Track number }
property Year: WideString read FYear; { Year }
property Genre: WideString read FGenre; { Genre name }
property Comment: WideString read FComment; { Comment }
end;
implementation
const
{ Object IDs }
WMA_HEADER_ID =
#48#38#178#117#142#102#207#17#166#217#0#170#0#98#206#108;
WMA_FILE_PROPERTIES_ID =
#161#220#171#140#71#169#207#17#142#228#0#192#12#32#83#101;
WMA_STREAM_PROPERTIES_ID =
#145#7#220#183#183#169#207#17#142#230#0#192#12#32#83#101;
WMA_CONTENT_DESCRIPTION_ID =
#51#38#178#117#142#102#207#17#166#217#0#170#0#98#206#108;
WMA_EXTENDED_CONTENT_DESCRIPTION_ID =
#64#164#208#210#7#227#210#17#151#240#0#160#201#94#168#80;
{ Max. number of supported comment fields }
WMA_FIELD_COUNT = 7;
{ Names of supported comment fields }
WMA_FIELD_NAME: array [1..WMA_FIELD_COUNT] of WideString =
('WM/TITLE', 'WM/AUTHOR', 'WM/ALBUMTITLE', 'WM/TRACK', 'WM/YEAR',
'WM/GENRE', 'WM/DESCRIPTION');
{ Max. number of characters in tag field }
WMA_MAX_STRING_SIZE = 250;
type
{ Object ID }
ObjectID = array [1..16] of Char;
{ Tag data }
TagData = array [1..WMA_FIELD_COUNT] of WideString;
{ File data - for internal use }
FileData = record
FileSize: Integer; { File size (bytes) }
MaxBitRate: Integer; { Max. bit rate (bps) }
Channels: Word; { Number of channels }
SampleRate: Integer; { Sample rate (hz) }
ByteRate: Integer; { Byte rate }
Tag: TagData; { WMA tag information }
end;
{ ********************* Auxiliary functions & procedures ******************** }
function ReadFieldString(const Source: TStream; DataSize: Word): WideString;
var
Iterator, StringSize: Integer;
FieldData: array [1..WMA_MAX_STRING_SIZE * 2] of Byte;
begin
{ Read field data and convert to Unicode string }
Result := '';
StringSize := DataSize div 2;
if StringSize > WMA_MAX_STRING_SIZE then StringSize := WMA_MAX_STRING_SIZE;
Source.ReadBuffer(FieldData, StringSize * 2);
Source.Seek(DataSize - StringSize * 2, soFromCurrent);
for Iterator := 1 to StringSize do
Result := Result +
WideChar(FieldData[Iterator * 2 - 1] + (FieldData[Iterator * 2] shl 8));
end;
{ --------------------------------------------------------------------------- }
procedure ReadTagStandard(const Source: TStream; var Tag: TagData);
var
Iterator: Integer;
FieldSize: array [1..5] of Word;
FieldValue: WideString;
begin
{ Read standard tag data }
Source.ReadBuffer(FieldSize, SizeOf(FieldSize));
for Iterator := 1 to 5 do
if FieldSize[Iterator] > 0 then
begin
{ Read field value }
FieldValue := ReadFieldString(Source, FieldSize[Iterator]);
{ Set corresponding tag field if supported }
case Iterator of
1: Tag[1] := FieldValue;
2: Tag[2] := FieldValue;
4: Tag[7] := FieldValue;
end;
end;
end;
{ --------------------------------------------------------------------------- }
procedure ReadTagExtended(const Source: TStream; var Tag: TagData);
var
Iterator1, Iterator2, FieldCount, DataSize, DataType: Word;
FieldName, FieldValue: WideString;
begin
{ Read extended tag data }
Source.ReadBuffer(FieldCount, SizeOf(FieldCount));
for Iterator1 := 1 to FieldCount do
begin
{ Read field name }
Source.ReadBuffer(DataSize, SizeOf(DataSize));
FieldName := ReadFieldString(Source, DataSize);
{ Read value data type }
Source.ReadBuffer(DataType, SizeOf(DataType));
{ Read field value only if string }
if DataType = 0 then
begin
Source.ReadBuffer(DataSize, SizeOf(DataSize));
FieldValue := ReadFieldString(Source, DataSize);
end
else
Source.Seek(DataSize, soFromCurrent);
{ Set corresponding tag field if supported }
for Iterator2 := 1 to WMA_FIELD_COUNT do
if UpperCase(Trim(FieldName)) = WMA_FIELD_NAME[Iterator2] then
Tag[Iterator2] := FieldValue;
end;
end;
{ --------------------------------------------------------------------------- }
procedure ReadObject(const ID: ObjectID; Source: TStream; var Data: FileData);
begin
{ Read data from header object if supported }
if ID = WMA_FILE_PROPERTIES_ID then
begin
{ Read file properties }
Source.Seek(80, soFromCurrent);
Source.ReadBuffer(Data.MaxBitRate, SizeOf(Data.MaxBitRate));
end;
if ID = WMA_STREAM_PROPERTIES_ID then
begin
{ Read stream properties }
Source.Seek(60, soFromCurrent);
Source.ReadBuffer(Data.Channels, SizeOf(Data.Channels));
Source.ReadBuffer(Data.SampleRate, SizeOf(Data.SampleRate));
Source.ReadBuffer(Data.ByteRate, SizeOf(Data.ByteRate));
end;
if ID = WMA_CONTENT_DESCRIPTION_ID then
begin
{ Read standard tag data }
Source.Seek(4, soFromCurrent);
ReadTagStandard(Source, Data.Tag);
end;
if ID = WMA_EXTENDED_CONTENT_DESCRIPTION_ID then
begin
{ Read extended tag data }
Source.Seek(4, soFromCurrent);
ReadTagExtended(Source, Data.Tag);
end;
end;
{ --------------------------------------------------------------------------- }
function ReadData(const FileName: string; var Data: FileData): Boolean;
var
Source: TFileStream;
ID: ObjectID;
Iterator, ObjectCount, ObjectSize, Position: Integer;
begin
{ Read file data }
try
Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
Data.FileSize := Source.Size;
{ Check for existing header }
Source.ReadBuffer(ID, SizeOf(ID));
if ID = WMA_HEADER_ID then
begin
Source.Seek(8, soFromCurrent);
Source.ReadBuffer(ObjectCount, SizeOf(ObjectCount));
Source.Seek(2, soFromCurrent);
{ Read all objects in header and get needed data }
for Iterator := 1 to ObjectCount do
begin
Position := Source.Position;
Source.ReadBuffer(ID, SizeOf(ID));
Source.ReadBuffer(ObjectSize, SizeOf(ObjectSize));
ReadObject(ID, Source, Data);
Source.Seek(Position + ObjectSize, soFromBeginning);
end;
end;
Source.Free;
Result := true;
except
Result := false;
end;
end;
{ --------------------------------------------------------------------------- }
function IsValid(const Data: FileData): Boolean;
begin
{ Check for data validity }
Result :=
(Data.MaxBitRate > 0) and (Data.MaxBitRate < 320000) and
((Data.Channels = WMA_CM_MONO) or (Data.Channels = WMA_CM_STEREO)) and
(Data.SampleRate >= 8000) and (Data.SampleRate <= 96000) and
(Data.ByteRate > 0) and (Data.ByteRate < 40000);
end;
{ --------------------------------------------------------------------------- }
function ExtractTrack(const TrackString: WideString): Integer;
var
Value, Code: Integer;
begin
{ Extract track from string }
Result := 0;
Val(TrackString, Value, Code);
if Code = 0 then Result := Value;
end;
{ ********************** Private functions & procedures ********************* }
procedure TWMAfile.FResetData;
begin
{ Reset variables }
FValid := false;
FFileSize := 0;
FChannelModeID := WMA_CM_UNKNOWN;
FSampleRate := 0;
FDuration := 0;
FBitRate := 0;
FTitle := '';
FArtist := '';
FAlbum := '';
FTrack := 0;
FYear := '';
FGenre := '';
FComment := '';
end;
{ --------------------------------------------------------------------------- }
function TWMAfile.FGetChannelMode: string;
begin
{ Get channel mode name }
Result := WMA_MODE[FChannelModeID];
end;
{ ********************** Public functions & procedures ********************** }
constructor TWMAfile.Create;
begin
{ Create object }
inherited;
FResetData;
end;
{ --------------------------------------------------------------------------- }
function TWMAfile.ReadFromFile(const FileName: string): Boolean;
var
Data: FileData;
begin
{ Reset variables and load file data }
FResetData;
FillChar(Data, SizeOf(Data), 0);
Result := ReadData(FileName, Data);
{ Process data if loaded and valid }
if Result and IsValid(Data) then
begin
FValid := true;
{ Fill properties with loaded data }
FFileSize := Data.FileSize;
FChannelModeID := Data.Channels;
FSampleRate := Data.SampleRate;
FDuration := Data.FileSize * 8 / Data.MaxBitRate;
FBitRate := Data.ByteRate * 8 div 1000;
FTitle := Trim(Data.Tag[1]);
FArtist := Trim(Data.Tag[2]);
FAlbum := Trim(Data.Tag[3]);
FTrack := ExtractTrack(Trim(Data.Tag[4]));
FYear := Trim(Data.Tag[5]);
FGenre := Trim(Data.Tag[6]);
FComment := Trim(Data.Tag[7]);
end;
end;
end.