diff --git a/requirements-dev.txt b/requirements-dev.txt index e230ed26..f7ea7715 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,3 @@ -Pillow; python_version>='3.8' pytest mypy ruff diff --git a/requirements.txt b/requirements.txt index 4d2cde35..bc6d1fd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,3 @@ requests[socks]>=2.20.0,<3.0.0 tqdm>=4.0,<5.0 typing_extensions jsonschema~=4.17.3 -dataclasses; python_version<='3.6' diff --git a/tests/unit/test_exifedit.py b/tests/unit/test_exifedit.py index 4077c76f..34e66181 100644 --- a/tests/unit/test_exifedit.py +++ b/tests/unit/test_exifedit.py @@ -6,8 +6,8 @@ import py.path +from mapillary_tools.exif_read import ExifRead from mapillary_tools.exif_write import ExifEdit -from PIL import ExifTags, Image, TiffImagePlugin this_file = Path(__file__) this_file_dir = this_file.parent @@ -21,27 +21,8 @@ FIXED_EXIF_FILE = data_dir.joinpath("fixed_exif.jpg") FIXED_EXIF_FILE_2 = data_dir.joinpath("fixed_exif_2.jpg") -# more info on the standard exif tags -# https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html -EXIF_PRIMARY_TAGS_DICT = {y: x for x, y in ExifTags.TAGS.items()} -EXIF_GPS_TAGS_DICT = {y: x for x, y in ExifTags.GPSTAGS.items()} - -def load_exif(filename=EMPTY_EXIF_FILE_TEST): - test_image = Image.open(filename) - return test_image.getexif() - - -def rational_to_tuple(rational): - if isinstance(rational, TiffImagePlugin.IFDRational): - return rational.numerator, rational.denominator - elif isinstance(rational, tuple): - return tuple(rational_to_tuple(x) for x in rational) - elif isinstance(rational, list): - return list(rational_to_tuple(x) for x in rational) - - -def add_image_description_general(test_obj, filename): +def add_image_description_general(_test_obj, filename): test_dictionary = { "key_numeric": 1, "key_string": "one", @@ -54,11 +35,7 @@ def add_image_description_general(test_obj, filename): empty_exifedit.add_image_description(test_dictionary) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - test_obj.assertEqual( - str(test_dictionary), - str(exif_data[EXIF_PRIMARY_TAGS_DICT["ImageDescription"]]).replace('"', "'"), - ) + _exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) def add_orientation_general(test_obj, filename: Path): @@ -69,10 +46,9 @@ def add_orientation_general(test_obj, filename: Path): empty_exifedit.add_orientation(test_orientation) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - test_obj.assertEqual( - test_orientation, exif_data[EXIF_PRIMARY_TAGS_DICT["Orientation"]] - ) + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) + orientation = exif_data.extract_orientation() + test_obj.assertEqual(test_orientation, orientation) def add_date_time_original_general(test_obj, filename: Path): @@ -83,19 +59,10 @@ def add_date_time_original_general(test_obj, filename: Path): empty_exifedit.add_date_time_original(test_datetime) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - test_obj.assertEqual( - test_datetime.strftime("%Y:%m:%d %H:%M:%S"), - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["ExifOffset"])[ - EXIF_PRIMARY_TAGS_DICT["DateTimeOriginal"] - ], - ) - test_obj.assertEqual( - "249000", - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["ExifOffset"])[ - EXIF_PRIMARY_TAGS_DICT["SubsecTimeOriginal"] - ], - ) + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) + dt = exif_data.extract_exif_datetime() + + test_obj.assertEqual(test_datetime, dt) def add_lat_lon_general(test_obj, filename): @@ -108,23 +75,9 @@ def add_lat_lon_general(test_obj, filename): empty_exifedit.add_lat_lon(test_latitude, test_longitude, precision) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - exif_gps_info = exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"]) + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) - test_obj.assertEqual( - ( - ExifEdit.decimal_to_dms(abs(test_latitude), precision), - ExifEdit.decimal_to_dms(abs(test_longitude), precision), - "N", - "E", - ), - ( - rational_to_tuple(exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLatitude"]]), - rational_to_tuple(exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLongitude"]]), - exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLatitudeRef"]], - exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLongitudeRef"]], - ), - ) + test_obj.assertEqual((test_longitude, test_latitude), exif_data.extract_lon_lat()) def add_altitude_general(test_obj, filename: Path): @@ -136,15 +89,8 @@ def add_altitude_general(test_obj, filename: Path): empty_exifedit.add_altitude(test_altitude, test_altitude_precision) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - test_obj.assertEqual( - (test_altitude * test_altitude_precision, test_altitude_precision), - rational_to_tuple( - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSAltitude"] - ] - ), - ) + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) + test_obj.assertEqual(test_altitude, exif_data.extract_altitude()) def add_repeatedly_time_original_general(test_obj, filename): @@ -162,12 +108,10 @@ def add_repeatedly_time_original_general(test_obj, filename): not_empty_exifedit.add_date_time_original(test_datetime) not_empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) test_obj.assertEqual( - test_datetime.strftime("%Y:%m:%d %H:%M:%S"), - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["ExifOffset"])[ - EXIF_PRIMARY_TAGS_DICT["DateTimeOriginal"] - ], + test_datetime, + exif_data.extract_exif_datetime(), ) @@ -183,14 +127,10 @@ def add_direction_general(test_obj, filename): ) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) test_obj.assertEqual( - (test_direction * test_direction_precision, test_direction_precision), - rational_to_tuple( - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSImgDirection"] - ] - ), + test_direction, + exif_data.extract_orientation(), ) @@ -231,12 +171,9 @@ def test_write_to_non_existing_file(self): empty_exifedit.add_date_time_original(test_datetime) empty_exifedit.write(NON_EXISTING_FILE) - exif_data = load_exif(NON_EXISTING_FILE) - self.assertEqual( - test_datetime.strftime("%Y:%m:%d %H:%M:%S"), - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["ExifOffset"])[ - EXIF_PRIMARY_TAGS_DICT["DateTimeOriginal"] - ], + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) + self.assertIsNone( + exif_data.extract_exif_datetime(), ) def test_add_repeatedly_time_original(self): @@ -258,13 +195,11 @@ def test_add_time_original_to_existing_exif(self): not_empty_exifedit.add_date_time_original(test_datetime) not_empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) self.assertEqual( test_altitude, - exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSAltitude"] - ], + exif_data.extract_altitude(), ) def test_add_negative_lat_lon(self): @@ -277,17 +212,9 @@ def test_add_negative_lat_lon(self): empty_exifedit.add_lat_lon(test_latitude, test_longitude, precision) empty_exifedit.write(EMPTY_EXIF_FILE_TEST) - exif_data = load_exif() - exif_gps_info = exif_data.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"]) - - assert ExifEdit.decimal_to_dms( - abs(test_latitude), precision - ) == rational_to_tuple(exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLatitude"]]) - assert ExifEdit.decimal_to_dms( - abs(test_longitude), precision - ) == rational_to_tuple(exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLongitude"]]) - assert "S" == exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLatitudeRef"]] - assert "W" == exif_gps_info[EXIF_GPS_TAGS_DICT["GPSLongitudeRef"]] + exif_data = ExifRead(EMPTY_EXIF_FILE_TEST) + + assert (test_longitude, test_latitude) == exif_data.extract_lon_lat() # REPEAT CERTAIN TESTS AND ADD ADDITIONAL TESTS FOR THE CORRUPT EXIF def test_load_and_dump_corrupt_exif(self): diff --git a/tests/unit/test_exifread.py b/tests/unit/test_exifread.py index 62e6a19e..cf7e989d 100644 --- a/tests/unit/test_exifread.py +++ b/tests/unit/test_exifread.py @@ -2,7 +2,6 @@ import os import typing as T -import unittest from pathlib import Path import py.path @@ -16,7 +15,6 @@ parse_datetimestr_with_subsec_and_offset, ) from mapillary_tools.exif_write import ExifEdit -from PIL import ExifTags, Image """Initialize all the neccessary data""" @@ -26,11 +24,6 @@ TEST_EXIF_FILE = Path(os.path.join(data_dir, "test_exif.jpg")) -# more info on the standard exif tags -# https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html -EXIF_PRIMARY_TAGS_DICT = {y: x for x, y in ExifTags.TAGS.items()} -EXIF_GPS_TAGS_DICT = {y: x for x, y in ExifTags.GPSTAGS.items()} - @pytest.fixture def setup_data(tmpdir: py.path.local): @@ -48,23 +41,14 @@ def gps_to_decimal(value, ref): return sign * (float(degrees) + float(minutes) / 60 + float(seconds) / 3600) -def load_exif_PIL(filename=TEST_EXIF_FILE): - test_image = Image.open(filename) - return test_image.getexif() - - -def read_orientation_general(test_obj, filename: Path): - exif_data_PIL = load_exif_PIL() - orientation_PIL = exif_data_PIL[EXIF_PRIMARY_TAGS_DICT["Orientation"]] - - exif_data_ExifRead = ExifRead(filename) +def test_read_orientation_general(): + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) orientation_ExifRead = exif_data_ExifRead.extract_orientation() - - test_obj.assertEqual(orientation_PIL, orientation_ExifRead) + assert 2 == orientation_ExifRead -def read_date_time_original_general(test_obj, filename: Path): - exif_data_ExifRead = ExifRead(filename) +def test_read_date_time_original_general(): + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) capture_time_ExifRead = exif_data_ExifRead.extract_capture_time() # exiftool -time:all tests/unit/data/test_exif.jpg # Date/Time Original : 2018:06:26 17:46:33.847 @@ -81,92 +65,53 @@ def read_date_time_original_general(test_obj, filename: Path): # because "Sub Sec Time" is not subsec for "Date/Time Original" -def read_lat_lon_general(test_obj, filename: Path): - exif_data_PIL = load_exif_PIL() - latitude_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSLatitude"] - ] - longitude_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSLongitude"] - ] - latitudeRef_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSLatitudeRef"] - ] - longitudeRef_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSLongitudeRef"] - ] +def test_read_lat_lon_general(): + latitude_PIL, latitudeRef_PIL = (35.0, 54.0, 45.06), "N" + longitude_PIL, longitudeRef_PIL = (14.0, 29.0, 55.7), "E" latitude_PIL = gps_to_decimal(latitude_PIL, latitudeRef_PIL) longitude_PIL = gps_to_decimal(longitude_PIL, longitudeRef_PIL) - exif_data_ExifRead = ExifRead(filename) + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) lonlat = exif_data_ExifRead.extract_lon_lat() assert lonlat longitude_ExifRead, latitude_ExifRead = lonlat - test_obj.assertEqual( - (latitude_PIL, longitude_PIL), (latitude_ExifRead, longitude_ExifRead) - ) + assert (latitude_PIL, longitude_PIL) == (latitude_ExifRead, longitude_ExifRead) -def read_camera_make_model_general(test_obj, filename: Path): - exif_data_PIL = load_exif_PIL() - make_PIL = exif_data_PIL[EXIF_PRIMARY_TAGS_DICT["Make"]] - model_PIL = exif_data_PIL[EXIF_PRIMARY_TAGS_DICT["Model"]] +def test_read_camera_make_model_general(): + make_PIL = "HTC" + model_PIL = "Legend" - exif_data_ExifRead = ExifRead(filename) + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) make_ExifRead = exif_data_ExifRead.extract_make() model_ExifRead = exif_data_ExifRead.extract_model() - test_obj.assertEqual((make_PIL, model_PIL), (make_ExifRead, model_ExifRead)) + assert (make_PIL, model_PIL) == (make_ExifRead, model_ExifRead) -def read_altitude_general(test_obj, filename: Path): - exif_data_PIL = load_exif_PIL() - altitude_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSAltitude"] - ] - altitude_value_PIL = altitude_PIL.numerator / altitude_PIL.denominator +def test_read_altitude_general(): + numerator = 69 + denominator = 1 - exif_data_ExifRead = ExifRead(filename) + altitude_value_PIL = numerator / denominator + + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) altitude_ExifRead = exif_data_ExifRead.extract_altitude() - test_obj.assertEqual(altitude_value_PIL, altitude_ExifRead) + assert altitude_value_PIL == altitude_ExifRead -def read_direction_general(test_obj, filename: Path): - exif_data_PIL = load_exif_PIL() - direction_PIL = exif_data_PIL.get_ifd(EXIF_PRIMARY_TAGS_DICT["GPSInfo"])[ - EXIF_GPS_TAGS_DICT["GPSImgDirection"] - ] - direction_value_PIL = direction_PIL.numerator / direction_PIL.denominator +def test_read_direction_general(): + numerator = 100 + denominator = 100 + direction_value_PIL = numerator / denominator - exif_data_ExifRead = ExifRead(filename) + exif_data_ExifRead = ExifRead(TEST_EXIF_FILE) direction_ExifRead = exif_data_ExifRead.extract_direction() - test_obj.assertEqual(direction_value_PIL, direction_ExifRead) - - -class ExifReadTests(unittest.TestCase): - """tests for main functions.""" - - def test_read_orientation(self): - read_orientation_general(self, TEST_EXIF_FILE) - - def test_read_date_time_original(self): - read_date_time_original_general(self, TEST_EXIF_FILE) - - def test_read_lat_lon(self): - read_lat_lon_general(self, TEST_EXIF_FILE) - - def test_read_camera_make_model(self): - read_camera_make_model_general(self, TEST_EXIF_FILE) - - def test_read_altitude(self): - read_altitude_general(self, TEST_EXIF_FILE) - - def test_read_direction(self): - read_direction_general(self, TEST_EXIF_FILE) + assert direction_value_PIL == direction_ExifRead def test_parse(): @@ -286,14 +231,14 @@ def test_read_and_write(setup_data: py.path.local): image_path = Path(setup_data, "test_exif.jpg") dts = [ datetime.datetime.now(), - datetime.datetime.utcnow(), + datetime.datetime.now(datetime.timezone.utc), # 86400 is total seconds of one day (24 * 3600) # to avoid "OSError: [Errno 22] Invalid argument" in WINDOWS https://bugs.python.org/issue36759 datetime.datetime.fromtimestamp(86400), - datetime.datetime.utcfromtimestamp(86400), - datetime.datetime.utcfromtimestamp(86400.0000001), - datetime.datetime.utcfromtimestamp(86400.123456), - datetime.datetime.utcfromtimestamp(86400.0123), + datetime.datetime.fromtimestamp(86400, tz=datetime.timezone.utc), + datetime.datetime.fromtimestamp(86400.0000001, tz=datetime.timezone.utc), + datetime.datetime.fromtimestamp(86400.123456, tz=datetime.timezone.utc), + datetime.datetime.fromtimestamp(86400.0123, tz=datetime.timezone.utc), ] dts = dts[:] + [dt.astimezone() for dt in dts] dts = dts[:] + [dt.astimezone(datetime.timezone.utc) for dt in dts]