Galia Guide 1.0

Metadata

Some image formats support embedded metadata, which may contain information about image characteristics, as well as user- and/or device-supplied information about authoring, copyright, camera settings, and so on. This metadata may be encoded in a standard cross-format encoding like EXIF, IPTC IIM, or XMP, or it may use an encoding that is specific to a particular image format. More than one encoding may be present in the same file.

During a response, an entirely new image is generated and returned to the client. In some cases it might be desired that this image contain some subset of metadata from the source image, or all of it, or that new metadata be added to it.

Standards

EXIF
Typically written by devices like cameras and scanners and not meant to be edited. EXIF utilizes the TIFF file format and extends the Baseline TIFF tag set include a custom IFD pointer and sub-IFD. These terms can also be represented in XMP.
IPTC IIM
Serialization and vocabulary developed by the International Press Telecommunications Council (IPTC). Simple binary encoding with a fixed vocabulary. The serialization is not used much anymore, but the vocabulary has been migrated to the "IPTC Core" schema used in XMP.
XMP
RDF-based encoding developed by Adobe. Widely supported across many formats and even non-image media files. May contain terms from the EXIF and IIM standards as well as from other vocabularies. The graph structure can easily represent key-value pairs as well as sequences, sets, lists, binary blobs, and so on, making it very versatile. The current de facto embedded metadata standard, generally.
Native standards
Some image formats define their own metadata standards. PNG and TIFF define their own term vocabularies, for example.
EXIF IPTC IIM XMP Native
AVIF × ×
BMP × × × ×
GIF × × ×
HEIF × ×
JPEG APP1 segment APP13 segment APP1 segment ×
JPEG2000 EXIF box IPTC box XMP box ×
PNG × ×
TIFF Stored in baseline IFD and several sub-IFDs Stored in sub-IFD 33723 Stored in sub-IFD 700 Custom tags and IFDs
WebP × ×

Viewing

Metadata can be returned in IIIF Image API information responses by implementing an customize_iiifn_information_response() delegate method. Example:

def customize_iiif3_information_response(info)
  info['xmp']  = context.dig("metadata", "xmp_string")
  info['iptc'] = context.dig("metadata", "iptc")
  info['exif'] = context.dig("metadata", "exif")
end

This will cause keys such as the following to be added to an information response:

{
  "@context": "http://iiif.io/api/image/2/context.json",
  "@id": "http://localhost:8182/iiif/3/metadata.jpg",
  "protocol": "http://iiif.io/api/image",
  "width": 64,
  "height": 56,
  ...
  "xmp": "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" ...",
  "iptc": [
    {
      "City": "Urbana"
    },
    {
      "ApplicationRecordVersion": 4
    }
  ],
  "exif": [
    {
      "tagSet": "Baseline TIFF",
      "fields": {
        "Orientation": [1],
        "XResolution": [[72, 1]],
        "YResolution": [[72, 1]],
        "ResolutionUnit": [2],
        "DateTime": ["2015:12:31 12:42:48"],
        "EXIFIFD": {
          "tagSet": "EXIF",
          "fields": {
            "ExposureTime": [[1, 40]],
            "FNumber": [[11, 5]],
            "PhotographicSensitivity": [40],
            "ExifVersion": ["MDIyMQ=="],
            "DateTimeOriginal": ["2015:12:31 12:42:48"],
            "DateTimeDigitized": ["2015:12:31 12:42:48"]
          }
        }
      }
    }
  ]
}

Note that the view is into the low-level metadata structures without any higher-level categorization. For example, EXIF metadata may be present in the exif key, but it may also be present in the XMP string.

EXIF string values ending with == are typically Base64-encoded binary data.

Copying & mutating

While several different source metadata formats can be read, only XMP can be written. This simplifies the metadata API and reduces crosswalking challenges, as XMP is flexible enough to express EXIF and IIM with no information loss. XMP can also be embedded into many image formats and is widely supported by imaging software.

XMP is serialized as RDF/XML. A very simple XMP packet might look like:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
                     xmlns:aux="http://ns.adobe.com/exif/1.0/aux/"
                     xmlns:xmp="http://ns.adobe.com/xap/1.0/"
                     xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
                     xmlns:dc="http://purl.org/dc/elements/1.1/">
        <aux:Lens>5.4-10.8mm</aux:Lens>
        <aux:FlashCompensation>0/1</aux:FlashCompensation>
        <aux:Firmware>Firmware Version 1.00</aux:Firmware>
        <aux:OwnerName>Ansel Adams</aux:OwnerName>
        <xmp:CreateDate>2002-07-14T09:01:42</xmp:CreateDate>
        <xmp:ModifyDate>2002-07-14T09:01:42</xmp:ModifyDate>
        <xmp:CreatorTool>Photos 1.5</xmp:CreatorTool>
        <photoshop:DateCreated>2002-07-14T09:01:42</photoshop:DateCreated>
        <dc:subject>
            <rdf:Bag>
                <rdf:li>Mountains</rdf:li>
                <rdf:li>Scenery</rdf:li>
                <rdf:li>Landscapes</rdf:li>
            </rdf:Bag>
        </dc:subject>
    </rdf:Description>
</rdf:RDF>

Note that the XMP standard is quite complex and is based on RDF, which itself is complex, so what follows is not implied to be a replacement for the several hundred pages of reading needed to fully grasp those respective standards.

Verbatim copying

The above XMP packet can be copied verbatim from a source image into variant images quite easily:

def metadata(options = {})
  context['metadata']['xmp_string']
end

Adding properties

Continuing with the example above, we assume that the source image's IPTC metadata contains a copyright statement that we'd like to copy into variant images' XMP data.

According to the IPTC IIM specification, the tag most likely to contain a copyright statement is CopyrightNotice (p. 39). We will choose to translate this into a Dublin Core rights element in XMP.

java_import java.io.StringWriter

def metadata(options = {})
  metadata = context['metadata']
  iptc     = metadata['iptc']
  if iptc
    # In IPTC IIM terminology, the basic data structure is the "data set."
    # IIM data may contain multiple data sets, each identified by a "tag" and
    # having a "data field" containing the value. This structure is translated
    # into the delegate context as an array of one-element hashes representing
    # the data sets, each with a tag name key and data field value.
    data_set = iptc.find{ |ds| ds.keys.first == 'CopyrightNotice' }
    if data_set
      copyright = data_set['CopyrightNotice']

      # Add a custom dc:rights property.
      res  = model.createResource
      obj  = model.createLiteral(copyright, false)
      prop = model.createProperty('http://purl.org/dc/elements/1.1/rights')
      stmt = model.createStatement(res, prop, obj)
      model.add(stmt)
  
      # Write the model to XML and return it.
      writer = StringWriter.new
      begin
        model.write(writer);
        return writer.toString
      ensure
        writer.close
      end
    end
  end
end

Modifying properties

In this example, we want to remove any dc:title property that may be present in the source XMP, and add our own. We use Jena, which is bundled into the application, to do this.

java_import java.io.StringWriter

def metadata(options = {})
  metadata = context['metadata']
  model    = metadata['xmp_model']
  if model
    # Search for dc:title properties.
    prop = model.createProperty('http://purl.org/dc/elements/1.1/title')
    it   = model.listStatements(nil, prop, nil)

    # Remove them.
    it.removeNext while it.hasNext

    # Add a custom dc:title property.
    res  = model.createResource
    obj  = model.createLiteral('Hello world', false)
    stmt = model.createStatement(res, prop, obj)
    model.add(stmt)

    # Write the model to XML and return it.
    writer = StringWriter.new
    begin
      model.write(writer)
      return writer.toString
    ensure
      writer.close
    end
  end
  nil
end

Implementation notes

  • After metadata is initially read from a source image, it may be cached, in which case subsequent requests will read it from the cache rather than the source image. In this case, changes to the source image's metadata will not be reflected in the application until the cached metadata becomes invalid and is re-read. If you need to change a source image's metadata, you should manually evict any cached content relating to it afterwards.
  • IPTC IIM supports many different character encodings, but Galia supports only UTF-8, ASCII, and ISO Latin 1, all of which are converted to UTF-8 internally.
  • XMP sidecar files are not supported.