Added Exif support. Closes #11.
This commit is contained in:
parent
6407a27208
commit
949671aa12
11 changed files with 400 additions and 49 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
Jun 05, 2013 - v0.1.4 Added Exif UUID read support.
|
||||||
|
|
||||||
Jun 02, 2013 - v0.1.3p1 Raising IOErrors when code block size and precinct
|
Jun 02, 2013 - v0.1.3p1 Raising IOErrors when code block size and precinct
|
||||||
sizes are not in harmony. Added statement to docs about upstream library
|
sizes are not in harmony. Added statement to docs about upstream library
|
||||||
dependence. Added roadmap to docs.
|
dependence. Added roadmap to docs.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
---
|
||||||
API
|
API
|
||||||
===
|
---
|
||||||
|
|
||||||
Jp2k
|
Jp2k
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = '0.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.1.3p1'
|
release = '0.1.4'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
************
|
------------
|
||||||
How do I...?
|
How do I...?
|
||||||
************
|
------------
|
||||||
|
|
||||||
Get the code?
|
Get the code?
|
||||||
=============
|
=============
|
||||||
|
|
@ -9,7 +9,7 @@ Go to http://github.com/quintusdias/glymur
|
||||||
|
|
||||||
Display metadata?
|
Display metadata?
|
||||||
=================
|
=================
|
||||||
There are two ways. From the unix command line, the script **jp2dump** is
|
There are two ways. From the unix command line, the script *jp2dump* is
|
||||||
available. ::
|
available. ::
|
||||||
|
|
||||||
$ jp2dump /path/to/glymur/installation/data/nemo.jp2
|
$ jp2dump /path/to/glymur/installation/data/nemo.jp2
|
||||||
|
|
@ -22,8 +22,8 @@ From within Python, it is as simple as printing the Jp2k object, i.e. ::
|
||||||
>>> j = Jp2k(file)
|
>>> j = Jp2k(file)
|
||||||
>>> print(j)
|
>>> print(j)
|
||||||
|
|
||||||
The primary emphasis is on JP2 metadata, but it is possible to
|
This prints the metadata found in the JP2 boxes, but in the case of the
|
||||||
display just raw codestream as well. This will display metadata present in the
|
codestream box, only the main header is printed. It is possible to print
|
||||||
codestream's main header only. ::
|
**only** the codestream information as well, i.e. ::
|
||||||
|
|
||||||
>>> print(j.get_codestream())
|
>>> print(j.get_codestream())
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
==================================
|
||||||
Welcome to glymur's documentation!
|
Welcome to glymur's documentation!
|
||||||
===================================
|
==================================
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
|
|
@ -16,9 +17,9 @@ Contents:
|
||||||
how_do_i
|
how_do_i
|
||||||
roadmap
|
roadmap
|
||||||
|
|
||||||
|
------------------
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
------------------
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
=========================================
|
----------------------------------------
|
||||||
Glymur: a Python interface for JPEG 2000
|
Glymur: a Python interface for JPEG 2000
|
||||||
=========================================
|
----------------------------------------
|
||||||
|
|
||||||
**Glymur** contains a Python interface to the OpenJPEG library
|
**Glymur** contains a Python interface to the OpenJPEG library
|
||||||
which allows linux and mac users to read and write JPEG 2000 files. For more
|
which allows linux and mac users to read and write JPEG 2000 files. For more
|
||||||
|
|
@ -13,15 +13,15 @@ Glymur supports both reading and writing of JPEG 2000 images (part 1). Writing
|
||||||
JPEG 2000 images is currently limited to images that can fit in memory,
|
JPEG 2000 images is currently limited to images that can fit in memory,
|
||||||
however.
|
however.
|
||||||
|
|
||||||
Of particular focus is retrieval of metadata. Reading XMP UUID
|
Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported,
|
||||||
boxes is supported, as the data block consists of XML. There is
|
as is reading XMP UUIDs as the XMP data packet is just XML. There is
|
||||||
some very limited support for reading JPX metadata. For instance,
|
some very limited support for reading JPX metadata. For instance,
|
||||||
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
|
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
|
||||||
be retrieved from such JPX files.
|
be retrieved from such JPX files.
|
||||||
|
|
||||||
------------
|
''''''''''''
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
''''''''''''
|
||||||
glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended.
|
glymur works on Python 2.7 and 3.3. Python 3.3 is strongly recommended.
|
||||||
|
|
||||||
OpenJPEG
|
OpenJPEG
|
||||||
|
|
@ -132,9 +132,9 @@ Windows
|
||||||
-------
|
-------
|
||||||
Not currently supported.
|
Not currently supported.
|
||||||
|
|
||||||
------------------------------------
|
''''''''''''''''''''''''''''''''''''
|
||||||
Installation, Testing, Configuration
|
Installation, Testing, Configuration
|
||||||
------------------------------------
|
''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
From this point forward, python3 will be referred to as just "python".
|
From this point forward, python3 will be referred to as just "python".
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
-------
|
||||||
Roadmap
|
Roadmap
|
||||||
=======
|
-------
|
||||||
|
|
||||||
Here's an incomplete list of what I'd like to focus on in the near future.
|
Here's an incomplete list of what I'd like to focus on in the near future.
|
||||||
|
|
||||||
* continue to monitor upstream changes in the **openjp2** library
|
* continue to monitor upstream changes in the **openjp2** library
|
||||||
* add read support or Exif UUIDs
|
|
||||||
* investigate using CFFI or cython instead of ctypes to wrap **openjp2**
|
* investigate using CFFI or cython instead of ctypes to wrap **openjp2**
|
||||||
|
* add read support for ICC profiles
|
||||||
|
|
||||||
|
|
|
||||||
340
glymur/jp2box.py
340
glymur/jp2box.py
|
|
@ -14,6 +14,7 @@ References
|
||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import pprint
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -1570,6 +1571,9 @@ class UUIDBox(Jp2kBox):
|
||||||
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
uuid_type = ' (XMP)'
|
uuid_type = ' (XMP)'
|
||||||
uuid_data = _pretty_print_xml(self.data)
|
uuid_data = _pretty_print_xml(self.data)
|
||||||
|
elif self.uuid.bytes == b'JpgTiffExif->JP2':
|
||||||
|
uuid_type = ' (Exif)'
|
||||||
|
uuid_data = '\n' + pprint.pformat(self.data)
|
||||||
else:
|
else:
|
||||||
uuid_type = ''
|
uuid_type = ''
|
||||||
uuid_data = '{0} bytes'.format(len(self.data))
|
uuid_data = '{0} bytes'.format(len(self.data))
|
||||||
|
|
@ -1610,7 +1614,7 @@ class UUIDBox(Jp2kBox):
|
||||||
n = offset + length - f.tell()
|
n = offset + length - f.tell()
|
||||||
buffer = f.read(n)
|
buffer = f.read(n)
|
||||||
if kwargs['uuid'] == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
if kwargs['uuid'] == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
# XMP data. Parse as XML. Seems to be a difference between
|
# XMP data. Parse as XML. Seems to be a difference between
|
||||||
# ElementTree in version 2.7 and 3.3.
|
# ElementTree in version 2.7 and 3.3.
|
||||||
if sys.hexversion < 0x03000000:
|
if sys.hexversion < 0x03000000:
|
||||||
parser = ET.XMLParser(encoding='utf-8')
|
parser = ET.XMLParser(encoding='utf-8')
|
||||||
|
|
@ -1619,18 +1623,25 @@ class UUIDBox(Jp2kBox):
|
||||||
text = buffer.decode('utf-8')
|
text = buffer.decode('utf-8')
|
||||||
kwargs['data'] = ET.fromstring(text)
|
kwargs['data'] = ET.fromstring(text)
|
||||||
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
|
elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2':
|
||||||
kwargs['data'] = Exif(buffer).exif_image
|
e = Exif(buffer)
|
||||||
|
d = {}
|
||||||
|
d['Exif'] = e.exif_image
|
||||||
|
d['Photo'] = e.exif_photo
|
||||||
|
d['GPSInfo'] = e.exif_gpsinfo
|
||||||
|
d['Iop'] = e.exif_iop
|
||||||
|
kwargs['data'] = d
|
||||||
else:
|
else:
|
||||||
kwargs['data'] = buffer
|
kwargs['data'] = buffer
|
||||||
box = UUIDBox(**kwargs)
|
box = UUIDBox(**kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
|
||||||
class Exif:
|
class Exif:
|
||||||
"""
|
"""
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
buffer : bytes
|
buffer : bytes
|
||||||
Raw byte stream consisting of the UUID data.
|
Raw byte stream consisting of the UUID data.
|
||||||
endian : str
|
endian : str
|
||||||
Either '<' for big-endian, or '>' for little-endian.
|
Either '<' for big-endian, or '>' for little-endian.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1657,7 +1668,7 @@ class Exif:
|
||||||
offset = data[3]
|
offset = data[3]
|
||||||
|
|
||||||
# This is the 'Exif Image' portion.
|
# This is the 'Exif Image' portion.
|
||||||
exif = ExifImageIfd(self.endian, buffer[6:], offset)
|
exif = ExifImageIfd(self.endian, buffer[6:], offset)
|
||||||
self.exif_image = exif.ifd
|
self.exif_image = exif.ifd
|
||||||
|
|
||||||
if 'ExifTag' in self.exif_image.keys():
|
if 'ExifTag' in self.exif_image.keys():
|
||||||
|
|
@ -1677,12 +1688,13 @@ class Exif:
|
||||||
gps = ExifGPSInfoIfd(self.endian, buffer[6:], offset)
|
gps = ExifGPSInfoIfd(self.endian, buffer[6:], offset)
|
||||||
self.exif_gpsinfo = gps.ifd
|
self.exif_gpsinfo = gps.ifd
|
||||||
|
|
||||||
|
|
||||||
class Ifd:
|
class Ifd:
|
||||||
"""
|
"""
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
buffer : bytes
|
buffer : bytes
|
||||||
Raw byte stream consisting of the UUID data.
|
Raw byte stream consisting of the UUID data.
|
||||||
datatype2fmt : dictionary
|
datatype2fmt : dictionary
|
||||||
Class attribute, maps the TIFF enumerated datatype to the python
|
Class attribute, maps the TIFF enumerated datatype to the python
|
||||||
datatype and data width.
|
datatype and data width.
|
||||||
|
|
@ -1706,36 +1718,45 @@ class Ifd:
|
||||||
self.endian = endian
|
self.endian = endian
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
|
|
||||||
self.num_tags, = struct.unpack(endian + 'H', buffer[offset:offset + 2])
|
self.num_tags, = struct.unpack(endian + 'H',
|
||||||
|
buffer[offset:offset + 2])
|
||||||
|
|
||||||
fmt = self.endian + 'HHII' * self.num_tags
|
fmt = self.endian + 'HHII' * self.num_tags
|
||||||
data = struct.unpack(fmt, buffer[offset + 2:offset + 2 + self.num_tags * 12])
|
ifd_buffer = buffer[offset + 2:offset + 2 + self.num_tags * 12]
|
||||||
|
data = struct.unpack(fmt, ifd_buffer)
|
||||||
self.raw_ifd = {}
|
self.raw_ifd = {}
|
||||||
for j, tag in enumerate(data[0::4]):
|
for j, tag in enumerate(data[0::4]):
|
||||||
tag_entry = buffer[offset + 2 + j * 12 + 8:offset + 2 + j * 12 + 8 + 4]
|
# The offset to the tag offset/payload is the offset to the IFD
|
||||||
payload = self.parse_tag(data[j * 4 + 1],
|
# plus 2 bytes for the number of tags plus 12 bytes for each
|
||||||
data[j * 4 + 2],
|
# tag entry plus 8 bytes to the offset/payload itself.
|
||||||
tag_entry)
|
toffp = buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4]
|
||||||
self.raw_ifd[tag] = payload
|
tag_data = self.parse_tag(data[j * 4 + 1],
|
||||||
|
data[j * 4 + 2],
|
||||||
|
toffp)
|
||||||
|
self.raw_ifd[tag] = tag_data
|
||||||
|
|
||||||
def parse_tag(self, dtype, count, offset_buf):
|
def parse_tag(self, dtype, count, offset_buf):
|
||||||
"""Interpret an Exif image tag data payload.
|
"""Interpret an Exif image tag data payload.
|
||||||
"""
|
"""
|
||||||
fmt = self.datatype2fmt[dtype][0] * count
|
fmt = self.datatype2fmt[dtype][0] * count
|
||||||
payload_size = self.datatype2fmt[dtype][1] * count
|
payload_size = self.datatype2fmt[dtype][1] * count
|
||||||
|
|
||||||
if payload_size <= 4:
|
if payload_size <= 4:
|
||||||
# Interpret the payload from the 4 bytes in the tag entry.
|
# Interpret the payload from the 4 bytes in the tag entry.
|
||||||
target_buffer = offset_buf[:payload_size]
|
target_buffer = offset_buf[:payload_size]
|
||||||
else:
|
else:
|
||||||
# Interpret the payload at the offset specified by the 4 bytes in the
|
# Interpret the payload at the offset specified by the 4 bytes in
|
||||||
# tag entry.
|
# the tag entry.
|
||||||
offset, = struct.unpack(self.endian + 'I', offset_buf)
|
offset, = struct.unpack(self.endian + 'I', offset_buf)
|
||||||
target_buffer = self.buffer[offset:offset + payload_size]
|
target_buffer = self.buffer[offset:offset + payload_size]
|
||||||
|
|
||||||
if dtype == 2:
|
if dtype == 2:
|
||||||
# ASCII
|
# ASCII
|
||||||
payload = target_buffer.decode('utf-8').rstrip()
|
if sys.hexversion < 0x03000000:
|
||||||
|
payload = target_buffer.rstrip('\x00')
|
||||||
|
else:
|
||||||
|
payload = target_buffer.decode('utf-8').rstrip('\x00')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
payload = struct.unpack(self.endian + fmt, target_buffer)
|
payload = struct.unpack(self.endian + fmt, target_buffer)
|
||||||
if dtype == 5 or dtype == 10:
|
if dtype == 5 or dtype == 10:
|
||||||
|
|
@ -1746,22 +1767,226 @@ class Ifd:
|
||||||
rational_payload.append(value)
|
rational_payload.append(value)
|
||||||
payload = rational_payload
|
payload = rational_payload
|
||||||
if count == 1:
|
if count == 1:
|
||||||
# If just a single value, then return a scalar instead of a
|
# If just a single value, then return a scalar instead of a
|
||||||
# tuple.
|
# tuple.
|
||||||
payload = payload[0]
|
payload = payload[0]
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
class ExifImageIfd(Ifd):
|
class ExifImageIfd(Ifd):
|
||||||
tagnum2name = {271: 'Make',
|
"""
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
tagnum2name : dict
|
||||||
|
Maps Exif image tag numbers to the tag names.
|
||||||
|
ifd : dict
|
||||||
|
Maps tag names to tag values.
|
||||||
|
"""
|
||||||
|
tagnum2name = {11: 'ProcessingSoftware',
|
||||||
|
254: 'NewSubfileType',
|
||||||
|
255: 'SubfileType',
|
||||||
|
256: 'ImageWidth',
|
||||||
|
257: 'ImageLength',
|
||||||
|
258: 'BitsPerSample',
|
||||||
|
259: 'Compression',
|
||||||
|
262: 'PhotometricInterpretation',
|
||||||
|
263: 'Threshholding',
|
||||||
|
264: 'CellWidth',
|
||||||
|
265: 'CellLength',
|
||||||
|
266: 'FillOrder',
|
||||||
|
269: 'DocumentName',
|
||||||
|
270: 'ImageDescription',
|
||||||
|
271: 'Make',
|
||||||
272: 'Model',
|
272: 'Model',
|
||||||
|
273: 'StripOffsets',
|
||||||
|
274: 'Orientation',
|
||||||
|
277: 'SamplesPerPixel',
|
||||||
|
278: 'RowsPerStrip',
|
||||||
|
279: 'StripByteCounts',
|
||||||
282: 'XResolution',
|
282: 'XResolution',
|
||||||
283: 'YResolution',
|
283: 'YResolution',
|
||||||
|
284: 'PlanarConfiguration',
|
||||||
|
290: 'GrayResponseUnit',
|
||||||
|
291: 'GrayResponseCurve',
|
||||||
|
292: 'T4Options',
|
||||||
|
293: 'T6Options',
|
||||||
296: 'ResolutionUnit',
|
296: 'ResolutionUnit',
|
||||||
|
301: 'TransferFunction',
|
||||||
|
305: 'Software',
|
||||||
|
306: 'DateTime',
|
||||||
|
315: 'Artist',
|
||||||
|
316: 'HostComputer',
|
||||||
|
317: 'Predictor',
|
||||||
|
318: 'WhitePoint',
|
||||||
|
319: 'PrimaryChromaticities',
|
||||||
|
320: 'ColorMap',
|
||||||
|
321: 'HalftoneHints',
|
||||||
|
322: 'TileWidth',
|
||||||
|
323: 'TileLength',
|
||||||
|
324: 'TileOffsets',
|
||||||
|
325: 'TileByteCounts',
|
||||||
|
330: 'SubIFDs',
|
||||||
|
332: 'InkSet',
|
||||||
|
333: 'InkNames',
|
||||||
|
334: 'NumberOfInks',
|
||||||
|
336: 'DotRange',
|
||||||
|
337: 'TargetPrinter',
|
||||||
|
338: 'ExtraSamples',
|
||||||
|
339: 'SampleFormat',
|
||||||
|
340: 'SMinSampleValue',
|
||||||
|
341: 'SMaxSampleValue',
|
||||||
|
342: 'TransferRange',
|
||||||
|
343: 'ClipPath',
|
||||||
|
344: 'XClipPathUnits',
|
||||||
|
345: 'YClipPathUnits',
|
||||||
|
346: 'Indexed',
|
||||||
|
347: 'JPEGTables',
|
||||||
|
351: 'OPIProxy',
|
||||||
|
512: 'JPEGProc',
|
||||||
|
513: 'JPEGInterchangeFormat',
|
||||||
|
514: 'JPEGInterchangeFormatLength',
|
||||||
|
515: 'JPEGRestartInterval',
|
||||||
|
517: 'JPEGLosslessPredictors',
|
||||||
|
518: 'JPEGPointTransforms',
|
||||||
|
519: 'JPEGQTables',
|
||||||
|
520: 'JPEGDCTables',
|
||||||
|
521: 'JPEGACTables',
|
||||||
|
529: 'YCbCrCoefficients',
|
||||||
|
530: 'YCbCrSubSampling',
|
||||||
531: 'YCbCrPositioning',
|
531: 'YCbCrPositioning',
|
||||||
|
532: 'ReferenceBlackWhite',
|
||||||
|
700: 'XMLPacket',
|
||||||
|
18246: 'Rating',
|
||||||
|
18249: 'RatingPercent',
|
||||||
|
32781: 'ImageID',
|
||||||
|
33421: 'CFARepeatPatternDim',
|
||||||
|
33422: 'CFAPattern',
|
||||||
|
33423: 'BatteryLevel',
|
||||||
|
33432: 'Copyright',
|
||||||
|
33434: 'ExposureTime',
|
||||||
|
33437: 'FNumber',
|
||||||
|
33723: 'IPTCNAA',
|
||||||
|
34377: 'ImageResources',
|
||||||
34665: 'ExifTag',
|
34665: 'ExifTag',
|
||||||
34853: 'GPSTag'}
|
34675: 'InterColorProfile',
|
||||||
|
34850: 'ExposureProgram',
|
||||||
|
34852: 'SpectralSensitivity',
|
||||||
|
34853: 'GPSTag',
|
||||||
|
34855: 'ISOSpeedRatings',
|
||||||
|
34856: 'OECF',
|
||||||
|
34857: 'Interlace',
|
||||||
|
34858: 'TimeZoneOffset',
|
||||||
|
34859: 'SelfTimerMode',
|
||||||
|
36867: 'DateTimeOriginal',
|
||||||
|
37122: 'CompressedBitsPerPixel',
|
||||||
|
37377: 'ShutterSpeedValue',
|
||||||
|
37378: 'ApertureValue',
|
||||||
|
37379: 'BrightnessValue',
|
||||||
|
37380: 'ExposureBiasValue',
|
||||||
|
37381: 'MaxApertureValue',
|
||||||
|
37382: 'SubjectDistance',
|
||||||
|
37383: 'MeteringMode',
|
||||||
|
37384: 'LightSource',
|
||||||
|
37385: 'Flash',
|
||||||
|
37386: 'FocalLength',
|
||||||
|
37387: 'FlashEnergy',
|
||||||
|
37388: 'SpatialFrequencyResponse',
|
||||||
|
37389: 'Noise',
|
||||||
|
37390: 'FocalPlaneXResolution',
|
||||||
|
37391: 'FocalPlaneYResolution',
|
||||||
|
37392: 'FocalPlaneResolutionUnit',
|
||||||
|
37393: 'ImageNumber',
|
||||||
|
37394: 'SecurityClassification',
|
||||||
|
37395: 'ImageHistory',
|
||||||
|
37396: 'SubjectLocation',
|
||||||
|
37397: 'ExposureIndex',
|
||||||
|
37398: 'TIFFEPStandardID',
|
||||||
|
37399: 'SensingMethod',
|
||||||
|
40091: 'XPTitle',
|
||||||
|
40092: 'XPComment',
|
||||||
|
40093: 'XPAuthor',
|
||||||
|
40094: 'XPKeywords',
|
||||||
|
40095: 'XPSubject',
|
||||||
|
50341: 'PrintImageMatching',
|
||||||
|
50706: 'DNGVersion',
|
||||||
|
50707: 'DNGBackwardVersion',
|
||||||
|
50708: 'UniqueCameraModel',
|
||||||
|
50709: 'LocalizedCameraModel',
|
||||||
|
50710: 'CFAPlaneColor',
|
||||||
|
50711: 'CFALayout',
|
||||||
|
50712: 'LinearizationTable',
|
||||||
|
50713: 'BlackLevelRepeatDim',
|
||||||
|
50714: 'BlackLevel',
|
||||||
|
50715: 'BlackLevelDeltaH',
|
||||||
|
50716: 'BlackLevelDeltaV',
|
||||||
|
50717: 'WhiteLevel',
|
||||||
|
50718: 'DefaultScale',
|
||||||
|
50719: 'DefaultCropOrigin',
|
||||||
|
50720: 'DefaultCropSize',
|
||||||
|
50721: 'ColorMatrix1',
|
||||||
|
50722: 'ColorMatrix2',
|
||||||
|
50723: 'CameraCalibration1',
|
||||||
|
50724: 'CameraCalibration2',
|
||||||
|
50725: 'ReductionMatrix1',
|
||||||
|
50726: 'ReductionMatrix2',
|
||||||
|
50727: 'AnalogBalance',
|
||||||
|
50728: 'AsShotNeutral',
|
||||||
|
50729: 'AsShotWhiteXY',
|
||||||
|
50730: 'BaselineExposure',
|
||||||
|
50731: 'BaselineNoise',
|
||||||
|
50732: 'BaselineSharpness',
|
||||||
|
50733: 'BayerGreenSplit',
|
||||||
|
50734: 'LinearResponseLimit',
|
||||||
|
50735: 'CameraSerialNumber',
|
||||||
|
50736: 'LensInfo',
|
||||||
|
50737: 'ChromaBlurRadius',
|
||||||
|
50738: 'AntiAliasStrength',
|
||||||
|
50739: 'ShadowScale',
|
||||||
|
50740: 'DNGPrivateData',
|
||||||
|
50741: 'MakerNoteSafety',
|
||||||
|
50778: 'CalibrationIlluminant1',
|
||||||
|
50779: 'CalibrationIlluminant2',
|
||||||
|
50780: 'BestQualityScale',
|
||||||
|
50781: 'RawDataUniqueID',
|
||||||
|
50827: 'OriginalRawFileName',
|
||||||
|
50828: 'OriginalRawFileData',
|
||||||
|
50829: 'ActiveArea',
|
||||||
|
50830: 'MaskedAreas',
|
||||||
|
50831: 'AsShotICCProfile',
|
||||||
|
50832: 'AsShotPreProfileMatrix',
|
||||||
|
50833: 'CurrentICCProfile',
|
||||||
|
50834: 'CurrentPreProfileMatrix',
|
||||||
|
50879: 'ColorimetricReference',
|
||||||
|
50931: 'CameraCalibrationSignature',
|
||||||
|
50932: 'ProfileCalibrationSignature',
|
||||||
|
50934: 'AsShotProfileName',
|
||||||
|
50935: 'NoiseReductionApplied',
|
||||||
|
50936: 'ProfileName',
|
||||||
|
50937: 'ProfileHueSatMapDims',
|
||||||
|
50938: 'ProfileHueSatMapData1',
|
||||||
|
50939: 'ProfileHueSatMapData2',
|
||||||
|
50940: 'ProfileToneCurve',
|
||||||
|
50941: 'ProfileEmbedPolicy',
|
||||||
|
50942: 'ProfileCopyright',
|
||||||
|
50964: 'ForwardMatrix1',
|
||||||
|
50965: 'ForwardMatrix2',
|
||||||
|
50966: 'PreviewApplicationName',
|
||||||
|
50967: 'PreviewApplicationVersion',
|
||||||
|
50968: 'PreviewSettingsName',
|
||||||
|
50969: 'PreviewSettingsDigest',
|
||||||
|
50970: 'PreviewColorSpace',
|
||||||
|
50971: 'PreviewDateTime',
|
||||||
|
50972: 'RawImageDigest',
|
||||||
|
50973: 'OriginalRawFileDigest',
|
||||||
|
50974: 'SubTileBlockSize',
|
||||||
|
50975: 'RowInterleaveFactor',
|
||||||
|
50981: 'ProfileLookTableDims',
|
||||||
|
50982: 'ProfileLookTableData',
|
||||||
|
51008: 'OpcodeList1',
|
||||||
|
51009: 'OpcodeList2',
|
||||||
|
51022: 'OpcodeList3',
|
||||||
|
51041: 'NoiseProfile'}
|
||||||
|
|
||||||
def __init__(self, endian, buffer, offset):
|
def __init__(self, endian, buffer, offset):
|
||||||
Ifd.__init__(self, endian, buffer, offset)
|
Ifd.__init__(self, endian, buffer, offset)
|
||||||
|
|
@ -1772,18 +1997,77 @@ class ExifImageIfd(Ifd):
|
||||||
tag_name = self.tagnum2name[tag]
|
tag_name = self.tagnum2name[tag]
|
||||||
self.ifd[tag_name] = value
|
self.ifd[tag_name] = value
|
||||||
|
|
||||||
|
|
||||||
class ExifPhotoIfd(Ifd):
|
class ExifPhotoIfd(Ifd):
|
||||||
tagnum2name = {34855: 'ISOSpeedRatings',
|
tagnum2name = {33434: 'ExposureTime',
|
||||||
|
33437: 'FNumber',
|
||||||
|
34850: 'ExposureProgram',
|
||||||
|
34852: 'SpectralSensitivity',
|
||||||
|
34855: 'ISOSpeedRatings',
|
||||||
|
34856: 'OECF',
|
||||||
|
34864: 'SensitivityType',
|
||||||
|
34865: 'StandardOutputSensitivity',
|
||||||
|
34866: 'RecommendedExposureIndex',
|
||||||
|
34867: 'ISOSpeed',
|
||||||
|
34868: 'ISOSpeedLatitudeyyy',
|
||||||
|
34869: 'ISOSpeedLatitudezzz',
|
||||||
36864: 'ExifVersion',
|
36864: 'ExifVersion',
|
||||||
36867: 'DateTimeOriginal',
|
36867: 'DateTimeOriginal',
|
||||||
36868: 'DateTimeDigitized',
|
36868: 'DateTimeDigitized',
|
||||||
37121: 'ComponentsConfiguration',
|
37121: 'ComponentsConfiguration',
|
||||||
|
37122: 'CompressedBitsPerPixel',
|
||||||
|
37377: 'ShutterSpeedValue',
|
||||||
|
37378: 'ApertureValue',
|
||||||
|
37379: 'BrightnessValue',
|
||||||
|
37380: 'ExposureBiasValue',
|
||||||
|
37381: 'MaxApertureValue',
|
||||||
|
37382: 'SubjectDistance',
|
||||||
|
37383: 'MeteringMode',
|
||||||
|
37384: 'LightSource',
|
||||||
|
37385: 'Flash',
|
||||||
37386: 'FocalLength',
|
37386: 'FocalLength',
|
||||||
|
37396: 'SubjectArea',
|
||||||
|
37500: 'MakerNote',
|
||||||
|
37510: 'UserComment',
|
||||||
|
37520: 'SubSecTime',
|
||||||
|
37521: 'SubSecTimeOriginal',
|
||||||
|
37522: 'SubSecTimeDigitized',
|
||||||
40960: 'FlashpixVersion',
|
40960: 'FlashpixVersion',
|
||||||
40961: 'ColorSpace',
|
40961: 'ColorSpace',
|
||||||
40962: 'PixelXDimension',
|
40962: 'PixelXDimension',
|
||||||
40963: 'PixelYDimension',
|
40963: 'PixelYDimension',
|
||||||
40965: 'InteroperabilityTag'}
|
40964: 'RelatedSoundFile',
|
||||||
|
40965: 'InteroperabilityTag',
|
||||||
|
41483: 'FlashEnergy',
|
||||||
|
41484: 'SpatialFrequencyResponse',
|
||||||
|
41486: 'FocalPlaneXResolution',
|
||||||
|
41487: 'FocalPlaneYResolution',
|
||||||
|
41488: 'FocalPlaneResolutionUnit',
|
||||||
|
41492: 'SubjectLocation',
|
||||||
|
41493: 'ExposureIndex',
|
||||||
|
41495: 'SensingMethod',
|
||||||
|
41728: 'FileSource',
|
||||||
|
41729: 'SceneType',
|
||||||
|
41730: 'CFAPattern',
|
||||||
|
41985: 'CustomRendered',
|
||||||
|
41986: 'ExposureMode',
|
||||||
|
41987: 'WhiteBalance',
|
||||||
|
41988: 'DigitalZoomRatio',
|
||||||
|
41989: 'FocalLengthIn35mmFilm',
|
||||||
|
41990: 'SceneCaptureType',
|
||||||
|
41991: 'GainControl',
|
||||||
|
41992: 'Contrast',
|
||||||
|
41993: 'Saturation',
|
||||||
|
41994: 'Sharpness',
|
||||||
|
41995: 'DeviceSettingDescription',
|
||||||
|
41996: 'SubjectDistanceRange',
|
||||||
|
42016: 'ImageUniqueID',
|
||||||
|
42032: 'CameraOwnerName',
|
||||||
|
42033: 'BodySerialNumber',
|
||||||
|
42034: 'LensSpecification',
|
||||||
|
42035: 'LensMake',
|
||||||
|
42036: 'LensModel',
|
||||||
|
42037: 'LensSerialNumber'}
|
||||||
|
|
||||||
def __init__(self, endian, buffer, offset):
|
def __init__(self, endian, buffer, offset):
|
||||||
Ifd.__init__(self, endian, buffer, offset)
|
Ifd.__init__(self, endian, buffer, offset)
|
||||||
|
|
@ -1794,6 +2078,7 @@ class ExifPhotoIfd(Ifd):
|
||||||
tag_name = self.tagnum2name[tag]
|
tag_name = self.tagnum2name[tag]
|
||||||
self.ifd[tag_name] = value
|
self.ifd[tag_name] = value
|
||||||
|
|
||||||
|
|
||||||
class ExifGPSInfoIfd(Ifd):
|
class ExifGPSInfoIfd(Ifd):
|
||||||
tagnum2name = {0: 'GPSVersionID',
|
tagnum2name = {0: 'GPSVersionID',
|
||||||
1: 'GPSLatitudeRef',
|
1: 'GPSLatitudeRef',
|
||||||
|
|
@ -1836,6 +2121,7 @@ class ExifGPSInfoIfd(Ifd):
|
||||||
tag_name = self.tagnum2name[tag]
|
tag_name = self.tagnum2name[tag]
|
||||||
self.ifd[tag_name] = value
|
self.ifd[tag_name] = value
|
||||||
|
|
||||||
|
|
||||||
class ExifInteroperabilityIfd(Ifd):
|
class ExifInteroperabilityIfd(Ifd):
|
||||||
tagnum2name = {1: 'InteroperabilityIndex',
|
tagnum2name = {1: 'InteroperabilityIndex',
|
||||||
2: 'InteroperabilityVersion',
|
2: 'InteroperabilityVersion',
|
||||||
|
|
@ -1875,6 +2161,7 @@ _box_with_id = {
|
||||||
'uuid': UUIDBox,
|
'uuid': UUIDBox,
|
||||||
'xml ': XMLBox}
|
'xml ': XMLBox}
|
||||||
|
|
||||||
|
|
||||||
def _indent(elem, level=0):
|
def _indent(elem, level=0):
|
||||||
"""Recipe for pretty printing XML. Please see
|
"""Recipe for pretty printing XML. Please see
|
||||||
|
|
||||||
|
|
@ -1894,6 +2181,7 @@ def _indent(elem, level=0):
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
if level and (not elem.tail or not elem.tail.strip()):
|
||||||
elem.tail = i
|
elem.tail = i
|
||||||
|
|
||||||
|
|
||||||
def _pretty_print_xml(xml, level=0):
|
def _pretty_print_xml(xml, level=0):
|
||||||
"""Pretty print XML data.
|
"""Pretty print XML data.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ class TestSuiteNegative(unittest.TestCase):
|
||||||
ofile.write(data, cbsize=(13, 12))
|
ofile.write(data, cbsize=(13, 12))
|
||||||
|
|
||||||
def test_codeblock_size_with_precinct_size(self):
|
def test_codeblock_size_with_precinct_size(self):
|
||||||
# Seems like code block sizes should never exceed half that of
|
# Seems like code block sizes should never exceed half that of
|
||||||
# precinct size.
|
# precinct size.
|
||||||
ifile = Jp2k(self.jp2file)
|
ifile = Jp2k(self.jp2file)
|
||||||
data = ifile.read(reduce=3)
|
data = ifile.read(reduce=3)
|
||||||
|
|
|
||||||
|
|
@ -810,5 +810,63 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_exif_uuid(self):
|
||||||
|
j = glymur.Jp2k(self.jp2file)
|
||||||
|
|
||||||
|
print(j.box[3])
|
||||||
|
actual = sys.stdout.getvalue().strip()
|
||||||
|
|
||||||
|
lines = ["UUID Box (uuid) @ (77, 638)",
|
||||||
|
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
|
||||||
|
" UUID Data: ",
|
||||||
|
"{'Exif': {'ExifTag': 138,",
|
||||||
|
" 'GPSTag': 354,",
|
||||||
|
" 'Make': 'HTC',",
|
||||||
|
" 'Model': 'HTC Glacier',",
|
||||||
|
" 'ResolutionUnit': 2,",
|
||||||
|
" 'XResolution': 72.0,",
|
||||||
|
" 'YCbCrPositioning': 1,",
|
||||||
|
" 'YResolution': 72.0},",
|
||||||
|
" 'GPSInfo': {'GPSAltitude': 0.0,",
|
||||||
|
" 'GPSAltitudeRef': 0,",
|
||||||
|
" 'GPSDateStamp': '2013:02:09',",
|
||||||
|
" 'GPSLatitude': [42.0, 20.0, 33.61],",
|
||||||
|
" 'GPSLatitudeRef': 'N',",
|
||||||
|
" 'GPSLongitude': [71.0, 5.0, 17.32],",
|
||||||
|
" 'GPSLongitudeRef': 'W',",
|
||||||
|
" 'GPSMapDatum': 'WGS-84',",
|
||||||
|
" 'GPSProcessingMethod': (65,",
|
||||||
|
" 83,",
|
||||||
|
" 67,",
|
||||||
|
" 73,",
|
||||||
|
" 73,",
|
||||||
|
" 0,",
|
||||||
|
" 0,",
|
||||||
|
" 0,",
|
||||||
|
" 78,",
|
||||||
|
" 69,",
|
||||||
|
" 84,",
|
||||||
|
" 87,",
|
||||||
|
" 79,",
|
||||||
|
" 82,",
|
||||||
|
" 75),",
|
||||||
|
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
|
||||||
|
" 'GPSVersionID': (2, 2, 0)},",
|
||||||
|
" 'Iop': None,",
|
||||||
|
" 'Photo': {'ColorSpace': 1,",
|
||||||
|
" 'ComponentsConfiguration': (1, 2, 3, 0),",
|
||||||
|
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
|
||||||
|
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
|
||||||
|
" 'ExifVersion': (48, 50, 50, 48),",
|
||||||
|
" 'FlashpixVersion': (48, 49, 48, 48),",
|
||||||
|
" 'FocalLength': 3.53,",
|
||||||
|
" 'ISOSpeedRatings': 76,",
|
||||||
|
" 'InteroperabilityTag': 324,",
|
||||||
|
" 'PixelXDimension': 2528,",
|
||||||
|
" 'PixelYDimension': 1424}}"]
|
||||||
|
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -1,7 +1,7 @@
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.1.3p1',
|
'version': '0.1.4',
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue