Skip to content
Get XMP Metadata

Get XMP Metadata

The XMP packet lives on the document catalog as a stream, and reading it takes three steps: fetch the object, unwrap the stream, then parse it into an xmputil.Document. From there you ask for a namespace model. GetPdfInfo returns the pdf: model, the one that mirrors the document information dictionary.

Doing it

metadata, ok := reader.GetCatalogMetadata()
if !ok {
    return errors.New("no XMP metadata in this document")
}

stream, ok := core.GetStream(metadata)
if !ok {
    return fmt.Errorf("catalog metadata is not a stream: %T", metadata)
}

xmpDoc, err := xmputil.LoadDocument(stream.Stream)
if err != nil {
    return err
}

pdfInfo, ok := xmpDoc.GetPdfInfo()
if !ok {
    return errors.New("no pdf: namespace in the XMP document")
}
fmt.Println(pdfInfo.PdfVersion, pdfInfo.Copyright, pdfInfo.Marked)

Both ok results matter, and they mean different things. GetCatalogMetadata returns false when the catalog has no /Metadata entry at all. GetPdfInfo returns false when the packet parsed but carries neither a pdf: nor an xmp: model.

core.GetStream resolves an indirect reference and asserts the stream type; the decoded XMP bytes are in its Stream field.

Reading the info fields

xmputil.PdfInfo is not model.PdfInfo. It has PdfVersion, Copyright and Marked as plain Go values, and everything that maps onto a standard info key is bundled into a single InfoDict of type core.PdfObject. Convert it to get named fields:

if pdfInfo.InfoDict != nil {
    infoDict, err := model.NewPdfInfoFromObject(pdfInfo.InfoDict)
    if err != nil {
        return err
    }
    fmt.Printf("%#v\n", infoDict)
}

InfoDict is nil when none of the mapped properties were present, so check before converting.

Limitations

GetPdfInfo falls back to the xmp: base model when there is no pdf: model, and that fallback only yields three keys: Creator from xmp:CreatorTool, CreationDate from xmp:CreateDate and ModDate from xmp:ModifyDate. Title, author, subject and keywords are not read from dc: here. If you need Dublin Core, go through Document.GetGoXmpDocument and query that model directly.

The values need not match the document’s /Info dictionary. Nothing keeps the two in sync, and plenty of tools write one and not the other.

LoadDocument parses in lenient mode, so one bad property gets skipped instead of failing the packet. That also means a field reading as empty may mean malformed rather than absent.

Run the example

The example prints the pdf: model of the file you pass it, converting InfoDict back to a model.PdfInfo for display, and reports how long parsing took.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/metadata
go run pdf_get_xmp_pdf_metadata.go input.pdf

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on