import os from unittest.mock import patch import pytest from PIL import Image as PILImage from sigal.gallery import Image from sigal.image import ( generate_image, generate_thumbnail, get_exif_data, get_exif_tags, get_image_metadata, get_iptc_data, get_size, process_image, ) from sigal.log import init_logging from sigal.settings import Status, create_settings CURRENT_DIR = os.path.dirname(__file__) SRCDIR = os.path.join(CURRENT_DIR, "sample", "pictures") TEST_IMAGE = "KeckObservatory20071020.jpg" SRCFILE = os.path.join(SRCDIR, "dir2", TEST_IMAGE) TEST_GIF_IMAGE = "example.gif" SRC_GIF_FILE = os.path.join(SRCDIR, "dir1", "test1", TEST_GIF_IMAGE) def test_process_image(tmpdir): "Test the process_image function." status = process_image(Image("foo.txt", "bar", create_settings())) assert status == Status.FAILURE settings = create_settings( img_processor="ResizeToFill", make_thumbs=False, source=os.path.join(SRCDIR, "dir2"), destination=str(tmpdir), ) image = Image(TEST_IMAGE, ".", settings) status = process_image(image) assert status == Status.SUCCESS with PILImage.open(os.path.join(str(tmpdir), TEST_IMAGE)) as im: assert im.size == settings["img_size"] def test_generate_image(tmpdir): "Test the generate_image function." dstfile = str(tmpdir.join(TEST_IMAGE)) for i, size in enumerate([(600, 600), (300, 200)]): settings = create_settings( img_size=size, img_processor="ResizeToFill", copy_exif_data=True ) options = None if i == 0 else {"quality": 85} generate_image(SRCFILE, dstfile, settings, options=options) with PILImage.open(dstfile) as im: assert im.size == size def test_generate_image_imgformat(tmpdir): "Test the effects of the img_format setting on generate_image." dstfile = str(tmpdir.join(TEST_IMAGE)) for i, outfmt in enumerate(["JPEG", "PNG", "TIFF"]): settings = create_settings( img_size=(300, 300), img_processor="ResizeToFill", copy_exif_data=True, img_format=outfmt, ) options = {"quality": 85} generate_image(SRCFILE, dstfile, settings, options=options) with PILImage.open(dstfile) as im: assert im.format == outfmt def test_resize_image_portrait(tmpdir): """Test that the area is the same regardless of aspect ratio.""" size = (300, 200) settings = create_settings(img_size=size) portrait_image = "m57_the_ring_nebula-587px.jpg" portrait_src = os.path.join( CURRENT_DIR, "sample", "pictures", "dir2", portrait_image ) portrait_dst = str(tmpdir.join(portrait_image)) generate_image(portrait_src, portrait_dst, settings) with PILImage.open(portrait_dst) as im: # In the default mode, PILKit resizes in a way to never make an image # smaller than either of the lengths, the other is scaled accordingly. # Hence we test that the shorter side has the smallest length. assert im.size[0] == 200 landscape_image = "KeckObservatory20071020.jpg" landscape_src = os.path.join( CURRENT_DIR, "sample", "pictures", "dir2", landscape_image ) landscape_dst = str(tmpdir.join(landscape_image)) generate_image(landscape_src, landscape_dst, settings) with PILImage.open(landscape_dst) as im: assert im.size[1] == 200 @pytest.mark.parametrize( ("image", "path"), [(TEST_IMAGE, SRCFILE), (TEST_GIF_IMAGE, SRC_GIF_FILE)] ) def test_generate_image_passthrough(tmpdir, image, path): "Test the generate_image function with use_orig=True." dstfile = str(tmpdir.join(image)) settings = create_settings(use_orig=True) generate_image(path, dstfile, settings) # Check the file was copied, not (sym)linked st_src = os.stat(path) st_dst = os.stat(dstfile) assert st_src.st_size == st_dst.st_size assert not os.path.samestat(st_src, st_dst) def test_generate_image_passthrough_symlink(tmpdir): "Test the generate_image function with use_orig=True and orig_link=True." dstfile = str(tmpdir.join(TEST_IMAGE)) settings = create_settings(use_orig=True, orig_link=True) generate_image(SRCFILE, dstfile, settings) # Check the file was symlinked assert os.path.islink(dstfile) assert os.path.samefile(SRCFILE, dstfile) def test_generate_image_processor(tmpdir): "Test generate_image with a wrong processor name." init_logging("sigal") dstfile = str(tmpdir.join(TEST_IMAGE)) settings = create_settings(img_size=(200, 200), img_processor="WrongMethod") with pytest.raises(SystemExit): generate_image(SRCFILE, dstfile, settings) @pytest.mark.parametrize( ("image", "path", "wide_size", "high_size"), [ (TEST_IMAGE, SRCFILE, (200, 133), (150, 100)), (TEST_GIF_IMAGE, SRC_GIF_FILE, (134, 150), (150, 168)), ], ) def test_generate_thumbnail(tmpdir, image, path, wide_size, high_size): "Test the generate_thumbnail function." dstfile = str(tmpdir.join(image)) for size in [(200, 150), (150, 200)]: generate_thumbnail(path, dstfile, size) with PILImage.open(dstfile) as im: assert im.size == size for size, thumb_size in [((200, 150), wide_size), ((150, 200), high_size)]: generate_thumbnail(path, dstfile, size, fit=False) with PILImage.open(dstfile) as im: assert im.size == thumb_size def test_get_exif_tags(): test_image = "11.jpg" src_file = os.path.join( CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) data = get_exif_data(src_file) simple = get_exif_tags(data, datetime_format="%d/%m/%Y") assert simple["fstop"] == 3.9 assert simple["focal"] == 12.0 assert simple["iso"] == 50 assert simple["Make"] == "NIKON" assert simple["datetime"] == "22/01/2006" assert simple["exposure"] == "100603/100000000" data = {"FNumber": [1, 0], "FocalLength": [1, 0], "ExposureTime": 10} simple = get_exif_tags(data) assert "fstop" not in simple assert "focal" not in simple assert simple["exposure"] == "10" data = { "ExposureTime": "--", "DateTimeOriginal": "---", "GPSInfo": { "GPSLatitude": ((34, 0), (1, 0), (4500, 100)), "GPSLatitudeRef": "N", "GPSLongitude": ((116, 0), (8, 0), (3900, 100)), "GPSLongitudeRef": "W", }, } simple = get_exif_tags(data) assert "exposure" not in simple assert "datetime" not in simple assert "gps" not in simple def test_get_heic_exif_tags(): pytest.importorskip("pillow_heif") test_image = "outdoor.heic" src_file = os.path.join( CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) data = get_exif_data(src_file) simple = get_exif_tags(data, datetime_format="%d/%m/%Y") assert simple["Make"] == "samsung" assert simple["datetime"] == "01/09/2024" def test_get_iptc_data(caplog): test_image = "1.jpg" src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "iptcTest", test_image) data = get_iptc_data(src_file) # Title assert ( data["title"] == "Haemostratulus clouds over Canberra - " + "2005-12-28 at 03-25-07" ) # Description assert ( data["description"] == '"Haemo" because they look like haemoglobin ' + 'cells and "stratulus" because I can\'t work out whether ' + "they're Stratus or Cumulus clouds.\nWe're driving down " + "the main drag in Canberra so it's Parliament House that " + "you can see at the end of the road." ) # This file has no IPTC data test_image = "21.jpg" src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "exifTest", test_image) assert get_iptc_data(src_file) == {} # Headline test_image = "3.jpg" src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "iptcTest", test_image) data = get_iptc_data(src_file) assert data["headline"] == "Ring Nebula, M57" # Test catching the SyntaxError -- assert output with patch("sigal.image.IptcImagePlugin.getiptcinfo", side_effect=SyntaxError): get_iptc_data(src_file) assert ["IPTC Error in"] == [log.message[:13] for log in caplog.records] def test_get_image_metadata_exceptions(): # image does not exist test_image = "bad_image.jpg" src_file = os.path.join(CURRENT_DIR, "sample", test_image) data = get_image_metadata(src_file) assert data == {"exif": {}, "iptc": {}, "size": {}} def test_iso_speed_ratings(): data = {"ISOSpeedRatings": ()} simple = get_exif_tags(data) assert "iso" not in simple data = {"ISOSpeedRatings": None} simple = get_exif_tags(data) assert "iso" not in simple data = {"ISOSpeedRatings": 125} simple = get_exif_tags(data) assert "iso" in simple def test_exif_copy(tmpdir): "Test if EXIF data can transferred copied to the resized image." test_image = "11.jpg" src_file = os.path.join( CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) dst_file = str(tmpdir.join(test_image)) settings = create_settings(img_size=(300, 400), copy_exif_data=True) generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) assert simple["iso"] == 50 settings["copy_exif_data"] = False generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) assert not simple def test_exif_gps(tmpdir): """Test reading out correct geo tags""" test_image = "flickr_jerquiaga_2394751088_cc-by-nc.jpg" src_file = os.path.join( CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) dst_file = str(tmpdir.join(test_image)) settings = create_settings(img_size=(400, 300), copy_exif_data=True) generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) assert "gps" in simple lat = 34.029167 lon = -116.144167 assert abs(simple["gps"]["lat"] - lat) < 0.0001 assert abs(simple["gps"]["lon"] - lon) < 0.0001 def test_get_size(tmpdir): """Test reading out image size""" test_image = "flickr_jerquiaga_2394751088_cc-by-nc.jpg" src_file = os.path.join( CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) result = get_size(src_file) assert result == {"height": 800, "width": 600} def test_get_size_with_invalid_path(tmpdir): """Test reading out image size with a missing file""" test_image = "missing-file.jpg" src_file = os.path.join(CURRENT_DIR, test_image) result = get_size(src_file) assert result is None