diff --git a/doc/appendices.rst b/doc/appendices.rst index dec742d..371598d 100644 --- a/doc/appendices.rst +++ b/doc/appendices.rst @@ -13,10 +13,14 @@ http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf +---------------+----------------------+ | BYTE | int | +---------------+----------------------+ +| SIGNED BYTE | int | ++---------------+----------------------+ | ASCII | str | +---------------+----------------------+ | SHORT | int | +---------------+----------------------+ +| SIGNED SHORT | int | ++---------------+----------------------+ | LONG | int | +---------------+----------------------+ | RATIONAL | (int, int) | @@ -25,6 +29,10 @@ http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf +---------------+----------------------+ | SRATIONAL | (int, int) | +---------------+----------------------+ +| FLOAT | float | ++---------------+----------------------+ +| DOUBLE | float | ++---------------+----------------------+ If value type is number(BYTE, SHORT, LONG, RATIONAL, or SRATIONAL) and value count is two or more number, it is expressed with tuple. diff --git a/doc/changes.rst b/doc/changes.rst index 0491af6..184358f 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -1,6 +1,12 @@ Changelog ========= +1.0.13 +------ + +- Added helper function to read and write "UserComment". +- Added to support for SignedByte, SigendShort, Float, and Double. + 1.0.12 ------ diff --git a/doc/helper.rst b/doc/helper.rst new file mode 100644 index 0000000..86531ff --- /dev/null +++ b/doc/helper.rst @@ -0,0 +1,38 @@ +================ +Helper Functions +================ + +UserComment +----------- +.. py:function:: piexif.helper.UserComment.load(data) + + Convert "UserComment" value in exif format to str. + + :param bytes data: "UserComment" value from exif + :return: u"foobar" + :rtype: str(Unicode) + +:: + + import piexif + import piexif.helper + exif_dict = piexif.load("foo.jpg") + user_comment = piexif.helper.UserComment.load(exif_dict["Exif"][piexif.ExifIFD.UserComment]) + +.. py:function:: piexif.helper.UserComment.dump(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 + +:: + + import piexif + import piexif.helper + user_comment = piexif.helper.UserComment.dump(u"Edit now.") + exif_dict = piexif.load("foo.jpg") + exif_dict["Exif"][piexif.ExifIFD.UserComment] = user_comment + exif_bytes = piexif.dump(exif_dict) diff --git a/doc/index.rst b/doc/index.rst index 060c18d..1811e82 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,7 @@ To simplify exif manipulations with python. Writing, reading, and more... Piexif about installation functions + helper appendices sample changes diff --git a/piexif.pyproj b/piexif.pyproj index 9141455..20c6cde 100644 --- a/piexif.pyproj +++ b/piexif.pyproj @@ -35,7 +35,11 @@ Code + + + Code + Code diff --git a/piexif/__init__.py b/piexif/__init__.py index 46133d2..600f499 100644 --- a/piexif/__init__.py +++ b/piexif/__init__.py @@ -4,7 +4,7 @@ from ._transplant import transplant from ._insert import insert from ._exif import * -from ._exeptions import * +from ._exceptions import * -VERSION = '1.0.12' +VERSION = '1.0.13' diff --git a/piexif/_common.py b/piexif/_common.py index f8ca2fd..28dc9f9 100644 --- a/piexif/_common.py +++ b/piexif/_common.py @@ -1,6 +1,6 @@ import struct -from ._exeptions import InvalidImageDataError +from ._exceptions import InvalidImageDataError def split_into_segments(data): diff --git a/piexif/_dump.py b/piexif/_dump.py index 5867fdc..4617a69 100644 --- a/piexif/_dump.py +++ b/piexif/_dump.py @@ -67,8 +67,6 @@ def dump(exif_dict_original): 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]) - #exif_bytes = b"".join(exif_set) - #exif_length = len(exif_bytes) else: exif_bytes = b"" exif_length = 0 @@ -163,18 +161,27 @@ def _get_thumbnail(jpeg): 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"" @@ -265,7 +272,33 @@ def _value_to_bytes(raw_value, value_type, offset): 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 @@ -294,7 +327,7 @@ def _dict_to_bytes(ifd_dict, ifd, ifd_offset): type_str = struct.pack(">H", value_type) four_bytes_over = b"" - if isinstance(raw_value, numbers.Integral): + 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) diff --git a/piexif/_exeptions.py b/piexif/_exceptions.py similarity index 100% rename from piexif/_exeptions.py rename to piexif/_exceptions.py diff --git a/piexif/_exif.py b/piexif/_exif.py index 1f91a8e..abdf0bc 100644 --- a/piexif/_exif.py +++ b/piexif/_exif.py @@ -4,10 +4,13 @@ class TYPES: Short = 3 Long = 4 Rational = 5 + SByte = 6 Undefined = 7 + SShort = 8 SLong = 9 SRational = 10 Float = 11 + DFloat = 12 TAGS = { @@ -196,7 +199,10 @@ class TYPES: 51009: {'name': 'OpcodeList2', 'type': TYPES.Undefined}, 51022: {'name': 'OpcodeList3', 'type': TYPES.Undefined}, 60606: {'name': 'ZZZTestSlong1', 'type': TYPES.SLong}, - 60607: {'name': 'ZZZTestSlong2', '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}, @@ -229,7 +235,7 @@ class TYPES: 37386: {'name': 'FocalLength', 'type': TYPES.Rational}, 37396: {'name': 'SubjectArea', 'type': TYPES.Short}, 37500: {'name': 'MakerNote', 'type': TYPES.Undefined}, - 37510: {'name': 'UserComment', 'type': TYPES.Ascii}, + 37510: {'name': 'UserComment', 'type': TYPES.Undefined}, 37520: {'name': 'SubSecTime', 'type': TYPES.Ascii}, 37521: {'name': 'SubSecTimeOriginal', 'type': TYPES.Ascii}, 37522: {'name': 'SubSecTimeDigitized', 'type': TYPES.Ascii}, @@ -503,6 +509,9 @@ class ImageIFD: NoiseProfile = 51041 ZZZTestSlong1 = 60606 ZZZTestSlong2 = 60607 + ZZZTestSByte = 60608 + ZZZTestSShort = 60609 + ZZZTestDFloat = 60610 class ExifIFD: diff --git a/piexif/_insert.py b/piexif/_insert.py index 6df0eac..6e390c7 100644 --- a/piexif/_insert.py +++ b/piexif/_insert.py @@ -2,7 +2,7 @@ import struct from ._common import * -from ._exeptions import InvalidImageDataError +from ._exceptions import InvalidImageDataError def insert(exif, image, new_file=None): diff --git a/piexif/_load.py b/piexif/_load.py index 784faab..7d3b088 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -1,7 +1,7 @@ import struct from ._common import * -from ._exeptions import InvalidImageDataError +from ._exceptions import InvalidImageDataError from ._exif import * @@ -133,20 +133,20 @@ def convert_value(self, val): length = val[1] value = val[2] - if t == 1: # BYTE + 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 == 2: # ASCII + 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 == 3: # SHORT + 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, @@ -154,7 +154,7 @@ def convert_value(self, val): else: data = struct.unpack(self.endian_mark + "H" * length, value[0:length * 2]) - elif t == 4: # LONG + 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, @@ -162,7 +162,7 @@ def convert_value(self, val): else: data = struct.unpack(self.endian_mark + "L" * length, value) - elif t == 5: # RATIONAL + elif t == TYPES.Rational: # RATIONAL pointer = struct.unpack(self.endian_mark + "L", value)[0] if length > 1: data = tuple( @@ -180,13 +180,28 @@ def convert_value(self, val): struct.unpack(self.endian_mark + "L", self.tiftag[pointer + 4: pointer + 8] )[0]) - elif t == 7: # UNDEFINED BYTES + 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 == 9: # SLONG + 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, @@ -194,7 +209,7 @@ def convert_value(self, val): else: data = struct.unpack(self.endian_mark + "l" * length, value) - elif t == 10: # SRATIONAL + elif t == TYPES.SRational: # SRATIONAL pointer = struct.unpack(self.endian_mark + "L", value)[0] if length > 1: data = tuple( @@ -210,6 +225,18 @@ def convert_value(self, val): 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" + diff --git a/piexif/helper.py b/piexif/helper.py new file mode 100644 index 0000000..0e4d10c --- /dev/null +++ b/piexif/helper.py @@ -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') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ad3cbfd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +alabaster==0.7.10 +Babel==2.5.0 +certifi==2017.7.27.1 +chardet==3.0.4 +docutils==0.14 +idna==2.6 +imagesize==0.7.1 +Jinja2==2.9.6 +MarkupSafe==1.0 +olefile==0.44 +Pillow==4.2.1 +Pygments==2.2.0 +pytz==2017.2 +requests==2.18.4 +six==1.10.0 +snowballstemmer==1.2.1 +Sphinx==1.6.3 +sphinxcontrib-websupport==1.0.1 +typing==3.6.2 +urllib3==1.22 diff --git a/tests/s_test.py b/tests/s_test.py index a76609f..8baf3e2 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import copy import glob import io @@ -10,6 +12,7 @@ from PIL import Image import piexif from piexif import _common, ImageIFD, ExifIFD, GPSIFD, TAGS, InvalidImageDataError +from piexif import helper print("piexif version: {0}".format(piexif.VERSION)) @@ -345,6 +348,57 @@ def test_dump_and_load3(self): self.assertEqual(e["Exif"][ExifIFD.ISOSpeed], long_v[x]) self.assertEqual(e["GPS"][GPSIFD.GPSVersionID], byte_v[x]) + def test_dump_and_load_specials(self): + """test dump and load special types(SingedByte, SiginedShort, DoubleFloat)""" + zeroth_ifd_original = { + ImageIFD.ZZZTestSByte:-128, + ImageIFD.ZZZTestSShort:-32768, + ImageIFD.ZZZTestDFloat:1.0e-100, + } + exif_dict = {"0th":zeroth_ifd_original} + exif_bytes = piexif.dump(exif_dict) + + exif = piexif.load(exif_bytes) + zeroth_ifd = exif["0th"] + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestSByte], + zeroth_ifd[ImageIFD.ZZZTestSByte] + ) + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestSShort], + zeroth_ifd[ImageIFD.ZZZTestSShort] + ) + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestDFloat], + zeroth_ifd[ImageIFD.ZZZTestDFloat] + ) + + def test_dump_and_load_specials2(self): + """test dump and load special types(SingedByte, SiginedShort, DoubleFloat)""" + zeroth_ifd_original = { + ImageIFD.ZZZTestSByte:(-128, -128), + ImageIFD.ZZZTestSShort:(-32768, -32768), + ImageIFD.ZZZTestDFloat:(1.0e-100, 1.0e-100), + } + exif_dict = {"0th":zeroth_ifd_original} + exif_bytes = piexif.dump(exif_dict) + + exif = piexif.load(exif_bytes) + zeroth_ifd = exif["0th"] + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestSByte], + zeroth_ifd[ImageIFD.ZZZTestSByte] + ) + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestSShort], + zeroth_ifd[ImageIFD.ZZZTestSShort] + ) + self.assertEqual( + zeroth_ifd_original[ImageIFD.ZZZTestDFloat], + zeroth_ifd[ImageIFD.ZZZTestDFloat] + ) + + def test_roundtrip_files(self): files = glob.glob(os.path.join("tests", "images", "r_*.jpg")) for input_file in files: @@ -680,11 +734,132 @@ def test_merge_segments(self): o = io.BytesIO(new_data) Image.open(o).close() + def test_dump_user_comment(self): + # ascii + header = b"\x41\x53\x43\x49\x49\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("ascii") + result = helper.UserComment.dump(string, "ascii") + self.assertEqual(binary, result) + + # jis + header = b"\x4a\x49\x53\x00\x00\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("shift_jis") + result = helper.UserComment.dump(string, "jis") + self.assertEqual(binary, result) + + # unicode + header = b"\x55\x4e\x49\x43\x4f\x44\x45\x00" + string = u"abcd" + binary = header + string.encode("utf-16-be") + result = helper.UserComment.dump(string, "unicode") + self.assertEqual(binary, result) + + # undefined + header = b"\x00\x00\x00\x00\x00\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("latin") + self.assertRaises(ValueError, helper.UserComment.dump, string, "undefined") + + + def test_load_user_comment(self): + # ascii + header = b"\x41\x53\x43\x49\x49\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("ascii") + result = helper.UserComment.load(binary) + self.assertEqual(string, result) + + # jis + header = b"\x4a\x49\x53\x00\x00\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("shift_jis") + result = helper.UserComment.load(binary) + self.assertEqual(string, result) + + # unicode + header = b"\x55\x4e\x49\x43\x4f\x44\x45\x00" + string = u"abcd" + binary = header + string.encode("utf-16-be") + result = helper.UserComment.load(binary) + self.assertEqual(string, result) + + # undefined + header = b"\x00\x00\x00\x00\x00\x00\x00\x00" + string = u"abcd" + binary = header + string.encode("ascii") + self.assertRaises(ValueError, helper.UserComment.load, binary) + + +class HelperTests(unittest.TestCase): + def test_headers(self): + """Are our headers the correct length?""" + self.assertEqual(len(helper.UserComment._ASCII_PREFIX), helper.UserComment._PREFIX_SIZE) + self.assertEqual(len(helper.UserComment._JIS_PREFIX), helper.UserComment._PREFIX_SIZE) + self.assertEqual(len(helper.UserComment._UNICODE_PREFIX), helper.UserComment._PREFIX_SIZE) + self.assertEqual(len(helper.UserComment._UNDEFINED_PREFIX), helper.UserComment._PREFIX_SIZE) + + def test_encode_ascii(self): + """Do we encode ASCII correctly?""" + text = 'hello world' + expected = b'\x41\x53\x43\x49\x49\x00\x00\x00hello world' + actual = helper.UserComment.dump(text, encoding='ascii') + self.assertEqual(expected, actual) + + def test_decode_ascii(self): + """Do we decode ASCII correctly?""" + binary = b'\x41\x53\x43\x49\x49\x00\x00\x00hello world' + expected = 'hello world' + actual = helper.UserComment.load(binary) + self.assertEqual(expected, actual) + + def test_encode_jis(self): + """Do we encode JIS correctly?""" + text = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' + expected = b'\x4a\x49\x53\x00\x00\x00\x00\x00' + text.encode('shift_jis') + actual = helper.UserComment.dump(text, encoding='jis') + self.assertEqual(expected, actual) + + def test_decode_jis(self): + """Do we decode JIS correctly?""" + expected = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' + binary = b'\x4a\x49\x53\x00\x00\x00\x00\x00' + expected.encode('shift_jis') + actual = helper.UserComment.load(binary) + self.assertEqual(expected, actual) + + def test_encode_unicode(self): + """Do we encode Unicode correctly?""" + text = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' + expected = b'\x55\x4e\x49\x43\x4f\x44\x45\x00' + text.encode('utf_16_be') + actual = helper.UserComment.dump(text, encoding='unicode') + self.assertEqual(expected, actual) + + def test_decode_unicode(self): + """Do we decode Unicode correctly?""" + expected = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' + binary = b'\x55\x4e\x49\x43\x4f\x44\x45\x00' + expected.encode('utf_16_be') + actual = helper.UserComment.load(binary) + self.assertEqual(expected, actual) + + def test_encode_bad_encoding(self): + """De we gracefully handle bad input when encoding?""" + self.assertRaises(ValueError, helper.UserComment.dump, 'hello world', 'koi-8r') + + def test_decode_bad_encoding(self): + """De we gracefully handle bad input when decoding?""" + self.assertRaises(ValueError, helper.UserComment.load, + b'\x00\x00\x00\x00\x00\x00\x00\x00hello') + self.assertRaises(ValueError, helper.UserComment.load, + b'\x12\x34\x56\x78\x9a\xbc\xde\xffhello') + self.assertRaises(ValueError, helper.UserComment.load, b'hello world') + def suite(): suite = unittest.TestSuite() suite.addTests([unittest.makeSuite(UTests), - unittest.makeSuite(ExifTests)]) + unittest.makeSuite(ExifTests), + unittest.makeSuite(HelperTests)]) return suite