必要なものを作っていく。
16進数と2進数の10進数変換
マイナスや小数点以下の数値、エラーを考慮せずに作ったので取扱注意。'16進数を2進数に変換 Function HexToBin(sHex As String) As String Dim rtnStr As String: rtnStr = "" Dim i As Long, tmp As String For i = Len(sHex) To 1 Step -1 tmp = WorksheetFunction.Hex2Bin(Mid(sHex, i, 1)) tmp = Right("000" & tmp, 4) '桁埋め rtnStr = tmp & rtnStr Next HexToBin = rtnStr End Function
'2進数を10進数に変換 Function BinToDec(sBin As String) As Long Dim tmp As String tmp = StrReverse(sBin) '文字を逆にする Dim iSum As Long: iSum = 0 Dim i As Long For i = 1 To Len(tmp) If Mid(tmp, i, 1) = "1" Then iSum = iSum + 2 ^ (i - 1) End If Next BinToDec = iSum End Function
'16進数を10進数に変換 Function HexToDec(sHex As String) As Long HexToDec = BinToDec(HexToBin(sHex)) End Function
Syncsafe Integer計算用。
ID3v2系のヘッダのサイズを調べるために使う。v2.3ではヘッダのみ。他にも使われるかもしれない。
'Syncsafe Integerを計算。16進数を10進数で返却。 '最上位8ビット目をスキップして連結 Function HexToSyncsafeInteger(sHex As String) As Long Dim rtnStr As String: rtnStr = "" Dim tmp As String tmp = HexToBin(sHex) '16進数を2進数に変換 tmp = StrReverse(tmp) '文字を逆にする '最上位8ビット目をスキップして連結 Dim i As Long For i = 1 To Len(tmp) If i Mod 8 <> 0 Then rtnStr = Mid(tmp, i, 1) & rtnStr Next HexToSyncsafeInteger = BinToDec(rtnStr) End Function
上記で作ったもののテスト
'引数は16進数そのまま 'アスキーコードの変換テスト Debug.Print Chr(HexToDec("49")) '49 44 33→ID3 'ヘッダサイズ取得テスト(Syncsafe Integer) Debug.Print HexToSyncsafeInteger("00001000") '2048バイト 'フレームサイズ取得テスト Debug.Print HexToDec("00000019") '25バイト 'UTF-16の変換テスト Debug.Print ChrW(HexToDec("9F8D")) '龍 'Shift-JISの変換テスト Debug.Print Chr(HexToDec("97B4")) '龍
あとはmp3のバイナリ構造に合わせて読み出すように組めばOK。
全体の構造ざっくり
ID3v1系はファイルの末尾にタグ情報がある。当記事では取り扱わない。ID3v2系はファイルの先頭にタグ情報がある。「49 44 33」と書かれている。これをアスキーコードで表すと「ID3」である。
ID3v2のヘッダ10バイト+ヘッダサイズ(Syncsafe Integer)の次にmp3の曲本体がある。
mp3の曲本体はFFFBから始まる。
使われていない部分は0で埋められている。
id3v2.3、id3v2.4のヘッダー
先頭10バイト
場所 | 長さ | 内容 | 備考 |
---|---|---|---|
1~3 | 3 | 識別子 | 「49 44 33」が入っている。アスキーコードで「ID3」。 |
4~5 | 2 | バージョン | 「02 00」「03 00」「04 00」のいずれかが入っている。 それぞれv2.2、v2.3、v2.4を表す。 |
6 | 1 | フラグ | 非同期化、拡張ヘッダ、実験中などを表す。基本は「00」。 本記事では「00」以外のパターンは謎とする。 |
7~10 | 4 | サイズ | フレーム全体のサイズ。Syncsafe Integer。 |
id3v2.3、id3v2.4のフレーム
ヘッダーの直後、11バイト目以降
場所 | 長さ | 内容 | 備考 |
---|---|---|---|
1~4 | 4 | フレームID | 「TIT2」「TPE1」「TALB」などが入る。 アスキーコードで書かれる。 |
5~8 | 4 | フレームサイズ | データ本体のサイズが入っている。 フレームサイズ10バイトは含まれない。 |
9~10 | 2 | フラグ | 謎。 |
11~ | 1~可変 | データ本体 | 文字コード、BOM、文字の順で書かれる。 |
主なフレームID(v2.3)
フレームID | 内容 |
---|---|
TIT2 | タイトル |
TPE1 | アーティスト |
TALB | アルバム |
TCON | ジャンル |
TRCK | トラックNo |
TYER | リリース年 |
COMM | コメント |
APIC | アートワーク |
文字コード
フレームデータの1バイト目に文字コードが書かれる。値 | 文字コード | 備考 |
---|---|---|
00 | ISO-8859-1 | |
01 | UTF-16 | BOM有 |
02 | UTF-16 | BOM無(v2.4) |
03 | UTF-8 | (v2.4) |
00が指定されていてもShift-JISが入っていることがあるので注意。
※1バイト目からテキストデータが始まる例を確認したが、フレームIDが「T〇〇〇」とは違った。特殊な例なのだろうか。
BOM(Byte Order Mark)
文字の読み取り順を指定するもので文字データの頭に、UTF-16は「FFFE」「FEFF」のどちらか、UTF-8は「EF BB BF」が書かれる。FFFEはリトルエンディアン、FEFFはビッグエンディアン。
v2.3はUTF-16で必ずBOMが付くので、それで判断する。多分リトルエンディアン。
'UTF-16の変換テスト Debug.Print ChrW(HexToDec("9F8D")) '龍
この例ではv2.3に「8D9F」と書かれており、前後を入れ替えて「9F8D」にした。リトルエンディアンは交互に書かれている。
ビッグエンディアンはそのままの順番で書かれる。
UTF-8のBOMは「EF BB BF」だが、そもそもUTF-8はBEかLEかの区別が無い。BOMの有無に関係無く書かれている順に処理する。
文字列の終了フラグ
UTF-16の文字の終わりは「00 00」。Shift-JISの文字の終わりは「00」。
UTF-8の文字の終わりは「00」。
最終バイト以外で終了フラグが出る場合は以降を無視するらしい。フレームサイズに注意して読む。
UTF-8
16進数を2進数にして、最初の1バイト目を見る。最初のビットが0なら1バイト文字。以下7ビットが有効。
最初のビットが110なら2バイト文字。以下5ビット+次の1バイトの下6ビットが有効。
最初のビットが1110なら3バイト文字。以下4ビット+次と次の1バイトの下6(略)。
最初のビットが11110なら4バイト文字。以下3ビット+次と次と次の(略)
最初のビットが10なら、最初のビットではない。
上記以外なら、UTF-8ではない。
※表のx部分が有効ビット
nバイト文字 | 1バイト | 2バイト | 3バイト | 4バイト |
---|---|---|---|---|
1 | 0xxxxxxx | |||
2 | 110xxxxx | 10xxxxxx | ||
3 | 1110xxxx | 10xxxxxx | 10xxxxxx | |
4 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
有効ビットを全て繋げて16進数に変換すればユニコード番号になる。
10進数に変換してChrW変換をかければ文字が出る。
例)E9BE8D
→11101001 10111110 10001101
→1001 111110 001101→1001 1111 1000 1101
→9F8D(10進数で40845)
→龍
以下、UTF-8変換テストコード
'テスト。「龍」が出る予定。 Private Sub test_getStringUTF8() Dim testString(1 To 3) As String testString(1) = "E9" testString(2) = "BE" testString(3) = "8D" Dim iStart As Long: iStart = 1 Dim iLen As Long: iLen = 3 Dim sFrameVal As String: sFrameVal = "" Dim tmp As String, tmpBin As String Dim i As Long, k As Long, iByte As Long Dim iEnd As Long: iEnd = (iStart + iLen - 1) For i = iStart To iEnd If testString(i) = "00" Then Exit For '終了フラグ iByte = NumByteUTF8(testString(i)) If iByte = 0 Then Exit For Select Case iByte Case 1 tmpBin = Right(HexToBin(testString(i)), 7) Case 2 To 6 tmpBin = Right(HexToBin(testString(i)), 7 - iByte) For k = 1 To iByte - 1 tmp = Right(HexToBin(testString(i + k)), 6) tmpBin = tmpBin + tmp Next Case Else '到達不可 End Select tmp = ChrW(BinToDec(tmpBin)) sFrameVal = sFrameVal & tmp i = i + iByte - 1 Next Debug.Print sFrameVal '龍 End Sub 'UTF-8のバイト数を判定。 Private Function NumByteUTF8(sHex1 As String) As Integer Dim rtnInt As Integer Dim sBin As String: sBin = HexToBin(sHex1) Select Case True Case Left(sBin, 2) = "10" rtnInt = 0 Debug.Print "先頭バイトではありません(" & sHex1 & ")" Case Left(sBin, 1) = "0" rtnInt = 1 Case Left(sBin, 3) = "110" rtnInt = 2 Case Left(sBin, 4) = "1110" rtnInt = 3 Case Left(sBin, 5) = "11110" rtnInt = 4 Case Left(sBin, 6) = "111110" rtnInt = 5 Case Left(sBin, 7) = "1111110" rtnInt = 6 Case Else rtnInt = 0 Debug.Print "判別不能(" & sHex1 & ")" End Select NumByteUTF8 = rtnInt End Function
Shift-JIS
1~2バイト文字。バイナリから復元するには1~2バイト文字の判別が必要。
'Shift-JISのバイト数を判定。1or2バイト。超無理矢理。 '2バイト文字の1バイト目は0x81~9F、0xE0~EF。 '2バイト文字の2バイト目は0x40~7F、0x80~FC。 'これ以外を1バイト文字と判定。 Function Is2byteShiftJIS(sHex1 As String, sHex2 As String) As Boolean Is2byteShiftJIS = False If (HexToDec("81") <= HexToDec(sHex1) And HexToDec(sHex1) <= HexToDec("9F")) Or _ (HexToDec("E0") <= HexToDec(sHex1) And HexToDec(sHex1) <= HexToDec("EF")) Then If (HexToDec("40") <= HexToDec(sHex2) And HexToDec(sHex2) <= HexToDec("7F")) Or _ (HexToDec("80") <= HexToDec(sHex2) And HexToDec(sHex2) <= HexToDec("FC")) Then Is2byteShiftJIS = True End If End If End Function
かじった程度の知識しかないのでフラグや拡張ヘッダーについては触れません。
サロゲートペア文字は知らなかったことにして回避しました。
次の記事でmp3タグ解析用(v2)のプログラムを書きます。
以上。