Full working example available on GitHub:
edit-xmp-in-psd-and-ai-files-using-groupdocs-metadata-dotnet

製作上の課題:誰も見ない場所にあるメタデータ

ブランドスタジオがキャンペーンを提供する:レイヤー化された PSD マスター、AI ソースファイル、数百のアセットがクライアントの DAM にプッシュされる。3 週間後、ライセンス部門がヒーロー画像の所有者を尋ねる。答えは存在するが、メールスレッドにしかなく、ファイル自体が空の dc:rights フィールドを持っているためである。XMP 編集は .NET 用の GroupDocs.Metadata の機能で、パイプラインレベルでこの種の問題を解決し、Adobe ソフトウェアを介さずに PSD と AI ファイル内の XMP メタデータパケットを読み書きする。

規模の問題は静かに現れる。1 人のデザイナーはしばらく手作業でメタデータパネルを丁寧に埋めることができる。四半期ごとに数千のアセットを扱う制作チームはできず、エージェンシー、スタジオ、クライアント間のすべてのハンドオフで、psd メタデータが誰も検証していないファイルが増えていく。検索は存在するアセットを見つけられなくなる。権利に関する質問は考古学的作業となり、考古学にはサービスレベル合意がない。

これらのチームが実際に必要としているものは API 仕様のように読める:取り込み時にファイルが持つすべてをスナップショットし、ライセンスゲートで特定のフィールドをチェックし、エクスポート時に所有権とキーワードを書き込む。Adobe の両フォーマットで同一のコードを使用する。

現実チェック:空の dc:rights フィールドを持つアセットが無許可というわけではないが、下流でそれを証明できる人を見つけなければ誰も証明できない。

なぜ従来の対策が不十分なのか

チームは通常、適切に自動化する前に 3 つのアプローチを試す:

  • Photoshop または Bridge での手動パネル編集:ファイル単位で機能するが監査できず、根本的にデータ入力作業であるため Adobe のライセンスが必要になる。
  • サイドカースプレッドシート:メタデータは存在するが、ファイルがコピー、リネーム、再配布される瞬間にアセットから切り離される。
  • 社内パーシング:PSD のリソースブロックや AI のコンテナは簡単なフォーマットではなく、Adobe が何かを改訂した最初の瞬間に自前のパーサーは保守負担になる。

GroupDocs.Metadata は 1 つの API でギャップを埋める:ルートパッケージを IXmp にキャストすれば、パケットは両フォーマットで読み書き可能になる。さらに、ドキュメンテーション に記載されている 170 以上のスキーマもサポートする。

ソリューション:パイプライン内での XMP 操作

.NET 用の GroupDocs.Metadata はアセットパイプラインの 3 つのポイントに組み込まれる。取り込み時に完全なパケットをデータベースがインデックスできる辞書にスナップショットする。ライセンスゲートでは Dublin Core(dc:rights と dc:creator が存在するスキーマ)を読み取る。エクスポート時には所有権と dc:subject キーワードを書き込み、XMP が全く無いファイルに対しては欠落しているスキーマを作成する。権利データなしで出荷されたアセットが根本原因となるリリース後の振り返りに何度も参加したが、振り返りは読み取りよりもコストが高いため、ゲートが必要になる。

実装を追うには、以下が必要です:

dotnet add package GroupDocs.Metadata --version 26.6.0

コンパニオンリポジトリ は各フォーマットのサンプルを 1 つ用意し、以下のすべてのステップを検証している。

ワークフローの実装ステップバイステップ

ステップ 1 – 取り込み時にすべてをスナップショット

1 回のパスでパケット、名前付きスキーマ、ベンダーツールが隠しているものすべてを取得する。辞書をアセットレコードの横に保存すれば、後の質問はデータベース検索になる。

// Full XMP snapshot: packet, schemes, then a deep sweep
var result = new Dictionary<string, string>();
using (var metadata = new Metadata(adobeFilePath))
{
    var root = metadata.GetRootPackage() as IXmp;
    if (root?.XmpPackage == null) return result;

    foreach (var property in root.XmpPackage)
    {
        result[property.Name] = property.InterpretedValue?.ToString()
            ?? property.Value?.ToString() ?? string.Empty;
    }
    // CollectScheme(...) repeats this loop for DublinCore, XmpBasic,
    // Photoshop, CameraRaw, PagedText, XmpDynamicMedia, XmpMediaManagement
    foreach (var p in metadata.FindProperties(p => p.Name != null))
    {
        if (!result.ContainsKey(p.Name))
        {
            result[p.Name] = p.InterpretedValue?.ToString()
                ?? p.Value?.ToString() ?? string.Empty;
        }
    }
}
return result;

InterpretedValue が常に最初に来るため、日付や列挙型は人が読める形になる。最後の FindProperties スイープは、名前付きスキーマが認識しないプラグインが書き込んだ Adobe Illustrator メタデータの完全性を保証する。

取り込み規模で実行した際の 2 つの運用上の注意点。スナップショットはアセット ID をキーにし、取得日でスタンプを付けて保存する。ファイルは変わる可能性があり、スナップショットが「以前」の状態を示すからだ。また、空の辞書はエラーではなくシグナルとして扱う。これにより、アセットはインテーク失敗せずに直接スタンプステップへ流れる。

ステップ 2 – ライセンスゲートで Dublin Core をチェック

9 つの dc:* フィールドが法務・ライセンスが実際に尋ねる質問に答える。そのスキーマだけを読むことでゲートは高速になる。

// dc:* fields only - Title, Creator, Rights, Subject and friends
var result = new Dictionary<string, string>();
using (var metadata = new Metadata(adobeFilePath))
{
    var root = metadata.GetRootPackage() as IXmp;
    var dc = root?.XmpPackage?.Schemes?.DublinCore;
    if (dc == null) return result;

    foreach (var property in dc)
    {
        result[property.Name] = property.InterpretedValue?.ToString()
            ?? property.Value?.ToString() ?? string.Empty;
    }
}
return result;

これらの設定が制作チームにとって重要な理由:

  • Null 条件チェーン:新規エクスポートでは XMP が無いファイルが日常的で、空の辞書は「スタンプしてください」を意味し、エラーではない。
  • スキーマの範囲:ゲートはすべてのアセット移動で実行されるため、全ツリーを読むより 9 フィールドだけ読む方がコストが低い。

ステップ 3 – エクスポート時に所有権をスタンプ

書き込みは 3 層に及び、すべてのリーダー(XMP 対応か否か)に同一の識別子が見える。ガードが欠落しているパケットとスキーマオブジェクトを先に作成する。

// Guard-create the packet and scheme, then write rights and creator
using (var metadata = new Metadata(inputPath))
{
    var root = metadata.GetRootPackage() as IXmp;
    if (root == null) return;

    if (root.XmpPackage == null)
        root.XmpPackage = new XmpPacketWrapper();
    if (root.XmpPackage.Schemes.DublinCore == null)
        root.XmpPackage.Schemes.DublinCore = new XmpDublinCorePackage();

    var dc = root.XmpPackage.Schemes.DublinCore;
    dc.SetRights(copyright);
    dc.Set("dc:creator", XmpArray.From(new[] { creator }, XmpArrayType.Ordered));
    // Mirror the identity for XmpBasic readers and tag-classified fields
    if (root.XmpPackage.Schemes.XmpBasic == null)
        root.XmpPackage.Schemes.XmpBasic = new XmpBasicPackage();
    root.XmpPackage.Schemes.XmpBasic.CreatorTool = creator;

    metadata.SetProperties(p => p.Tags.Contains(Tags.Person.Creator),
        new PropertyValue(creator));

    metadata.Save(outputPath);
}

Tags.Person.Creator を使用した SetProperties 呼び出しは注目すべきポイントで、ライブラリがクリエイターフィールドと分類したすべてのプロパティを更新する。フォーマットがどこに保存していても、XMP を読まないツールでも正しい名前が表示される。

ステップ 4 – 検索用キーワードを書き込む

dc:subject は DAM 検索がインデックスする語彙である。これが無ければ、アセットは存在してもクエリに一致しない。

// Replace the dc:subject bag with the pipeline's keyword list
root.XmpPackage.Schemes.DublinCore.Set(
    "dc:subject",
    XmpArray.From(keywords, XmpArrayType.Unordered));
metadata.Save(outputPath);

書き込みは既存のバッグを置き換えるため、追加タグ付けは「読み取り → C# でマージ → 書き込み」になる。リポジトリは 3 つのサンプルキーワードを書き込み、最初のキーワードが保存バイトに残ることを検証している。タクソノミーをバージョン管理するチームは通常、キャンペーンごとに正規キーワードセットを保存し、パイプラインがエクスポート時にファイルと照合させる。これによりキーワードのドリフトが議論ではなく差分になる。

メタデータ修正だけに Photoshop ライセンスは必要ですか?

いいえ、これが自動化の目的でもある。GroupDocs.Metadata は .NET でパケットを直接読み書きできるため、サーバー側のジョブでアーカイブ全体の権利スタンプやキーワード修正が可能で、Adobe アプリケーションを一つも開く必要がない。デザイナーはデザイン作業のためにツールを保持し、パイプラインがスケールでメタデータの衛生管理を担う。

ビジネスに