Packaged basic Piexif module
This commit is contained in:
parent
366b771b72
commit
c6eb6148cc
13 changed files with 1850 additions and 1 deletions
|
|
@ -8,7 +8,7 @@ import logging
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
import piexif
|
from openflexure_microscope.camera import piexif
|
||||||
|
|
||||||
PIL_FORMATS = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF']
|
PIL_FORMATS = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF']
|
||||||
EXIF_FORMATS = ['JPG', 'JPEG', 'TIF', 'TIFF']
|
EXIF_FORMATS = ['JPG', 'JPEG', 'TIF', 'TIFF']
|
||||||
|
|
|
||||||
21
openflexure_microscope/camera/piexif/LICENSE.txt
Normal file
21
openflexure_microscope/camera/piexif/LICENSE.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014, 2015 hMatoba
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
11
openflexure_microscope/camera/piexif/__init__.py
Normal file
11
openflexure_microscope/camera/piexif/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from ._remove import remove
|
||||||
|
from ._load import load
|
||||||
|
from ._dump import dump
|
||||||
|
from ._transplant import transplant
|
||||||
|
from ._insert import insert
|
||||||
|
from ._exif import *
|
||||||
|
from ._exceptions import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
VERSION = '1.1.2'
|
||||||
94
openflexure_microscope/camera/piexif/_common.py
Normal file
94
openflexure_microscope/camera/piexif/_common.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from ._exceptions import InvalidImageDataError
|
||||||
|
|
||||||
|
|
||||||
|
def split_into_segments(data):
|
||||||
|
"""Slices JPEG meta data into a list from JPEG binary data.
|
||||||
|
"""
|
||||||
|
if data[0:2] != b"\xff\xd8":
|
||||||
|
raise InvalidImageDataError("Given data isn't JPEG.")
|
||||||
|
|
||||||
|
head = 2
|
||||||
|
segments = [b"\xff\xd8"]
|
||||||
|
while 1:
|
||||||
|
if data[head: head + 2] == b"\xff\xda":
|
||||||
|
segments.append(data[head:])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
length = struct.unpack(">H", data[head + 2: head + 4])[0]
|
||||||
|
endPoint = head + length + 2
|
||||||
|
seg = data[head: endPoint]
|
||||||
|
segments.append(seg)
|
||||||
|
head = endPoint
|
||||||
|
|
||||||
|
if (head >= len(data)):
|
||||||
|
raise InvalidImageDataError("Wrong JPEG data.")
|
||||||
|
return segments
|
||||||
|
|
||||||
|
def read_exif_from_file(filename):
|
||||||
|
"""Slices JPEG meta data into a list from JPEG binary data.
|
||||||
|
"""
|
||||||
|
f = open(filename, "rb")
|
||||||
|
data = f.read(6)
|
||||||
|
|
||||||
|
if data[0:2] != b"\xff\xd8":
|
||||||
|
raise InvalidImageDataError("Given data isn't JPEG.")
|
||||||
|
|
||||||
|
head = data[2:6]
|
||||||
|
HEAD_LENGTH = 4
|
||||||
|
exif = None
|
||||||
|
while 1:
|
||||||
|
length = struct.unpack(">H", head[2: 4])[0]
|
||||||
|
|
||||||
|
if head[:2] == b"\xff\xe1":
|
||||||
|
segment_data = f.read(length - 2)
|
||||||
|
if segment_data[:4] != b'Exif':
|
||||||
|
head = f.read(HEAD_LENGTH)
|
||||||
|
continue
|
||||||
|
exif = head + segment_data
|
||||||
|
break
|
||||||
|
elif head[0:1] == b"\xff":
|
||||||
|
f.read(length - 2)
|
||||||
|
head = f.read(HEAD_LENGTH)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
return exif
|
||||||
|
|
||||||
|
def get_exif_seg(segments):
|
||||||
|
"""Returns Exif from JPEG meta data list
|
||||||
|
"""
|
||||||
|
for seg in segments:
|
||||||
|
if seg[0:2] == b"\xff\xe1" and seg[4:10] == b"Exif\x00\x00":
|
||||||
|
return seg
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def merge_segments(segments, exif=b""):
|
||||||
|
"""Merges Exif with APP0 and APP1 manipulations.
|
||||||
|
"""
|
||||||
|
if segments[1][0:2] == b"\xff\xe0" and \
|
||||||
|
segments[2][0:2] == b"\xff\xe1" and \
|
||||||
|
segments[2][4:10] == b"Exif\x00\x00":
|
||||||
|
if exif:
|
||||||
|
segments[2] = exif
|
||||||
|
segments.pop(1)
|
||||||
|
elif exif is None:
|
||||||
|
segments.pop(2)
|
||||||
|
else:
|
||||||
|
segments.pop(1)
|
||||||
|
elif segments[1][0:2] == b"\xff\xe0":
|
||||||
|
if exif:
|
||||||
|
segments[1] = exif
|
||||||
|
elif segments[1][0:2] == b"\xff\xe1" and \
|
||||||
|
segments[1][4:10] == b"Exif\x00\x00":
|
||||||
|
if exif:
|
||||||
|
segments[1] = exif
|
||||||
|
elif exif is None:
|
||||||
|
segments.pop(1)
|
||||||
|
else:
|
||||||
|
if exif:
|
||||||
|
segments.insert(1, exif)
|
||||||
|
return b"".join(segments)
|
||||||
346
openflexure_microscope/camera/piexif/_dump.py
Normal file
346
openflexure_microscope/camera/piexif/_dump.py
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
import copy
|
||||||
|
import numbers
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from ._common import *
|
||||||
|
from ._exif import *
|
||||||
|
|
||||||
|
|
||||||
|
TIFF_HEADER_LENGTH = 8
|
||||||
|
|
||||||
|
|
||||||
|
def dump(exif_dict_original):
|
||||||
|
"""
|
||||||
|
py:function:: piexif.load(data)
|
||||||
|
|
||||||
|
Return exif as bytes.
|
||||||
|
|
||||||
|
:param dict exif: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
|
||||||
|
:return: Exif
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
exif_dict = copy.deepcopy(exif_dict_original)
|
||||||
|
header = b"Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"
|
||||||
|
exif_is = False
|
||||||
|
gps_is = False
|
||||||
|
interop_is = False
|
||||||
|
first_is = False
|
||||||
|
|
||||||
|
if "0th" in exif_dict:
|
||||||
|
zeroth_ifd = exif_dict["0th"]
|
||||||
|
else:
|
||||||
|
zeroth_ifd = {}
|
||||||
|
|
||||||
|
if (("Exif" in exif_dict) and len(exif_dict["Exif"]) or
|
||||||
|
("Interop" in exif_dict) and len(exif_dict["Interop"]) ):
|
||||||
|
zeroth_ifd[ImageIFD.ExifTag] = 1
|
||||||
|
exif_is = True
|
||||||
|
exif_ifd = exif_dict["Exif"]
|
||||||
|
if ("Interop" in exif_dict) and len(exif_dict["Interop"]):
|
||||||
|
exif_ifd[ExifIFD. InteroperabilityTag] = 1
|
||||||
|
interop_is = True
|
||||||
|
interop_ifd = exif_dict["Interop"]
|
||||||
|
elif ExifIFD. InteroperabilityTag in exif_ifd:
|
||||||
|
exif_ifd.pop(ExifIFD.InteroperabilityTag)
|
||||||
|
elif ImageIFD.ExifTag in zeroth_ifd:
|
||||||
|
zeroth_ifd.pop(ImageIFD.ExifTag)
|
||||||
|
|
||||||
|
if ("GPS" in exif_dict) and len(exif_dict["GPS"]):
|
||||||
|
zeroth_ifd[ImageIFD.GPSTag] = 1
|
||||||
|
gps_is = True
|
||||||
|
gps_ifd = exif_dict["GPS"]
|
||||||
|
elif ImageIFD.GPSTag in zeroth_ifd:
|
||||||
|
zeroth_ifd.pop(ImageIFD.GPSTag)
|
||||||
|
|
||||||
|
if (("1st" in exif_dict) and
|
||||||
|
("thumbnail" in exif_dict) and
|
||||||
|
(exif_dict["thumbnail"] is not None)):
|
||||||
|
first_is = True
|
||||||
|
exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] = 1
|
||||||
|
exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength] = 1
|
||||||
|
first_ifd = exif_dict["1st"]
|
||||||
|
|
||||||
|
zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0)
|
||||||
|
zeroth_length = (len(zeroth_set[0]) + exif_is * 12 + gps_is * 12 + 4 +
|
||||||
|
len(zeroth_set[1]))
|
||||||
|
|
||||||
|
if exif_is:
|
||||||
|
exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length)
|
||||||
|
exif_length = len(exif_set[0]) + interop_is * 12 + len(exif_set[1])
|
||||||
|
else:
|
||||||
|
exif_bytes = b""
|
||||||
|
exif_length = 0
|
||||||
|
if gps_is:
|
||||||
|
gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length)
|
||||||
|
gps_bytes = b"".join(gps_set)
|
||||||
|
gps_length = len(gps_bytes)
|
||||||
|
else:
|
||||||
|
gps_bytes = b""
|
||||||
|
gps_length = 0
|
||||||
|
if interop_is:
|
||||||
|
offset = zeroth_length + exif_length + gps_length
|
||||||
|
interop_set = _dict_to_bytes(interop_ifd, "Interop", offset)
|
||||||
|
interop_bytes = b"".join(interop_set)
|
||||||
|
interop_length = len(interop_bytes)
|
||||||
|
else:
|
||||||
|
interop_bytes = b""
|
||||||
|
interop_length = 0
|
||||||
|
if first_is:
|
||||||
|
offset = zeroth_length + exif_length + gps_length + interop_length
|
||||||
|
first_set = _dict_to_bytes(first_ifd, "1st", offset)
|
||||||
|
thumbnail = _get_thumbnail(exif_dict["thumbnail"])
|
||||||
|
thumbnail_max_size = 64000
|
||||||
|
if len(thumbnail) > thumbnail_max_size:
|
||||||
|
raise ValueError("Given thumbnail is too large. max 64kB")
|
||||||
|
else:
|
||||||
|
first_bytes = b""
|
||||||
|
if exif_is:
|
||||||
|
pointer_value = TIFF_HEADER_LENGTH + zeroth_length
|
||||||
|
pointer_str = struct.pack(">I", pointer_value)
|
||||||
|
key = ImageIFD.ExifTag
|
||||||
|
key_str = struct.pack(">H", key)
|
||||||
|
type_str = struct.pack(">H", TYPES.Long)
|
||||||
|
length_str = struct.pack(">I", 1)
|
||||||
|
exif_pointer = key_str + type_str + length_str + pointer_str
|
||||||
|
else:
|
||||||
|
exif_pointer = b""
|
||||||
|
if gps_is:
|
||||||
|
pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length
|
||||||
|
pointer_str = struct.pack(">I", pointer_value)
|
||||||
|
key = ImageIFD.GPSTag
|
||||||
|
key_str = struct.pack(">H", key)
|
||||||
|
type_str = struct.pack(">H", TYPES.Long)
|
||||||
|
length_str = struct.pack(">I", 1)
|
||||||
|
gps_pointer = key_str + type_str + length_str + pointer_str
|
||||||
|
else:
|
||||||
|
gps_pointer = b""
|
||||||
|
if interop_is:
|
||||||
|
pointer_value = (TIFF_HEADER_LENGTH +
|
||||||
|
zeroth_length + exif_length + gps_length)
|
||||||
|
pointer_str = struct.pack(">I", pointer_value)
|
||||||
|
key = ExifIFD.InteroperabilityTag
|
||||||
|
key_str = struct.pack(">H", key)
|
||||||
|
type_str = struct.pack(">H", TYPES.Long)
|
||||||
|
length_str = struct.pack(">I", 1)
|
||||||
|
interop_pointer = key_str + type_str + length_str + pointer_str
|
||||||
|
else:
|
||||||
|
interop_pointer = b""
|
||||||
|
if first_is:
|
||||||
|
pointer_value = (TIFF_HEADER_LENGTH + zeroth_length +
|
||||||
|
exif_length + gps_length + interop_length)
|
||||||
|
first_ifd_pointer = struct.pack(">L", pointer_value)
|
||||||
|
thumbnail_pointer = (pointer_value + len(first_set[0]) + 24 +
|
||||||
|
4 + len(first_set[1]))
|
||||||
|
thumbnail_p_bytes = (b"\x02\x01\x00\x04\x00\x00\x00\x01" +
|
||||||
|
struct.pack(">L", thumbnail_pointer))
|
||||||
|
thumbnail_length_bytes = (b"\x02\x02\x00\x04\x00\x00\x00\x01" +
|
||||||
|
struct.pack(">L", len(thumbnail)))
|
||||||
|
first_bytes = (first_set[0] + thumbnail_p_bytes +
|
||||||
|
thumbnail_length_bytes + b"\x00\x00\x00\x00" +
|
||||||
|
first_set[1] + thumbnail)
|
||||||
|
else:
|
||||||
|
first_ifd_pointer = b"\x00\x00\x00\x00"
|
||||||
|
|
||||||
|
zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer +
|
||||||
|
first_ifd_pointer + zeroth_set[1])
|
||||||
|
if exif_is:
|
||||||
|
exif_bytes = exif_set[0] + interop_pointer + exif_set[1]
|
||||||
|
|
||||||
|
return (header + zeroth_bytes + exif_bytes + gps_bytes +
|
||||||
|
interop_bytes + first_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_thumbnail(jpeg):
|
||||||
|
segments = split_into_segments(jpeg)
|
||||||
|
while (b"\xff\xe0" <= segments[1][0:2] <= b"\xff\xef"):
|
||||||
|
segments.pop(1)
|
||||||
|
thumbnail = b"".join(segments)
|
||||||
|
return thumbnail
|
||||||
|
|
||||||
|
|
||||||
|
def _pack_byte(*args):
|
||||||
|
return struct.pack("B" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_signed_byte(*args):
|
||||||
|
return struct.pack("b" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_short(*args):
|
||||||
|
return struct.pack(">" + "H" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_signed_short(*args):
|
||||||
|
return struct.pack(">" + "h" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_long(*args):
|
||||||
|
return struct.pack(">" + "L" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_slong(*args):
|
||||||
|
return struct.pack(">" + "l" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_float(*args):
|
||||||
|
return struct.pack(">" + "f" * len(args), *args)
|
||||||
|
|
||||||
|
def _pack_double(*args):
|
||||||
|
return struct.pack(">" + "d" * len(args), *args)
|
||||||
|
|
||||||
|
|
||||||
|
def _value_to_bytes(raw_value, value_type, offset):
|
||||||
|
four_bytes_over = b""
|
||||||
|
value_str = b""
|
||||||
|
|
||||||
|
if value_type == TYPES.Byte:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 4:
|
||||||
|
value_str = (_pack_byte(*raw_value) +
|
||||||
|
b"\x00" * (4 - length))
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_byte(*raw_value)
|
||||||
|
elif value_type == TYPES.Short:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 2:
|
||||||
|
value_str = (_pack_short(*raw_value) +
|
||||||
|
b"\x00\x00" * (2 - length))
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_short(*raw_value)
|
||||||
|
elif value_type == TYPES.Long:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 1:
|
||||||
|
value_str = _pack_long(*raw_value)
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_long(*raw_value)
|
||||||
|
elif value_type == TYPES.SLong:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 1:
|
||||||
|
value_str = _pack_slong(*raw_value)
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_slong(*raw_value)
|
||||||
|
elif value_type == TYPES.Ascii:
|
||||||
|
try:
|
||||||
|
new_value = raw_value.encode("latin1") + b"\x00"
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
new_value = raw_value + b"\x00"
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError("Got invalid type to convert.")
|
||||||
|
length = len(new_value)
|
||||||
|
if length > 4:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = new_value
|
||||||
|
else:
|
||||||
|
value_str = new_value + b"\x00" * (4 - length)
|
||||||
|
elif value_type == TYPES.Rational:
|
||||||
|
if isinstance(raw_value[0], numbers.Integral):
|
||||||
|
length = 1
|
||||||
|
num, den = raw_value
|
||||||
|
new_value = struct.pack(">L", num) + struct.pack(">L", den)
|
||||||
|
elif isinstance(raw_value[0], tuple):
|
||||||
|
length = len(raw_value)
|
||||||
|
new_value = b""
|
||||||
|
for n, val in enumerate(raw_value):
|
||||||
|
num, den = val
|
||||||
|
new_value += (struct.pack(">L", num) +
|
||||||
|
struct.pack(">L", den))
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = new_value
|
||||||
|
elif value_type == TYPES.SRational:
|
||||||
|
if isinstance(raw_value[0], numbers.Integral):
|
||||||
|
length = 1
|
||||||
|
num, den = raw_value
|
||||||
|
new_value = struct.pack(">l", num) + struct.pack(">l", den)
|
||||||
|
elif isinstance(raw_value[0], tuple):
|
||||||
|
length = len(raw_value)
|
||||||
|
new_value = b""
|
||||||
|
for n, val in enumerate(raw_value):
|
||||||
|
num, den = val
|
||||||
|
new_value += (struct.pack(">l", num) +
|
||||||
|
struct.pack(">l", den))
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = new_value
|
||||||
|
elif value_type == TYPES.Undefined:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length > 4:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
try:
|
||||||
|
four_bytes_over = b"" + raw_value
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError("Got invalid type to convert.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value_str = raw_value + b"\x00" * (4 - length)
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError("Got invalid type to convert.")
|
||||||
|
elif value_type == TYPES.SByte: # Signed Byte
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 4:
|
||||||
|
value_str = (_pack_signed_byte(*raw_value) +
|
||||||
|
b"\x00" * (4 - length))
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_signed_byte(*raw_value)
|
||||||
|
elif value_type == TYPES.SShort: # Signed Short
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 2:
|
||||||
|
value_str = (_pack_signed_short(*raw_value) +
|
||||||
|
b"\x00\x00" * (2 - length))
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_signed_short(*raw_value)
|
||||||
|
elif value_type == TYPES.Float:
|
||||||
|
length = len(raw_value)
|
||||||
|
if length <= 1:
|
||||||
|
value_str = _pack_float(*raw_value)
|
||||||
|
else:
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_float(*raw_value)
|
||||||
|
elif value_type == TYPES.DFloat: # Double
|
||||||
|
length = len(raw_value)
|
||||||
|
value_str = struct.pack(">I", offset)
|
||||||
|
four_bytes_over = _pack_double(*raw_value)
|
||||||
|
|
||||||
|
length_str = struct.pack(">I", length)
|
||||||
|
return length_str, value_str, four_bytes_over
|
||||||
|
|
||||||
|
def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
|
||||||
|
tag_count = len(ifd_dict)
|
||||||
|
entry_header = struct.pack(">H", tag_count)
|
||||||
|
if ifd in ("0th", "1st"):
|
||||||
|
entries_length = 2 + tag_count * 12 + 4
|
||||||
|
else:
|
||||||
|
entries_length = 2 + tag_count * 12
|
||||||
|
entries = b""
|
||||||
|
values = b""
|
||||||
|
|
||||||
|
for n, key in enumerate(sorted(ifd_dict)):
|
||||||
|
if (ifd == "0th") and (key in (ImageIFD.ExifTag, ImageIFD.GPSTag)):
|
||||||
|
continue
|
||||||
|
elif (ifd == "Exif") and (key == ExifIFD.InteroperabilityTag):
|
||||||
|
continue
|
||||||
|
elif (ifd == "1st") and (key in (ImageIFD.JPEGInterchangeFormat, ImageIFD.JPEGInterchangeFormatLength)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_value = ifd_dict[key]
|
||||||
|
key_str = struct.pack(">H", key)
|
||||||
|
value_type = TAGS[ifd][key]["type"]
|
||||||
|
type_str = struct.pack(">H", value_type)
|
||||||
|
four_bytes_over = b""
|
||||||
|
|
||||||
|
if isinstance(raw_value, numbers.Integral) or isinstance(raw_value, float):
|
||||||
|
raw_value = (raw_value,)
|
||||||
|
offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + len(values)
|
||||||
|
|
||||||
|
try:
|
||||||
|
length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
|
||||||
|
value_type,
|
||||||
|
offset)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
'"dump" got wrong type of exif value.\n' +
|
||||||
|
'{0} in {1} IFD. Got as {2}.'.format(key, ifd, type(ifd_dict[key]))
|
||||||
|
)
|
||||||
|
|
||||||
|
entries += key_str + type_str + length_str + value_str
|
||||||
|
values += four_bytes_over
|
||||||
|
return (entry_header + entries, values)
|
||||||
2
openflexure_microscope/camera/piexif/_exceptions.py
Normal file
2
openflexure_microscope/camera/piexif/_exceptions.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
class InvalidImageDataError(ValueError):
|
||||||
|
pass
|
||||||
638
openflexure_microscope/camera/piexif/_exif.py
Normal file
638
openflexure_microscope/camera/piexif/_exif.py
Normal file
|
|
@ -0,0 +1,638 @@
|
||||||
|
class TYPES:
|
||||||
|
Byte = 1
|
||||||
|
Ascii = 2
|
||||||
|
Short = 3
|
||||||
|
Long = 4
|
||||||
|
Rational = 5
|
||||||
|
SByte = 6
|
||||||
|
Undefined = 7
|
||||||
|
SShort = 8
|
||||||
|
SLong = 9
|
||||||
|
SRational = 10
|
||||||
|
Float = 11
|
||||||
|
DFloat = 12
|
||||||
|
|
||||||
|
|
||||||
|
TAGS = {
|
||||||
|
'Image': {11: {'name': 'ProcessingSoftware', 'type': TYPES.Ascii},
|
||||||
|
254: {'name': 'NewSubfileType', 'type': TYPES.Long},
|
||||||
|
255: {'name': 'SubfileType', 'type': TYPES.Short},
|
||||||
|
256: {'name': 'ImageWidth', 'type': TYPES.Long},
|
||||||
|
257: {'name': 'ImageLength', 'type': TYPES.Long},
|
||||||
|
258: {'name': 'BitsPerSample', 'type': TYPES.Short},
|
||||||
|
259: {'name': 'Compression', 'type': TYPES.Short},
|
||||||
|
262: {'name': 'PhotometricInterpretation', 'type': TYPES.Short},
|
||||||
|
263: {'name': 'Threshholding', 'type': TYPES.Short},
|
||||||
|
264: {'name': 'CellWidth', 'type': TYPES.Short},
|
||||||
|
265: {'name': 'CellLength', 'type': TYPES.Short},
|
||||||
|
266: {'name': 'FillOrder', 'type': TYPES.Short},
|
||||||
|
269: {'name': 'DocumentName', 'type': TYPES.Ascii},
|
||||||
|
270: {'name': 'ImageDescription', 'type': TYPES.Ascii},
|
||||||
|
271: {'name': 'Make', 'type': TYPES.Ascii},
|
||||||
|
272: {'name': 'Model', 'type': TYPES.Ascii},
|
||||||
|
273: {'name': 'StripOffsets', 'type': TYPES.Long},
|
||||||
|
274: {'name': 'Orientation', 'type': TYPES.Short},
|
||||||
|
277: {'name': 'SamplesPerPixel', 'type': TYPES.Short},
|
||||||
|
278: {'name': 'RowsPerStrip', 'type': TYPES.Long},
|
||||||
|
279: {'name': 'StripByteCounts', 'type': TYPES.Long},
|
||||||
|
282: {'name': 'XResolution', 'type': TYPES.Rational},
|
||||||
|
283: {'name': 'YResolution', 'type': TYPES.Rational},
|
||||||
|
284: {'name': 'PlanarConfiguration', 'type': TYPES.Short},
|
||||||
|
290: {'name': 'GrayResponseUnit', 'type': TYPES.Short},
|
||||||
|
291: {'name': 'GrayResponseCurve', 'type': TYPES.Short},
|
||||||
|
292: {'name': 'T4Options', 'type': TYPES.Long},
|
||||||
|
293: {'name': 'T6Options', 'type': TYPES.Long},
|
||||||
|
296: {'name': 'ResolutionUnit', 'type': TYPES.Short},
|
||||||
|
301: {'name': 'TransferFunction', 'type': TYPES.Short},
|
||||||
|
305: {'name': 'Software', 'type': TYPES.Ascii},
|
||||||
|
306: {'name': 'DateTime', 'type': TYPES.Ascii},
|
||||||
|
315: {'name': 'Artist', 'type': TYPES.Ascii},
|
||||||
|
316: {'name': 'HostComputer', 'type': TYPES.Ascii},
|
||||||
|
317: {'name': 'Predictor', 'type': TYPES.Short},
|
||||||
|
318: {'name': 'WhitePoint', 'type': TYPES.Rational},
|
||||||
|
319: {'name': 'PrimaryChromaticities', 'type': TYPES.Rational},
|
||||||
|
320: {'name': 'ColorMap', 'type': TYPES.Short},
|
||||||
|
321: {'name': 'HalftoneHints', 'type': TYPES.Short},
|
||||||
|
322: {'name': 'TileWidth', 'type': TYPES.Short},
|
||||||
|
323: {'name': 'TileLength', 'type': TYPES.Short},
|
||||||
|
324: {'name': 'TileOffsets', 'type': TYPES.Short},
|
||||||
|
325: {'name': 'TileByteCounts', 'type': TYPES.Short},
|
||||||
|
330: {'name': 'SubIFDs', 'type': TYPES.Long},
|
||||||
|
332: {'name': 'InkSet', 'type': TYPES.Short},
|
||||||
|
333: {'name': 'InkNames', 'type': TYPES.Ascii},
|
||||||
|
334: {'name': 'NumberOfInks', 'type': TYPES.Short},
|
||||||
|
336: {'name': 'DotRange', 'type': TYPES.Byte},
|
||||||
|
337: {'name': 'TargetPrinter', 'type': TYPES.Ascii},
|
||||||
|
338: {'name': 'ExtraSamples', 'type': TYPES.Short},
|
||||||
|
339: {'name': 'SampleFormat', 'type': TYPES.Short},
|
||||||
|
340: {'name': 'SMinSampleValue', 'type': TYPES.Short},
|
||||||
|
341: {'name': 'SMaxSampleValue', 'type': TYPES.Short},
|
||||||
|
342: {'name': 'TransferRange', 'type': TYPES.Short},
|
||||||
|
343: {'name': 'ClipPath', 'type': TYPES.Byte},
|
||||||
|
344: {'name': 'XClipPathUnits', 'type': TYPES.Long},
|
||||||
|
345: {'name': 'YClipPathUnits', 'type': TYPES.Long},
|
||||||
|
346: {'name': 'Indexed', 'type': TYPES.Short},
|
||||||
|
347: {'name': 'JPEGTables', 'type': TYPES.Undefined},
|
||||||
|
351: {'name': 'OPIProxy', 'type': TYPES.Short},
|
||||||
|
512: {'name': 'JPEGProc', 'type': TYPES.Long},
|
||||||
|
513: {'name': 'JPEGInterchangeFormat', 'type': TYPES.Long},
|
||||||
|
514: {'name': 'JPEGInterchangeFormatLength', 'type': TYPES.Long},
|
||||||
|
515: {'name': 'JPEGRestartInterval', 'type': TYPES.Short},
|
||||||
|
517: {'name': 'JPEGLosslessPredictors', 'type': TYPES.Short},
|
||||||
|
518: {'name': 'JPEGPointTransforms', 'type': TYPES.Short},
|
||||||
|
519: {'name': 'JPEGQTables', 'type': TYPES.Long},
|
||||||
|
520: {'name': 'JPEGDCTables', 'type': TYPES.Long},
|
||||||
|
521: {'name': 'JPEGACTables', 'type': TYPES.Long},
|
||||||
|
529: {'name': 'YCbCrCoefficients', 'type': TYPES.Rational},
|
||||||
|
530: {'name': 'YCbCrSubSampling', 'type': TYPES.Short},
|
||||||
|
531: {'name': 'YCbCrPositioning', 'type': TYPES.Short},
|
||||||
|
532: {'name': 'ReferenceBlackWhite', 'type': TYPES.Rational},
|
||||||
|
700: {'name': 'XMLPacket', 'type': TYPES.Byte},
|
||||||
|
18246: {'name': 'Rating', 'type': TYPES.Short},
|
||||||
|
18249: {'name': 'RatingPercent', 'type': TYPES.Short},
|
||||||
|
32781: {'name': 'ImageID', 'type': TYPES.Ascii},
|
||||||
|
33421: {'name': 'CFARepeatPatternDim', 'type': TYPES.Short},
|
||||||
|
33422: {'name': 'CFAPattern', 'type': TYPES.Byte},
|
||||||
|
33423: {'name': 'BatteryLevel', 'type': TYPES.Rational},
|
||||||
|
33432: {'name': 'Copyright', 'type': TYPES.Ascii},
|
||||||
|
33434: {'name': 'ExposureTime', 'type': TYPES.Rational},
|
||||||
|
34377: {'name': 'ImageResources', 'type': TYPES.Byte},
|
||||||
|
34665: {'name': 'ExifTag', 'type': TYPES.Long},
|
||||||
|
34675: {'name': 'InterColorProfile', 'type': TYPES.Undefined},
|
||||||
|
34853: {'name': 'GPSTag', 'type': TYPES.Long},
|
||||||
|
34857: {'name': 'Interlace', 'type': TYPES.Short},
|
||||||
|
34858: {'name': 'TimeZoneOffset', 'type': TYPES.Long},
|
||||||
|
34859: {'name': 'SelfTimerMode', 'type': TYPES.Short},
|
||||||
|
37387: {'name': 'FlashEnergy', 'type': TYPES.Rational},
|
||||||
|
37388: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined},
|
||||||
|
37389: {'name': 'Noise', 'type': TYPES.Undefined},
|
||||||
|
37390: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational},
|
||||||
|
37391: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational},
|
||||||
|
37392: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short},
|
||||||
|
37393: {'name': 'ImageNumber', 'type': TYPES.Long},
|
||||||
|
37394: {'name': 'SecurityClassification', 'type': TYPES.Ascii},
|
||||||
|
37395: {'name': 'ImageHistory', 'type': TYPES.Ascii},
|
||||||
|
37397: {'name': 'ExposureIndex', 'type': TYPES.Rational},
|
||||||
|
37398: {'name': 'TIFFEPStandardID', 'type': TYPES.Byte},
|
||||||
|
37399: {'name': 'SensingMethod', 'type': TYPES.Short},
|
||||||
|
40091: {'name': 'XPTitle', 'type': TYPES.Byte},
|
||||||
|
40092: {'name': 'XPComment', 'type': TYPES.Byte},
|
||||||
|
40093: {'name': 'XPAuthor', 'type': TYPES.Byte},
|
||||||
|
40094: {'name': 'XPKeywords', 'type': TYPES.Byte},
|
||||||
|
40095: {'name': 'XPSubject', 'type': TYPES.Byte},
|
||||||
|
50341: {'name': 'PrintImageMatching', 'type': TYPES.Undefined},
|
||||||
|
50706: {'name': 'DNGVersion', 'type': TYPES.Byte},
|
||||||
|
50707: {'name': 'DNGBackwardVersion', 'type': TYPES.Byte},
|
||||||
|
50708: {'name': 'UniqueCameraModel', 'type': TYPES.Ascii},
|
||||||
|
50709: {'name': 'LocalizedCameraModel', 'type': TYPES.Byte},
|
||||||
|
50710: {'name': 'CFAPlaneColor', 'type': TYPES.Byte},
|
||||||
|
50711: {'name': 'CFALayout', 'type': TYPES.Short},
|
||||||
|
50712: {'name': 'LinearizationTable', 'type': TYPES.Short},
|
||||||
|
50713: {'name': 'BlackLevelRepeatDim', 'type': TYPES.Short},
|
||||||
|
50714: {'name': 'BlackLevel', 'type': TYPES.Rational},
|
||||||
|
50715: {'name': 'BlackLevelDeltaH', 'type': TYPES.SRational},
|
||||||
|
50716: {'name': 'BlackLevelDeltaV', 'type': TYPES.SRational},
|
||||||
|
50717: {'name': 'WhiteLevel', 'type': TYPES.Short},
|
||||||
|
50718: {'name': 'DefaultScale', 'type': TYPES.Rational},
|
||||||
|
50719: {'name': 'DefaultCropOrigin', 'type': TYPES.Short},
|
||||||
|
50720: {'name': 'DefaultCropSize', 'type': TYPES.Short},
|
||||||
|
50721: {'name': 'ColorMatrix1', 'type': TYPES.SRational},
|
||||||
|
50722: {'name': 'ColorMatrix2', 'type': TYPES.SRational},
|
||||||
|
50723: {'name': 'CameraCalibration1', 'type': TYPES.SRational},
|
||||||
|
50724: {'name': 'CameraCalibration2', 'type': TYPES.SRational},
|
||||||
|
50725: {'name': 'ReductionMatrix1', 'type': TYPES.SRational},
|
||||||
|
50726: {'name': 'ReductionMatrix2', 'type': TYPES.SRational},
|
||||||
|
50727: {'name': 'AnalogBalance', 'type': TYPES.Rational},
|
||||||
|
50728: {'name': 'AsShotNeutral', 'type': TYPES.Short},
|
||||||
|
50729: {'name': 'AsShotWhiteXY', 'type': TYPES.Rational},
|
||||||
|
50730: {'name': 'BaselineExposure', 'type': TYPES.SRational},
|
||||||
|
50731: {'name': 'BaselineNoise', 'type': TYPES.Rational},
|
||||||
|
50732: {'name': 'BaselineSharpness', 'type': TYPES.Rational},
|
||||||
|
50733: {'name': 'BayerGreenSplit', 'type': TYPES.Long},
|
||||||
|
50734: {'name': 'LinearResponseLimit', 'type': TYPES.Rational},
|
||||||
|
50735: {'name': 'CameraSerialNumber', 'type': TYPES.Ascii},
|
||||||
|
50736: {'name': 'LensInfo', 'type': TYPES.Rational},
|
||||||
|
50737: {'name': 'ChromaBlurRadius', 'type': TYPES.Rational},
|
||||||
|
50738: {'name': 'AntiAliasStrength', 'type': TYPES.Rational},
|
||||||
|
50739: {'name': 'ShadowScale', 'type': TYPES.SRational},
|
||||||
|
50740: {'name': 'DNGPrivateData', 'type': TYPES.Byte},
|
||||||
|
50741: {'name': 'MakerNoteSafety', 'type': TYPES.Short},
|
||||||
|
50778: {'name': 'CalibrationIlluminant1', 'type': TYPES.Short},
|
||||||
|
50779: {'name': 'CalibrationIlluminant2', 'type': TYPES.Short},
|
||||||
|
50780: {'name': 'BestQualityScale', 'type': TYPES.Rational},
|
||||||
|
50781: {'name': 'RawDataUniqueID', 'type': TYPES.Byte},
|
||||||
|
50827: {'name': 'OriginalRawFileName', 'type': TYPES.Byte},
|
||||||
|
50828: {'name': 'OriginalRawFileData', 'type': TYPES.Undefined},
|
||||||
|
50829: {'name': 'ActiveArea', 'type': TYPES.Short},
|
||||||
|
50830: {'name': 'MaskedAreas', 'type': TYPES.Short},
|
||||||
|
50831: {'name': 'AsShotICCProfile', 'type': TYPES.Undefined},
|
||||||
|
50832: {'name': 'AsShotPreProfileMatrix', 'type': TYPES.SRational},
|
||||||
|
50833: {'name': 'CurrentICCProfile', 'type': TYPES.Undefined},
|
||||||
|
50834: {'name': 'CurrentPreProfileMatrix', 'type': TYPES.SRational},
|
||||||
|
50879: {'name': 'ColorimetricReference', 'type': TYPES.Short},
|
||||||
|
50931: {'name': 'CameraCalibrationSignature', 'type': TYPES.Byte},
|
||||||
|
50932: {'name': 'ProfileCalibrationSignature', 'type': TYPES.Byte},
|
||||||
|
50934: {'name': 'AsShotProfileName', 'type': TYPES.Byte},
|
||||||
|
50935: {'name': 'NoiseReductionApplied', 'type': TYPES.Rational},
|
||||||
|
50936: {'name': 'ProfileName', 'type': TYPES.Byte},
|
||||||
|
50937: {'name': 'ProfileHueSatMapDims', 'type': TYPES.Long},
|
||||||
|
50938: {'name': 'ProfileHueSatMapData1', 'type': TYPES.Float},
|
||||||
|
50939: {'name': 'ProfileHueSatMapData2', 'type': TYPES.Float},
|
||||||
|
50940: {'name': 'ProfileToneCurve', 'type': TYPES.Float},
|
||||||
|
50941: {'name': 'ProfileEmbedPolicy', 'type': TYPES.Long},
|
||||||
|
50942: {'name': 'ProfileCopyright', 'type': TYPES.Byte},
|
||||||
|
50964: {'name': 'ForwardMatrix1', 'type': TYPES.SRational},
|
||||||
|
50965: {'name': 'ForwardMatrix2', 'type': TYPES.SRational},
|
||||||
|
50966: {'name': 'PreviewApplicationName', 'type': TYPES.Byte},
|
||||||
|
50967: {'name': 'PreviewApplicationVersion', 'type': TYPES.Byte},
|
||||||
|
50968: {'name': 'PreviewSettingsName', 'type': TYPES.Byte},
|
||||||
|
50969: {'name': 'PreviewSettingsDigest', 'type': TYPES.Byte},
|
||||||
|
50970: {'name': 'PreviewColorSpace', 'type': TYPES.Long},
|
||||||
|
50971: {'name': 'PreviewDateTime', 'type': TYPES.Ascii},
|
||||||
|
50972: {'name': 'RawImageDigest', 'type': TYPES.Undefined},
|
||||||
|
50973: {'name': 'OriginalRawFileDigest', 'type': TYPES.Undefined},
|
||||||
|
50974: {'name': 'SubTileBlockSize', 'type': TYPES.Long},
|
||||||
|
50975: {'name': 'RowInterleaveFactor', 'type': TYPES.Long},
|
||||||
|
50981: {'name': 'ProfileLookTableDims', 'type': TYPES.Long},
|
||||||
|
50982: {'name': 'ProfileLookTableData', 'type': TYPES.Float},
|
||||||
|
51008: {'name': 'OpcodeList1', 'type': TYPES.Undefined},
|
||||||
|
51009: {'name': 'OpcodeList2', 'type': TYPES.Undefined},
|
||||||
|
51022: {'name': 'OpcodeList3', 'type': TYPES.Undefined},
|
||||||
|
60606: {'name': 'ZZZTestSlong1', 'type': TYPES.SLong},
|
||||||
|
60607: {'name': 'ZZZTestSlong2', 'type': TYPES.SLong},
|
||||||
|
60608: {'name': 'ZZZTestSByte', 'type': TYPES.SByte},
|
||||||
|
60609: {'name': 'ZZZTestSShort', 'type': TYPES.SShort},
|
||||||
|
60610: {'name': 'ZZZTestDFloat', 'type': TYPES.DFloat},},
|
||||||
|
'Exif': {33434: {'name': 'ExposureTime', 'type': TYPES.Rational},
|
||||||
|
33437: {'name': 'FNumber', 'type': TYPES.Rational},
|
||||||
|
34850: {'name': 'ExposureProgram', 'type': TYPES.Short},
|
||||||
|
34852: {'name': 'SpectralSensitivity', 'type': TYPES.Ascii},
|
||||||
|
34855: {'name': 'ISOSpeedRatings', 'type': TYPES.Short},
|
||||||
|
34856: {'name': 'OECF', 'type': TYPES.Undefined},
|
||||||
|
34864: {'name': 'SensitivityType', 'type': TYPES.Short},
|
||||||
|
34865: {'name': 'StandardOutputSensitivity', 'type': TYPES.Long},
|
||||||
|
34866: {'name': 'RecommendedExposureIndex', 'type': TYPES.Long},
|
||||||
|
34867: {'name': 'ISOSpeed', 'type': TYPES.Long},
|
||||||
|
34868: {'name': 'ISOSpeedLatitudeyyy', 'type': TYPES.Long},
|
||||||
|
34869: {'name': 'ISOSpeedLatitudezzz', 'type': TYPES.Long},
|
||||||
|
36864: {'name': 'ExifVersion', 'type': TYPES.Undefined},
|
||||||
|
36867: {'name': 'DateTimeOriginal', 'type': TYPES.Ascii},
|
||||||
|
36868: {'name': 'DateTimeDigitized', 'type': TYPES.Ascii},
|
||||||
|
36880: {'name': 'OffsetTime', 'type': TYPES.Ascii},
|
||||||
|
36881: {'name': 'OffsetTimeOriginal', 'type': TYPES.Ascii},
|
||||||
|
36882: {'name': 'OffsetTimeDigitized', 'type': TYPES.Ascii},
|
||||||
|
37121: {'name': 'ComponentsConfiguration', 'type': TYPES.Undefined},
|
||||||
|
37122: {'name': 'CompressedBitsPerPixel', 'type': TYPES.Rational},
|
||||||
|
37377: {'name': 'ShutterSpeedValue', 'type': TYPES.SRational},
|
||||||
|
37378: {'name': 'ApertureValue', 'type': TYPES.Rational},
|
||||||
|
37379: {'name': 'BrightnessValue', 'type': TYPES.SRational},
|
||||||
|
37380: {'name': 'ExposureBiasValue', 'type': TYPES.SRational},
|
||||||
|
37381: {'name': 'MaxApertureValue', 'type': TYPES.Rational},
|
||||||
|
37382: {'name': 'SubjectDistance', 'type': TYPES.Rational},
|
||||||
|
37383: {'name': 'MeteringMode', 'type': TYPES.Short},
|
||||||
|
37384: {'name': 'LightSource', 'type': TYPES.Short},
|
||||||
|
37385: {'name': 'Flash', 'type': TYPES.Short},
|
||||||
|
37386: {'name': 'FocalLength', 'type': TYPES.Rational},
|
||||||
|
37396: {'name': 'SubjectArea', 'type': TYPES.Short},
|
||||||
|
37500: {'name': 'MakerNote', 'type': TYPES.Undefined},
|
||||||
|
37510: {'name': 'UserComment', 'type': TYPES.Undefined},
|
||||||
|
37520: {'name': 'SubSecTime', 'type': TYPES.Ascii},
|
||||||
|
37521: {'name': 'SubSecTimeOriginal', 'type': TYPES.Ascii},
|
||||||
|
37522: {'name': 'SubSecTimeDigitized', 'type': TYPES.Ascii},
|
||||||
|
37888: {'name': 'Temperature', 'type': TYPES.SRational},
|
||||||
|
37889: {'name': 'Humidity', 'type': TYPES.Rational},
|
||||||
|
37890: {'name': 'Pressure', 'type': TYPES.Rational},
|
||||||
|
37891: {'name': 'WaterDepth', 'type': TYPES.SRational},
|
||||||
|
37892: {'name': 'Acceleration', 'type': TYPES.Rational},
|
||||||
|
37893: {'name': 'CameraElevationAngle', 'type': TYPES.SRational},
|
||||||
|
40960: {'name': 'FlashpixVersion', 'type': TYPES.Undefined},
|
||||||
|
40961: {'name': 'ColorSpace', 'type': TYPES.Short},
|
||||||
|
40962: {'name': 'PixelXDimension', 'type': TYPES.Long},
|
||||||
|
40963: {'name': 'PixelYDimension', 'type': TYPES.Long},
|
||||||
|
40964: {'name': 'RelatedSoundFile', 'type': TYPES.Ascii},
|
||||||
|
40965: {'name': 'InteroperabilityTag', 'type': TYPES.Long},
|
||||||
|
41483: {'name': 'FlashEnergy', 'type': TYPES.Rational},
|
||||||
|
41484: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined},
|
||||||
|
41486: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational},
|
||||||
|
41487: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational},
|
||||||
|
41488: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short},
|
||||||
|
41492: {'name': 'SubjectLocation', 'type': TYPES.Short},
|
||||||
|
41493: {'name': 'ExposureIndex', 'type': TYPES.Rational},
|
||||||
|
41495: {'name': 'SensingMethod', 'type': TYPES.Short},
|
||||||
|
41728: {'name': 'FileSource', 'type': TYPES.Undefined},
|
||||||
|
41729: {'name': 'SceneType', 'type': TYPES.Undefined},
|
||||||
|
41730: {'name': 'CFAPattern', 'type': TYPES.Undefined},
|
||||||
|
41985: {'name': 'CustomRendered', 'type': TYPES.Short},
|
||||||
|
41986: {'name': 'ExposureMode', 'type': TYPES.Short},
|
||||||
|
41987: {'name': 'WhiteBalance', 'type': TYPES.Short},
|
||||||
|
41988: {'name': 'DigitalZoomRatio', 'type': TYPES.Rational},
|
||||||
|
41989: {'name': 'FocalLengthIn35mmFilm', 'type': TYPES.Short},
|
||||||
|
41990: {'name': 'SceneCaptureType', 'type': TYPES.Short},
|
||||||
|
41991: {'name': 'GainControl', 'type': TYPES.Short},
|
||||||
|
41992: {'name': 'Contrast', 'type': TYPES.Short},
|
||||||
|
41993: {'name': 'Saturation', 'type': TYPES.Short},
|
||||||
|
41994: {'name': 'Sharpness', 'type': TYPES.Short},
|
||||||
|
41995: {'name': 'DeviceSettingDescription', 'type': TYPES.Undefined},
|
||||||
|
41996: {'name': 'SubjectDistanceRange', 'type': TYPES.Short},
|
||||||
|
42016: {'name': 'ImageUniqueID', 'type': TYPES.Ascii},
|
||||||
|
42032: {'name': 'CameraOwnerName', 'type': TYPES.Ascii},
|
||||||
|
42033: {'name': 'BodySerialNumber', 'type': TYPES.Ascii},
|
||||||
|
42034: {'name': 'LensSpecification', 'type': TYPES.Rational},
|
||||||
|
42035: {'name': 'LensMake', 'type': TYPES.Ascii},
|
||||||
|
42036: {'name': 'LensModel', 'type': TYPES.Ascii},
|
||||||
|
42037: {'name': 'LensSerialNumber', 'type': TYPES.Ascii},
|
||||||
|
42240: {'name': 'Gamma', 'type': TYPES.Rational}},
|
||||||
|
'GPS': {0: {'name': 'GPSVersionID', 'type': TYPES.Byte},
|
||||||
|
1: {'name': 'GPSLatitudeRef', 'type': TYPES.Ascii},
|
||||||
|
2: {'name': 'GPSLatitude', 'type': TYPES.Rational},
|
||||||
|
3: {'name': 'GPSLongitudeRef', 'type': TYPES.Ascii},
|
||||||
|
4: {'name': 'GPSLongitude', 'type': TYPES.Rational},
|
||||||
|
5: {'name': 'GPSAltitudeRef', 'type': TYPES.Byte},
|
||||||
|
6: {'name': 'GPSAltitude', 'type': TYPES.Rational},
|
||||||
|
7: {'name': 'GPSTimeStamp', 'type': TYPES.Rational},
|
||||||
|
8: {'name': 'GPSSatellites', 'type': TYPES.Ascii},
|
||||||
|
9: {'name': 'GPSStatus', 'type': TYPES.Ascii},
|
||||||
|
10: {'name': 'GPSMeasureMode', 'type': TYPES.Ascii},
|
||||||
|
11: {'name': 'GPSDOP', 'type': TYPES.Rational},
|
||||||
|
12: {'name': 'GPSSpeedRef', 'type': TYPES.Ascii},
|
||||||
|
13: {'name': 'GPSSpeed', 'type': TYPES.Rational},
|
||||||
|
14: {'name': 'GPSTrackRef', 'type': TYPES.Ascii},
|
||||||
|
15: {'name': 'GPSTrack', 'type': TYPES.Rational},
|
||||||
|
16: {'name': 'GPSImgDirectionRef', 'type': TYPES.Ascii},
|
||||||
|
17: {'name': 'GPSImgDirection', 'type': TYPES.Rational},
|
||||||
|
18: {'name': 'GPSMapDatum', 'type': TYPES.Ascii},
|
||||||
|
19: {'name': 'GPSDestLatitudeRef', 'type': TYPES.Ascii},
|
||||||
|
20: {'name': 'GPSDestLatitude', 'type': TYPES.Rational},
|
||||||
|
21: {'name': 'GPSDestLongitudeRef', 'type': TYPES.Ascii},
|
||||||
|
22: {'name': 'GPSDestLongitude', 'type': TYPES.Rational},
|
||||||
|
23: {'name': 'GPSDestBearingRef', 'type': TYPES.Ascii},
|
||||||
|
24: {'name': 'GPSDestBearing', 'type': TYPES.Rational},
|
||||||
|
25: {'name': 'GPSDestDistanceRef', 'type': TYPES.Ascii},
|
||||||
|
26: {'name': 'GPSDestDistance', 'type': TYPES.Rational},
|
||||||
|
27: {'name': 'GPSProcessingMethod', 'type': TYPES.Undefined},
|
||||||
|
28: {'name': 'GPSAreaInformation', 'type': TYPES.Undefined},
|
||||||
|
29: {'name': 'GPSDateStamp', 'type': TYPES.Ascii},
|
||||||
|
30: {'name': 'GPSDifferential', 'type': TYPES.Short},
|
||||||
|
31: {'name': 'GPSHPositioningError', 'type': TYPES.Rational}},
|
||||||
|
'Interop': {1: {'name': 'InteroperabilityIndex', 'type': TYPES.Ascii}},
|
||||||
|
}
|
||||||
|
|
||||||
|
TAGS["0th"] = TAGS["Image"]
|
||||||
|
TAGS["1st"] = TAGS["Image"]
|
||||||
|
|
||||||
|
class ImageIFD:
|
||||||
|
"""Exif tag number reference - 0th IFD"""
|
||||||
|
ProcessingSoftware = 11
|
||||||
|
NewSubfileType = 254
|
||||||
|
SubfileType = 255
|
||||||
|
ImageWidth = 256
|
||||||
|
ImageLength = 257
|
||||||
|
BitsPerSample = 258
|
||||||
|
Compression = 259
|
||||||
|
PhotometricInterpretation = 262
|
||||||
|
Threshholding = 263
|
||||||
|
CellWidth = 264
|
||||||
|
CellLength = 265
|
||||||
|
FillOrder = 266
|
||||||
|
DocumentName = 269
|
||||||
|
ImageDescription = 270
|
||||||
|
Make = 271
|
||||||
|
Model = 272
|
||||||
|
StripOffsets = 273
|
||||||
|
Orientation = 274
|
||||||
|
SamplesPerPixel = 277
|
||||||
|
RowsPerStrip = 278
|
||||||
|
StripByteCounts = 279
|
||||||
|
XResolution = 282
|
||||||
|
YResolution = 283
|
||||||
|
PlanarConfiguration = 284
|
||||||
|
GrayResponseUnit = 290
|
||||||
|
GrayResponseCurve = 291
|
||||||
|
T4Options = 292
|
||||||
|
T6Options = 293
|
||||||
|
ResolutionUnit = 296
|
||||||
|
TransferFunction = 301
|
||||||
|
Software = 305
|
||||||
|
DateTime = 306
|
||||||
|
Artist = 315
|
||||||
|
HostComputer = 316
|
||||||
|
Predictor = 317
|
||||||
|
WhitePoint = 318
|
||||||
|
PrimaryChromaticities = 319
|
||||||
|
ColorMap = 320
|
||||||
|
HalftoneHints = 321
|
||||||
|
TileWidth = 322
|
||||||
|
TileLength = 323
|
||||||
|
TileOffsets = 324
|
||||||
|
TileByteCounts = 325
|
||||||
|
SubIFDs = 330
|
||||||
|
InkSet = 332
|
||||||
|
InkNames = 333
|
||||||
|
NumberOfInks = 334
|
||||||
|
DotRange = 336
|
||||||
|
TargetPrinter = 337
|
||||||
|
ExtraSamples = 338
|
||||||
|
SampleFormat = 339
|
||||||
|
SMinSampleValue = 340
|
||||||
|
SMaxSampleValue = 341
|
||||||
|
TransferRange = 342
|
||||||
|
ClipPath = 343
|
||||||
|
XClipPathUnits = 344
|
||||||
|
YClipPathUnits = 345
|
||||||
|
Indexed = 346
|
||||||
|
JPEGTables = 347
|
||||||
|
OPIProxy = 351
|
||||||
|
JPEGProc = 512
|
||||||
|
JPEGInterchangeFormat = 513
|
||||||
|
JPEGInterchangeFormatLength = 514
|
||||||
|
JPEGRestartInterval = 515
|
||||||
|
JPEGLosslessPredictors = 517
|
||||||
|
JPEGPointTransforms = 518
|
||||||
|
JPEGQTables = 519
|
||||||
|
JPEGDCTables = 520
|
||||||
|
JPEGACTables = 521
|
||||||
|
YCbCrCoefficients = 529
|
||||||
|
YCbCrSubSampling = 530
|
||||||
|
YCbCrPositioning = 531
|
||||||
|
ReferenceBlackWhite = 532
|
||||||
|
XMLPacket = 700
|
||||||
|
Rating = 18246
|
||||||
|
RatingPercent = 18249
|
||||||
|
ImageID = 32781
|
||||||
|
CFARepeatPatternDim = 33421
|
||||||
|
CFAPattern = 33422
|
||||||
|
BatteryLevel = 33423
|
||||||
|
Copyright = 33432
|
||||||
|
ExposureTime = 33434
|
||||||
|
ImageResources = 34377
|
||||||
|
ExifTag = 34665
|
||||||
|
InterColorProfile = 34675
|
||||||
|
GPSTag = 34853
|
||||||
|
Interlace = 34857
|
||||||
|
TimeZoneOffset = 34858
|
||||||
|
SelfTimerMode = 34859
|
||||||
|
FlashEnergy = 37387
|
||||||
|
SpatialFrequencyResponse = 37388
|
||||||
|
Noise = 37389
|
||||||
|
FocalPlaneXResolution = 37390
|
||||||
|
FocalPlaneYResolution = 37391
|
||||||
|
FocalPlaneResolutionUnit = 37392
|
||||||
|
ImageNumber = 37393
|
||||||
|
SecurityClassification = 37394
|
||||||
|
ImageHistory = 37395
|
||||||
|
ExposureIndex = 37397
|
||||||
|
TIFFEPStandardID = 37398
|
||||||
|
SensingMethod = 37399
|
||||||
|
XPTitle = 40091
|
||||||
|
XPComment = 40092
|
||||||
|
XPAuthor = 40093
|
||||||
|
XPKeywords = 40094
|
||||||
|
XPSubject = 40095
|
||||||
|
PrintImageMatching = 50341
|
||||||
|
DNGVersion = 50706
|
||||||
|
DNGBackwardVersion = 50707
|
||||||
|
UniqueCameraModel = 50708
|
||||||
|
LocalizedCameraModel = 50709
|
||||||
|
CFAPlaneColor = 50710
|
||||||
|
CFALayout = 50711
|
||||||
|
LinearizationTable = 50712
|
||||||
|
BlackLevelRepeatDim = 50713
|
||||||
|
BlackLevel = 50714
|
||||||
|
BlackLevelDeltaH = 50715
|
||||||
|
BlackLevelDeltaV = 50716
|
||||||
|
WhiteLevel = 50717
|
||||||
|
DefaultScale = 50718
|
||||||
|
DefaultCropOrigin = 50719
|
||||||
|
DefaultCropSize = 50720
|
||||||
|
ColorMatrix1 = 50721
|
||||||
|
ColorMatrix2 = 50722
|
||||||
|
CameraCalibration1 = 50723
|
||||||
|
CameraCalibration2 = 50724
|
||||||
|
ReductionMatrix1 = 50725
|
||||||
|
ReductionMatrix2 = 50726
|
||||||
|
AnalogBalance = 50727
|
||||||
|
AsShotNeutral = 50728
|
||||||
|
AsShotWhiteXY = 50729
|
||||||
|
BaselineExposure = 50730
|
||||||
|
BaselineNoise = 50731
|
||||||
|
BaselineSharpness = 50732
|
||||||
|
BayerGreenSplit = 50733
|
||||||
|
LinearResponseLimit = 50734
|
||||||
|
CameraSerialNumber = 50735
|
||||||
|
LensInfo = 50736
|
||||||
|
ChromaBlurRadius = 50737
|
||||||
|
AntiAliasStrength = 50738
|
||||||
|
ShadowScale = 50739
|
||||||
|
DNGPrivateData = 50740
|
||||||
|
MakerNoteSafety = 50741
|
||||||
|
CalibrationIlluminant1 = 50778
|
||||||
|
CalibrationIlluminant2 = 50779
|
||||||
|
BestQualityScale = 50780
|
||||||
|
RawDataUniqueID = 50781
|
||||||
|
OriginalRawFileName = 50827
|
||||||
|
OriginalRawFileData = 50828
|
||||||
|
ActiveArea = 50829
|
||||||
|
MaskedAreas = 50830
|
||||||
|
AsShotICCProfile = 50831
|
||||||
|
AsShotPreProfileMatrix = 50832
|
||||||
|
CurrentICCProfile = 50833
|
||||||
|
CurrentPreProfileMatrix = 50834
|
||||||
|
ColorimetricReference = 50879
|
||||||
|
CameraCalibrationSignature = 50931
|
||||||
|
ProfileCalibrationSignature = 50932
|
||||||
|
AsShotProfileName = 50934
|
||||||
|
NoiseReductionApplied = 50935
|
||||||
|
ProfileName = 50936
|
||||||
|
ProfileHueSatMapDims = 50937
|
||||||
|
ProfileHueSatMapData1 = 50938
|
||||||
|
ProfileHueSatMapData2 = 50939
|
||||||
|
ProfileToneCurve = 50940
|
||||||
|
ProfileEmbedPolicy = 50941
|
||||||
|
ProfileCopyright = 50942
|
||||||
|
ForwardMatrix1 = 50964
|
||||||
|
ForwardMatrix2 = 50965
|
||||||
|
PreviewApplicationName = 50966
|
||||||
|
PreviewApplicationVersion = 50967
|
||||||
|
PreviewSettingsName = 50968
|
||||||
|
PreviewSettingsDigest = 50969
|
||||||
|
PreviewColorSpace = 50970
|
||||||
|
PreviewDateTime = 50971
|
||||||
|
RawImageDigest = 50972
|
||||||
|
OriginalRawFileDigest = 50973
|
||||||
|
SubTileBlockSize = 50974
|
||||||
|
RowInterleaveFactor = 50975
|
||||||
|
ProfileLookTableDims = 50981
|
||||||
|
ProfileLookTableData = 50982
|
||||||
|
OpcodeList1 = 51008
|
||||||
|
OpcodeList2 = 51009
|
||||||
|
OpcodeList3 = 51022
|
||||||
|
NoiseProfile = 51041
|
||||||
|
ZZZTestSlong1 = 60606
|
||||||
|
ZZZTestSlong2 = 60607
|
||||||
|
ZZZTestSByte = 60608
|
||||||
|
ZZZTestSShort = 60609
|
||||||
|
ZZZTestDFloat = 60610
|
||||||
|
|
||||||
|
|
||||||
|
class ExifIFD:
|
||||||
|
"""Exif tag number reference - Exif IFD"""
|
||||||
|
ExposureTime = 33434
|
||||||
|
FNumber = 33437
|
||||||
|
ExposureProgram = 34850
|
||||||
|
SpectralSensitivity = 34852
|
||||||
|
ISOSpeedRatings = 34855
|
||||||
|
OECF = 34856
|
||||||
|
SensitivityType = 34864
|
||||||
|
StandardOutputSensitivity = 34865
|
||||||
|
RecommendedExposureIndex = 34866
|
||||||
|
ISOSpeed = 34867
|
||||||
|
ISOSpeedLatitudeyyy = 34868
|
||||||
|
ISOSpeedLatitudezzz = 34869
|
||||||
|
ExifVersion = 36864
|
||||||
|
DateTimeOriginal = 36867
|
||||||
|
DateTimeDigitized = 36868
|
||||||
|
OffsetTime = 36880
|
||||||
|
OffsetTimeOriginal = 36881
|
||||||
|
OffsetTimeDigitized = 36882
|
||||||
|
ComponentsConfiguration = 37121
|
||||||
|
CompressedBitsPerPixel = 37122
|
||||||
|
ShutterSpeedValue = 37377
|
||||||
|
ApertureValue = 37378
|
||||||
|
BrightnessValue = 37379
|
||||||
|
ExposureBiasValue = 37380
|
||||||
|
MaxApertureValue = 37381
|
||||||
|
SubjectDistance = 37382
|
||||||
|
MeteringMode = 37383
|
||||||
|
LightSource = 37384
|
||||||
|
Flash = 37385
|
||||||
|
FocalLength = 37386
|
||||||
|
Temperature = 37888
|
||||||
|
Humidity = 37889
|
||||||
|
Pressure = 37890
|
||||||
|
WaterDepth = 37891
|
||||||
|
Acceleration = 37892
|
||||||
|
CameraElevationAngle = 37893
|
||||||
|
SubjectArea = 37396
|
||||||
|
MakerNote = 37500
|
||||||
|
UserComment = 37510
|
||||||
|
SubSecTime = 37520
|
||||||
|
SubSecTimeOriginal = 37521
|
||||||
|
SubSecTimeDigitized = 37522
|
||||||
|
FlashpixVersion = 40960
|
||||||
|
ColorSpace = 40961
|
||||||
|
PixelXDimension = 40962
|
||||||
|
PixelYDimension = 40963
|
||||||
|
RelatedSoundFile = 40964
|
||||||
|
InteroperabilityTag = 40965
|
||||||
|
FlashEnergy = 41483
|
||||||
|
SpatialFrequencyResponse = 41484
|
||||||
|
FocalPlaneXResolution = 41486
|
||||||
|
FocalPlaneYResolution = 41487
|
||||||
|
FocalPlaneResolutionUnit = 41488
|
||||||
|
SubjectLocation = 41492
|
||||||
|
ExposureIndex = 41493
|
||||||
|
SensingMethod = 41495
|
||||||
|
FileSource = 41728
|
||||||
|
SceneType = 41729
|
||||||
|
CFAPattern = 41730
|
||||||
|
CustomRendered = 41985
|
||||||
|
ExposureMode = 41986
|
||||||
|
WhiteBalance = 41987
|
||||||
|
DigitalZoomRatio = 41988
|
||||||
|
FocalLengthIn35mmFilm = 41989
|
||||||
|
SceneCaptureType = 41990
|
||||||
|
GainControl = 41991
|
||||||
|
Contrast = 41992
|
||||||
|
Saturation = 41993
|
||||||
|
Sharpness = 41994
|
||||||
|
DeviceSettingDescription = 41995
|
||||||
|
SubjectDistanceRange = 41996
|
||||||
|
ImageUniqueID = 42016
|
||||||
|
CameraOwnerName = 42032
|
||||||
|
BodySerialNumber = 42033
|
||||||
|
LensSpecification = 42034
|
||||||
|
LensMake = 42035
|
||||||
|
LensModel = 42036
|
||||||
|
LensSerialNumber = 42037
|
||||||
|
Gamma = 42240
|
||||||
|
|
||||||
|
|
||||||
|
class GPSIFD:
|
||||||
|
"""Exif tag number reference - GPS IFD"""
|
||||||
|
GPSVersionID = 0
|
||||||
|
GPSLatitudeRef = 1
|
||||||
|
GPSLatitude = 2
|
||||||
|
GPSLongitudeRef = 3
|
||||||
|
GPSLongitude = 4
|
||||||
|
GPSAltitudeRef = 5
|
||||||
|
GPSAltitude = 6
|
||||||
|
GPSTimeStamp = 7
|
||||||
|
GPSSatellites = 8
|
||||||
|
GPSStatus = 9
|
||||||
|
GPSMeasureMode = 10
|
||||||
|
GPSDOP = 11
|
||||||
|
GPSSpeedRef = 12
|
||||||
|
GPSSpeed = 13
|
||||||
|
GPSTrackRef = 14
|
||||||
|
GPSTrack = 15
|
||||||
|
GPSImgDirectionRef = 16
|
||||||
|
GPSImgDirection = 17
|
||||||
|
GPSMapDatum = 18
|
||||||
|
GPSDestLatitudeRef = 19
|
||||||
|
GPSDestLatitude = 20
|
||||||
|
GPSDestLongitudeRef = 21
|
||||||
|
GPSDestLongitude = 22
|
||||||
|
GPSDestBearingRef = 23
|
||||||
|
GPSDestBearing = 24
|
||||||
|
GPSDestDistanceRef = 25
|
||||||
|
GPSDestDistance = 26
|
||||||
|
GPSProcessingMethod = 27
|
||||||
|
GPSAreaInformation = 28
|
||||||
|
GPSDateStamp = 29
|
||||||
|
GPSDifferential = 30
|
||||||
|
GPSHPositioningError = 31
|
||||||
|
|
||||||
|
|
||||||
|
class InteropIFD:
|
||||||
|
"""Exif tag number reference - Interoperability IFD"""
|
||||||
|
InteroperabilityIndex = 1
|
||||||
60
openflexure_microscope/camera/piexif/_insert.py
Normal file
60
openflexure_microscope/camera/piexif/_insert.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import io
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ._common import *
|
||||||
|
from ._exceptions import InvalidImageDataError
|
||||||
|
from piexif import _webp
|
||||||
|
|
||||||
|
def insert(exif, image, new_file=None):
|
||||||
|
"""
|
||||||
|
py:function:: piexif.insert(exif_bytes, filename)
|
||||||
|
|
||||||
|
Insert exif into JPEG.
|
||||||
|
|
||||||
|
:param bytes exif_bytes: Exif as bytes
|
||||||
|
:param str filename: JPEG
|
||||||
|
"""
|
||||||
|
if exif[0:6] != b"\x45\x78\x69\x66\x00\x00":
|
||||||
|
raise ValueError("Given data is not exif data")
|
||||||
|
|
||||||
|
output_file = False
|
||||||
|
# Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
|
||||||
|
maybe_image = sys.version_info >= (3,0,0) or isinstance(image, str)
|
||||||
|
|
||||||
|
if maybe_image and image[0:2] == b"\xff\xd8":
|
||||||
|
image_data = image
|
||||||
|
file_type = "jpeg"
|
||||||
|
elif maybe_image and image[0:4] == b"RIFF" and image[8:12] == b"WEBP":
|
||||||
|
image_data = image
|
||||||
|
file_type = "webp"
|
||||||
|
else:
|
||||||
|
with open(image, 'rb') as f:
|
||||||
|
image_data = f.read()
|
||||||
|
if image_data[0:2] == b"\xff\xd8":
|
||||||
|
file_type = "jpeg"
|
||||||
|
elif image_data[0:4] == b"RIFF" and image_data[8:12] == b"WEBP":
|
||||||
|
file_type = "webp"
|
||||||
|
else:
|
||||||
|
raise InvalidImageDataError
|
||||||
|
output_file = True
|
||||||
|
|
||||||
|
if file_type == "jpeg":
|
||||||
|
exif = b"\xff\xe1" + struct.pack(">H", len(exif) + 2) + exif
|
||||||
|
segments = split_into_segments(image_data)
|
||||||
|
new_data = merge_segments(segments, exif)
|
||||||
|
elif file_type == "webp":
|
||||||
|
exif = exif[6:]
|
||||||
|
new_data = _webp.insert(image_data, exif)
|
||||||
|
|
||||||
|
if isinstance(new_file, io.BytesIO):
|
||||||
|
new_file.write(new_data)
|
||||||
|
new_file.seek(0)
|
||||||
|
elif new_file:
|
||||||
|
with open(new_file, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
elif output_file:
|
||||||
|
with open(image, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
else:
|
||||||
|
raise ValueError("Give a 3rd argument to 'insert' to output file")
|
||||||
270
openflexure_microscope/camera/piexif/_load.py
Normal file
270
openflexure_microscope/camera/piexif/_load.py
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ._common import *
|
||||||
|
from ._exceptions import InvalidImageDataError
|
||||||
|
from ._exif import *
|
||||||
|
from piexif import _webp
|
||||||
|
|
||||||
|
LITTLE_ENDIAN = b"\x49\x49"
|
||||||
|
|
||||||
|
|
||||||
|
def load(input_data, key_is_name=False):
|
||||||
|
"""
|
||||||
|
py:function:: piexif.load(filename)
|
||||||
|
|
||||||
|
Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). "thumbnail" value is JPEG as bytes.
|
||||||
|
|
||||||
|
:param str filename: JPEG or TIFF
|
||||||
|
:return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
exif_dict = {"0th":{},
|
||||||
|
"Exif":{},
|
||||||
|
"GPS":{},
|
||||||
|
"Interop":{},
|
||||||
|
"1st":{},
|
||||||
|
"thumbnail":None}
|
||||||
|
exifReader = _ExifReader(input_data)
|
||||||
|
if exifReader.tiftag is None:
|
||||||
|
return exif_dict
|
||||||
|
|
||||||
|
if exifReader.tiftag[0:2] == LITTLE_ENDIAN:
|
||||||
|
exifReader.endian_mark = "<"
|
||||||
|
else:
|
||||||
|
exifReader.endian_mark = ">"
|
||||||
|
|
||||||
|
pointer = struct.unpack(exifReader.endian_mark + "L",
|
||||||
|
exifReader.tiftag[4:8])[0]
|
||||||
|
exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th")
|
||||||
|
first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer")
|
||||||
|
if ImageIFD.ExifTag in exif_dict["0th"]:
|
||||||
|
pointer = exif_dict["0th"][ImageIFD.ExifTag]
|
||||||
|
exif_dict["Exif"] = exifReader.get_ifd_dict(pointer, "Exif")
|
||||||
|
if ImageIFD.GPSTag in exif_dict["0th"]:
|
||||||
|
pointer = exif_dict["0th"][ImageIFD.GPSTag]
|
||||||
|
exif_dict["GPS"] = exifReader.get_ifd_dict(pointer, "GPS")
|
||||||
|
if ExifIFD.InteroperabilityTag in exif_dict["Exif"]:
|
||||||
|
pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag]
|
||||||
|
exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop")
|
||||||
|
if first_ifd_pointer != b"\x00\x00\x00\x00":
|
||||||
|
pointer = struct.unpack(exifReader.endian_mark + "L",
|
||||||
|
first_ifd_pointer)[0]
|
||||||
|
exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st")
|
||||||
|
if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and
|
||||||
|
ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]):
|
||||||
|
end = (exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] +
|
||||||
|
exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength])
|
||||||
|
thumb = exifReader.tiftag[exif_dict["1st"][ImageIFD.JPEGInterchangeFormat]:end]
|
||||||
|
exif_dict["thumbnail"] = thumb
|
||||||
|
|
||||||
|
if key_is_name:
|
||||||
|
exif_dict = _get_key_name_dict(exif_dict)
|
||||||
|
return exif_dict
|
||||||
|
|
||||||
|
|
||||||
|
class _ExifReader(object):
|
||||||
|
def __init__(self, data):
|
||||||
|
# Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
|
||||||
|
maybe_image = sys.version_info >= (3,0,0) or isinstance(data, str)
|
||||||
|
|
||||||
|
if maybe_image and data[0:2] == b"\xff\xd8": # JPEG
|
||||||
|
segments = split_into_segments(data)
|
||||||
|
app1 = get_exif_seg(segments)
|
||||||
|
if app1:
|
||||||
|
self.tiftag = app1[10:]
|
||||||
|
else:
|
||||||
|
self.tiftag = None
|
||||||
|
elif maybe_image and data[0:2] in (b"\x49\x49", b"\x4d\x4d"): # TIFF
|
||||||
|
self.tiftag = data
|
||||||
|
elif maybe_image and data[0:4] == b"RIFF" and data[8:12] == b"WEBP":
|
||||||
|
self.tiftag = _webp.get_exif(data)
|
||||||
|
elif maybe_image and data[0:4] == b"Exif": # Exif
|
||||||
|
self.tiftag = data[6:]
|
||||||
|
else:
|
||||||
|
with open(data, 'rb') as f:
|
||||||
|
magic_number = f.read(2)
|
||||||
|
if magic_number == b"\xff\xd8": # JPEG
|
||||||
|
app1 = read_exif_from_file(data)
|
||||||
|
if app1:
|
||||||
|
self.tiftag = app1[10:]
|
||||||
|
else:
|
||||||
|
self.tiftag = None
|
||||||
|
elif magic_number in (b"\x49\x49", b"\x4d\x4d"): # TIFF
|
||||||
|
with open(data, 'rb') as f:
|
||||||
|
self.tiftag = f.read()
|
||||||
|
else:
|
||||||
|
with open(data, 'rb') as f:
|
||||||
|
header = f.read(12)
|
||||||
|
if header[0:4] == b"RIFF"and header[8:12] == b"WEBP":
|
||||||
|
with open(data, 'rb') as f:
|
||||||
|
file_data = f.read()
|
||||||
|
self.tiftag = _webp.get_exif(file_data)
|
||||||
|
else:
|
||||||
|
raise InvalidImageDataError("Given file is neither JPEG nor TIFF.")
|
||||||
|
|
||||||
|
def get_ifd_dict(self, pointer, ifd_name, read_unknown=False):
|
||||||
|
ifd_dict = {}
|
||||||
|
tag_count = struct.unpack(self.endian_mark + "H",
|
||||||
|
self.tiftag[pointer: pointer+2])[0]
|
||||||
|
offset = pointer + 2
|
||||||
|
if ifd_name in ["0th", "1st"]:
|
||||||
|
t = "Image"
|
||||||
|
else:
|
||||||
|
t = ifd_name
|
||||||
|
p_and_value = []
|
||||||
|
for x in range(tag_count):
|
||||||
|
pointer = offset + 12 * x
|
||||||
|
tag = struct.unpack(self.endian_mark + "H",
|
||||||
|
self.tiftag[pointer: pointer+2])[0]
|
||||||
|
value_type = struct.unpack(self.endian_mark + "H",
|
||||||
|
self.tiftag[pointer + 2: pointer + 4])[0]
|
||||||
|
value_num = struct.unpack(self.endian_mark + "L",
|
||||||
|
self.tiftag[pointer + 4: pointer + 8]
|
||||||
|
)[0]
|
||||||
|
value = self.tiftag[pointer+8: pointer+12]
|
||||||
|
p_and_value.append((pointer, value_type, value_num, value))
|
||||||
|
v_set = (value_type, value_num, value, tag)
|
||||||
|
if tag in TAGS[t]:
|
||||||
|
ifd_dict[tag] = self.convert_value(v_set)
|
||||||
|
elif read_unknown:
|
||||||
|
ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag)
|
||||||
|
#else:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
if ifd_name == "0th":
|
||||||
|
pointer = offset + 12 * tag_count
|
||||||
|
ifd_dict["first_ifd_pointer"] = self.tiftag[pointer:pointer + 4]
|
||||||
|
return ifd_dict
|
||||||
|
|
||||||
|
def convert_value(self, val):
|
||||||
|
data = None
|
||||||
|
t = val[0]
|
||||||
|
length = val[1]
|
||||||
|
value = val[2]
|
||||||
|
|
||||||
|
if t == TYPES.Byte: # BYTE
|
||||||
|
if length > 4:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack("B" * length,
|
||||||
|
self.tiftag[pointer: pointer + length])
|
||||||
|
else:
|
||||||
|
data = struct.unpack("B" * length, value[0:length])
|
||||||
|
elif t == TYPES.Ascii: # ASCII
|
||||||
|
if length > 4:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = self.tiftag[pointer: pointer+length - 1]
|
||||||
|
else:
|
||||||
|
data = value[0: length - 1]
|
||||||
|
elif t == TYPES.Short: # SHORT
|
||||||
|
if length > 2:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "H" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*2])
|
||||||
|
else:
|
||||||
|
data = struct.unpack(self.endian_mark + "H" * length,
|
||||||
|
value[0:length * 2])
|
||||||
|
elif t == TYPES.Long: # LONG
|
||||||
|
if length > 1:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "L" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*4])
|
||||||
|
else:
|
||||||
|
data = struct.unpack(self.endian_mark + "L" * length,
|
||||||
|
value)
|
||||||
|
elif t == TYPES.Rational: # RATIONAL
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
if length > 1:
|
||||||
|
data = tuple(
|
||||||
|
(struct.unpack(self.endian_mark + "L",
|
||||||
|
self.tiftag[pointer + x * 8:
|
||||||
|
pointer + 4 + x * 8])[0],
|
||||||
|
struct.unpack(self.endian_mark + "L",
|
||||||
|
self.tiftag[pointer + 4 + x * 8:
|
||||||
|
pointer + 8 + x * 8])[0])
|
||||||
|
for x in range(length)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = (struct.unpack(self.endian_mark + "L",
|
||||||
|
self.tiftag[pointer: pointer + 4])[0],
|
||||||
|
struct.unpack(self.endian_mark + "L",
|
||||||
|
self.tiftag[pointer + 4: pointer + 8]
|
||||||
|
)[0])
|
||||||
|
elif t == TYPES.SByte: # SIGNED BYTES
|
||||||
|
if length > 4:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack("b" * length,
|
||||||
|
self.tiftag[pointer: pointer + length])
|
||||||
|
else:
|
||||||
|
data = struct.unpack("b" * length, value[0:length])
|
||||||
|
elif t == TYPES.Undefined: # UNDEFINED BYTES
|
||||||
|
if length > 4:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = self.tiftag[pointer: pointer+length]
|
||||||
|
else:
|
||||||
|
data = value[0:length]
|
||||||
|
elif t == TYPES.SShort: # SIGNED SHORT
|
||||||
|
if length > 2:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "h" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*2])
|
||||||
|
else:
|
||||||
|
data = struct.unpack(self.endian_mark + "h" * length,
|
||||||
|
value[0:length * 2])
|
||||||
|
elif t == TYPES.SLong: # SLONG
|
||||||
|
if length > 1:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "l" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*4])
|
||||||
|
else:
|
||||||
|
data = struct.unpack(self.endian_mark + "l" * length,
|
||||||
|
value)
|
||||||
|
elif t == TYPES.SRational: # SRATIONAL
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
if length > 1:
|
||||||
|
data = tuple(
|
||||||
|
(struct.unpack(self.endian_mark + "l",
|
||||||
|
self.tiftag[pointer + x * 8: pointer + 4 + x * 8])[0],
|
||||||
|
struct.unpack(self.endian_mark + "l",
|
||||||
|
self.tiftag[pointer + 4 + x * 8: pointer + 8 + x * 8])[0])
|
||||||
|
for x in range(length)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = (struct.unpack(self.endian_mark + "l",
|
||||||
|
self.tiftag[pointer: pointer + 4])[0],
|
||||||
|
struct.unpack(self.endian_mark + "l",
|
||||||
|
self.tiftag[pointer + 4: pointer + 8]
|
||||||
|
)[0])
|
||||||
|
elif t == TYPES.Float: # FLOAT
|
||||||
|
if length > 1:
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "f" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*4])
|
||||||
|
else:
|
||||||
|
data = struct.unpack(self.endian_mark + "f" * length,
|
||||||
|
value)
|
||||||
|
elif t == TYPES.DFloat: # DOUBLE
|
||||||
|
pointer = struct.unpack(self.endian_mark + "L", value)[0]
|
||||||
|
data = struct.unpack(self.endian_mark + "d" * length,
|
||||||
|
self.tiftag[pointer: pointer+length*8])
|
||||||
|
else:
|
||||||
|
raise ValueError("Exif might be wrong. Got incorrect value " +
|
||||||
|
"type to decode.\n" +
|
||||||
|
"tag: " + str(val[3]) + "\ntype: " + str(t))
|
||||||
|
|
||||||
|
if isinstance(data, tuple) and (len(data) == 1):
|
||||||
|
return data[0]
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _get_key_name_dict(exif_dict):
|
||||||
|
new_dict = {
|
||||||
|
"0th":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["0th"].items()},
|
||||||
|
"Exif":{TAGS["Exif"][n]["name"]:value for n, value in exif_dict["Exif"].items()},
|
||||||
|
"1st":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["1st"].items()},
|
||||||
|
"GPS":{TAGS["GPS"][n]["name"]:value for n, value in exif_dict["GPS"].items()},
|
||||||
|
"Interop":{TAGS["Interop"][n]["name"]:value for n, value in exif_dict["Interop"].items()},
|
||||||
|
"thumbnail":exif_dict["thumbnail"],
|
||||||
|
}
|
||||||
|
return new_dict
|
||||||
56
openflexure_microscope/camera/piexif/_remove.py
Normal file
56
openflexure_microscope/camera/piexif/_remove.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
from ._common import *
|
||||||
|
from piexif import _webp
|
||||||
|
|
||||||
|
def remove(src, new_file=None):
|
||||||
|
"""
|
||||||
|
py:function:: piexif.remove(filename)
|
||||||
|
|
||||||
|
Remove exif from JPEG.
|
||||||
|
|
||||||
|
:param str filename: JPEG
|
||||||
|
"""
|
||||||
|
output_is_file = False
|
||||||
|
if src[0:2] == b"\xff\xd8":
|
||||||
|
src_data = src
|
||||||
|
file_type = "jpeg"
|
||||||
|
elif src[0:4] == b"RIFF" and src[8:12] == b"WEBP":
|
||||||
|
src_data = src
|
||||||
|
file_type = "webp"
|
||||||
|
else:
|
||||||
|
with open(src, 'rb') as f:
|
||||||
|
src_data = f.read()
|
||||||
|
output_is_file = True
|
||||||
|
if src_data[0:2] == b"\xff\xd8":
|
||||||
|
file_type = "jpeg"
|
||||||
|
elif src_data[0:4] == b"RIFF" and src_data[8:12] == b"WEBP":
|
||||||
|
file_type = "webp"
|
||||||
|
|
||||||
|
if file_type == "jpeg":
|
||||||
|
segments = split_into_segments(src_data)
|
||||||
|
exif = get_exif_seg(segments)
|
||||||
|
if exif:
|
||||||
|
new_data = src_data.replace(exif, b"")
|
||||||
|
else:
|
||||||
|
new_data = src_data
|
||||||
|
elif file_type == "webp":
|
||||||
|
try:
|
||||||
|
new_data = _webp.remove(src_data)
|
||||||
|
except ValueError:
|
||||||
|
new_data = src_data
|
||||||
|
except e:
|
||||||
|
print(e.args)
|
||||||
|
raise ValueError("Error occurred.")
|
||||||
|
|
||||||
|
if isinstance(new_file, io.BytesIO):
|
||||||
|
new_file.write(new_data)
|
||||||
|
new_file.seek(0)
|
||||||
|
elif new_file:
|
||||||
|
with open(new_file, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
elif output_is_file:
|
||||||
|
with open(src, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
else:
|
||||||
|
raise ValueError("Give a second argument to 'remove' to output file")
|
||||||
45
openflexure_microscope/camera/piexif/_transplant.py
Normal file
45
openflexure_microscope/camera/piexif/_transplant.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
from ._common import *
|
||||||
|
|
||||||
|
|
||||||
|
def transplant(exif_src, image, new_file=None):
|
||||||
|
"""
|
||||||
|
py:function:: piexif.transplant(filename1, filename2)
|
||||||
|
|
||||||
|
Transplant exif from filename1 to filename2.
|
||||||
|
|
||||||
|
:param str filename1: JPEG
|
||||||
|
:param str filename2: JPEG
|
||||||
|
"""
|
||||||
|
if exif_src[0:2] == b"\xff\xd8":
|
||||||
|
src_data = exif_src
|
||||||
|
else:
|
||||||
|
with open(exif_src, 'rb') as f:
|
||||||
|
src_data = f.read()
|
||||||
|
segments = split_into_segments(src_data)
|
||||||
|
exif = get_exif_seg(segments)
|
||||||
|
if exif is None:
|
||||||
|
raise ValueError("not found exif in input")
|
||||||
|
|
||||||
|
output_file = False
|
||||||
|
if image[0:2] == b"\xff\xd8":
|
||||||
|
image_data = image
|
||||||
|
else:
|
||||||
|
with open(image, 'rb') as f:
|
||||||
|
image_data = f.read()
|
||||||
|
output_file = True
|
||||||
|
segments = split_into_segments(image_data)
|
||||||
|
new_data = merge_segments(segments, exif)
|
||||||
|
|
||||||
|
if isinstance(new_file, io.BytesIO):
|
||||||
|
new_file.write(new_data)
|
||||||
|
new_file.seek(0)
|
||||||
|
elif new_file:
|
||||||
|
with open(new_file, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
elif output_file:
|
||||||
|
with open(image, "wb+") as f:
|
||||||
|
f.write(new_data)
|
||||||
|
else:
|
||||||
|
raise ValueError("Give a 3rd argument to 'transplant' to output file")
|
||||||
240
openflexure_microscope/camera/piexif/_webp.py
Normal file
240
openflexure_microscope/camera/piexif/_webp.py
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
def split(data):
|
||||||
|
if data[0:4] != b"RIFF" or data[8:12] != b"WEBP":
|
||||||
|
raise ValueError("Not WebP")
|
||||||
|
|
||||||
|
webp_length_bytes = data[4:8]
|
||||||
|
webp_length = struct.unpack("<L", webp_length_bytes)[0]
|
||||||
|
RIFF_HEADER_SIZE = 8
|
||||||
|
file_size = RIFF_HEADER_SIZE + webp_length
|
||||||
|
|
||||||
|
start = 12
|
||||||
|
pointer = start
|
||||||
|
CHUNK_FOURCC_LENGTH = 4
|
||||||
|
LENGTH_BYTES_LENGTH = 4
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
while pointer + CHUNK_FOURCC_LENGTH + LENGTH_BYTES_LENGTH < file_size:
|
||||||
|
fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
|
||||||
|
pointer += CHUNK_FOURCC_LENGTH
|
||||||
|
chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
|
||||||
|
chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
|
||||||
|
pointer += LENGTH_BYTES_LENGTH
|
||||||
|
|
||||||
|
chunk_data = data[pointer:pointer + chunk_length]
|
||||||
|
chunks.append({"fourcc":fourcc, "length_bytes":chunk_length_bytes, "data":chunk_data})
|
||||||
|
|
||||||
|
padding = 1 if chunk_length % 2 else 0
|
||||||
|
|
||||||
|
pointer += chunk_length + padding
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
def merge_chunks(chunks):
|
||||||
|
merged = b"".join([chunk["fourcc"]
|
||||||
|
+ chunk["length_bytes"]
|
||||||
|
+ chunk["data"]
|
||||||
|
+ (len(chunk["data"]) % 2) * b"\x00"
|
||||||
|
for chunk in chunks])
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def _get_size_from_vp8x(chunk):
|
||||||
|
width_minus_one_bytes = chunk["data"][-6:-3] + b"\x00"
|
||||||
|
width_minus_one = struct.unpack("<L", width_minus_one_bytes)[0]
|
||||||
|
width = width_minus_one + 1
|
||||||
|
height_minus_one_bytes = chunk["data"][-3:] + b"\x00"
|
||||||
|
height_minus_one = struct.unpack("<L", height_minus_one_bytes)[0]
|
||||||
|
height = height_minus_one + 1
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
def _get_size_from_vp8(chunk):
|
||||||
|
BEGIN_CODE = b"\x9d\x01\x2a"
|
||||||
|
begin_index = chunk["data"].find(BEGIN_CODE)
|
||||||
|
if begin_index == -1:
|
||||||
|
ValueError("wrong VP8")
|
||||||
|
else:
|
||||||
|
BEGIN_CODE_LENGTH = len(BEGIN_CODE)
|
||||||
|
LENGTH_BYTES_LENGTH = 2
|
||||||
|
length_start = begin_index + BEGIN_CODE_LENGTH
|
||||||
|
width_bytes = chunk["data"][length_start:length_start + LENGTH_BYTES_LENGTH]
|
||||||
|
width = struct.unpack("<H", width_bytes)[0]
|
||||||
|
height_bytes = chunk["data"][length_start + LENGTH_BYTES_LENGTH:length_start + 2 * LENGTH_BYTES_LENGTH]
|
||||||
|
height = struct.unpack("<H", height_bytes)[0]
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
def _vp8L_contains_alpha(chunk_data):
|
||||||
|
flag = ord(chunk_data[4:5]) >> 5-1 & ord(b"\x01")
|
||||||
|
contains = 1 * flag
|
||||||
|
return contains
|
||||||
|
|
||||||
|
def _get_size_from_vp8L(chunk):
|
||||||
|
b1 = chunk["data"][1:2]
|
||||||
|
b2 = chunk["data"][2:3]
|
||||||
|
b3 = chunk["data"][3:4]
|
||||||
|
b4 = chunk["data"][4:5]
|
||||||
|
|
||||||
|
width_minus_one = (ord(b2) & ord(b"\x3F")) << 8 | ord(b1)
|
||||||
|
width = width_minus_one + 1
|
||||||
|
|
||||||
|
height_minus_one = (ord(b4) & ord(b"\x0F")) << 10 | ord(b3) << 2 | (ord(b2) & ord(b"\xC0")) >> 6
|
||||||
|
height = height_minus_one + 1
|
||||||
|
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
def _get_size_from_anmf(chunk):
|
||||||
|
width_minus_one_bytes = chunk["data"][6:9] + b"\x00"
|
||||||
|
width_minus_one = struct.unpack("<L", width_minus_one_bytes)[0]
|
||||||
|
width = width_minus_one + 1
|
||||||
|
height_minus_one_bytes = chunk["data"][9:12] + b"\x00"
|
||||||
|
height_minus_one = struct.unpack("<L", height_minus_one_bytes)[0]
|
||||||
|
height = height_minus_one + 1
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
def set_vp8x(chunks):
|
||||||
|
|
||||||
|
width = None
|
||||||
|
height = None
|
||||||
|
flags = [b"0", b"0", b"0", b"0", b"0", b"0", b"0", b"0"] # [0, 0, ICC, Alpha, EXIF, XMP, Anim, 0]
|
||||||
|
|
||||||
|
for chunk in chunks:
|
||||||
|
if chunk["fourcc"] == b"VP8X":
|
||||||
|
width, height = _get_size_from_vp8x(chunk)
|
||||||
|
elif chunk["fourcc"] == b"VP8 ":
|
||||||
|
width, height = _get_size_from_vp8(chunk)
|
||||||
|
elif chunk["fourcc"] == b"VP8L":
|
||||||
|
is_rgba = _vp8L_contains_alpha(chunk["data"])
|
||||||
|
if is_rgba:
|
||||||
|
flags[3] = b"1"
|
||||||
|
width, height = _get_size_from_vp8L(chunk)
|
||||||
|
elif chunk["fourcc"] == b"ANMF":
|
||||||
|
width, height = _get_size_from_anmf(chunk)
|
||||||
|
elif chunk["fourcc"] == b"ICCP":
|
||||||
|
flags[2] = b"1"
|
||||||
|
elif chunk["fourcc"] == b"ALPH":
|
||||||
|
flags[3] = b"1"
|
||||||
|
elif chunk["fourcc"] == b"EXIF":
|
||||||
|
flags[4] = b"1"
|
||||||
|
elif chunk["fourcc"] == b"XMP ":
|
||||||
|
flags[5] = b"1"
|
||||||
|
elif chunk["fourcc"] == b"ANIM":
|
||||||
|
flags[6] = b"1"
|
||||||
|
width_minus_one = width - 1
|
||||||
|
height_minus_one = height - 1
|
||||||
|
|
||||||
|
if chunks[0]["fourcc"] == b"VP8X":
|
||||||
|
chunks.pop(0)
|
||||||
|
|
||||||
|
header_bytes = b"VP8X"
|
||||||
|
length_bytes = b"\x0a\x00\x00\x00"
|
||||||
|
flags_bytes = struct.pack("B", int(b"".join(flags), 2))
|
||||||
|
padding_bytes = b"\x00\x00\x00"
|
||||||
|
width_bytes = struct.pack("<L", width_minus_one)[:3]
|
||||||
|
height_bytes = struct.pack("<L", height_minus_one)[:3]
|
||||||
|
|
||||||
|
data_bytes = flags_bytes + padding_bytes + width_bytes + height_bytes
|
||||||
|
|
||||||
|
vp8x_chunk = {"fourcc":header_bytes, "length_bytes":length_bytes, "data":data_bytes}
|
||||||
|
chunks.insert(0, vp8x_chunk)
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
def get_file_header(chunks):
|
||||||
|
WEBP_HEADER_LENGTH = 4
|
||||||
|
FOURCC_LENGTH = 4
|
||||||
|
LENGTH_BYTES_LENGTH = 4
|
||||||
|
|
||||||
|
length = WEBP_HEADER_LENGTH
|
||||||
|
for chunk in chunks:
|
||||||
|
data_length = struct.unpack("<L", chunk["length_bytes"])[0]
|
||||||
|
data_length += 1 if data_length % 2 else 0
|
||||||
|
length += FOURCC_LENGTH + LENGTH_BYTES_LENGTH + data_length
|
||||||
|
length_bytes = struct.pack("<L", length)
|
||||||
|
riff = b"RIFF"
|
||||||
|
webp_header = b"WEBP"
|
||||||
|
file_header = riff + length_bytes + webp_header
|
||||||
|
return file_header
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_exif(data):
|
||||||
|
if data[0:4] != b"RIFF" or data[8:12] != b"WEBP":
|
||||||
|
raise ValueError("Not WebP")
|
||||||
|
|
||||||
|
if data[12:16] != b"VP8X":
|
||||||
|
raise ValueError("doesnot have exif")
|
||||||
|
|
||||||
|
webp_length_bytes = data[4:8]
|
||||||
|
webp_length = struct.unpack("<L", webp_length_bytes)[0]
|
||||||
|
RIFF_HEADER_SIZE = 8
|
||||||
|
file_size = RIFF_HEADER_SIZE + webp_length
|
||||||
|
|
||||||
|
start = 12
|
||||||
|
pointer = start
|
||||||
|
CHUNK_FOURCC_LENGTH = 4
|
||||||
|
LENGTH_BYTES_LENGTH = 4
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
exif = b""
|
||||||
|
while pointer < file_size:
|
||||||
|
fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
|
||||||
|
pointer += CHUNK_FOURCC_LENGTH
|
||||||
|
chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
|
||||||
|
chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
|
||||||
|
if chunk_length % 2:
|
||||||
|
chunk_length += 1
|
||||||
|
pointer += LENGTH_BYTES_LENGTH
|
||||||
|
if fourcc == b"EXIF":
|
||||||
|
return data[pointer:pointer + chunk_length]
|
||||||
|
pointer += chunk_length
|
||||||
|
return None # if there isn't exif, return None.
|
||||||
|
|
||||||
|
|
||||||
|
def insert_exif_into_chunks(chunks, exif_bytes):
|
||||||
|
EXIF_HEADER = b"EXIF"
|
||||||
|
exif_length_bytes = struct.pack("<L", len(exif_bytes))
|
||||||
|
exif_chunk = {"fourcc":EXIF_HEADER, "length_bytes":exif_length_bytes, "data":exif_bytes}
|
||||||
|
|
||||||
|
xmp_index = None
|
||||||
|
animation_index = None
|
||||||
|
|
||||||
|
for index, chunk in enumerate(chunks):
|
||||||
|
if chunk["fourcc"] == b"EXIF":
|
||||||
|
chunks.pop(index)
|
||||||
|
|
||||||
|
for index, chunk in enumerate(chunks):
|
||||||
|
if chunk["fourcc"] == b"XMP ":
|
||||||
|
xmp_index = index
|
||||||
|
elif chunk["fourcc"] == b"ANIM":
|
||||||
|
animation_index = index
|
||||||
|
if xmp_index is not None:
|
||||||
|
chunks.insert(xmp_index, exif_chunk)
|
||||||
|
elif animation_index is not None:
|
||||||
|
chunks.insert(animation_index, exif_chunk)
|
||||||
|
else:
|
||||||
|
chunks.append(exif_chunk)
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
def insert(webp_bytes, exif_bytes):
|
||||||
|
chunks = split(webp_bytes)
|
||||||
|
chunks = insert_exif_into_chunks(chunks, exif_bytes)
|
||||||
|
chunks = set_vp8x(chunks)
|
||||||
|
file_header = get_file_header(chunks)
|
||||||
|
merged = merge_chunks(chunks)
|
||||||
|
new_webp_bytes = file_header + merged
|
||||||
|
return new_webp_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def remove(webp_bytes):
|
||||||
|
chunks = split(webp_bytes)
|
||||||
|
for index, chunk in enumerate(chunks):
|
||||||
|
if chunk["fourcc"] == b"EXIF":
|
||||||
|
chunks.pop(index)
|
||||||
|
chunks = set_vp8x(chunks)
|
||||||
|
file_header = get_file_header(chunks)
|
||||||
|
merged = merge_chunks(chunks)
|
||||||
|
new_webp_bytes = file_header + merged
|
||||||
|
return new_webp_bytes
|
||||||
66
openflexure_microscope/camera/piexif/helper.py
Normal file
66
openflexure_microscope/camera/piexif/helper.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
class UserComment:
|
||||||
|
#
|
||||||
|
# Names of encodings that we publicly support.
|
||||||
|
#
|
||||||
|
ASCII = 'ascii'
|
||||||
|
JIS = 'jis'
|
||||||
|
UNICODE = 'unicode'
|
||||||
|
ENCODINGS = (ASCII, JIS, UNICODE)
|
||||||
|
|
||||||
|
#
|
||||||
|
# The actual encodings accepted by the standard library differ slightly from
|
||||||
|
# the above.
|
||||||
|
#
|
||||||
|
_JIS = 'shift_jis'
|
||||||
|
_UNICODE = 'utf_16_be'
|
||||||
|
|
||||||
|
_PREFIX_SIZE = 8
|
||||||
|
#
|
||||||
|
# From Table 9: Character Codes and their Designation
|
||||||
|
#
|
||||||
|
_ASCII_PREFIX = b'\x41\x53\x43\x49\x49\x00\x00\x00'
|
||||||
|
_JIS_PREFIX = b'\x4a\x49\x53\x00\x00\x00\x00\x00'
|
||||||
|
_UNICODE_PREFIX = b'\x55\x4e\x49\x43\x4f\x44\x45\x00'
|
||||||
|
_UNDEFINED_PREFIX = b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, data):
|
||||||
|
"""
|
||||||
|
Convert "UserComment" value in exif format to str.
|
||||||
|
|
||||||
|
:param bytes data: "UserComment" value from exif
|
||||||
|
:return: u"foobar"
|
||||||
|
:rtype: str(Unicode)
|
||||||
|
:raises: ValueError if the data does not conform to the EXIF specification,
|
||||||
|
or the encoding is unsupported.
|
||||||
|
"""
|
||||||
|
if len(data) < cls._PREFIX_SIZE:
|
||||||
|
raise ValueError('not enough data to decode UserComment')
|
||||||
|
prefix = data[:cls._PREFIX_SIZE]
|
||||||
|
body = data[cls._PREFIX_SIZE:]
|
||||||
|
if prefix == cls._UNDEFINED_PREFIX:
|
||||||
|
raise ValueError('prefix is UNDEFINED, unable to decode UserComment')
|
||||||
|
try:
|
||||||
|
encoding = {
|
||||||
|
cls._ASCII_PREFIX: cls.ASCII, cls._JIS_PREFIX: cls._JIS, cls._UNICODE_PREFIX: cls._UNICODE,
|
||||||
|
}[prefix]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError('unable to determine appropriate encoding')
|
||||||
|
return body.decode(encoding, errors='replace')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dump(cls, data, encoding="ascii"):
|
||||||
|
"""
|
||||||
|
Convert str to appropriate format for "UserComment".
|
||||||
|
|
||||||
|
:param data: Like u"foobar"
|
||||||
|
:param str encoding: "ascii", "jis", or "unicode"
|
||||||
|
:return: b"ASCII\x00\x00\x00foobar"
|
||||||
|
:rtype: bytes
|
||||||
|
:raises: ValueError if the encoding is unsupported.
|
||||||
|
"""
|
||||||
|
if encoding not in cls.ENCODINGS:
|
||||||
|
raise ValueError('encoding %r must be one of %r' % (encoding, cls.ENCODINGS))
|
||||||
|
prefix = {cls.ASCII: cls._ASCII_PREFIX, cls.JIS: cls._JIS_PREFIX, cls.UNICODE: cls._UNICODE_PREFIX}[encoding]
|
||||||
|
internal_encoding = {cls.UNICODE: cls._UNICODE, cls.JIS: cls._JIS}.get(encoding, encoding)
|
||||||
|
return prefix + data.encode(internal_encoding, errors='replace')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue