アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Metadata Editor をつくる 3

Metadata Editor を自作するシリーズその 3。ASF メタデータ読み取りの基本部分ができた。書き込みはスタブ状態だけど設計方針などを記録しておきたいので公開 & 更新しておく。

データ型の齟齬

ASF のメタデータをダンプして気が付いたのだが Extended Content Description Object などに定義される型と、Windows Media Format Runtime における WMTATTRDATATYPE の内容には齟齬がある。

定義としては WMTATTRDATATYPE にある GUID がファイルの方ではバイト配列や文字列になるなど一定していない。メタデータ操作でこのような型の揺らぎがあると不便なので、これはライブラリ側で吸収することにする。

外部公開する型としては WMTATTRDATATYPE を踏襲する。ファイルから読み取る際は実際のデータ型から外部公開型への変換を試みる。型が一致するならそのままに、不一致なら可能な限り変換する。ちなみにこれらのデータは以下のように定義した。

まずは外部公開型。これは WMTATTRDATATYPE をそのまま踏襲する。

/// <summary>
/// タグ情報の型を定義します。
/// 各値の実数は Windows Media Format の WMT_ATTR_DATATYPE に対応しています。
/// </summary>
public enum AsfTagDataType
{
    UInt32 = 0,
    String = 1,
    Binary = 2,
    Bool   = 3,
    UInt64 = 4,
    UInt16 = 5,
    Guid   = 6,
}

次にファイル側のデータ型。アクセス修飾子を省略しているので internal 扱いとなる。ASF のクラスとしては Asf を接頭語に持つものを外部公開、それ以外は internal としている。今後、複数のファイルフォーマットをサポートした場合はそれらも同様の方針で実装する予定。

/// <summary>
/// タグのデータ型を表す値を定義しています。
/// 各値の実数は ASF ファイル内のデータ型を表す値に対応しています。
/// 定義が Windows Media Format の WMT_ATTR_DATATYPE とは一致しない点に注意してください。
/// </summary>
enum TagDataType
{
    String = 0,
    Binary,
    Bool,
    UInt32,
    UInt64,
    UInt16
}

データ読み込みについて

メタデータの中にはジャケット画像のように巨大なものもある。データの大半はテキストになると思われるが、 SF の文字列は UTF-16LE なので一文字が 2 バイト以上もある。これらを常にメモリへ読み込むのはリソースの観点から好ましくない。

そこでデータを読み出すための情報だけ格納して必要になるまで読み込みを遅延することを検討する。ASF ファイルへのストリームを確保していることを前提とするならデータ読み込みに必要な情報は以下の 2 つ。

  1. ストリーム中の読み出し開始位置
  2. 読み出すサイズ (バイト数)

.Net Framework の Stream クラスは位置が long、サイズが int として定義されているので計 12 バイトあればよい。考え方としては 12 バイトのポインタを用意することとなる。実際には他にもデータを表すための情報が必要だが基本方針としてはこの方法でゆく。

ちなみに現時点で必要だと思われる情報をまとめたら定義は以下のようになった。長いのでコンストラクタは省略。

/// <summary>
/// タグの読み書きをおこなうための情報を格納します。
/// </summary>
class TagData
{
    /// <summary>
    /// 削除されることを示す値を取得または設定します。
    /// </summary>
    public bool IsRemove { get; set; }

    /// <summary>
    /// ストリームから読み取るサイズ ( バイト単位 ) を取得します。
    /// </summary>
    public int Length { get; private set; }

    /// <summary>
    /// ストーリム上の読み出し位置 ( バイト単位 ) を取得します。
    /// </summary>
    public long Position { get; private set; }

    /// <summary>
    /// データ型を取得します。
    /// この値は AsfTagDataType と一致しないことがあるので注意してください。
    /// </summary>
    public TagDataType Type { get; private set; }

    /// <summary>
    /// 書き込み候補、または複数の値から算出された情報をキャッシュした値を、取得または設定します。
    /// </summary>
    public object Value { get; set; }
}

遅延読み込みをおこなおうとして気付いたのだが演奏時間などは複数の値から算出する必要があり、それらの関連をうまく汎用化できなかった。書き込み候補として指定されたデータがある場合は読み込み時にそれを返す必要があるので、データを領域を用意して対応。

設計を要約すると、以下のようになる。

  • 基本は読み出し位置とサイズを記録して遅延読み込み
  • 複数値からの算出が必要なものは算出結果となるデータを記録する
  • 書き込み指定があったときはそのデータを記録する
  • 読み込み時にデータがあればそれを返す
  • 読み込み時にデータがなければ位置とサイズを指定してファイルから読み込む

定義済みのタグ情報

Windows Media Format Runtime ではタグを自由に追加できるようになっているが、実際に使用される場面はすくないと思われる。現時点で基本タグとして定義されているものだけでも相当数あり、とても使い切れないだろう。

そこでこのライブラリでは基本タグのみをサポートすることにした。タグの定義をプログラム的に実装することで操作を自動化しやすくする。AsfDefinedTags というタグ定義を管理するための static クラスを作成してその内部クラスとして以下を定義する。長いのでコンストラクタは省略。

/// <summary>
/// タグの基本情報を表します。
/// </summary>
public class Info
{
    /// <summary>
    /// 読み取り専用であることを示す値を取得します。
    /// </summary>
    public bool IsReadOnly { get; private set; }

    /// <summary>
    /// 名前を取得します。
    /// </summary>
    public string Name { get; private set; }

    /// <summary>
    /// データ型を取得します。
    /// </summary>
    public AsfTagDataType Type { get; private set; }
}

AsfDefinedTags はすべてのタグを個別の static プロパティとして持ち、それぞれは上記のデータとなる。例えば AsfDefinedTags.Title とすればタイトルのタグ定義が得られる。また、タグ定義をコレクションとして操作できるように、内部的にこれらの参照を private static Dictionary<string, Info> Info として持っておき、以下のメソッドを公開しておく。

/// <summary>
/// 指定された名前を持つタグ情報を取得します。
/// </summary>
/// <param name="name">名前。</param>
/// <returns>成功時はタグ情報。それ以外は null 参照。</returns>
public static Info GetInfo( string name )
{
    Info info;
    return ( AsfDefinedTags.Infos.TryGetValue( name, out info ) ? info : null );
}

/// <summary>
/// タグ名のコレクションを取得します。
/// </summary>
/// <returns>タグ名のコレクション。</returns>
public static IEnumerable< string > GetNames()
{
    return AsfDefinedTags.Infos.Keys;
}

タグ定義にある読み取り専用フラグが true なら書き込みや削除で例外を発行する。ASF を解析してゆき安全を確認できるまで基本的に読み取り専用としておくつもり。

ストリームと言語依存のデータ

ASF ファイルには複数のストリームが格納されることがありメタデータを個別に割り当てられる。言語の区別も可能。後者は Windows Media Player の拡張タグエディタで言語を指定してコメントなどを書くと、それぞれの言語用のデータが書き込まれる。

このデータは Header Extension Object という領域内に Metadata ObjectMetadata Library Object として格納される。前者はストリームのみ依存、後者やストリームと言語に依存する。当初これらのサポートも考えたのだが以下の理由から見送ることにした。

  • Windows Media Player の拡張タグエディタでも全データを言語指定できるわけではない
  • mp3infp や SuperTagEditor などの著名なエディタが対応していない
  • ストリームと言語の指定を使いこなすメリットを感じられない

今回の成果物

今回の成果物を以下に公開する。

TestApp.exe は 2 種類のダンプを実装。一方はファイル構造に忠実で、もう一方は実際にメタデータエディタとして利用した場合の読み取り方法によるもの。上記の配布イメージでは後者だけを有効にしてある。

今回はここまで。次回は書き込みにチャレンジする予定。