npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mtillmann/jpeg-xmp-writer

v0.0.6

Published

small library to write XMP data into JPEGs

Downloads

103

Readme

jpeg-xmp-writer

A very simple library to write XMP metadata to JPEG files in the browser and node. Based on Simon Boddy's xmp-jpeg.

Installation

npm i install @mtillmann/jpeg-xmp-writer

Basic Usage (Browser)

import { writeXMP } from '@mtillmann/jpeg-xmp-writer'

const arrayBuffer = /*...*/ // your JPEG file as an ArrayBuffer

// Write XMP metadata to the JPEG
const xmpArrayBuffer = writeXMP(arrayBuffer, {'xmp:Title': 'Hello, World!'})

Using Simple Attributes

The second argument to writeXMP can be an object with the XMP attributes you want to write. The keys are the XMP attribute names, and the values are the attribute values. Here's an exhaustive list of XMP attributes. Note that the attribute names are case-sensitive and must be prefixed with xmp:.

import { writeXMP } from '@mtillmann/jpeg-xmp-writer'

const arrayBuffer = /*...*/ // your JPEG file as an ArrayBuffer

// Write common Lightroom (Classic) attributes to the JPEG
const xmpArrayBuffer = writeXMP(arrayBuffer, {'xmp:Label': 'Green', 'xmp:Rating': 3})

Manipulating/Replacing the XMP Metadata DOM

The second argument also accepts a function that receives the XMP metadata DOM as an argument. This is useful if you want to manipulate the existing XMP metadata or replace it entirely.

import { writeXMP } from '@mtillmann/jpeg-xmp-writer'

const arrayBuffer = /*...*/ // your JPEG file as an ArrayBuffer

// manipulate the XMP metadata DOM
const xmpArrayBuffer = writeXMP(arrayBuffer, dom => {
  // use getElementsByTagName to find namespaced elements
  dom.getElementsByTagName('rdf:Description')[0].setAttribute('xmp:Title', 'Hello, World!')
  dom.getElementsByTagName('rdf:Description')[0].insertAdjacentHTML('beforeend', '<dc:creator><rdf:Seq><rdf:li>Martin</rdf:li></rdf:Seq></dc:creator>')
  return dom
})

In the example above I set the xmp:Title attribute of the first (and only) <rdf:Description> element to "Hello, World!" and added a <dc:creator> element with the value "Martin".

Note that you can't use querySelector or querySelectorAll to find namespaced elements, use getElementsByTagName()[0] instead.

If you only want set attributes, use the attribute map option described above.

Next, I'll show you how to replace the entire XMP metadata DOM:

// replace the XMP metadata DOM
const xmpArrayBuffer2 = writeXMP(arrayBuffer, dom => {
    // add a fake root node to the XMP metadata, because innerHTML is used to extract actual XML
  return (new DOMParser()).parseFromString(`<root><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.5-c002 1.148022, 2012/07/15-18:06:45        ">
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
        <rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" 
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" 
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmp:Label="Gelb"
            xmp:Rating="2"
            xmp:Title="I'm an Image yay">
        </rdf:Description>
    </rdf:RDF>
</x:xmpmeta></root>`, 'text/xml').documentElement // <- return the documentElement (<root>)
})

It's pretty straightforward and the only thing to keep in mind is that your XML/DOM needs some element that wraps <x:xmpmeta>, in the example above it's simply called <root>. You also need to return that wrapping root node by returning yourDOMObject.documentElement.

Working with Blobs

If your source image data is a Blob, use these one-liners to convert it to ArrayBuffer and back:

import { writeXMP } from '@mtillmann/jpeg-xmp-writer'

const originalBlob = new Blob(/*...*/)

// Blob -> ArrayBuffer
const arrayBuffer = await new Response(originalBlob).arrayBuffer()

// inject XMP metadata
const bufferWithXMP = writeXMP(arrayBuffer, {'xmp:Title': 'I was a Blob once!'})

// ArrayBuffer -> Blob
const blobWithXMP = new Blob([bufferWithXMP], { type: "image/jpeg" })

Working with data URLs

If your source image data is a data URL, I've included two helper functions to convert to ArrayBuffer and back to data URL:

import { writeXMP, arrayBufferToDataURL, dataURLToArrayBuffer } from '@mtillmann/jpeg-xmp-writer'

const originalDataURL = 'data:image/jpeg;base64,...'

// data URL -> ArrayBuffer
const arrayBuffer = dataURLToArrayBuffer(originalDataURL)

// inject XMP metadata
const bufferWithXMP = writeXMP(arrayBuffer, {'xmp:Title': 'I was a Data URL once!'})

// ArrayBuffer -> Data URL
const dataURLWithXMP = arrayBufferToDataURL(bufferWithXMP)

Basic Usage (Node)

Since node lacks a native DomParser and has no global crypto object, you need install and additional dependency and pass a few extra arguments to the writeXMP function:

Install @xmldom/xmldom:

npm i install @xmldom/xmldom

Then, in your script, run writeXMP like this:

// for demo purposes
import { readFileSync, writeFileSync } from 'fs'

// import writeXMP and the necessary dependencies
import { writeXMP } from '@mtillmann/jpeg-xmp-writer'
import { DOMParser, XMLSerializer } from '@xmldom/xmldom'
import crypto from 'crypto'

// read the JPEG file and convert it to an ArrayBuffer
const originalBuffer = readFileSync('./test/test.jpg')
const arrayBuffer = new Uint8Array(originalBuffer).buffer

// Write XMP metadata to the JPEG, note the extra arguments
const xmpedArrayBuffer = writeXMP(arrayBuffer, { 'xmp:Title': 'Written by Node :)' }, DOMParser, new XMLSerializer().serializeToString, crypto)

// write the modified ArrayBuffer to a new file
const outBuffer = Buffer.from(xmpedArrayBuffer)
writeFileSync('./test/test-out.jpg', outBuffer)

The three extra arguments are:

  • Parser - a constructable DOMParser object, thats somewhat compatible with the browser's DOMParser.
  • serializer - a function that serializes a DOM object to a string, I used XMLSerializer::serializeToString method from the @xmldom/xmldom package.
  • crypto - a crypto object that has a getRandomValues method, I used the node's built-in crypto module.