つらつら Excel VBA

私の備忘録です。

mp3バイナリ解析準備

必要なものを作っていく。


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~33識別子「49 44 33」が入っている。アスキーコードで「ID3」。
4~52バージョン「02 00」「03 00」「04 00」のいずれかが入っている。
それぞれv2.2、v2.3、v2.4を表す。
61フラグ非同期化、拡張ヘッダ、実験中などを表す。基本は「00」。
本記事では「00」以外のパターンは謎とする。
7~104サイズフレーム全体のサイズ。Syncsafe Integer。


id3v2.3、id3v2.4のフレーム
ヘッダーの直後、11バイト目以降
場所長さ内容備考
1~44フレームID「TIT2」「TPE1」「TALB」などが入る。
アスキーコードで書かれる。
5~84フレームサイズデータ本体のサイズが入っている。
フレームサイズ10バイトは含まれない。
9~102フラグ謎。
11~1~可変データ本体文字コード、BOM、文字の順で書かれる。


主なフレームID(v2.3)

フレームID 内容
TIT2タイトル
TPE1アーティスト
TALBアルバム
TCONジャンル
TRCKトラックNo
TYERリリース年
COMMコメント
APICアートワーク


文字コード

フレームデータの1バイト目に文字コードが書かれる。
文字コード備考
00ISO-8859-1
01UTF-16BOM有
02UTF-16BOM無(v2.4)
03UTF-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バイト
10xxxxxxx
2110xxxxx10xxxxxx
31110xxxx10xxxxxx10xxxxxx
411110xxx10xxxxxx10xxxxxx10xxxxxx

有効ビットを全て繋げて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)のプログラムを書きます。

以上。

mp3のタグ文字化け

ナビで音楽を再生→文字化け発見→
→タグ編集ソフトでUnicodeに書き換える
→文字化けなおる!

インターネットで少し調べれば上記の事は分かるけど、編集時や運用で気を付けることがあるのでメモメモ。

色々書く。



wmaの「アルバムのアーティスト」

いきなり文字化けとは違うけど苦労した件をメモ。

私の使ってるナビはアルバム一覧が表示できる。

このナビはwmaを「アルバムのアーティスト」と「アルバム」の組み合わせでアルバムを認識する模様。

同じアルバム内でアーティストが複数いる場合があり、アルバムのアーティストに「VA」だか「さまざまなアーティスト」を入れることで同一アルバムと認識させる事ができる。これをナビで再生すると「さまざまなアーティスト」さんが歌っていると表示される。私は嫌。

それならばと、アルバムのアーティストに個別のアーティスト名を入れると、ズラズラと同じアルバム名が並んで判別不可能に。

アルバムのアーティストの中身を消すと「Unknown Artist」とナビに表示された。アーティストタグに登録されている名前は出ない模様。詰んだ。
wmaと異なるタグを有するmp3との共存は難しい。

wmaは消した。諦めた。
mp3の1本でいきます。

※この件はアルバム一覧を使わない事で解決する事ができる。

※ナビの説明書に「アルバムのアーティスト」という単語は無かった。ふしぎ!



文字化け対策

タグ編集ソフトを使ってUnicode(UTF-16)に書き換えると解決!UTF-8でも可!

上記で書き換えた後にファイルのプロパティを編集すると、書き換えたタグの文字コードが変わるので注意。関係ない項目を変更しても変わるのは理不尽だと思う。

※稀にUTF-16LEで文字化けするパターンに遭遇して調査中。これは別記事で書く。



Gracenote

タグ編集ソフトで登録したタグとは違う文字がナビに表示される事がある。改名前の名前を入れたのに改名後の名前が出たり、半角で入れたのに全角表示されたり。気付きにくいのも嫌だ。

Gracenoteはナビ等に備わっているタグ情報を更新してくれるサービス。仕組みは謎。

犯人はGracenote。
正しく黙らせる方法を知りたい。

強制的に黙らせる方法はインターネットで検索すると出てくる。ナビでGracenoteのアップデート途中に電源を(略)、復活も可能との事だが、自分のではやりたくない。

長いタイトル、アーティスト名、アルバム名とか、ナビ画面の横幅が足りなくて表示しきれない事が多々ある。だから編集してるのにGracenoteが勝手に(略)

ナビもナビで横スクロールくらいしてほしい。



上記に書いてない文字化けまとめ

文字化けの原因は、指定されてる文字コードを守ってないから。

id3v1系ではシフトJISが使えた。しかしid3v2系の規格にシフトJISは無い。それでもシフトJISを使いたいヤツがいた。文字コードISO8859-1と指定されてる所にシフトJISの文字をブッ込むやり方が蔓延した。そりゃ文字化けするわ。

インターネットで「文字コードシフトJISで書き込んで下さい」とハッキリ書いてある公式説明書(PDF)も見つけた。v1~v2対応らしい。草生える。


タグ編集ソフトを使ってUnicodeを指定する!
古い機器で再生しないならid3v1のタグは消す!タグ編集ソフトで消せる!

タグ編集ソフトを使う!


※古い機器でもタグを表示できるようにとv2で規定された非同期化という技術とUnicodeの組み合わせで文字化けする例もあるらしいけど、これについては文字化けに遭遇してから考えます。

以上。

【Word VBA】図のエフェクトまとめ

以前まとめた同記事でエフェクト効果の定数対応表が間違っていた上に、図のエフェクト定数を検索するとGoogle検索のトップに出るというやらかしをしてしまい、前の記事は削除しました。

私も他のブログを鵜呑みにしてコピペしただけなんですぅー!なんて言い訳をします。
(msoEffectNoneが効果なしで定数値17なんて少し見れば間違いに気づくだろうに、あぁ~)


図のエフェクト

図のエフェクトは主にアート効果。同じ名前や似た効果に注意。
パラメータはInsert時点で初期値が設定される。
PictureEffectsは1~27をだけを使用する。これ以外を使用するとオートメーションエラーとなり面倒。0もだめ。エラートラップもできなかった。
一例を作ったけどそのままでは動かないので注意。

Dim eff As PictureEffect
For Each eff In shp.Fill.PictureEffects
    If eff.Type = msoEffectMarker Then eff.Delete 'マーカーを事前に削除
Next
With myShape.Fill.PictureEffects
    Set eff = .Insert(msoEffectMarker) 'マーカー
    'eff.EffectParameters(1).Value = pt1 '透明度(0~1)
    'eff.EffectParameters(2).Value = pt2 'サイズ(0~100)
End With


エフェクトのパラメータ

パラメータが2つあるエフェクトと初期値、通常設定できる範囲の一覧。
※全てアート効果。操作上の見た目順に並べた。
エフェクト名param1def1param2def2入力範囲
マーカー透明度0サイズ970~100
鉛筆:モノクロ透明度0鉛筆のサイズ270~100
鉛筆:スケッチ透明度0筆圧220~100
線画透明度0.25鉛筆のサイズ00~100
チョーク:スケッチ透明度0筆圧00~4
ペイント:描線透明度0強度50~10
ペイント:ブラシ透明度0ブラシのサイズ20~10
光彩:デフューズ透明度0強度50~10
パッチワーク透明度0グリッドサイズ40~10
水彩:スポンジ透明度0ブラシのサイズ20~10
フィルム粒子透明度0粒度400~100
モザイク:バブル透明度0筆圧140~100
ガラス透明度0拡大縮小340~100
セメント透明度0ヒビの間隔240~100
テクスチャライザー透明度0拡大縮小340~100
十字模様:エッチング透明度0.75筆圧300~100
パステル:滑らか透明度0拡大縮小340~100
ラップフィルム透明度0滑らかさ50~10
カットアウト透明度0影の数20~6
白黒コピー透明度0.3詳細30~10
光彩:輪郭透明度0.15滑らかさ30~10
明るさ/コントラスト明るさ0コントラスト0-100%~100%


パラメータが1つのエフェクトと初期値一覧。
※主に図の修正。ぼかしはアート効果で唯一パラメータ値が1つ。

エフェクト名param1def1入力範囲
ぼかし半径100~100
シャープネス鮮明度0-100%~100%
色の彩度鮮やかさ10%~400%
色のトーン温度65001500K~11500K


エフェクト効果の定数(修正版)

定数エフェクト名
0msoEffectNoneなし
1msoEffectBackgroundRemoval背景削除
2msoEffectBlurぼかし
3msoEffectBrightnessContrast明るさ/コントラスト
4msoEffectCementセメント
5msoEffectCrisscrossEtching十字模様:エッチング
6msoEffectChalkSketchチョーク:スケッチ
7msoEffectColorTemperature色のトーン
8msoEffectCutoutカットアウト
9msoEffectFilmGrainフィルム粒子
10msoEffectGlassガラス
11msoEffectGlowDiffused光彩:デフューズ
12msoEffectGlowEdges光彩:輪郭
13msoEffectLightScreenパッチワーク
14msoEffectLineDrawing線画
15msoEffectMarkerマーカー
16msoEffectMosiaicBubblesモザイク:バブル
17msoEffectPaintBrushペイント:ブラシ
18msoEffectPaintStrokesペイント:描線
19msoEffectPastelsSmoothパステル:滑らか
20msoEffectPencilGrayscale鉛筆:モノクロ
21msoEffectPencilSketch鉛筆:スケッチ
22msoEffectPhotocopy白黒コピー
23msoEffectPlasticWrapラップフィルム
24msoEffectSaturation色の彩度
25msoEffectSharpenSoftenシャープネス
26msoEffectTexturizerテクスチャライザー
27msoEffectWatercolorSponge水彩:スポンジ


「背景の削除」の初期値

Insert直後に設定された値は4つで「0.1、0.1、0.9、0.9」だった。これは通常のGUI操作では設定できないと思われる値。
おそらく画像に対して外側から追い込む距離のパーセンテージだと考えているが、ソースが見つからない。
別に近々で使う処理でもないので放置。

以上。

Wordに挿入した画像を一括保存

Wordのマクロの記録を使って図を保存する操作からコードを調べようとしたら、マクロの記録中に右クリックできなくて詰んだので、VBA以外の方法をメモ。


1.図を右クリックして「図として保存」。これを繰り返す。
ファイル数が少ない場合はこれでOK。ちからわざ。


2.文書をHTML形式で保存。
挿入画像がフォルダにまとめて保存される。これ楽。
ただし保存後にWordがWebレイアウトになる。
画面右下のレイアウト変更設定から戻せる。印刷レイアウトにする。


3.拡張子をzipにして(略)imageフォルダからコピー。
Excelファイルも同手順で可能。古いバージョンのファイルは構造が違う?ので出来ない。
他Officeのファイルは手元に無いので試してない。
ファイルを開かずにできるので、よく考えたらセキュリティ的に(
拡張子を元に戻したりファイルが壊れたりすると面倒なので、ファイルコピーしてからやる。


図の画質に関して注意。
Wordに画像を挿入した時点で劣化する。Wordの設定で挿入画像の画質設定がある模様。

以上。

オートシェイプ名を配列にしてグループ化

オートシェイプを作るたびに名前を配列に格納し、最後に配列からグループ化。

Dim myShape As Shape
Dim arrName() As String

Set myShape = オートシェイプを作る処理01
Call ShapeNameArray(arrName, myShape.Name)
'~略~
Set myShape = オートシェイプを作る処理20
Call ShapeNameArray(arrName, myShape.Name)

Shapes.Range(arrName).Group.Name = "魔法陣1"


何度も実行するので処理をまとめた。やっつけ仕事。

'配列を1つ増やして追加する
Sub ShapeNameArray(arr, s)
    On Error GoTo add1
    Dim i As Integer: i = 0
    i = UBound(arr)
add1:
    i = i + 1
    ReDim Preserve arr(1 To i)
    arr(i) = s
End Sub


部品ごとにグループ化されていると移動や回転が容易。

以上。

円に添うワードアートを作る

このようにしたい。


文字を円状に変形させる手順は、図形の書式>文字の効果>変形>円。
オートシェイプの円を作成して直接文字を追加して文字を円状に変形させるとこうなる。内側すぎる。物足りない。


円とは別にワードアートを同じサイズで作ってみる。惜しい。


少しだけワードアートのサイズを円よりも大きくすれば合いそう。
円状の文字を別の円に完璧に添わせるには、中心を同じにする。
大きさの違うオートシェイプの中心を計算して、ワードアートのサイズを微調整して(ry

できたのがコレ。ざっくり。

Sub 円に添うワードアートを作成する試行錯誤()
    
    '共通の中心点をここで指定
    Dim 中心点Left As Double
    Dim 中心点Top As Double
    中心点Left = Range("D12").Left
    中心点Top = Range("D12").Top
    
    Dim 円Left As Double
    Dim 円Top As Double
    Dim 直径 As Double
    
    Dim 半径 As Double
    半径 = 100 '円のサイズをここで指定
    
    円Left = 中心点Left - 半径
    円Top = 中心点Top - 半径
    直径 = 半径 * 2
    
    Dim myShape As Shape
    Set myShape = Shapes.AddShape(msoShapeOval, 円Left, 円Top, 直径, 直径)
    
    myShape.Line.Weight = 2
    myShape.Line.ForeColor.RGB = rgbBlue '外枠
    myShape.Fill.Visible = msoFalse '塗りつぶし無し
    'myShape.Fill.ForeColor.RGB = iForeColor '塗りつぶし色
    
    
    Dim ワードアートサイズ As Double
    ワードアートサイズ = 104 'ここで指定。半径であることに注意。
    
    Dim 文字Left As Double
    Dim 文字Top As Double
    文字Left = 中心点Left - ワードアートサイズ
    文字Top = 中心点Top - ワードアートサイズ
    
    Dim msg1 As String
    msg1 = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわをん"
    
    Set myShape = Shapes.AddTextEffect(msoTextEffect1, msg1, "MS Pゴシック", 30, _
        msoFalse, msoFalse, 文字Left, 文字Top)
    
    myShape.Height = ワードアートサイズ * 2
    myShape.Width = ワードアートサイズ * 2
    myShape.TextEffect.PresetShape = msoTextEffectShapeCircleCurve
    'myShape.ZOrder msoSendToBack '最背面へ移動
    
    'グループ化
    ActiveSheet.Shapes.SelectAll
    Selection.ShapeRange.Group.Name = "test"
    
End Sub


MSPゴシックを指定しているのに游ゴシックになるのマジ勘弁。
直接編集して変更できたので、とりあえずOK。
本記事トップ画像のように内側に添わせるには、104を87に変更すればできる。

円状に文字を変形すると、文字の数によってフォントサイズが勝手に変更されてしまう模様。Excelの仕様?

そしてこれは多分ワードアートじゃなくても多分大丈夫。エフェクトが気に食わないぞ。

以上。

ステータスバーに進捗を表示

ステータスバーにプログレスバーぽいヤツを作った!小文字のLはカッコいい気がする!

GIF見れない人用

よく見る■のパターン


以下ソース。

Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)

Sub ステータスバーのテスト()
    
    'ThisWorkbook.Activate
    Application.StatusBar = "処理開始"
    Sleep 2000
    
    Dim i As Long
    For i = 1 To 100
        Application.StatusBar = String(i, "l") 'iの数だけ文字を繰り返す。この関数は最大255文字。
        'Cells(1, 1).Value = i '確認用
        Sleep 30
    Next i
    
    Application.StatusBar = "終了しました"
    Sleep 2000
    Application.StatusBar = False
    
End Sub




おまけ。ウィンドウサイズを狭めるとこうなる。エラーにはならない。

表示限界なのかバグってるのか何が起きているのか分からないが、以下のコードでステータスバーの文字をセルに出力できている。

Cells(1, 1).Value = Application.StatusBar

調査はしません。




おまけのおまけ。
本記事のGIF動画キャプチャ中にPCが重くてステータスバーが、というかエクセルの表示が固まることが多々あった。
ループの中にDoEventsを入れることで表示が安定した。

以上。