2019年6月11日火曜日

C# TagLib#のMP3タグ文字化け対策

C#でMP3タグの編集を行うアプリを作るのにTagLib-sharpを使ってみました。
まずは、こんな簡単にタグを設定できます、といった例が見つかります。

    TagLib.File tagFile = TagLib.File.Create(filePath);
    tagFile.Tag.Title = "My Favorite Things";
    tagFile.Tag.Album = "BEST";
    tagFile.Tag.Performers = new string[] { "My Favorite Singer" };
    tagFile.Save();

確かにこれでできるのですが、日本語だとWindows 10のファイルエクスプローラで文字化けすることがあります。そんなときもGrooveミュージックやiTunesでは表示されるので、これはTagLibの問題ではなく、タグを解釈するアプリ側の問題です。

手っ取り早い解決策は、次の例のようにタグのバージョンをID3v2.4にすることです。

    tag = (TagLib.Id3v2.Tag)TagFile.GetTag(TagLib.TagTypes.Id3v2, true);
    tag.Version = 4;

File作成時はMP3ファイルのタグバージョンが適用されるので、読み込み後に変更します。
GetTag()の第二引数にtrueをつけると、Tagがない場合は新規作成します。

いささか解せないのは、タグ未設定のMP3にエクスプローラのプロパティーで属性を設定すると、ID3V2.3のタグが設定されることです。ID3V2.3がデフォルトバージョンならちゃんと対応してほしいものですが...

もうひとつの方法はいったんID3V2.3を全て削除し、必要なタグだけ設定する方法です。これは後述しますが、ID3V2タグを再作成すると非同期化がOFFになるからです。

    TagFile = TagLib.File.Create(filePath);
    TagFile.RemoveTags(TagLib.TagTypes.Id3v2);
    TagLib.Id3v2.Tag tag =
        (TagLib.Id3v2.Tag)TagFile.GetTag(TagLib.TagTypes.Id3v2, true);

余分なタグを消してファイルサイズを小さくできるので、場合によっては有用でしょう。

これで一件落着なのですが、なぜ文字化けが発生するか調べたので書いておきます。

冒頭のコードで発生するエクスプローラの文字化けには、次の要因が絡んでいます。
①ID3v2タグの非同期化フラグがONになっている。
②項目により文字化ける場合と無効(非表示)となる場合がある。
③ID3V2タグで未設定だがID3V1に対応する項目があると、ID3V1の項目が適用される。
④ID3V2タグが無効だがID3V1に対応する項目があると、変更は無視され、ID3V1の項目が表示される。

①の非同期化とは、MP3v2タグに対応していなアプリがタグを音声データと誤認しないようにするための処理で、この結果UTF16文字列の先頭BOMが0xFFEE"だと”0xFF00EE"に変換されます。この場合にWindows 10のエクスプローラで文字化けが発生します。試しにバイナリエディタで”0xFFEE00"に変更してみると、文字化けが解消します。

冒頭のコードのように tagFile.TagのプロパティーにStringをセットした場合は、ID3V2.3ではUTF16ですが、ID3V2.4ではUTF8で出力されます。そのため、ID3V2.4では”0xFF00EE"は発生しません。

非同期化フラグはID3V2.4にも存在し、ONの場合に類似の処理が行われます。以下のコードでUTF16で出力すると”0xFF00EE"というバイトシーケンスが発生します。ですが、エクスプローラはID3V2.4は適切に処理してくれるようで、文字化けしません。

frameの文字コードを設定し、frameのTextにStringをセットします。

    TextInformationFrame fTIT2 =
        TextInformationFrame.Get(tag, FrameType.TIT2, StringType.UTF16, true);
    fTIT2.TextEncoding = StringType.UTF16;
    fTIT2.Text = new String[] { "タイトル” };

非同期化をOFFにすれば文字化けしなくなるのですが、MP3ファイルの非同期化フラグはTagLib.Tagクラスのプライベート変数にセットされ、それを操作するパブリックメソッドはありません。

ただし冒頭に書いたように、いったんID3V2タグを削除し再作成すれば、非同期化がOFFの状態になります。

TagLibのソースコードを変更すればプログラムでの対応も可能で、例えば次のようにTagLib.Id3v2.Tag.cs にメソッドを追加し、Save()を呼ぶ前にこのメソッドを呼ぶと非同期化をOFFにできます。

    public void ClearUnsynchronisationFlag ()
    {
        header.Flags &= ~HeaderFlags.Unsynchronisation;
    }

非同期化はID3V2.3を認識しない再生アプリのための処理なので、そんな古いアプリを考慮する必要がなければ問題ないでしょう。Windows 10のエクスプローラでも、MP3ファイルのタグ情報をプロパティダイアログの詳細で変更、保存すると非同期化はOFFになります。これを利用すればID3V2.3のままでの文字化け対策にもなります。

項目によって現象が異なり、TitleとAlbumは次の要領でUTF16BEを適用することで文字化けを解消できます。UTF16BEにはBOMがなく”0xFF00EE"が発生しないことが影響しているのでしょう。

    TextInformationFrame fTIT2 = TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.UTF16BE, true);
    TextInformationFrame fTALB = TextInformationFrame.Get(tag2, FrameType.TALB, StringType.UTF16BE, true);
    fTIT2.TextEncoding = StringType.UTF16BE;
    fTALB.TextEncoding = StringType.UTF16BE;
    fTIT2.Text = new String[] { TitleString };
    fTALB.Text = new String[] { AlbumString };

既存のFrameがある場合、Getが返すFrameのStringTypeは既存の設定のままなので、StringTypeの再設定を行っています。

残念ながらPerformers(参加アーティスト)はこれでも文字化けます。あまり現実的ではありませんが、Performersは半角英数字(Laten1)に限れば次のコードで文字化け回避できます。

    TextInformationFrame fTPE1 = TextInformationFrame.Get(tag2, FrameType.TPE1, StringType.Latin1, true);
    fTPE1.TextEncoding = StringType.Latin1;
    fTPE1.Text = new String[] { PerformersString };

③の現象は、MP3ファイルにまだID3V2タグがない、あるいは対象項目がセットされていない場合に発生します。
TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.UTF16, true);を使用することでID3V2タグがまだない場合は新規作成してくれます。
 TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.Latin1, true);を使用することで、対象項目がセットされていない場合は追加してくれます。

それでも非同期化がONだと④の現象が発生し、ID3V2への変更は無視され、ID3V1の情報が表示されます。

ついでながら、TagLibではUTF16LEも定義されています。TagLibとしては処理しているのですが、UTF16LEではエクスプローラに文字が表示されません。GrooveミュージックでもiTunesでもダメです。調べた範囲では、ID3V2の仕様ではUTF16LEは定義されていないようです。

----------

【追記】
既に設定済みのMP3タグをTagLibで読み込むと文字化けしてしまうことがあります。
次のページに対応策が紹介されています。