Browse Source

Merge pull request #519 from ususdei/feat_heif

Add basic support for .heic files
pull/518/merge
Simon Conseil 1 year ago committed by GitHub
parent
commit
53526990a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      pyproject.toml
  2. 12
      src/sigal/gallery.py
  3. 24
      src/sigal/image.py
  4. 11
      src/sigal/settings.py
  5. 2
      src/sigal/templates/sigal.conf.py
  6. BIN
      tests/sample/pictures/dir1/test1/outdoor.heic
  7. 13
      tests/test_gallery.py
  8. 12
      tests/test_image.py
  9. 9
      tests/test_zip.py

2
pyproject.toml

@ -32,7 +32,7 @@ dependencies = [
dynamic = ["version"]
[project.optional-dependencies]
all = ["brotli", "feedgenerator", "zopfli", "cryptography"]
all = ["brotli", "feedgenerator", "zopfli", "cryptography", "pillow-heif"]
tests = ["pytest", "pytest-cov"]
docs = ["Sphinx>=4.1.0", "furo", "cryptography"]

12
src/sigal/gallery.py

@ -45,7 +45,13 @@ from natsort import natsort_keygen, ns
from PIL import Image as PILImage
from . import image, signals, video
from .image import get_exif_tags, get_image_metadata, get_size, process_image
from .image import (
EXIF_EXTENSIONS,
get_exif_tags,
get_image_metadata,
get_size,
process_image,
)
from .settings import Status, get_thumb
from .utils import (
Devnull,
@ -264,7 +270,7 @@ class Image(Media):
datetime_format = self.settings["datetime_format"]
return (
get_exif_tags(self.raw_exif, datetime_format=datetime_format)
if self.raw_exif and self.src_ext in (".jpg", ".jpeg")
if self.raw_exif and self.src_ext in EXIF_EXTENSIONS
else None
)
@ -289,7 +295,7 @@ class Image(Media):
@cached_property
def raw_exif(self):
"""If not `None`, contains the raw EXIF tags."""
if self.src_ext in (".jpg", ".jpeg"):
if self.src_ext in EXIF_EXTENSIONS:
return self.file_metadata["exif"]
@cached_property

24
src/sigal/image.py

@ -44,11 +44,21 @@ from PIL.TiffImagePlugin import IFDRational
from pilkit.processors import Transpose
from pilkit.utils import save_image
try:
from pillow_heif import HeifImagePlugin # noqa: F401
HAS_HEIF = True
except ImportError:
HAS_HEIF = False
from . import signals, utils
from .settings import Status
# Force loading of truncated files
ImageFile.LOAD_TRUNCATED_IMAGES = True
EXIF_EXTENSIONS = (".jpg", ".jpeg", ".heic")
def _has_exif_tags(img):
return hasattr(img, "info") and "exif" in img.info
@ -66,7 +76,7 @@ def _read_image(file_path):
for w in caught_warnings:
logger.warning(
f"PILImage reported a warning for file {file_path}\n"
f"Pillow reported a warning for file {file_path}\n"
f"{w.category}: {w.message}"
)
return im
@ -180,6 +190,11 @@ def process_image(media):
options = media.settings["jpg_options"]
elif media.src_ext == ".png":
options = {"optimize": True}
elif media.src_ext == ".heic" and not HAS_HEIF:
logger.warning(
f"cannot open {media.src_path}, pillow-heif is needed to open .heic files"
)
return Status.FAILURE
else:
options = {}
@ -220,7 +235,10 @@ def get_exif_data(filename):
try:
with warnings.catch_warnings(record=True) as caught_warnings:
exif = img._getexif() or {}
exif = {}
exifdata = img.getexif()
if exifdata:
exif = exifdata._get_merged_dict()
except ZeroDivisionError:
logger.warning("Failed to read EXIF data.")
return None
@ -290,7 +308,7 @@ def get_image_metadata(filename):
logger.error("Could not open image %s metadata: %s", filename, e)
else:
try:
if os.path.splitext(filename)[1].lower() in (".jpg", ".jpeg"):
if os.path.splitext(filename)[1].lower() in EXIF_EXTENSIONS:
exif = get_exif_data(img)
except Exception as e:
logger.warning("Could not read EXIF data from %s: %s", filename, e)

11
src/sigal/settings.py

@ -42,7 +42,16 @@ _DEFAULT_CONFIG = {
"google_tag_manager": "",
"ignore_directories": [],
"ignore_files": [],
"img_extensions": [".jpg", ".jpeg", ".png", ".gif", ".tif", ".tiff", ".webp"],
"img_extensions": [
".jpg",
".jpeg",
".png",
".gif",
".heic",
".tif",
".tiff",
".webp",
],
"img_processor": "ResizeToFit",
"img_size": (640, 480),
"img_format": None,

2
src/sigal/templates/sigal.conf.py

@ -66,7 +66,7 @@ img_size = (800, 600)
# map_height = '500px'
# File extensions that should be treated as images
# img_extensions = ['.jpg', '.jpeg', '.png', '.gif']
# img_extensions = [".jpg", ".jpeg", ".png", ".gif", ".heic", ".tif", ".tiff", ".webp"]
# Pilkit processor used to resize the image
# (see http://pilkit.readthedocs.org/en/latest/#processors)

BIN
tests/sample/pictures/dir1/test1/outdoor.heic

Binary file not shown.

13
tests/test_gallery.py

@ -10,6 +10,13 @@ from PIL import Image as PILImage
from sigal.gallery import Album, Gallery, Image, Media, Video
from sigal.video import SubprocessException
try:
from pillow_heif import HeifImagePlugin # noqa: F401
HAS_HEIF = True
except ImportError:
HAS_HEIF = False
CURRENT_DIR = os.path.dirname(__file__)
REF = {
@ -301,7 +308,11 @@ def test_gallery(settings, tmp_path, caplog):
gal = Gallery(settings, ncpu=1)
gal.build()
assert re.match(r"CSS file .* could not be found", caplog.records[3].message)
if HAS_HEIF:
assert re.match(r"CSS file .* could not be found", caplog.records[3].message)
else:
assert re.match(r"CSS file .* could not be found", caplog.records[4].message)
with open(tmp_path / "my.css", mode="w") as f:
f.write("color: red")

12
tests/test_image.py

@ -202,6 +202,18 @@ def test_get_exif_tags():
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)

9
tests/test_zip.py

@ -24,16 +24,11 @@ def test_zipped_correctly(tmpdir):
gallery = make_gallery(destination=outpath, zip_gallery="archive.zip")
gallery.build()
zipf = os.path.join(outpath, "test1", "archive.zip")
zipf = os.path.join(outpath, "test2", "archive.zip")
assert os.path.isfile(zipf)
zip_file = zipfile.ZipFile(zipf, "r")
expected = (
"11.jpg",
"CMB_Timeline300_no_WMAP.jpg",
"flickr_jerquiaga_2394751088_cc-by-nc.jpg",
"example.gif",
)
expected = ("21.tiff", "22.jpg", "CMB_Timeline300_no_WMAP.jpg")
for filename in zip_file.namelist():
assert filename in expected

Loading…
Cancel
Save