Skip to content

Commit

Permalink
Merge pull request #519 from ususdei/feat_heif
Browse files Browse the repository at this point in the history
Add basic support for .heic files
  • Loading branch information
saimn authored Dec 16, 2024
2 parents 5752eca + 536f658 commit 5352699
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
12 changes: 9 additions & 3 deletions src/sigal/gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@
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,
Expand Down Expand Up @@ -264,7 +270,7 @@ def exif(self):
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
)

Expand All @@ -289,7 +295,7 @@ def _get_markdown_metadata(self):
@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
Expand Down
24 changes: 21 additions & 3 deletions src/sigal/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,21 @@
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
Expand All @@ -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
Expand Down Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion src/sigal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@
"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,
Expand Down
2 changes: 1 addition & 1 deletion src/sigal/templates/sigal.conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
# 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)
Expand Down
Binary file added tests/sample/pictures/dir1/test1/outdoor.heic
Binary file not shown.
13 changes: 12 additions & 1 deletion tests/test_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
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 = {
Expand Down Expand Up @@ -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")
Expand Down
12 changes: 12 additions & 0 deletions tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 2 additions & 7 deletions tests/test_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5352699

Please sign in to comment.