Skip to content

Reference

ImageMetadata

Read and write image metadata properties using native macOS APIs.

Parameters:

Name Type Description Default
filepath FilePath

The path to the image file.

required

Raises:

Type Description
FileNotFoundError

If the file does not exist.

ValueError

If the file is not an image file.

Source code in cgmetadata/classes.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
class ImageMetadata:
    """Read and write image metadata properties using native macOS APIs.

    Args:
        filepath: The path to the image file.

    Raises:
        FileNotFoundError: If the file does not exist.
        ValueError: If the file is not an image file.
    """

    def __init__(self, filepath: FilePath):
        self.filepath = pathlib.Path(filepath).resolve()
        if not self.filepath.exists():
            raise FileNotFoundError(f"File not found: {self.filepath}")
        if not is_image(self.filepath):
            raise ValueError(f"Not an image file: {self.filepath}")
        self._context_manager = False
        self._load()

    @property
    def properties(self) -> dict[str, Any]:
        """Return the metadata properties dictionary from the image.

        The dictionary keys are named 'IPTC', 'EXIF', etc.
        Some of the values are themselves dictionaries.
        For example, the 'IPTC' value is a dictionary of IPTC metadata.

        Reference: https://developer.apple.com/documentation/imageio/image_properties?language=objc
        for more information.
        """

        # change keys to remove the leading '{' and trailing '}'
        # e.g. '{IPTC}' -> 'IPTC' but only if the key starts with '{'
        # also change Exif -> EXIF, WebP -> WEBP to match the other keys
        properties = CFDictionary_to_dict(self._properties)
        properties = {
            key[1:-1] if key.startswith("{") else key: value
            for key, value in properties.items()
        }
        if "Exif" in properties:
            properties["EXIF"] = properties.pop("Exif")
        if "WebP" in properties:
            properties["WEBP"] = properties.pop("WebP")
        return properties

    @property
    def exif(self) -> dict[str, Any]:
        """Return the EXIF properties dictionary from the image."""
        return self.properties.get(EXIF, {})

    @property
    def iptc(self) -> dict[str, Any]:
        """Return the IPTC properties dictionary from the image."""
        return self.properties.get(IPTC, {})

    @property
    def tiff(self) -> dict[str, Any]:
        """Return the TIFF properties dictionary from the image."""
        return self.properties.get(TIFF, {})

    @property
    def gps(self) -> dict[str, Any]:
        """Return the GPS properties dictionary from the image."""
        return self.properties.get(GPS, {})

    @property
    def xmp(self) -> dict[str, Any]:
        """Return the XMP metadata dictionary for the image.

        The dictionary keys are in form "prefix:name", e.g. "dc:creator".
        """
        return metadata_dictionary_from_image_metadata_ref(self._metadata_ref)

    def xmp_dumps(self, header: bool = True) -> str:
        """Return the serialized XMP metadata for the image.

        Args:
            header: If True, include the XMP packet header in the serialized XMP.

        Returns:
            The serialized XMP metadata for the image as a string.
        """
        xmp = metadata_ref_serialize_xmp(self._metadata_ref).decode("utf-8")
        if header:
            xmp = f"{XMP_PACKET_HEADER}\n{xmp}\n{XMP_PACKET_FOOTER}"
        return xmp

    def xmp_dump(self, fp: IO[str], header: bool = True):
        """Write the serialized XMP metadata for the image to a file.

        Args:
            fp: The file pointer to write the XMP metadata to.
            header: If True, include the XMP packet header in the serialized XMP.
        """
        xmp = metadata_ref_serialize_xmp(self._metadata_ref).decode("utf-8")
        if header:
            xmp = XMP_PACKET_HEADER + xmp + XMP_PACKET_FOOTER
        fp.write(xmp)

    def xmp_loads(self, xmp: str):
        """Load XMP metadata from a string.

        Args:
            xmp: The XMP metadata as a string.

        Note:
            This does not write the metadata to the image file.
            Use write() to write the loaded metadata to the image file.
            The XMP standard allows quoted strings to use either single or double quotes.
            For example, exiftool uses single quotes. However, the native macOS APIs
            (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes.
            This does not appear to be documented anywhere in the Apple documentation.
            This function replaces single quotes with double quotes to avoid this issue.
        """
        self._xmp_set_from_str(xmp)

    def xmp_load(self, fp: IO[str]):
        """Load XMP metadata from a file.

        Args:
            fp: The file pointer to read the XMP metadata from.

        Note:
            This does not write the metadata to the image file.
            Use write() to write the loaded metadata to the image file.
            The XMP standard allows quoted strings to use either single or double quotes.
            For example, exiftool uses single quotes. However, the native macOS APIs
            (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes.
            This does not appear to be documented anywhere in the Apple documentation.
            This function replaces single quotes with double quotes to avoid this issue.
        """
        xmp = fp.read()
        self._xmp_set_from_str(xmp)

    def set(
        self,
        group: Literal["EXIF", "IPTC", "TIFF", "GPS", "XMP"],
        key: str,
        value: Any,
    ):
        """Set a metadata property for the image.

        Args:
            group: The metadata group type to set the property for, for example, "IPTC", "XMP"
            key: The key or key path of the metadata property to set;
                for "XMP" metadata, the key is in form "prefix:name", e.g. "dc:creator", "dc:description"...
                for other metadata, the key is the name of the property, e.g. "LensModel", "Make", "Keywords"...
            value: The value to set the metadata property to.

        Note:
            This does not write the metadata to the image file unless used in conjunction with the context manager.
            Use write() to write the metadata to the image file after setting one or more values.
            Metadata keys may be specified as a literal string, e.g. "LensModel" or using
            one of the constants from the ImageIO module, e.g. kCGImagePropertyExifLensModel,
            which are referenced here: https://developer.apple.com/documentation/imageio/exif_dictionary_keys
            These are available in the pyobjc Quartz module as Quartz.kCGImagePropertyExifLensModel, etc.
            You are responsible for passing the correct type of value for the metadata key,
            for example, str or list[str]. See https://github.com/adobe/xmp-docs/tree/master
            for more information on XMP metadata and expected types.
        """
        if group == XMP:
            self._metadata_ref = metadata_ref_set_tag_with_path(
                self._metadata_ref, key, value
            )
        else:
            self._metadata_ref = metadata_ref_set_tag_for_dict(
                self._metadata_ref, group, key, value
            )

    def write(self):
        """Write the metadata to the image file then reloads the metadata from the image."""
        metadata_ref_write_to_file(self.filepath, self._metadata_ref)
        self.reload()

    def reload(self):
        """Reload the metadata from the image file."""
        self._load()

    def asdict(self) -> dict[str, Any]:
        """Return the metadata as a dictionary."""
        dict_data = self.properties.copy()
        dict_data[XMP] = self.xmp
        return dict_data

    def _load(self):
        try:
            del self._metadata_ref
        except AttributeError:
            pass
        try:
            del self._properties
        except AttributeError:
            pass
        properties = load_image_properties(self.filepath)
        self._properties = properties.mutableCopy()
        del properties

        metadata_ref = load_image_metadata_ref(self.filepath)
        self._metadata_ref = metadata_ref_create_mutable_copy(metadata_ref)
        del metadata_ref

    def _xmp_set_from_str(self, xmp: str):
        """Set the XMP metadata from a string representing serialized XMP."""

        # The Apple API requires that the XMP data use double quotes for quoted strings
        # and that the XMP data not contain the XMP packet headers
        xmp = single_quotes_to_double_quotes(xmp)
        xmp = strip_xmp_packet(xmp)
        xmp = xmp.encode("utf-8")
        self._xmp_set_from_bytes(xmp)

    def _xmp_set_from_bytes(self, xmp: bytes):
        """Set the XMP metadata from a bytes object representing serialized XMP."""
        metadata = metadata_ref_create_from_xmp(xmp)
        self._metadata_ref = metadata_ref_create_mutable_copy(metadata)
        del metadata

    def __enter__(self):
        """Enter the context manager."""
        self._context_manager = True
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """Exit the context manager."""
        if self._context_manager:
            self.write()
            self.reload()
        self._context_manager = False

    def __del__(self):
        if self._metadata_ref is not None:
            del self._metadata_ref
        if self._properties is not None:
            del self._properties

exif: dict[str, Any] property

Return the EXIF properties dictionary from the image.

gps: dict[str, Any] property

Return the GPS properties dictionary from the image.

iptc: dict[str, Any] property

Return the IPTC properties dictionary from the image.

properties: dict[str, Any] property

Return the metadata properties dictionary from the image.

The dictionary keys are named 'IPTC', 'EXIF', etc. Some of the values are themselves dictionaries. For example, the 'IPTC' value is a dictionary of IPTC metadata.

Reference: https://developer.apple.com/documentation/imageio/image_properties?language=objc for more information.

tiff: dict[str, Any] property

Return the TIFF properties dictionary from the image.

xmp: dict[str, Any] property

Return the XMP metadata dictionary for the image.

The dictionary keys are in form "prefix:name", e.g. "dc:creator".

__enter__()

Enter the context manager.

Source code in cgmetadata/classes.py
258
259
260
261
def __enter__(self):
    """Enter the context manager."""
    self._context_manager = True
    return self

__exit__(exc_type, exc_value, traceback)

Exit the context manager.

Source code in cgmetadata/classes.py
263
264
265
266
267
268
def __exit__(self, exc_type, exc_value, traceback):
    """Exit the context manager."""
    if self._context_manager:
        self.write()
        self.reload()
    self._context_manager = False

asdict()

Return the metadata as a dictionary.

Source code in cgmetadata/classes.py
219
220
221
222
223
def asdict(self) -> dict[str, Any]:
    """Return the metadata as a dictionary."""
    dict_data = self.properties.copy()
    dict_data[XMP] = self.xmp
    return dict_data

reload()

Reload the metadata from the image file.

Source code in cgmetadata/classes.py
215
216
217
def reload(self):
    """Reload the metadata from the image file."""
    self._load()

set(group, key, value)

Set a metadata property for the image.

Parameters:

Name Type Description Default
group Literal['EXIF', 'IPTC', 'TIFF', 'GPS', 'XMP']

The metadata group type to set the property for, for example, "IPTC", "XMP"

required
key str

The key or key path of the metadata property to set; for "XMP" metadata, the key is in form "prefix:name", e.g. "dc:creator", "dc:description"... for other metadata, the key is the name of the property, e.g. "LensModel", "Make", "Keywords"...

required
value Any

The value to set the metadata property to.

required
Note

This does not write the metadata to the image file unless used in conjunction with the context manager. Use write() to write the metadata to the image file after setting one or more values. Metadata keys may be specified as a literal string, e.g. "LensModel" or using one of the constants from the ImageIO module, e.g. kCGImagePropertyExifLensModel, which are referenced here: https://developer.apple.com/documentation/imageio/exif_dictionary_keys These are available in the pyobjc Quartz module as Quartz.kCGImagePropertyExifLensModel, etc. You are responsible for passing the correct type of value for the metadata key, for example, str or list[str]. See https://github.com/adobe/xmp-docs/tree/master for more information on XMP metadata and expected types.

Source code in cgmetadata/classes.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def set(
    self,
    group: Literal["EXIF", "IPTC", "TIFF", "GPS", "XMP"],
    key: str,
    value: Any,
):
    """Set a metadata property for the image.

    Args:
        group: The metadata group type to set the property for, for example, "IPTC", "XMP"
        key: The key or key path of the metadata property to set;
            for "XMP" metadata, the key is in form "prefix:name", e.g. "dc:creator", "dc:description"...
            for other metadata, the key is the name of the property, e.g. "LensModel", "Make", "Keywords"...
        value: The value to set the metadata property to.

    Note:
        This does not write the metadata to the image file unless used in conjunction with the context manager.
        Use write() to write the metadata to the image file after setting one or more values.
        Metadata keys may be specified as a literal string, e.g. "LensModel" or using
        one of the constants from the ImageIO module, e.g. kCGImagePropertyExifLensModel,
        which are referenced here: https://developer.apple.com/documentation/imageio/exif_dictionary_keys
        These are available in the pyobjc Quartz module as Quartz.kCGImagePropertyExifLensModel, etc.
        You are responsible for passing the correct type of value for the metadata key,
        for example, str or list[str]. See https://github.com/adobe/xmp-docs/tree/master
        for more information on XMP metadata and expected types.
    """
    if group == XMP:
        self._metadata_ref = metadata_ref_set_tag_with_path(
            self._metadata_ref, key, value
        )
    else:
        self._metadata_ref = metadata_ref_set_tag_for_dict(
            self._metadata_ref, group, key, value
        )

write()

Write the metadata to the image file then reloads the metadata from the image.

Source code in cgmetadata/classes.py
210
211
212
213
def write(self):
    """Write the metadata to the image file then reloads the metadata from the image."""
    metadata_ref_write_to_file(self.filepath, self._metadata_ref)
    self.reload()

xmp_dump(fp, header=True)

Write the serialized XMP metadata for the image to a file.

Parameters:

Name Type Description Default
fp IO[str]

The file pointer to write the XMP metadata to.

required
header bool

If True, include the XMP packet header in the serialized XMP.

True
Source code in cgmetadata/classes.py
128
129
130
131
132
133
134
135
136
137
138
def xmp_dump(self, fp: IO[str], header: bool = True):
    """Write the serialized XMP metadata for the image to a file.

    Args:
        fp: The file pointer to write the XMP metadata to.
        header: If True, include the XMP packet header in the serialized XMP.
    """
    xmp = metadata_ref_serialize_xmp(self._metadata_ref).decode("utf-8")
    if header:
        xmp = XMP_PACKET_HEADER + xmp + XMP_PACKET_FOOTER
    fp.write(xmp)

xmp_dumps(header=True)

Return the serialized XMP metadata for the image.

Parameters:

Name Type Description Default
header bool

If True, include the XMP packet header in the serialized XMP.

True

Returns:

Type Description
str

The serialized XMP metadata for the image as a string.

Source code in cgmetadata/classes.py
114
115
116
117
118
119
120
121
122
123
124
125
126
def xmp_dumps(self, header: bool = True) -> str:
    """Return the serialized XMP metadata for the image.

    Args:
        header: If True, include the XMP packet header in the serialized XMP.

    Returns:
        The serialized XMP metadata for the image as a string.
    """
    xmp = metadata_ref_serialize_xmp(self._metadata_ref).decode("utf-8")
    if header:
        xmp = f"{XMP_PACKET_HEADER}\n{xmp}\n{XMP_PACKET_FOOTER}"
    return xmp

xmp_load(fp)

Load XMP metadata from a file.

Parameters:

Name Type Description Default
fp IO[str]

The file pointer to read the XMP metadata from.

required
Note

This does not write the metadata to the image file. Use write() to write the loaded metadata to the image file. The XMP standard allows quoted strings to use either single or double quotes. For example, exiftool uses single quotes. However, the native macOS APIs (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes. This does not appear to be documented anywhere in the Apple documentation. This function replaces single quotes with double quotes to avoid this issue.

Source code in cgmetadata/classes.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def xmp_load(self, fp: IO[str]):
    """Load XMP metadata from a file.

    Args:
        fp: The file pointer to read the XMP metadata from.

    Note:
        This does not write the metadata to the image file.
        Use write() to write the loaded metadata to the image file.
        The XMP standard allows quoted strings to use either single or double quotes.
        For example, exiftool uses single quotes. However, the native macOS APIs
        (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes.
        This does not appear to be documented anywhere in the Apple documentation.
        This function replaces single quotes with double quotes to avoid this issue.
    """
    xmp = fp.read()
    self._xmp_set_from_str(xmp)

xmp_loads(xmp)

Load XMP metadata from a string.

Parameters:

Name Type Description Default
xmp str

The XMP metadata as a string.

required
Note

This does not write the metadata to the image file. Use write() to write the loaded metadata to the image file. The XMP standard allows quoted strings to use either single or double quotes. For example, exiftool uses single quotes. However, the native macOS APIs (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes. This does not appear to be documented anywhere in the Apple documentation. This function replaces single quotes with double quotes to avoid this issue.

Source code in cgmetadata/classes.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def xmp_loads(self, xmp: str):
    """Load XMP metadata from a string.

    Args:
        xmp: The XMP metadata as a string.

    Note:
        This does not write the metadata to the image file.
        Use write() to write the loaded metadata to the image file.
        The XMP standard allows quoted strings to use either single or double quotes.
        For example, exiftool uses single quotes. However, the native macOS APIs
        (CGImageMetadataCreateFromXMPData) returns nil if the XMP data contains single quotes.
        This does not appear to be documented anywhere in the Apple documentation.
        This function replaces single quotes with double quotes to avoid this issue.
    """
    self._xmp_set_from_str(xmp)

VideoMetadata

Read video metadata properties using native macOS APIs.

Parameters:

Name Type Description Default
filepath FilePath

The path to the video file.

required

Raises:

Type Description
FileNotFoundError

If the file does not exist.

ValueError

If the file is not an video file.

Note: Unlike ImageMetadata, this class does not provide write access to the metadata.

Source code in cgmetadata/classes.py
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
class VideoMetadata:
    """Read video metadata properties using native macOS APIs.

    Args:
        filepath: The path to the video file.

    Raises:
        FileNotFoundError: If the file does not exist.
        ValueError: If the file is not an video file.

    Note: Unlike ImageMetadata, this class does not provide write access to the metadata.
    """

    def __init__(self, filepath: FilePath):
        self.filepath = pathlib.Path(filepath).resolve()
        if not self.filepath.exists():
            raise FileNotFoundError(f"File not found: {self.filepath}")
        if not is_video(self.filepath):
            raise ValueError(f"Not a video file: {self.filepath}")
        self._context_manager = False
        self._load()

    @property
    def properties(self) -> dict[str, Any]:
        """Return the metadata properties dictionary from the image.

        The dictionary keys are named with the namespace, such as 'mdta', 'udta'.
        Some of the values are themselves dictionaries.
        """
        return self._properties

    @property
    def xmp(self) -> dict[str, Any]:
        """Return the XMP metadata dictionary for the image.

        The dictionary keys are in form "prefix:name", e.g. "dc:creator".
        """
        return self._properties.get(XMP, {})

    def xmp_dumps(self, header: bool = True) -> str:
        """Return the serialized XMP metadata for the video.

        Args:
            header: If True, include the XMP packet header in the serialized XMP.

        Returns:
            The serialized XMP metadata for the image as a string.
        """
        xmp = self._xmp
        if not header:
            xmp = strip_xmp_packet(xmp)
        return xmp

    def xmp_dump(self, fp: IO[str], header: bool = True):
        """Write the serialized XMP metadata for the video to a file.

        Args:
            fp: The file pointer to write the XMP metadata to.
            header: If True, include the XMP packet header in the serialized XMP.
        """
        xmp = self.xmp_dumps(header)
        xmp = xmp.encode("utf-8")
        fp.write(xmp)

    def reload(self):
        """Reload the metadata from the image file."""
        self._load()

    def asdict(self) -> dict[str, Any]:
        """Return the metadata as a dictionary."""
        dict_data = self._properties.copy()
        return dict_data

    def _load(self):
        try:
            del self._properties
        except AttributeError:
            pass
        self._properties = load_video_metadata(self.filepath)
        self._xmp = load_video_xmp(self.filepath)

properties: dict[str, Any] property

Return the metadata properties dictionary from the image.

The dictionary keys are named with the namespace, such as 'mdta', 'udta'. Some of the values are themselves dictionaries.

xmp: dict[str, Any] property

Return the XMP metadata dictionary for the image.

The dictionary keys are in form "prefix:name", e.g. "dc:creator".

asdict()

Return the metadata as a dictionary.

Source code in cgmetadata/classes.py
351
352
353
354
def asdict(self) -> dict[str, Any]:
    """Return the metadata as a dictionary."""
    dict_data = self._properties.copy()
    return dict_data

reload()

Reload the metadata from the image file.

Source code in cgmetadata/classes.py
347
348
349
def reload(self):
    """Reload the metadata from the image file."""
    self._load()

xmp_dump(fp, header=True)

Write the serialized XMP metadata for the video to a file.

Parameters:

Name Type Description Default
fp IO[str]

The file pointer to write the XMP metadata to.

required
header bool

If True, include the XMP packet header in the serialized XMP.

True
Source code in cgmetadata/classes.py
336
337
338
339
340
341
342
343
344
345
def xmp_dump(self, fp: IO[str], header: bool = True):
    """Write the serialized XMP metadata for the video to a file.

    Args:
        fp: The file pointer to write the XMP metadata to.
        header: If True, include the XMP packet header in the serialized XMP.
    """
    xmp = self.xmp_dumps(header)
    xmp = xmp.encode("utf-8")
    fp.write(xmp)

xmp_dumps(header=True)

Return the serialized XMP metadata for the video.

Parameters:

Name Type Description Default
header bool

If True, include the XMP packet header in the serialized XMP.

True

Returns:

Type Description
str

The serialized XMP metadata for the image as a string.

Source code in cgmetadata/classes.py
322
323
324
325
326
327
328
329
330
331
332
333
334
def xmp_dumps(self, header: bool = True) -> str:
    """Return the serialized XMP metadata for the video.

    Args:
        header: If True, include the XMP packet header in the serialized XMP.

    Returns:
        The serialized XMP metadata for the image as a string.
    """
    xmp = self._xmp
    if not header:
        xmp = strip_xmp_packet(xmp)
    return xmp

metadata_dictionary_from_xmp_packet

Extract key-value pairs from an XMP packet.

Parameters:

Name Type Description Default
xmp str | bytes | __NSCFData

str or bytes for XMP packet

required

Returns: dict with key/value pairs from the XMP packet.

Raises:

Type Description
ValueError

if XMP packet cannot be decoded.

Source code in cgmetadata/xmp.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def metadata_dictionary_from_xmp_packet(
    xmp: str | bytes | __NSCFData,
) -> dict[str, Any]:
    """Extract key-value pairs from an XMP packet.

    Args:
        xmp: str or bytes for XMP packet

    Returns: dict with key/value pairs from the XMP packet.

    Raises:
        ValueError: if XMP packet cannot be decoded.
    """
    with objc.autorelease_pool():
        if isinstance(xmp, (bytes, __NSCFData)):
            xmp = xmp.decode("utf-8")
        xmp = single_quotes_to_double_quotes(xmp)
        xmp = strip_xmp_packet(xmp)
        xmp = xmp.encode("utf-8")
        if mdref := metadata_ref_create_from_xmp(xmp):
            return metadata_dictionary_from_image_metadata_ref(mdref)
        raise ValueError("Failed to create metadata ref from XMP packet")