Skip to content

Commit 816575b

Browse files
committed
allow specifying thumbnail with the original filename
fix #476
1 parent 61ff855 commit 816575b

File tree

5 files changed

+118
-75
lines changed

5 files changed

+118
-75
lines changed

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Sigal now requires Python 3.11+.
1414
- Fix issue with latest version of feedgenerator.
1515
- Fix compat with click 8.2.0.
1616
- Fix theme URL for photoswipe.
17+
- Thumbnail can now be set in ``index.md`` either with the original filename or
18+
with the thumbnail name after format conversion [:issue:`476`].
1719

1820
Version 2.5
1921
~~~~~~~~~~~

src/sigal/gallery.py

Lines changed: 65 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
get_size,
5353
process_image,
5454
)
55-
from .settings import Status, get_thumb
55+
from .settings import IMG_EXTENSIONS, Status, get_thumb
5656
from .utils import (
5757
Devnull,
5858
check_or_create_dir,
@@ -246,15 +246,10 @@ def __init__(self, filename, path, settings):
246246
super().__init__(filename, path, settings)
247247
imgformat = settings.get("img_format")
248248

249-
# Register all formats
250-
PILImage.init()
251-
252-
if imgformat and PILImage.EXTENSION[self.src_ext] != imgformat.upper():
249+
if imgformat and IMG_EXTENSIONS.ext2format[self.src_ext] != imgformat.upper():
253250
# Find the extension that should match img_format
254-
extensions = {v: k for k, v in PILImage.EXTENSION.items()}
255-
ext = extensions[imgformat.upper()]
251+
ext = IMG_EXTENSIONS.format2ext[imgformat.upper()]
256252
self.dst_filename = self.basename + ext
257-
self.thumb_name = get_thumb(self.settings, self.dst_filename)
258253

259254
@cached_property
260255
def date(self):
@@ -593,76 +588,76 @@ def thumbnail(self):
593588
# Test the thumbnail from the Markdown file.
594589
thumbnail = self.meta.get("thumbnail", [""])[0]
595590

596-
if thumbnail and isfile(join(self.src_path, thumbnail)):
597-
self._thumbnail = url_from_path(
598-
join(self.name, get_thumb(self.settings, thumbnail))
599-
)
591+
if thumbnail:
592+
# if thumbnail is set in the markdown, it can be either the
593+
# original filename or the generated name after format conversion
594+
if isfile(join(self.src_path, thumbnail)):
595+
thumbnail = get_thumb(self.settings, thumbnail)
596+
self._thumbnail = url_from_path(join(self.name, thumbnail))
600597
self.logger.debug("Thumbnail for %r : %s", self, self._thumbnail)
601598
return self._thumbnail
602-
else:
603-
# find and return the first landscape image
604-
for f in self.medias:
605-
ext = splitext(f.dst_filename)[1]
606-
if ext.lower() not in self.settings["img_extensions"]:
607-
continue
608-
609-
# Use f.size if available as it is quicker (in cache), but
610-
# fallback to the size of src_path if dst_path is missing
611-
size = f.input_size
612-
if size is None:
613-
size = f.file_metadata["size"]
614-
615-
if size["width"] > size["height"]:
599+
600+
# find and return the first landscape image
601+
for f in self.medias:
602+
ext = splitext(f.dst_filename)[1]
603+
if ext.lower() not in self.settings["img_extensions"]:
604+
continue
605+
606+
# Use f.size if available as it is quicker (in cache), but
607+
# fallback to the size of src_path if dst_path is missing
608+
size = f.input_size
609+
if size is None:
610+
size = f.file_metadata["size"]
611+
612+
if size["width"] > size["height"]:
613+
try:
614+
self._thumbnail = url_quote(self.name) + "/" + f.thumbnail
615+
except Exception as e:
616+
self.logger.info(
617+
"Failed to get thumbnail for %s: %s", f.dst_filename, e
618+
)
619+
else:
620+
self.logger.debug(
621+
"Use 1st landscape image as thumbnail for %r : %s",
622+
self,
623+
self._thumbnail,
624+
)
625+
return self._thumbnail
626+
627+
# else simply return the 1st media file
628+
if not self._thumbnail and self.medias:
629+
for media in self.medias:
630+
if media.thumbnail is not None:
616631
try:
617-
self._thumbnail = url_quote(self.name) + "/" + f.thumbnail
632+
self._thumbnail = url_quote(self.name) + "/" + media.thumbnail
618633
except Exception as e:
619634
self.logger.info(
620-
"Failed to get thumbnail for %s: %s", f.dst_filename, e
635+
"Failed to get thumbnail for %s: %s",
636+
media.dst_filename,
637+
e,
621638
)
622639
else:
623-
self.logger.debug(
624-
"Use 1st landscape image as thumbnail for %r : %s",
625-
self,
626-
self._thumbnail,
627-
)
628-
return self._thumbnail
629-
630-
# else simply return the 1st media file
631-
if not self._thumbnail and self.medias:
632-
for media in self.medias:
633-
if media.thumbnail is not None:
634-
try:
635-
self._thumbnail = (
636-
url_quote(self.name) + "/" + media.thumbnail
637-
)
638-
except Exception as e:
639-
self.logger.info(
640-
"Failed to get thumbnail for %s: %s",
641-
media.dst_filename,
642-
e,
643-
)
644-
else:
645-
break
646-
else:
647-
self.logger.warning("No thumbnail found for %r", self)
648-
return
640+
break
641+
else:
642+
self.logger.warning("No thumbnail found for %r", self)
643+
return
649644

650-
self.logger.debug(
651-
"Use the 1st image as thumbnail for %r : %s", self, self._thumbnail
652-
)
653-
return self._thumbnail
654-
655-
# use the thumbnail of their sub-directories
656-
if not self._thumbnail:
657-
for path, album in self.gallery.get_albums(self.path):
658-
if album.thumbnail:
659-
self._thumbnail = url_quote(self.name) + "/" + album.thumbnail
660-
self.logger.debug(
661-
"Using thumbnail from sub-directory for %r : %s",
662-
self,
663-
self._thumbnail,
664-
)
665-
return self._thumbnail
645+
self.logger.debug(
646+
"Use the 1st image as thumbnail for %r : %s", self, self._thumbnail
647+
)
648+
return self._thumbnail
649+
650+
# use the thumbnail of their sub-directories
651+
if not self._thumbnail:
652+
for path, album in self.gallery.get_albums(self.path):
653+
if album.thumbnail:
654+
self._thumbnail = url_quote(self.name) + "/" + album.thumbnail
655+
self.logger.debug(
656+
"Using thumbnail from sub-directory for %r : %s",
657+
self,
658+
self._thumbnail,
659+
)
660+
return self._thumbnail
666661

667662
self.logger.error("Thumbnail not found for %r", self)
668663

src/sigal/settings.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from os.path import abspath, isabs, join, normpath
2727
from pprint import pformat
2828

29+
from PIL import Image as PILImage
30+
2931
_DEFAULT_CONFIG = {
3032
"albums_sort_attr": "name",
3133
"albums_sort_reverse": False,
@@ -112,6 +114,20 @@ class Status:
112114
FAILURE = 1
113115

114116

117+
class _ImgExtensions:
118+
def __init__(self):
119+
# Register all formats
120+
PILImage.init()
121+
122+
self.ext2format = PILImage.EXTENSION
123+
self.format2ext = {v: k for k, v in PILImage.EXTENSION.items()}
124+
self.format2ext["JPEG"] = ".jpg" # prefered ext for jpg
125+
self.format2ext["PNG"] = ".png" # prefered ext for png
126+
127+
128+
IMG_EXTENSIONS = _ImgExtensions()
129+
130+
115131
def get_thumb(settings, filename):
116132
"""Return the path to the thumb.
117133
@@ -132,6 +148,11 @@ def get_thumb(settings, filename):
132148

133149
if ext.lower() in settings["video_extensions"]:
134150
ext = ".jpg"
151+
152+
imgformat = settings.get("img_format")
153+
if imgformat:
154+
ext = IMG_EXTENSIONS.format2ext[imgformat]
155+
135156
return join(
136157
path,
137158
settings["thumb_dir"],

tests/sample/sigal.conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
thumb_suffix = ".tn"
55
keep_orig = True
66
thumb_video_delay = 5
7-
# img_format = 'jpeg'
7+
# img_format = "JPEG"
88

99
links = [
1010
("Example link", "http://example.org"),

tests/test_gallery.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import re
5+
import shutil
56
from os.path import join
67

78
import pytest
@@ -151,14 +152,14 @@ def test_media_iptc_override(settings):
151152

152153

153154
def test_media_img_format(settings):
154-
settings["img_format"] = "jpeg"
155+
settings["img_format"] = "JPEG"
155156
m = Image("11.tiff", "dir1/test1", settings)
156157
path = join("dir1", "test1")
157-
thumb = join("thumbnails", "11.tn.jpeg")
158+
thumb = join("thumbnails", "11.tn.jpg")
158159

159-
assert m.dst_filename == "11.jpeg"
160+
assert m.dst_filename == "11.jpg"
160161
assert m.src_path == join(settings["source"], path, "11.tiff")
161-
assert m.dst_path == join(settings["destination"], path, "11.jpeg")
162+
assert m.dst_path == join(settings["destination"], path, "11.jpg")
162163
assert m.thumb_name == thumb
163164
assert m.thumb_path == join(settings["destination"], path, thumb)
164165
assert m.title == "Foo Bar"
@@ -430,3 +431,27 @@ def test_ignores(settings, tmp_path):
430431
assert not (tmp_path / "test2").exists()
431432
assert not (tmp_path / "test1" / "example.gif").exists()
432433
assert not (tmp_path / "test1" / "CMB_Timeline300_no_WMAP.jpg").exists()
434+
435+
436+
@pytest.mark.parametrize("thumbnail", ["outdoor.heic", "outdoor.tn.jpg"])
437+
def test_thumbnail_with_img_format(settings, tmp_path, thumbnail):
438+
"""Test that outdoor.heic is correctly converted as jpg and used as thumbnail"""
439+
src_path = tmp_path / "pictures"
440+
src_path.mkdir()
441+
shutil.copytree(
442+
os.path.join(settings["source"], "dir1", "test1"), src_path / "test1"
443+
)
444+
desc = (src_path / "test1" / "index.md").read_text()
445+
desc = desc.replace("Thumbnail: 11.jpg", f"Thumbnail: {thumbnail}")
446+
(src_path / "test1" / "index.md").write_text(desc)
447+
448+
settings["img_format"] = "JPEG"
449+
settings["thumb_dir"] = ""
450+
settings["source"] = str(src_path)
451+
settings["destination"] = str(tmp_path / "build")
452+
gal = Gallery(settings, ncpu=1)
453+
gal.build()
454+
455+
assert (tmp_path / "build" / "test1" / "outdoor.tn.jpg").is_file()
456+
index = (tmp_path / "build" / "index.html").read_text()
457+
assert 'src="test1/outdoor.tn.jpg" class="album_thumb"' in index

0 commit comments

Comments
 (0)