interface
uses
Windows;
type
//Ver 1
TMyID3v1 =
record
Ver: Byte;
//ID3v1タグがあれば1、なければ0
Major: Byte;
//1.0なら0、1.1なら1
Title:
String[30];
Author:
String[30];
Album:
String[30];
Year:
String[4];
Comment:
String[30];
Track: Byte;
Genre: Byte;
end;
type
//Ver 2
{タグヘッダー}
//制限情報 %ppqrrstt
TMyID3HeaderExtendedLimit =
record //書き込み時のみに必要な情報
iTagSize: Byte;
//タグサイズの制限
{
00 128 フレーム以下でトータルタグサイズ 1 MB 以下
01 64 フレーム以下でトータルタグサイズ 128 KB 以下
10 32 フレーム以下でトータルタグサイズ 40 KB 以下
11 32 フレーム以下でトータルタグサイズ 4 KB 以下
}
bCharCode: Boolean;
//文字コードの制限
{
0 制限なし
1 ISO-8859-1 [ISO-8859-1] もしくはUTF-8 [UTF-8]のみ使用可
}
iTextSize: WORD;
//テキストフィールドサイズの制限 実際の制限数をセット
{
00 制限なし
01 文字列は 1024 文字以内
10 文字列は 128 文字以内
11 文字列は 30 文字以内
これらの文字制限では、文字のバイト数ではなく文字数に言及している点
に注意すること。最大バイト数は文字コードによって変動する。ひとつの
テキストフレーム中に、複数の文字列が入っている場合は、それらの合計
値に対する制限となる。
}
bPicFormat: Boolean;
//画像フォーマットの制限
{
0 制限なし
1 画像はPNG [PNG] もしくは JPEG [JFIF] フォーマットのみ
}
iPicSize: Byte;
//画像サイズの制限
{
00 制限なし
01 全ての画像は256x256 ピクセルかそれ以下
10 全ての画像は 64x64 ピクセルかそれ以下
11 特にサイズ指定の無い画像は全て 64x64 ピクセル固定
}
end;
//拡張ヘッダー
TMyID3HeaderExtended =
record //2.3〜
iSize: DWORD;
iPaddingSize: DWORD;
//2.3のみ
bUpdate: Boolean;
//2.4
bCrc: Boolean;
iCrcData: DWORD;
bLimit: Boolean;
//2.4
rLimit: TMyID3HeaderExtendedLimit;
//2.4
end;
//ヘッダーフラグ
TMyID3HeaderFlag =
record
bUnsync: Boolean;
bCompress: Boolean;
//2.2のみ 圧縮されているか
bExtended: Boolean;
//2.3〜
bExperimental: Boolean;
//2.3〜
bFooter: Boolean;
//2.4のみ
end;
//ヘッダー
TMyID3Header =
record
iSize: DWORD;
rFlag: TMyID3HeaderFlag;
rExtended: TMyID3HeaderExtended;
end;
{フレーム}
//フレームのステータスフラグ
TMyID3FrameStatusFlag =
record
bDelFromTag: Boolean;
//このフレームを認識できずタグを変更したときにこのフレームを削除すべきか
bDelFromFile: Boolean;
//このフレームを認識できずタグ以外のデータを変更したときにこのフレームを削除すべきか
bReadOnly: Boolean;
//読み込み専用か
bUnknown: Boolean;
//未知のフラグが立っているか
end;
//フレームのフォーマットフラグ
TMyID3FrameFormatFlag =
record
bGroup: Boolean;
//グループ化されているか
iGroupID: Byte;
// グループ識別子
bCompress: Boolean;
//圧縮されているか
iDecompSize: DWORD;
// 圧縮伸張後のサイズ
bEncrypt: Boolean;
//暗号化されているか
iEncryptMethod: Byte;
// 暗号化の方法
bUnsync: Boolean;
//非同期化されているか
bDataLen: Boolean;
//フラグが何も指定されていない場合のフレームサイズを記したデータが付加されているか
iDataLen: DWORD;
// フラグが何も指定されていない場合のフレームサイズ
bUnknown: Boolean;
//未知のフラグが立っているか
end;
//フレームヘッダー
TMyID3Frame =
record
sFrameID:
String[4];
//フレームを判別するための'0'..'9','A'..'Z'で構成されている識別子
iHeaderSize: Byte;
//ヘッダーのサイズ 2.2とそれ以降(2.3〜)で違う
iSize: DWORD;
//フレームサイズ
iReadSize: DWORD;
//正味のフレームサイズ
rStatusFlag: TMyID3FrameStatusFlag;
rFormatFlag: TMyID3FrameFormatFlag;
end;
TMyTagID3 =
class(TObject)
private
F_sFileName:
WideString;
//Version
F_sVersion:
WideString;
F_iVer,
F_iMajor,
F_iRev: Byte;
//Tag
F_sTrack,
//TRCK トラックの番号/セット中の位置
F_sSeries,
//TIT1 内容の属するグループの説明
F_sTitle,
//TIT2 タイトル/曲名/内容の説明
F_sSubTitle,
//TIT3 サブタイトル/説明の追加情報
F_sAlbum,
//TALB アルバム/映画/ショーのタイトル
F_sAuthor,
//TPE1 主な演奏者/ソリスト
F_sBand,
//TPE2 バンド/オーケストラ/伴奏
F_sConductor,
//TPE3 指揮者/演奏者詳細情報
F_sComposer,
//TCOM 作曲者
F_sWriter,
//TEXT 作詞家/文書作成者
F_sTranslator,
//TPE4 翻訳者, リミックス, その他の修正
F_sPublisher,
//TPUB 出版社, レーベル
F_sGenre,
//TCON ジャンル
F_sLength,
//TLEN 長さ
F_sYear,
//TYER 年
F_sDate,
//TDAT 日付
F_sLylic,
//USLT 非同期 歌詞/文書のコピー
F_sOriginalAlbum,
//TOAL オリジナルのアルバム/映画/ショーのタイトル
F_sOriginalWriter,
//TOLY オリジナルの作詞家/文書作成者
F_sOriginalAuthor,
//TOPE オリジナルアーティスト/演奏者
F_sOriginalRelease,
//TORY オリジナルのリリース年
F_sComment:
WideString;
//COMM
F_rHeader: TMyID3Header;
F_rFrame: TMyID3Frame;
function F_DecodeUnsyncData(pData: PAnsiChar; iLen: DWORD): DWORD;
function F_GetSyncsafeSize(pData: PAnsiChar; iCount: Byte): Longword;
function F_GetSize(pData: PAnsiChar; iCount: Byte): Longword;
procedure F_ReadHeader;
function F_GetText (pData: PAnsiChar; iCount: DWORD):
WideString;
function F_GetFullText(pData: PAnsiChar; iCount: DWORD):
WideString;
// function F_GetUserText(pData: PAnsiChar; iCount: DWORD): WideString;
procedure F_ReadFrame(pData: PAnsiChar);
procedure F_SetFileName(sFileName:
WideString);
procedure F_Clear;
procedure F_ReadV1;
procedure F_ReadV2;
// procedure F_WriteV1;
public
constructor Create(sFile:
WideString);
property FileName:
WideString read F_sFileName
write F_SetFileName;
property Version:
WideString read F_sVersion;
property Major: Byte
read F_iMajor;
property Ver: Byte
read F_iVer;
property Rev: Byte
read F_iRev;
property Track:
WideString read F_sTrack;
property Series:
WideString read F_sSeries;
property Title:
WideString read F_sTitle;
property SubTitle:
WideString read F_sSubTitle;
property Album:
WideString read F_sAlbum;
property Author:
WideString read F_sAuthor;
property Band:
WideString read F_sBand;
//TPE2 バンド/オーケストラ/伴奏
property Conductor:
WideString read F_sConductor;
//TPE3 指揮者/演奏者詳細情報
property Composer:
WideString read F_sComposer;
//TCOM 作曲者
property Writer:
WideString read F_sWriter;
//TEXT 作詞家/文書作成者
property Translator:
WideString read F_sTranslator;
//TPE4 翻訳者, リミックス, その他の修正
property Publisher:
WideString read F_sPublisher;
//TPUB 出版社, レーベル
property Genre:
WideString read F_sGenre;
property Length:
WideString read F_sLength;
//TLEN 長さ
property Year:
WideString read F_sYear;
//TYER 年
property Date:
WideString read F_sDate;
//TDAT 日付
property Lylic:
WideString read F_sLylic;
//USLT 非同期 歌詞/文書のコピー
property OriginalAlbum:
WideString read F_sOriginalAlbum;
//TOAL オリジナルのアルバム/映画/ショーのタイトル
property OriginalWriter:
WideString read F_sOriginalWriter;
//TOLY オリジナルの作詞家/文書作成者
property OriginalAuthor:
WideString read F_sOriginalAuthor;
//TOPE オリジナルアーティスト/演奏者
property OriginalRelease:
WideString read F_sOriginalRelease;
//TORY オリジナルのリリース年
property Comment:
WideString read F_sComment;
end;
//------------------------------------------------------------------------------
implementation
uses
SysUtils,
Forms;
//------------------------------------------------------------------------------
//汎用ルーチン
function gfniFileRead(
var pData: PAnsiChar; sFile:
WideString; iOffset, iByte: DWORD): DWORD;
//ファイルからiByteバイトを読み込んでpDataにセットし、読み込んだバイト数を返す
//iOffsetはファイルの先頭からの0ベースのオフセット
var
lh_Handle: THandle;
lr_Overlapped: TOverlapped;
li_Size: DWORD;
begin
lh_handle := CreateFileW(
PWideChar(sFile),
//ファイル名
GENERIC_READ,
//アクセスモード
FILE_SHARE_READ,
//共有モード
nil,
//セキュリティ
OPEN_EXISTING,
//作成方法
FILE_ATTRIBUTE_NORMAL,
//ファイル属性
0
//テンプレート
);
try
Result := 0;
if (lh_Handle <> 0)
then begin
FillChar(lr_Overlapped, SizeOf(TOverlapped), 0);
lr_Overlapped.Offset := iOffset;
li_Size := GetFileSize(lh_Handle,
nil);
//4GB以上のファイルはNG
if (iOffset < li_Size)
then begin
//オフセット分を引く
Dec(li_Size, iOffset);
if (iByte < li_Size)
then begin
li_Size := iByte;
end;
//呼び出し側でpDataのメモリーを開放をする必要あり
pData := AllocMem(li_Size + 2);
//WideString(pData)としても問題ないように
ReadFile(lh_Handle, pData^, li_Size, Result,
@lr_Overlapped);
end;
end;
finally
CloseHandle(lh_Handle);
end;
end;
function gfniFileEndRead(
var pData: PAnsiChar; sFile:
WideString; iByte: DWORD): DWORD;
//ファイルの後ろからiByteバイトを読み込んでpDataにセットし、読み込んだバイト数を返す
var
lh_Handle: THandle;
lr_Overlapped: TOverlapped;
li_Offset: DWORD;
begin
lh_handle := CreateFileW(
PWideChar(sFile),
//ファイル名
GENERIC_READ,
//アクセスモード
FILE_SHARE_READ,
//共有モード
nil,
//セキュリティ
OPEN_EXISTING,
//作成方法
FILE_ATTRIBUTE_NORMAL,
//ファイル属性
0);
//テンプレート
try
Result := 0;
if (lh_Handle <> 0)
then begin
FillChar(lr_Overlapped, SizeOf(TOverlapped), 0);
li_Offset := GetFileSize(lh_Handle, nil) - iByte;
//4GB以上のファイルはNG
if (li_Offset > 0)
then begin
lr_Overlapped.Offset := iOffset;
//呼び出し側でpDataのメモリーを開放をする必要あり
pData := AllocMem(iByte + 2);
//WideString(pData)としても問題ないように
ReadFile(lh_Handle, pData^, iByte, Result,
@lr_Overlapped);
end;
end;
finally
CloseHandle(lh_Handle);
end;
end;
function gfnsByteCopy(pStr: PAnsiChar; iIndex, iCount: DWORD): AnsiString;
//pStr[iIndex]からiCount個の文字列をコピーして返す
var
i: DWORD;
begin
SetLength(Result, iCount);
for i := 1
to iCount
do begin
Result[i] := pStr[iIndex + i -1];
end;
end;
type
TMyCharCode = (cdShift_JIS, cdUnicodeLE, cdUnicodeBE, cdUTF_16LE,
cdUTF_16BE, cdUTF_8, cdUTF_8N);
function gfnsWStrBCopy(pStr: PAnsiChar; iIndex, iCount: DWORD; cdCode: TMyCharCode):
WideString;
//pStr[iIndex]からiCountバイトの文字列をコピーしてWideStringにして返す
var
i: DWORD;
ls_Temp: AnsiString;
lp_Buff: PAnsiChar;
begin
if (pStr[iIndex] = #$EF)
and (pStr[iIndex +1] = #$BB)
and (pStr[iIndex +2] = #$BF)
then begin
//UTF-8 BOMあり
cdCode := cdUTF_8;
Inc(iIndex, 3);
Dec(iCount, 3);
end else
if (pStr[iIndex] = #$FF)
and (pStr[iIndex +1] = #$FE)
then begin
//UTF-16 BOMありのリトルエンディアン
cdCode := cdUnicodeLE;
Inc(iIndex, 2);
Dec(iCount, 2);
end else
if (pStr[iIndex] = #$FE)
and (pStr[iIndex +1] = #$FF)
then begin
//UTF-16 BOMありのビッグエンディアン
cdCode := cdUnicodeBE;
Inc(iIndex, 2);
Dec(iCount, 2);
end;
if (cdCode = cdUTF_16BE)
or (cdCode = cdUnicodeBE)
then begin
//UTF-16 ビッグエンディアン
lp_Buff := AllocMem(iCount + 2);
try
for i := 0
to iCount -1
do begin
if (i
mod 2 = 0)
then begin
lp_Buff[i] := pStr[iIndex + i +1];
lp_Buff[i + 1] := pStr[iIndex + i];
end;
end;
Result :=
WideString(PWideChar(lp_Buff));
finally
FreeMem(lp_Buff);
end;
end else begin
ls_Temp := gfnsByteCopy(pStr, iIndex, iCount);
if (cdCode = cdShift_JIS)
then begin
//Shift-JIS
Result := AnsiString(ls_Temp);
end else if (cdCode = cdUTF_8)
or (cdCode = cdUTF_8N)
then begin
//UTF-8
Result := UTF8Decode(ls_Temp);
end else if (cdCode = cdUnicodeLE)
or (cdCode = cdUTF_16LE)
then begin
//UTF-16 リトルエンディアン
Result :=
WideString(PWideChar(PChar(ls_Temp + #0#0)));
end;
end;
end;
//汎用ルーチン終わり
//==============================================================================
const
lciTAG_HEADERSIZE = 10;
//------------------------------------------------------------------------------
constructor TMyTagID3.Create(sFile:
WideString);
begin
inherited Create;
F_SetFileName(sFile);
end;
procedure TMyTagID3.
F_Clear;
begin
F_sVersion := '';
F_iVer := 0;
F_iMajor := 0;
F_iRev := 0;
F_sTrack := '';
//TRCK トラックの番号/セット中の位置
F_sSeries := '';
//TIT1 内容の属するグループの説明
F_sTitle := '';
//TIT2 タイトル/曲名/内容の説明
F_sSubTitle := '';
//TIT3 サブタイトル/説明の追加情報
F_sAlbum := '';
//TALB アルバム/映画/ショーのタイトル
F_sAuthor := '';
//TPE1 主な演奏者/ソリスト
F_sBand := '';
//TPE2 バンド/オーケストラ/伴奏
F_sConductor := '';
//TPE3 指揮者/演奏者詳細情報
F_sComposer := '';
//TCOM 作曲者
F_sWriter := '';
//TEXT 作詞家/文書作成者
F_sTranslator := '';
//TPE4 翻訳者, リミックス, その他の修正
F_sPublisher := '';
//TPUB 出版社, レーベル
F_sGenre := '';
//TCON ジャンル
F_sLength := '';
//TLEN 長さ
F_sYear := '';
//TYER 年
F_sDate := '';
//TDAT 日付
F_sLylic := '';
//USLT 非同期 歌詞/文書のコピー
F_sOriginalAlbum := '';
//TOAL オリジナルのアルバム/映画/ショーのタイトル
F_sOriginalWriter := '';
//TOLY オリジナルの作詞家/文書作成者
F_sOriginalAuthor := '';
//TOPE オリジナルアーティスト/演奏者
F_sOriginalRelease := '';
//TORY オリジナルのリリース年
F_sComment := '';
//COMM コメント
end;
function TMyTagID3.F_DecodeUnsyncData(pData: PAnsiChar; iLen: DWORD): DWORD;
{
非同期化処理されたデータを元に戻し、元のサイズを返す
ヘッダーのフラグで非同期フラグが立っている場合、ヘッダーのデータ中に同期信号と同
じパターンが現れるのを避けるための処理が行われているのでそれを元に戻す。
回避処理の内容は、同期信号のパターンが2進表記で
%11111111 111xxxxx
となるので xFF の後に x00 を挿入して
%11111111 00000000 111xxxxx
とする。これで同期信号と同じパターンを回避する。
回避処理の内容は頭からデータを1バイトづつ見ていき xFF に出くわし、その後の1バイト
が xE0 以上または x00 なら x00 を加える。または単純に xFF の後に x00 を加える。
このルーチンではその逆を行うため xFF の次に現れる x00 を取り除く処理を行う。
この結果ヘッダーの実際のサイズは取り除いた x00 の分だけ減る。
}
var
i, li_Shift: DWORD;
lp_Pos: PAnsiChar;
begin
lp_Pos := pData;
li_Shift := 0;
for i := 0
to iLen - (1 + li_Shift)
do begin
if (i > 0)
and (lp_Pos[-1] = #$FF)
and (lp_Pos[0] = #$0)
then begin
Inc(li_Shift);
Inc(lp_Pos);
lp_Pos[-li_Shift] := lp_Pos[0];
end else if (li_Shift > 0)
then begin
lp_Pos[-li_Shift] := lp_Pos[0];
end;
Inc(lp_Pos);
end;
Result := iLen - li_Shift;
end;
function lfniPower(iBase, iExponent: WORD): Longword;
var
i: Integer;
begin
if (iExponent = 0)
then begin
Result := 1;
end else begin
Result := iBase;
for i := 1
to iExponent - 1
do begin
Result := Result * iBase;
end;
end;
end;
function TMyTagID3.
F_GetSyncsafeSize(pData: PAnsiChar; iCount: Byte): Longword;
{同期信号である %11111111 111xxxxx と同じパターンになるのを防ぐために最上位ビット
が常に0であるような表現の数値として返す。
%11111111 は16進ならxFF、10進なら255。
%01111111 は16進ならx7F、10進なら127。
通常1バイトは255(xFF)が最大値となるが上記の場合は127(x7F)が最大値であり128(x80)に
なると桁あがりする。
よってx100は通常なら256だが上記のような場合は128となる。
x10000は通常なら65536(256*256)だが16384(128*128)となる。
}
var
i: Integer;
begin
Result := 0;
for i := 0
to iCount -1
do begin
Inc(Result, Ord(pData[(iCount - i) -1]) *
lfniPower($80, i));
end;
{
↑は
Result :=
Ord(pData[0]) * $200000 + //128 * 128 * 128
Ord(pData[1]) * $4000 + //128 * 128
Ord(pData[2]) * $80 + //128
Ord(pData[3]);
というようなことを汎用的にやっている
}
end;
function TMyTagID3.
F_GetSize(pData: PAnsiChar; iCount: Byte): Longword;
//↑と違い通常の数値として返す。
var
i: Integer;
begin
Result := 0;
for i := 0
to iCount -1
do begin
Inc(Result, Ord(pData[(iCount - i) -1]) *
lfniPower($100, i));
end;
end;
function TMyTagID3.
F_GetText(pData: PAnsiChar; iCount: DWORD):
WideString;
//改行を含まないテキストを返す
var
li_Index, li_Enc: DWORD;
begin
li_Enc := Ord(pData[0]);
li_Index := 1;
//文字エンコードを飛ばす
Dec(iCount);
//文字エンコード(1Byte)分を減らす
if (li_Enc = $0)
then begin
Result :=
gfnsByteCopy(pData, li_Index, iCount);
end else if (li_Enc = $1)
then begin
//BOMありのUTF-16
//とりあえずリトルエンディアンを指定しているが関数内でBOMを自動判定している
Result :=
gfnsWStrBCopy(pData, li_Index, iCount, cdUnicodeLE);
end else if (li_Enc = $2)
then begin
//BOMなしのビッグエンディアン
Result :=
gfnsWStrBCopy(pData, li_Index, iCount, cdUTF_16BE);
end else if (li_Enc = $3)
then begin
//UTF-8
Result := UTF8Decode(
gfnsByteCopy(pData, li_Index, iCount));
end;
end;
function TMyTagID3.
F_GetFullText(pData: PAnsiChar; iCount: DWORD):
WideString;
//USLT,COMMなどの改行を含むテキストを返す
var
i, li_Enc, li_Index: DWORD;
begin
Result := '';
li_Enc := Ord(pData[0]);
li_Index := (1 + 3);
//文字エンコード(1Byte)と言語コード(3Byte)を読み飛ばす
Dec(iCount, (1 + 3));
if (li_Enc = $0)
//ISO-8859-1
or (li_Enc = $3)
//UTF-8
then begin
if (iCount > 0)
then begin
//Content decriptorを読み飛ばす
for i := 0
to iCount -1
do begin
if (pData[li_Index + i] = #0)
then begin
Dec(iCount, (i + 1));
Inc(li_Index, (i + 1));
Break;
end;
end;
Result :=
gfnsByteCopy(pData, li_Index, iCount);
//必要なら説明文を頭に加える
//Result := gfnsByteCopy(pData, 0, li_Index - 2) + #13 + Result;
if (li_Enc = $3)
then begin
//UTF-8
Result := UTF8Decode(Result);
end;
end;
end else begin
//Unicode
//Content decriptorを読み飛ばす
for i := 0
to iCount -1
do begin //iCountは文字数ではなくバイト数
if (i
mod 2 = 1)
//Unicodeなので2バイトごとの処理
and (pData[li_Index + (i -1)] = #0)
//終了文字
and (pData[li_Index + i] = #0)
//2つで終了
then begin
Dec(iCount, (i + 1));
Inc(li_Index, (i + 1));
Break;
end;
end;
if (iCount > 0)
then begin
if (li_Enc = $1)
then begin
//BOMありのUTF-16
Result :=
gfnsWStrBCopy(pData, li_Index, iCount, cdUnicodeLE);
end else begin
//BOMなしのビッグエンディアン
Result :=
gfnsWStrBCopy(pData, li_Index, iCount, cdUTF_16BE);
end;
end;
end;
end;
procedure TMyTagID3.
F_ReadHeader;
var
lp_Buff: PAnsiChar;
li_Flag: Integer;
li_Size: DWORD;
begin
{
ID3v2ヘッダ 10バイト固定
オフセット長さ 内容
0 3 ID3 の識別文字3文字
3 2 バージョン
5 1 フラグ
6 4 サイズ(Syncsafe)
}
FillChar(F_rHeader, SizeOf(F_rHeader), 0);
li_Size :=
gfniFileRead(lp_Buff, F_sFileName, lciTAG_HEADERSIZE);
//10バイト固定
try
if (li_Size = lciTAG_HEADERSIZE)
then begin
if (lp_Buff[0] = 'I')
and (lp_Buff[1] = 'D')
and (lp_Buff[2] = '3')
then begin
F_iVer := 2;
//最初の3バイトが'ID3'ならID3v2
F_iMajor := Ord(lp_Buff[3]);
F_iRev := Ord(lp_Buff[4]);
li_Flag := Ord(lp_Buff[5]);
F_rHeader.iSize := F_GetSyncsafeSize(@lp_Buff[6], 4);
//サイズにはヘッダー自身の10バイトとフッターの10バイトは含まない
F_rHeader.rFlag.bUnsync := ByteBool(li_Flag
and $80);
//7bit 非同期化処理されているか
if (F_iMajor = 2)
then begin
F_rHeader.rFlag.bCompress := ByteBool(li_Flag
and $40);
//6bit 2.2のみ 圧縮
end else if (F_iMajor >= 3)
then begin
F_rHeader.rFlag.bExtended := ByteBool(li_Flag
and $40);
//6bit 2.3〜 拡張ヘッダーがあるか
F_rHeader.rFlag.bExperimental := ByteBool(li_Flag
and $20);
//5bit 2.3〜 テスト用か
if (F_iMajor >= 4)
then begin
F_rHeader.rFlag.bFooter := ByteBool(li_Flag
and $10);
//4bit 2.4 フッターがあるか
end;
end;
end;
end;
finally
FreeMem(lp_Buff);
end;
end;
procedure TMyTagID3.
F_ReadFrame(pData: PAnsiChar);
function lfni_GetSize(pFrameData: PAnsiChar; iByte: Byte): Integer;
begin
Result := F_GetSize(@pFrameData[F_rFrame.iHeaderSize], iByte);
Inc(F_rFrame.iHeaderSize, iByte);
Dec(F_rFrame.iSize, iByte);
end;
begin
FillChar(F_rFrame, SizeOf(TMyID3Frame), 0);
//フレームIDが正しくない場合はフレームヘッダーのサイズは0のまま
if (pData[0]
in ['0'..'9', 'A'..'Z'])
then begin
if (F_iMajor = 2)
then begin
//Ver2.2
F_rFrame.sFrameID :=
gfnsByteCopy(pData, 0, 3);
//ID、サイズとも3バイトでフラグのヘッダーサイズは6バイト
F_rFrame.iHeaderSize := 3 + 3;
F_rFrame.iSize :=
F_GetSize(@pData[3], 3);
F_rFrame.iReadSize := F_rFrame.iSize;
end else if (F_iMajor >= 3)
then begin
//Ver2.3以上
F_rFrame.sFrameID :=
gfnsByteCopy(pData, 0, 4);
//ID、サイズとも4バイト、2バイトのフラグでフラグのヘッダーサイズは10バイト
F_rFrame.iHeaderSize := 4 + 4 + 2;
if (F_iMajor = 3)
then begin
//Ver2.3
F_rFrame.iSize :=
F_GetSize(@pData[4], 4);
F_rFrame.rStatusFlag.bDelFromTag := ByteBool(Ord(pData[8])
and $80);
//タグが変更されたらこのフレームを削除すべきか
F_rFrame.rStatusFlag.bDelFromFile := ByteBool(Ord(pData[8])
and $40);
//本体データの一部が変更されたらこのフレームを削除すべきか(データが完全に置き換えられた場合は関係ない)
F_rFrame.rStatusFlag.bReadOnly := ByteBool(Ord(pData[8])
and $20);
//読み取り専用か
//未知のフラグが立っていたらクリアしない限り変更してはいけない
F_rFrame.rStatusFlag.bUnknown := ByteBool(Ord(pData[8])
and $10)
or ByteBool(Ord(pData[8])
and $08)
or ByteBool(Ord(pData[8])
and $04)
or ByteBool(Ord(pData[8])
and $02)
or ByteBool(Ord(pData[8])
and $01);
//未知のフラグが立っているか
F_rFrame.rFormatFlag.bCompress := ByteBool(Ord(pData[9])
and $80);
//圧縮されているか
F_rFrame.rFormatFlag.bEncrypt := ByteBool(Ord(pData[9])
and $40);
//暗号化されているか
F_rFrame.rFormatFlag.bGroup := ByteBool(Ord(pData[9])
and $20);
//グループ化されているか
F_rFrame.rFormatFlag.bUnknown := ByteBool(Ord(pData[9])
and $10)
//未知のフラグが立っているか
or ByteBool(Ord(pData[9])
and $08)
or ByteBool(Ord(pData[9])
and $04)
or ByteBool(Ord(pData[9])
and $02)
or ByteBool(Ord(pData[9])
and $01);
//以降の順番は変えてはいけない
if (F_rFrame.rFormatFlag.bCompress)
then F_rFrame.rFormatFlag.iDecompSize := lfni_GetSize(pData, 4);
if (F_rFrame.rFormatFlag.bEncrypt)
then F_rFrame.rFormatFlag.iEncryptMethod := lfni_GetSize(pData, 1);
if (F_rFrame.rFormatFlag.bGroup)
then F_rFrame.rFormatFlag.iGroupID := lfni_GetSize(pData, 1);
F_rFrame.iReadSize := F_rFrame.iSize;
end else begin
//Ver2.4以上
F_rFrame.iSize := F_GetSyncsafeSize(@pData[4], 4);
F_rFrame.rStatusFlag.bDelFromTag := ByteBool(Ord(pData[8])
and $40);
//タグ変更でこのフレームを削除するか
F_rFrame.rStatusFlag.bDelFromFile := ByteBool(Ord(pData[8])
and $20);
//本体データの変更でこのフレームを削除するか
F_rFrame.rStatusFlag.bReadOnly := ByteBool(Ord(pData[8])
and $10);
//読み取り専用か
F_rFrame.rStatusFlag.bUnknown := ByteBool(Ord(pData[8])
and $80)
//未知のフラグが立っているか
or ByteBool(Ord(pData[8])
and $08)
or ByteBool(Ord(pData[8])
and $04)
or ByteBool(Ord(pData[8])
and $02)
or ByteBool(Ord(pData[8])
and $01);
F_rFrame.rFormatFlag.bGroup := ByteBool(Ord(pData[9])
and $40);
//グループ化
F_rFrame.rFormatFlag.bCompress := ByteBool(Ord(pData[9])
and $8);
//圧縮(v2.3と違いフレームヘッダの後に圧縮前のサイズは追加されていない)
F_rFrame.rFormatFlag.bEncrypt := ByteBool(Ord(pData[9])
and $4);
//暗号化
F_rFrame.rFormatFlag.bUnsync := ByteBool(Ord(pData[9])
and $2);
//非同期化処理
F_rFrame.rFormatFlag.bDataLen := ByteBool(Ord(pData[9])
and $1);
//データ長指定子
F_rFrame.rFormatFlag.bUnknown := ByteBool(Ord(pData[9])
and $80)
//未知のフラグが立っているか
or ByteBool(Ord(pData[9])
and $20)
or ByteBool(Ord(pData[9])
and $10);
//最初に非同期化処理解除行う
if (F_rFrame.rFormatFlag.bUnsync)
then begin
F_rFrame.iReadSize :=
F_DecodeUnsyncData(@pData[F_rFrame.iHeaderSize], F_rFrame.iSize);
end else begin
F_rFrame.iReadSize := F_rFrame.iSize;
end;
//次に暗号化を解く
if (F_rFrame.rFormatFlag.bEncrypt)
then begin
//暗号化を解く。。のはまぁ無理なのでこのフラグが立っていたら次のフレームに飛ばすようにした方が良いのかも
end;
//最後に圧縮解除
if (F_rFrame.rFormatFlag.bCompress)
then begin
//圧縮解除
end;
//以降の順番は変えてはいけない
if (F_rFrame.rFormatFlag.bGroup)
then F_rFrame.rFormatFlag.iGroupID := lfni_GetSize(pData, 1);
//グループ化
if (F_rFrame.rFormatFlag.bEncrypt)
then F_rFrame.rFormatFlag.iEncryptMethod := lfni_GetSize(pData, 1);
//暗号化
if (F_rFrame.rFormatFlag.bDataLen)
then F_rFrame.rFormatFlag.iDataLen := lfni_GetSize(pData, 4);
//データ長指定子
end;
end;
end;
end;
procedure TMyTagID3.
F_SetFileName(sFileName:
WideString);
begin
F_sFileName := sFileName;
F_Clear;
//ID3v2としてヘッダーを読んでみる
F_ReadHeader;
if (F_iVer = 2)
then begin
//ID3v2
F_iVer := 2;
F_sVersion := WideFormat('%d.%d.%d', [F_iVer, F_iMajor, F_iRev]);
F_ReadV2;
end else begin
//ID3v2ではないのでID3v1として読んでみる
F_ReadV1;
if (F_iVer = 1)
then begin
//ID3v1だった
F_sVersion := WideFormat('%d.%d', [F_iVer, F_iMajor]);
end;
end;
end;
procedure TMyTagID3.
F_ReadV2;
var
lp_Buff: PAnsiChar;
li_Size, li_Offset: DWORD;
li_SeekLen, li_SeekPos, li_SeekCount: DWORD;
ls_Text:
WideString;
begin
if (F_rHeader.iSize > 0)
then begin
li_Size :=
gfniFileRead(lp_Buff, F_sFileName, lciTAG_HEADERSIZE, F_rHeader.iSize);
//10バイト目から読み込む
try
if (li_Size = F_rHeader.iSize)
then begin
li_Offset := 0;
if (F_rHeader.rFlag.bUnsync)
then begin
//タグ全体に非同期化処理を行ってあるので元に戻し、戻した後のサイズを返す
F_rHeader.iSize :=
F_DecodeUnSyncData(lp_Buff, li_Size);
end;
if (F_rHeader.rFlag.bExtended)
then begin
//拡張ヘッダー
F_rHeader.rExtended.iSize :=
F_GetSize(lp_Buff, 4);
if (F_iMajor = 3)
then begin
//2.3
F_rHeader.rExtended.iPaddingSize :=
F_GetSize(@lp_Buff[6], 4);
F_rHeader.rExtended.bCrc := ByteBool(Ord(lp_Buff[4])
and $80);
if (F_rHeader.rExtended.bCrc)
then begin
F_rHeader.rExtended.iCrcData
:=
F_GetSize(@lp_Buff[10], 4);
end;
Dec(F_rHeader.iSize, F_rHeader.rExtended.iPaddingSize);
//パディング分を引いておく
{
拡張ヘッダーサイズ(rExtended.iSize)には拡張ヘッダーサイズを表す4バイトは含まれない。
そのためrExtended.iSizeは6か10のどちらかとなる。
6というのはCRCフラグがオフの場合でパディングサイズを表す4バイトとフラグの2バイト。
10というのはCRCフラグがオンで上記の6バイトにCRCデータの4バイト。
で、結局フレームを読むためのオフセットはヘッダーサイズを表した4バイト + rExtended.iSize となる。
}
li_Offset := 4 + F_rHeader.rExtended.iSize;
end else if (F_iMajor = 4)
then begin
//2.4
//2.4は2.3と違い拡張ヘッダーサイズを表す4バイトも含んだサイズ。
//なのでオフセットは単純にrExtended.iSizeを足すだけ。
li_Offset := F_rHeader.rExtended.iSize;
//拡張ヘッダーの詳細は読み込みには必要ないので割愛
end;
end;
repeat
F_ReadFrame(@lp_Buff[li_Offset]);
Inc(li_Offset, F_rFrame.iHeaderSize);
//暗号化されていたら(復元できないので)処理に入らない
if (F_rFrame.iSize > 0)
and not(F_rFrame.rFormatFlag.bEncrypt)
then begin
if (F_rFrame.sFrameID = 'COMM')
or (F_rFrame.sFrameID = 'COM')
then begin
//コメント
//複数のコメントタグがあったら追加する
F_sComment := F_sComment + #13#13 +
F_GetFullText(@lp_Buff[li_Offset], F_rFrame.iReadSize);
end else if (F_rFrame.sFrameID = 'USLT')
or (F_rFrame.sFrameID = 'ULT')
then begin
//歌詞
//複数の歌詞タグがあったら追加する
F_sLylic := F_sLylic +
#13#13 +
F_GetFullText(@lp_Buff[li_Offset], F_rFrame.iReadSize);
// end else if (F_rFrame.sFrameID = 'TXXX') or (F_rFrame.sFrameID = 'TXX') then begin
// //ユーザー定義テキスト(これのみ複数行テキスト)
// //必要ならコードを書く
// F_sUserText := F_sUserText
+ #13#13 + F_GetUserText(@lp_Buff[li_Offset], F_rFrame.iReadSize);
// end else if (F_rFrame.sFrameID = 'SEEK') then begin
//※要検証
// //タグ分割
// li_SeekLen :=
F_GetSize(@lp_Buff[li_Offset], 4); //分割タグまでの距離
// li_SeekCount := F_rHeader.iSize
- li_Offset - F_rFrame.iSize; //分割の残りのバイト数
// li_SeekPos :=
F_rHeader.iSize - li_SeekCount + li_SeekLen; //分割タグのインデックス
//
// //until〜 の条件に合致するように調整
// //ヘッダーのサイズから今まで読み込んだ分と、このSEEKフレーム分を差し引く。
// Dec(F_rHeader.iSize,
(li_Offset + F_rFrame.iSize));
// li_Offset := 0;
//
// //残りを読み込みなおす
// FreeMem(lp_Buff);
// gfniFileRead(lp_Buff, li_SeekPos, li_SeekCount);
//
// Continue;
end else if (F_rFrame.sFrameID[1] = 'T')
then begin
//改行無しのテキスト
ls_Text :=
F_GetText(@lp_Buff[li_Offset], F_rFrame.iReadSize);
if (F_rFrame.sFrameID = 'TRCK')
or (F_rFrame.sFrameID = 'TRK')
then begin
//トラックの番号/セット中の位置
F_sTrack := ls_Text;
end else if (F_rFrame.sFrameID = 'TIT1')
or (F_rFrame.sFrameID = 'TT1')
then begin
//内容の属するグループの説明
F_sSeries := ls_Text;
end else if (F_rFrame.sFrameID = 'TIT2')
or (F_rFrame.sFrameID = 'TT2')
then begin
//タイトル/曲名/内容の説明
F_sTitle := ls_Text;
end else if (F_rFrame.sFrameID = 'TIT3')
or (F_rFrame.sFrameID = 'TT3')
then begin
//サブタイトル/説明の追加情報
F_sSubTitle := ls_Text;
end else if (F_rFrame.sFrameID = 'TALB')
or (F_rFrame.sFrameID = 'TAL')
then begin
//アルバム/映画/ショーのタイトル
F_sAlbum := ls_Text;
end else if (F_rFrame.sFrameID = 'TPE1')
or (F_rFrame.sFrameID = 'TP1')
then begin
//主な演奏者/ソリスト
F_sAuthor := ls_Text;
end else if (F_rFrame.sFrameID = 'TPE2')
or (F_rFrame.sFrameID = 'TP2')
then begin
//バンド/オーケストラ/伴奏
F_sBand := ls_Text;
end else if (F_rFrame.sFrameID = 'TPE3')
or (F_rFrame.sFrameID = 'TP3')
then begin
//指揮者/演奏者詳細情報
F_sConductor := ls_Text;
end else if (F_rFrame.sFrameID = 'TCOM')
or (F_rFrame.sFrameID = 'TCM')
then begin
//作曲者
F_sComposer := ls_Text;
end else if (F_rFrame.sFrameID = 'TEXT')
or (F_rFrame.sFrameID = 'TXT')
then begin
//作詞家/文書作成者
F_sWriter := ls_Text;
end else if (F_rFrame.sFrameID = 'TPE4')
or (F_rFrame.sFrameID = 'TP4')
then begin
//翻訳者, リミックス, その他の修正
F_sTranslator := ls_Text;
end else if (F_rFrame.sFrameID = 'TPUB')
or (F_rFrame.sFrameID = 'TPB')
then begin
//出版社, レーベル
F_sPublisher := ls_Text;
end else if (F_rFrame.sFrameID = 'TCON')
or (F_rFrame.sFrameID = 'TCN')
then begin
//ジャンル
F_sGenre := ls_Text;
end else if (F_rFrame.sFrameID = 'TLEN')
or (F_rFrame.sFrameID = 'TLE')
then begin
//長さ
F_sLength := ls_Text;
end else if (F_rFrame.sFrameID = 'TYER')
or (F_rFrame.sFrameID = 'TYE')
then begin
//年
F_sYear := ls_Text;
end else if (F_rFrame.sFrameID = 'TDAT')
or (F_rFrame.sFrameID = 'TDA')
then begin
//日付
F_sDate := ls_Text;
end else if (F_rFrame.sFrameID = 'TOAL')
or (F_rFrame.sFrameID = 'TOT')
then begin
//オリジナルのアルバム/映画/ショーのタイトル
F_sOriginalAlbum := ls_Text;
end else if (F_rFrame.sFrameID = 'TOLY')
or (F_rFrame.sFrameID = 'TOL')
then begin
//オリジナルの作詞家/文書作成者
F_sOriginalWriter := ls_Text;
end else if (F_rFrame.sFrameID = 'TOPE')
or (F_rFrame.sFrameID = 'TOA')
then begin
//オリジナルアーティスト/演奏者
F_sOriginalAuthor := ls_Text;
end else if (F_rFrame.sFrameID = 'TORY')
or (F_rFrame.sFrameID = 'TOR')
then begin
//オリジナルのリリース年
F_sOriginalRelease := ls_Text;
end;
end;
//F_rFrame.iSizeにはフレーム個別の非同期化解除前のサイズが入っている。
//次のフレームの開始位置を得るにはF_rFrame.rFlag.iUnsyncLengthではなくこっちを用いる
Inc(li_Offset, F_rFrame.iSize);
end;
Application.ProcessMessages;
until (li_Offset >= F_rHeader.iSize)
or (F_rFrame.iHeaderSize = 0);
end;
finally
FreeMem(lp_Buff);
end;
end;
end;
procedure TMyTagID3.
F_ReadV1;
{2008-04-02:
ID3タグVer1を取得
http://ja.wikipedia.org/wiki/ID3タグ
}
var
lp_Buff: PAnsiChar;
li_Size: DWORD;
begin
li_Size :=
gfniFileEndRead(lp_Buff, F_sFileName, 128);
try
if (li_Size = 128)
then begin
if (lp_Buff[0] = 'T')
and (lp_Buff[1] = 'A')
and (lp_Buff[2] = 'G')
then begin
F_iVer := 1;
F_sTitle :=
gfnsByteCopy(lp_Buff, 3, 30);
//曲名
F_sAuthor :=
gfnsByteCopy(lp_Buff, 33, 30);
//アーティスト
F_sAlbum :=
gfnsByteCopy(lp_Buff, 63, 30);
//アルバム
F_sYear :=
gfnsByteCopy(lp_Buff, 93, 4);
//日付
F_sComment :=
gfnsByteCopy(lp_Buff, 97, 30);
//コメント
F_sGenre := IntToStr(Ord(lp_Buff[127]));
//ジャンル
if (lp_Buff[125] = #0)
and (lp_Buff[126] <> #0)
then begin
F_sTrack := IntToStr(Ord(lp_Buff[126]));
//トラック
F_iMajor := 1;
end;
end;
end;
finally
FreeMem(lp_Buff);
end;
end;