전체 작업 예제가 GitHub에 있습니다:
manage-xmp-in-psd-and-ai-files-python

소개

마케팅 팀이 400개의 PSD 파일을 자산 플랫폼에 업로드합니다. 업로드는 정상적으로 동작하지만, 검색은 동작하지 않습니다. 파일에 키워드가 없고, 절반은 저작권 고지가 없으며, 디자이너 이름은 어디선가 스프레드시트에만 존재하기 때문입니다. 해결책은 더 큰 스프레드시트가 아닙니다. XMP 관리란 Photoshop PSD와 Illustrator AI 파일에 내장된 메타데이터 패킷을 읽고 쓰는 .NET을 통한 Python용 GroupDocs.Metadata 기능으로, 소유권 및 검색 데이터를 파일 자체에 저장할 수 있게 해줍니다.

XMP는 이진 컨테이너 내부에 있는 XML 패킷이며, 스키마로 구성됩니다: 모든 시스템이 이해하는 필드를 위한 Dublin Core, 편집 컨텍스트를 위한 Photoshop 스키마, 도구 식별을 위한 XmpBasic. PSD를 수동으로 파싱해 해당 패킷에 도달하는 것은 실제로 매우 어렵습니다. Metadata 클래스를 사용하면 세 개의 속성 조회만으로 가능하며, 동일한 코드는 AI 파일에도 적용됩니다.

이 튜토리얼은 네 단계로 전체 라운드 트립을 안내합니다: 전체 패킷 스냅샷, 중요한 스키마 읽기, 저작권 및 작성자 쓰기, 검색을 위한 키워드 태그 지정. 모든 스니펫은 작성된 값이 지속되는지를 검증하는 실행 가능한 리포지토리에서 가져왔습니다.

전제 조건

시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • pip가 포함된 Python 3
  • .NET을 통한 Python용 GroupDocs.Metadata (리포지토리는 버전 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 스키마를 읽어 도시, 크레딧, 생성일 등 편집 컨텍스트를 얻으세요. 전체 패킷 스윕은 모든 것을 캡처해야 하는 인제스트 작업에 남겨두세요.

단계 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 방어문으로 읽으며, Adobe 파일의 Bridge, Lightroom, DAM 검색 필터가 이 필드를 기준으로 합니다.

XMP가 없는 파일에서는 방어문이 빈 딕셔너리를 반환하고 예외를 발생시키지 않는다는 점을 기억하세요. 새로 내보낸 자산은 이 경우가 흔하므로, 통합 로직에서도 이 동작을 유지해야 합니다.

같은 세 가지 조회는 Illustrator 파일에서도 동작합니다. campaign-hero.psd를 brand-mark.ai로 바꾸면 다른 점이 없으며, 이는 혼합된 Adobe 아카이브에 대해 단일 코드 경로가 현실적인 이유입니다. 실제로 새 AI 내보내기는 Photoshop 저장보다 채워진 스키마가 적은 경우가 많아, 빈 딕셔너리 경로가 더 자주 실행됩니다.

단계 3 - 저작권 및 작성자 쓰기

이제 쓰기 경로입니다. 소유권 스탬프는 세 필드를 건드리므로 모든 리더가 동일한 신원을 확인합니다: 법적 고지를 위한 dc:rights, 순서가 있는 목록인 dc:creator, 그리고 XmpBasic 스키마 대신 Dublin Core를 읽는 도구를 위한 xmp:CreatorTool. 한 번은 디자이너가 태그를 지정했다고 주장했지만 도구가 xmp:CreatorTool만 읽어 “Unknown author” 배너가 나타난 적이 있습니다. 두 필드를 모두 쓰면 이 종류의 버그가 사라집니다.

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가 없는 파일에도 쓰기가 가능합니다.
  • 작성자 순서가 의미 있는 ORDERED 배열: 저자 순서는 의미가 있으므로 XmpArray는 ORDERED 타입을 사용합니다.
  • 새 경로에 저장: 원본 파일은 그대로 두고, 이는 내보내기 단계의 올바른 기본값입니다.

단계 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 파일 모두에 동일 코드를 사용할 수 있습니다.

프로젝트에 바로 적용하고 싶으신가요? 다음 단계들을 참고하세요:

추가 리소스

Questions about your XMP workflow? Ask on the support forum.