完整的工作示例可在 GitHub 上获取:
manage-xmp-in-psd-and-ai-files-python

介绍

营销团队向你的资产平台投放了 400 个 PSD 文件。上传成功,搜索却不行,因为这些文件没有关键词,一半缺少版权声明,设计师姓名只在某个电子表格里。解决办法不是更大的电子表格。XMP 管理是 GroupDocs.Metadata 在 Python 通过 .NET 提供的功能,能够读取和写入嵌入在 Photoshop PSD 和 Illustrator AI 文件中的元数据包,这意味着所有权和搜索数据可以直接存放在文件本身。

XMP 是二进制容器内的 XML 包,按方案组织:Dublin Core 用于所有系统都能理解的字段,Photoshop 方案用于编辑上下文,XmpBasic 用于工具标识。手动解析 PSD 以获取该包非常困难。使用 Metadata 类只需三次属性查找,同样的代码也适用于 AI 文件。

本教程通过四个步骤演示完整的往返操作:快照整个包、读取关键方案、写入版权和创建者、以及为搜索标记关键词。每段代码都来自可运行的仓库,并断言写入的值能够持久保存。

前置条件

在开始之前,请确保你已具备:

  • Python 3 与 pip
  • GroupDocs.Metadata for Python via .NET(仓库固定为 26.5 版)
  • 一个用于实验的 PSD 或 AI 文件

安装

pip install groupdocs-metadata-net==26.5

步骤 1 - 快照整个 XMP 包

首先查看文件携带的全部信息。快照遍历根包、每个已注册的方案,最后扫荡属性树中所有非标准属性,将它们全部收集到一个扁平字典中。

result = {}

def put(props, prop):
    value = (str(prop.interpreted_value) if prop.interpreted_value is not None
             else (str(prop.value) if prop.value is not None else ""))
    props[prop.name] = value

with Metadata("campaign-hero.psd") as metadata:
    root = metadata.get_root_package()
    xmp = getattr(root, "xmp_package", None)
    if xmp is not None:
        for p in xmp:                                  # 根包属性
            put(result, p)
        schemes = xmp.schemes
        for scheme in (schemes.dublin_core, schemes.xmp_basic, schemes.photoshop,
                       schemes.camera_raw, schemes.paged_text,
                       schemes.xmp_dynamic_media, schemes.xmp_media_management):
            if scheme is None:
                continue
            for p in scheme:
                put(result, p)
    for p in metadata.find_properties(lambda p: p.name is not None):
        if p.name not in result:                       # 捕获自定义包
            put(result, p)

关键点:

  • 先使用 interpreted_value:日期和枚举会以可读形式返回,而不是原始值。
  • 七个方案加一次扫荡:最后的 find_properties 通过遍历捕获未被命名方案覆盖的厂商包。
  • 一次文件打开:整个快照只需一个 Metadata 上下文,这在批量摄取时尤为重要。

提示:在摄取时对该字典建立索引,之后的大多数元数据查询都可以直接通过字典查找,而无需再次读取文件。

我的集成应首先读取哪个 XMP 方案?

先从 Dublin Core 开始。它的九个 dc: 字段包含标题、创建者、权利和主题等值,这些是大多数 DAM 系统、搜索索引和授权检查所共同遵循的,并且 PSD 与 AI 文件对这些字段的暴露方式完全相同。随后读取 Photoshop 方案,以获取编辑上下文(如 City、Credit、DateCreated)。完整包的扫荡留给必须捕获全部信息的摄取作业。

步骤 2 - 读取回答实际问题的方案

在请求时的代码中,只需针对单一方案进行读取。Dublin Core 能回答所有权和搜索相关的问题:

dc_fields = {}
with Metadata("campaign-hero.psd") as metadata:
    xmp = getattr(metadata.get_root_package(), "xmp_package", None)
    dc = xmp.schemes.dublin_core if xmp is not None else None
    if dc is not None:
        for p in dc:
            dc_fields[p.name] = (str(p.interpreted_value)
                                 if p.interpreted_value is not None else
                                 str(p.value) if p.value is not None else "")

print(dc_fields.get("dc:rights", "<no rights recorded>"))

Photoshop 方案的读取方式相同,只是通过强类型属性访问:ps.color_mode、ps.icc_profile、ps.city、ps.country、ps.date_created、ps.caption_writer、ps.credit、ps.source,每个属性都要加 None 防护。这些字段正是 Bridge、Lightroom 和 DAM 搜索过滤器在 Adobe 文件中使用的关键。

注意在没有 XMP 的文件上会发生什么:防护会返回空字典,而不是抛出异常。新导出的资产经常出现这种情况,所以在集成中保持这种行为是必要的。

相同的三次查找同样适用于 Illustrator 文件。只需把 campaign-hero.psd 换成 brand-mark.ai,其他代码不变,这正是处理混合 Adobe 档案时单一路径可行的原因。实际上,AI 导出时往往只填充少量方案,因此空字典路径会更频繁被触发。

步骤 3 - 写入版权和创建者

现在来看写入路径。所有者标记涉及三个字段,确保每个读取器看到相同的身份信息:dc:rights 用于法律声明,dc:creator 作为有序列表,以及 xmp:CreatorTool 用于读取 XmpBasic 方案而非 Dublin Core 的工具。我曾因资产上显示 “Unknown author” 的授权横幅而浪费了一个下午,原因是值只在 dc:creator 中,而工具只读取了 xmp:CreatorTool。两者都写入后此类 bug 便消失。

with Metadata("campaign-hero.psd") as metadata:
    root = metadata.get_root_package()
    xmp = getattr(root, "xmp_package", None)
    if xmp is None:                          # 文件根本没有 XMP
        root.xmp_package = XmpPacketWrapper()
        xmp = root.xmp_package
    if xmp.schemes.dublin_core is None:
        xmp.schemes.dublin_core = XmpDublinCorePackage()

    dc = xmp.schemes.dublin_core
    dc.set_rights("(C) 2026 GroupDocs Sample")
    dc.set("dc:creator", XmpArray.from_(["Digital Asset Team"],
                                        XmpArrayType.ORDERED))

    if xmp.schemes.xmp_basic is None:
        xmp.schemes.xmp_basic = XmpBasicPackage()
    xmp.schemes.xmp_basic.creator_tool = "Digital Asset Team"

    metadata.save("campaign-hero-stamped.psd")

关键点:

  • 防护会创建缺失层:XmpPacketWrapper 与 XmpDublinCorePackage 按需创建,使写入在没有 XMP 的文件上也能正常工作。
  • 有序数组用于创建者:作者顺序有意义,因此使用有序的 XmpArray。
  • 保存到新路径:源文件保持不变,这是导出步骤的默认正确做法。

步骤 4 - 为搜索标记关键词

dc:subject 是 DAM 搜索索引使用的关键词集合。写入时一次性替换整个集合:

with Metadata("campaign-hero.psd") as metadata:
    root = metadata.get_root_package()
    xmp = getattr(root, "xmp_package", None)
    if xmp is None:
        root.xmp_package = XmpPacketWrapper()
        xmp = root.xmp_package
    if xmp.schemes.dublin_core is None:
        xmp.schemes.dublin_core = XmpDublinCorePackage()

    xmp.schemes.dublin_core.set(
        "dc:subject",
        XmpArray.from_(["landscape", "sunset", "commercial"],
                       XmpArrayType.UNORDERED))
    metadata.save("campaign-hero-tagged.psd")

关键词使用 UNORDERED 数组,因为对索引器而言顺序并不重要。而且 set 会替换已有集合,若需要增量标记而非覆盖,请先读取当前关键词,在 Python 中合并后再写回。

要验证写入是否成功,只需对输出文件重新运行步骤 2 的读取代码。仓库已经自动化了这一过程:它重新读取输出并断言版权字符串和第一个关键词仍然保存在保存的字节中。

实际应用

DAM 摄取

对每个进入的文件执行步骤 1 的快照,并将得到的字典与资产记录一起存储。随后搜索、去重和权利检查都可以直接在数据库中完成,而无需再次打开二进制文件。仓库中小样本 PSD 的快照已经在一次遍历中返回了大量属性,同样的调用在处理成千上万文件的文件夹时也保持同样的表现。

许可执行

在资产发布到客户门户之前,要求 dc:rights 非空。未通过检查的文件会自动执行步骤 3 的标记处理,确保没有文件在离开时缺少版权声明。

批量重新标记

当分类体系变更时,读取每个文件的 dc:subject,在 Python 中将旧术语映射为新术语,然后使用步骤 4 将合并后的集合写回。PSD 与 AI 档案使用相同的循环,无需 Photoshop 授权。

最佳实践与提示

  • 将空视为正常:没有 XMP 的文件是常态,而非错误;提前返回的模式可以让流水线保持流畅。
  • 写入关键词前先合并:set 会替换 dc:subject,因此增量标记需要先读取、扩展再写入。
  • 在两个方案中都写入身份信息:将 dc:creator 与 xmp:CreatorTool 配对,可让 Dublin Core 读取器和 XmpBasic 读取器保持一致。
  • 通过重新读取验证写入:保存后立即再次读取成本低,能立刻捕获容器层面的异常。
  • 生产环境请使用许可证:评估模式可以运行本文示例;在对真实客户资产进行标记前请先激活许可证。

结论

在 Adobe 文件中读取和写入 XMP 归结为三步:通过 get_root_package() 获取包、对所需方案进行防护、然后读取或写入强类型值。掌握这三步后,你就完成了本教程的完整往返:从包快照到方案读取,再到版权标记和关键词标记,且同一套代码同时适用于 PSD 与 AI 文件。

准备在项目中实现这些功能了吗?以下是后续建议:

附加资源

关于 XMP 工作流的疑问?请在 support forum 提问。