sigal 2.5__tar.gz → 2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. {sigal-2.5 → sigal-2.6}/.github/workflows/publish.yml +2 -2
  2. {sigal-2.5 → sigal-2.6}/.github/workflows/python-tests.yml +7 -5
  3. {sigal-2.5 → sigal-2.6}/.pre-commit-config.yaml +3 -3
  4. {sigal-2.5 → sigal-2.6}/.readthedocs.yml +2 -2
  5. {sigal-2.5 → sigal-2.6}/LICENSE +1 -1
  6. {sigal-2.5 → sigal-2.6}/PKG-INFO +10 -9
  7. {sigal-2.5 → sigal-2.6}/README.rst +2 -2
  8. {sigal-2.5 → sigal-2.6}/docs/changelog.rst +17 -0
  9. {sigal-2.5 → sigal-2.6}/docs/conf.py +1 -1
  10. {sigal-2.5 → sigal-2.6}/pyproject.toml +6 -7
  11. {sigal-2.5 → sigal-2.6}/src/sigal/__init__.py +1 -1
  12. {sigal-2.5 → sigal-2.6}/src/sigal/__main__.py +12 -9
  13. {sigal-2.5 → sigal-2.6}/src/sigal/gallery.py +66 -79
  14. {sigal-2.5 → sigal-2.6}/src/sigal/image.py +5 -3
  15. {sigal-2.5 → sigal-2.6}/src/sigal/log.py +1 -1
  16. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/feeds.py +1 -0
  17. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/media_page.py +1 -1
  18. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/nonmedia_files.py +26 -15
  19. {sigal-2.5 → sigal-2.6}/src/sigal/settings.py +23 -2
  20. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/templates/album.html +30 -28
  21. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/templates/album_items.html +1 -1
  22. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/styles.css +4 -5
  23. sigal-2.6/src/sigal/themes/photoswipe/templates/album.html +9 -0
  24. sigal-2.6/src/sigal/themes/photoswipe/templates/album_items.html +31 -0
  25. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/templates/album_list.html +5 -0
  26. sigal-2.6/src/sigal/themes/photoswipe/templates/base.html +92 -0
  27. {sigal-2.5 → sigal-2.6}/src/sigal/utils.py +1 -1
  28. {sigal-2.5 → sigal-2.6}/src/sigal/version.py +16 -3
  29. {sigal-2.5 → sigal-2.6}/src/sigal/video.py +1 -1
  30. {sigal-2.5 → sigal-2.6}/src/sigal/writer.py +1 -1
  31. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/PKG-INFO +10 -9
  32. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/SOURCES.txt +3 -0
  33. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/requires.txt +2 -1
  34. sigal-2.6/tests/sample/pictures/exifTest/22.jpg +0 -0
  35. sigal-2.6/tests/sample/pictures/nonmedia_files/dummy.txt +0 -0
  36. {sigal-2.5 → sigal-2.6}/tests/sample/sigal.conf.py +1 -1
  37. {sigal-2.5 → sigal-2.6}/tests/test_gallery.py +29 -4
  38. {sigal-2.5 → sigal-2.6}/tests/test_settings.py +3 -2
  39. {sigal-2.5 → sigal-2.6}/tox.ini +10 -12
  40. sigal-2.5/src/sigal/themes/photoswipe/templates/album.html +0 -93
  41. sigal-2.5/src/sigal/themes/photoswipe/templates/base.html +0 -43
  42. {sigal-2.5 → sigal-2.6}/.git-blame-ignore-revs +0 -0
  43. {sigal-2.5 → sigal-2.6}/.github/dependabot.yml +0 -0
  44. {sigal-2.5 → sigal-2.6}/.gitignore +0 -0
  45. {sigal-2.5 → sigal-2.6}/AUTHORS +0 -0
  46. {sigal-2.5 → sigal-2.6}/CONTRIBUTING.rst +0 -0
  47. {sigal-2.5 → sigal-2.6}/docs/Makefile +0 -0
  48. {sigal-2.5 → sigal-2.6}/docs/album_information.rst +0 -0
  49. {sigal-2.5 → sigal-2.6}/docs/changelog.py +0 -0
  50. {sigal-2.5 → sigal-2.6}/docs/configuration.rst +0 -0
  51. {sigal-2.5 → sigal-2.6}/docs/contribute.rst +0 -0
  52. {sigal-2.5 → sigal-2.6}/docs/faq.rst +0 -0
  53. {sigal-2.5 → sigal-2.6}/docs/getting_started.rst +0 -0
  54. {sigal-2.5 → sigal-2.6}/docs/image_information.rst +0 -0
  55. {sigal-2.5 → sigal-2.6}/docs/index.rst +0 -0
  56. {sigal-2.5 → sigal-2.6}/docs/installation.rst +0 -0
  57. {sigal-2.5 → sigal-2.6}/docs/license.rst +0 -0
  58. {sigal-2.5 → sigal-2.6}/docs/make.bat +0 -0
  59. {sigal-2.5 → sigal-2.6}/docs/plugins.rst +0 -0
  60. {sigal-2.5 → sigal-2.6}/docs/themes.rst +0 -0
  61. {sigal-2.5 → sigal-2.6}/setup.cfg +0 -0
  62. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/__init__.py +0 -0
  63. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/adjust.py +0 -0
  64. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/compress_assets.py +0 -0
  65. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/copyright.py +0 -0
  66. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/__init__.py +0 -0
  67. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/encrypt.py +0 -0
  68. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/endec.py +0 -0
  69. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/static/decrypt.js +0 -0
  70. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/static/keycheck.txt +0 -0
  71. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/encrypt/static/sw.js +0 -0
  72. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/extended_caching.py +0 -0
  73. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/nomedia.py +0 -0
  74. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/titleregexp.py +0 -0
  75. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/watermark.py +0 -0
  76. {sigal-2.5 → sigal-2.6}/src/sigal/plugins/zip_gallery.py +0 -0
  77. {sigal-2.5 → sigal-2.6}/src/sigal/signals.py +0 -0
  78. {sigal-2.5 → sigal-2.6}/src/sigal/templates/sigal.conf.py +0 -0
  79. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/css/colorbox.css +0 -0
  80. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/css/skeleton.css +0 -0
  81. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/css/style.css +0 -0
  82. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/images/controls.png +0 -0
  83. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/images/loading.gif +0 -0
  84. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/app-with-media-page.js +0 -0
  85. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/app.js +0 -0
  86. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery-2.2.1.js +0 -0
  87. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery-2.2.1.min.js +0 -0
  88. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery.colorbox-min.js +0 -0
  89. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery.colorbox.js +0 -0
  90. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery.touchSwipe.js +0 -0
  91. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/static/js/jquery.touchSwipe.min.js +0 -0
  92. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/templates/album_list.html +0 -0
  93. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/templates/base.html +0 -0
  94. {sigal-2.5 → sigal-2.6}/src/sigal/themes/colorbox/templates/media.html +0 -0
  95. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/Control.FullScreen.css +0 -0
  96. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/Control.FullScreen.js +0 -0
  97. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/Leaflet.Photo.css +0 -0
  98. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/Leaflet.Photo.js +0 -0
  99. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/MarkerCluster.Default.css +0 -0
  100. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/MarkerCluster.css +0 -0
  101. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/README +0 -0
  102. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/icon-fullscreen-2x.png +0 -0
  103. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/icon-fullscreen.png +0 -0
  104. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/images/layers-2x.png +0 -0
  105. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/images/layers.png +0 -0
  106. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/images/marker-icon-2x.png +0 -0
  107. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/images/marker-icon.png +0 -0
  108. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/images/marker-shadow.png +0 -0
  109. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet-providers.js +0 -0
  110. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet-src.js +0 -0
  111. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet.css +0 -0
  112. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet.js +0 -0
  113. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet.markercluster-src.js +0 -0
  114. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/static/leaflet/leaflet.markercluster.js +0 -0
  115. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/analytics.html +0 -0
  116. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/breadcrumb.html +0 -0
  117. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/decrypt.html +0 -0
  118. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/default_head.html +0 -0
  119. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/description.html +0 -0
  120. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/download_zip.html +0 -0
  121. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/footer.html +0 -0
  122. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/gtm.html +0 -0
  123. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/links.html +0 -0
  124. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/map.html +0 -0
  125. {sigal-2.5 → sigal-2.6}/src/sigal/themes/default/templates/piwik.html +0 -0
  126. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/README +0 -0
  127. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/css/normalize.css +0 -0
  128. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/css/style.css +0 -0
  129. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/galleria.js +0 -0
  130. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/galleria.min.js +0 -0
  131. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/img/empty.png +0 -0
  132. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/img/fullscreen.png +0 -0
  133. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/jquery-3.3.1.js +0 -0
  134. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/jquery-3.3.1.min.js +0 -0
  135. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/plugins/history/galleria.history.js +0 -0
  136. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/plugins/history/galleria.history.min.js +0 -0
  137. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/azur/galleria.azur.css +0 -0
  138. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/azur/galleria.azur.js +0 -0
  139. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/azur/galleria.azur.min.css +0 -0
  140. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/azur/galleria.azur.min.js +0 -0
  141. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/classic/galleria.classic.css +0 -0
  142. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/classic/galleria.classic.js +0 -0
  143. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/classic/galleria.classic.min.css +0 -0
  144. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/classic/galleria.classic.min.js +0 -0
  145. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/folio/galleria.folio.css +0 -0
  146. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/folio/galleria.folio.js +0 -0
  147. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/folio/galleria.folio.min.css +0 -0
  148. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/folio/galleria.folio.min.js +0 -0
  149. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/fullscreen/galleria.fullscreen.css +0 -0
  150. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/fullscreen/galleria.fullscreen.js +0 -0
  151. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/fullscreen/galleria.fullscreen.min.css +0 -0
  152. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/fullscreen/galleria.fullscreen.min.js +0 -0
  153. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/fullscreen/index.html +0 -0
  154. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/miniml/galleria.miniml.css +0 -0
  155. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/miniml/galleria.miniml.js +0 -0
  156. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/miniml/galleria.miniml.min.css +0 -0
  157. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/miniml/galleria.miniml.min.js +0 -0
  158. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/twelve/galleria.twelve.css +0 -0
  159. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/twelve/galleria.twelve.js +0 -0
  160. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/twelve/galleria.twelve.min.css +0 -0
  161. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/static/themes/twelve/galleria.twelve.min.js +0 -0
  162. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/templates/album.html +0 -0
  163. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/templates/album_list.html +0 -0
  164. {sigal-2.5 → sigal-2.6}/src/sigal/themes/galleria/templates/base.html +0 -0
  165. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.js +0 -0
  166. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.min.js +0 -0
  167. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-fullscreen.esm.js +0 -0
  168. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-fullscreen.esm.min.js +0 -0
  169. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-lightbox.esm.js +0 -0
  170. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-lightbox.esm.js.map +0 -0
  171. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-lightbox.esm.min.js +0 -0
  172. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-video-plugin.esm.js +0 -0
  173. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe-video-plugin.esm.min.js +0 -0
  174. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe.css +0 -0
  175. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe.esm.js +0 -0
  176. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe.esm.js.map +0 -0
  177. {sigal-2.5 → sigal-2.6}/src/sigal/themes/photoswipe/static/photoswipe.esm.min.js +0 -0
  178. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/dependency_links.txt +0 -0
  179. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/entry_points.txt +0 -0
  180. {sigal-2.5 → sigal-2.6}/src/sigal.egg-info/top_level.txt +0 -0
  181. {sigal-2.5 → sigal-2.6}/tests/conftest.py +0 -0
  182. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/.venv/lib64/fake.png +0 -0
  183. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/accentu/303/251/11.jpg" +0 -0
  184. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/accentu/303/251/h/303/251lico/303/257de.jpg" +0 -0
  185. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/accentu/303/251/test?<special> chars#.jpg" +0 -0
  186. {sigal-2.5/tests/sample/pictures/dir1/test2 → sigal-2.6/tests/sample/pictures/dir1}/22.jpg +0 -0
  187. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/empty/fake.txt +0 -0
  188. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/index.md +0 -0
  189. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/11.jpg +0 -0
  190. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/11.md +0 -0
  191. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/CMB_Timeline300_no_WMAP.jpg +0 -0
  192. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/example.gif +0 -0
  193. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/flickr_jerquiaga_2394751088_cc-by-nc.jpg +0 -0
  194. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/flickr_jerquiaga_2394751088_cc-by-nc.md +0 -0
  195. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/index.md +0 -0
  196. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test1/outdoor.heic +0 -0
  197. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/21.md +0 -0
  198. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/21.tiff +0 -0
  199. {sigal-2.5/tests/sample/pictures/encryptTest → sigal-2.6/tests/sample/pictures/dir1/test2}/22.jpg +0 -0
  200. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/22.md +0 -0
  201. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/CMB_Timeline300_no_WMAP.jpg +0 -0
  202. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/CMB_Timeline300_no_WMAP.md +0 -0
  203. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test2/index.md +0 -0
  204. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test3/3.jpg +0 -0
  205. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir1/test3/index.md +0 -0
  206. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/.nozip_gallery +0 -0
  207. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/Hubble Interacting Galaxy NGC 5257.jpg +0 -0
  208. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/Hubble ultra deep field.jpg +0 -0
  209. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/KeckObservatory20071020.jpg +0 -0
  210. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/index.md +0 -0
  211. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/dir2/m57_the_ring_nebula-587px.jpg +0 -0
  212. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/empty/fake.txt +0 -0
  213. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/encryptTest/21.jpg +0 -0
  214. {sigal-2.5/tests/sample/pictures/exifTest → sigal-2.6/tests/sample/pictures/encryptTest}/22.jpg +0 -0
  215. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/exifTest/21.jpg +0 -0
  216. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/exifTest/noexif.png +0 -0
  217. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/index.md +0 -0
  218. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/iptcTest/1.jpg +0 -0
  219. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/iptcTest/2.jpg +0 -0
  220. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/iptcTest/2.md +0 -0
  221. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/iptcTest/3.jpg +0 -0
  222. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/iptcTest/index.md +0 -0
  223. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/created/.nomedia +0 -0
  224. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/created/ignored/should_be_ignored2.jpg +0 -0
  225. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/created/should_be_created.jpg +0 -0
  226. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/created/should_be_ignored3.jpg +0 -0
  227. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/ignored/.nomedia +0 -0
  228. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/ignored/recursively_ignored/should_be_ignored.jpg +0 -0
  229. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nomedia/ignored/should_be_ignored_1.jpg +0 -0
  230. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/nonmedia_files/dummy.pdf +0 -0
  231. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/video/example video.md +0 -0
  232. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/video/example video.ogv +0 -0
  233. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/webp/_MG_7805_lossy80.webp +0 -0
  234. {sigal-2.5 → sigal-2.6}/tests/sample/pictures/webp/_MG_7808_lossy80.webp +0 -0
  235. {sigal-2.5 → sigal-2.6}/tests/sample/watermark.png +0 -0
  236. {sigal-2.5 → sigal-2.6}/tests/test_cli.py +0 -0
  237. {sigal-2.5 → sigal-2.6}/tests/test_compress_assets_plugin.py +0 -0
  238. {sigal-2.5 → sigal-2.6}/tests/test_encrypt.py +0 -0
  239. {sigal-2.5 → sigal-2.6}/tests/test_extended_caching.py +0 -0
  240. {sigal-2.5 → sigal-2.6}/tests/test_image.py +0 -0
  241. {sigal-2.5 → sigal-2.6}/tests/test_plugins.py +0 -0
  242. {sigal-2.5 → sigal-2.6}/tests/test_utils.py +0 -0
  243. {sigal-2.5 → sigal-2.6}/tests/test_video.py +0 -0
  244. {sigal-2.5 → sigal-2.6}/tests/test_zip.py +0 -0
@@ -16,10 +16,10 @@ jobs:
16
16
  if: ((github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || contains(github.event.pull_request.labels.*.name, 'Build wheels'))
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v4
19
+ - uses: actions/checkout@v6
20
20
  with:
21
21
  fetch-depth: 0
22
- - uses: actions/setup-python@v5
22
+ - uses: actions/setup-python@v6
23
23
  with:
24
24
  python-version: "3.13"
25
25
 
@@ -16,20 +16,22 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  strategy:
18
18
  matrix:
19
- python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
19
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
20
20
 
21
21
  steps:
22
- - uses: actions/checkout@v4
22
+ - uses: actions/checkout@v6
23
23
  with:
24
24
  fetch-depth: 0
25
25
  - name: Set up Python ${{ matrix.python-version }}
26
- uses: actions/setup-python@v5
26
+ uses: actions/setup-python@v6
27
27
  with:
28
28
  python-version: ${{ matrix.python-version }}
29
- - name: Install FFmpeg
29
+ cache: 'pip'
30
+ cache-dependency-path: pyproject.toml
31
+ - name: Install FFmpeg & Poppler
30
32
  run: |
31
33
  sudo apt update
32
- sudo apt install ffmpeg
34
+ sudo apt install ffmpeg poppler-utils
33
35
  ffmpeg -version
34
36
  - name: Install Tox
35
37
  run: python -m pip install tox tox-gh-actions coverage
@@ -1,7 +1,7 @@
1
1
  repos:
2
2
 
3
3
  - repo: https://github.com/pre-commit/pre-commit-hooks
4
- rev: v5.0.0
4
+ rev: v6.0.0
5
5
  hooks:
6
6
  - id: check-added-large-files
7
7
  - id: check-merge-conflict
@@ -12,8 +12,8 @@ repos:
12
12
  exclude: ".*(galleria|photoswipe|jquery|leaflet).*$"
13
13
 
14
14
  - repo: https://github.com/astral-sh/ruff-pre-commit
15
- rev: v0.9.7
15
+ rev: v0.15.0
16
16
  hooks:
17
- - id: ruff
17
+ - id: ruff-check
18
18
  args: [ --fix, --show-fixes, --exit-non-zero-on-fix ]
19
19
  - id: ruff-format
@@ -1,9 +1,9 @@
1
1
  version: 2
2
2
 
3
3
  build:
4
- os: ubuntu-22.04
4
+ os: ubuntu-24.04
5
5
  tools:
6
- python: "3.12"
6
+ python: "3.13"
7
7
 
8
8
  sphinx:
9
9
  configuration: docs/conf.py
@@ -1,5 +1,5 @@
1
1
  The MIT License (MIT)
2
- Copyright (c) 2009-2023 - Simon Conseil
2
+ Copyright (c) 2009-2026 - Simon Conseil
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to
@@ -1,28 +1,27 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: sigal
3
- Version: 2.5
3
+ Version: 2.6
4
4
  Summary: Simple static gallery generator
5
5
  Author-email: Simon Conseil <contact@saimon.org>
6
- License: MIT License
6
+ License-Expression: MIT
7
7
  Project-URL: repository, https://github.com/saimn/sigal
8
- Project-URL: documentation, http://sigal.saimon.org/en/latest/
8
+ Project-URL: documentation, https://sigal.readthedocs.io/en/latest/
9
9
  Keywords: gallery,static,generator,image,video,galleria
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Environment :: Console
12
- Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Operating System :: OS Independent
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Classifier: Topic :: Internet :: WWW/HTTP
16
15
  Classifier: Topic :: Multimedia :: Graphics :: Viewers
17
16
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
- Requires-Python: >=3.9
17
+ Requires-Python: >=3.11
19
18
  Description-Content-Type: text/x-rst
20
19
  License-File: LICENSE
21
20
  Requires-Dist: blinker
22
21
  Requires-Dist: click
23
22
  Requires-Dist: Jinja2>=2.7
24
23
  Requires-Dist: Markdown
25
- Requires-Dist: Pillow>=8.0.0
24
+ Requires-Dist: Pillow>=10.0.0
26
25
  Requires-Dist: pilkit
27
26
  Requires-Dist: natsort
28
27
  Provides-Extra: all
@@ -31,6 +30,7 @@ Requires-Dist: feedgenerator; extra == "all"
31
30
  Requires-Dist: zopfli; extra == "all"
32
31
  Requires-Dist: cryptography; extra == "all"
33
32
  Requires-Dist: pillow-heif; extra == "all"
33
+ Requires-Dist: pdf2image; extra == "all"
34
34
  Provides-Extra: tests
35
35
  Requires-Dist: pytest; extra == "tests"
36
36
  Requires-Dist: pytest-cov; extra == "tests"
@@ -38,6 +38,7 @@ Provides-Extra: docs
38
38
  Requires-Dist: Sphinx>=4.1.0; extra == "docs"
39
39
  Requires-Dist: furo; extra == "docs"
40
40
  Requires-Dist: cryptography; extra == "docs"
41
+ Dynamic: license-file
41
42
 
42
43
  Sigal - Simple Static Gallery Generator
43
44
  =======================================
@@ -64,7 +65,7 @@ The idea behind Sigal is to ease the use of the javascript libraries like
64
65
  galleria_. These libraries do a great job to display the images, Sigal does
65
66
  what is missing: resize images, create thumbnails, generate HTML pages.
66
67
 
67
- Sigal requires Python 3.9+.
68
+ Sigal requires Python 3.11+.
68
69
 
69
70
  Links
70
71
  -----
@@ -83,7 +84,7 @@ Javascript libraries:
83
84
  - `galleria demo`_
84
85
  - `photoswipe demo`_
85
86
 
86
- .. _website: http://sigal.saimon.org/
87
+ .. _website: https://sigal.readthedocs.io/en/latest/
87
88
  .. _GitHub: https://github.com/saimn/sigal/
88
89
  .. _PyPI: https://pypi.org/project/sigal/
89
90
  .. _galleria: https://github.com/GalleriaJS/galleria
@@ -23,7 +23,7 @@ The idea behind Sigal is to ease the use of the javascript libraries like
23
23
  galleria_. These libraries do a great job to display the images, Sigal does
24
24
  what is missing: resize images, create thumbnails, generate HTML pages.
25
25
 
26
- Sigal requires Python 3.9+.
26
+ Sigal requires Python 3.11+.
27
27
 
28
28
  Links
29
29
  -----
@@ -42,7 +42,7 @@ Javascript libraries:
42
42
  - `galleria demo`_
43
43
  - `photoswipe demo`_
44
44
 
45
- .. _website: http://sigal.saimon.org/
45
+ .. _website: https://sigal.readthedocs.io/en/latest/
46
46
  .. _GitHub: https://github.com/saimn/sigal/
47
47
  .. _PyPI: https://pypi.org/project/sigal/
48
48
  .. _galleria: https://github.com/GalleriaJS/galleria
@@ -2,6 +2,23 @@
2
2
  Changelog
3
3
  ===========
4
4
 
5
+ Version 2.6
6
+ ~~~~~~~~~~~~~~
7
+
8
+ Released on 2026-02-06.
9
+
10
+ Sigal now requires Python 3.11+.
11
+
12
+ - With the photoswipe theme, albums can contain both images and sub-albums
13
+ [:issue:`535`].
14
+ - Fix issue with latest version of feedgenerator.
15
+ - Fix compat with click 8.2.0.
16
+ - 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`].
19
+ - Allow portrait orientation for thumbnails [:issue:`531`].
20
+ - nonmedia_files plugin: adding support for PDF thumbnails [:issue:`536`].
21
+
5
22
  Version 2.5
6
23
  ~~~~~~~~~~~
7
24
 
@@ -27,7 +27,7 @@ master_doc = "index"
27
27
 
28
28
  # General information about the project.
29
29
  project = "Sigal"
30
- copyright = "2012-2023, Simon Conseil"
30
+ copyright = "2012-2026, Simon Conseil"
31
31
 
32
32
  # The version info for the project you're documenting, acts as replacement for
33
33
  # |version| and |release|, also used in various other places throughout the
@@ -8,31 +8,31 @@ description = "Simple static gallery generator"
8
8
  readme = "README.rst"
9
9
  authors = [{name = "Simon Conseil", email = "contact@saimon.org"}]
10
10
  keywords = ["gallery", "static", "generator", "image", "video", "galleria"]
11
- license = {text = "MIT License"}
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
12
13
  classifiers = [
13
14
  "Development Status :: 5 - Production/Stable",
14
15
  "Environment :: Console",
15
- "License :: OSI Approved :: MIT License",
16
16
  "Operating System :: OS Independent",
17
17
  "Programming Language :: Python :: 3",
18
18
  "Topic :: Internet :: WWW/HTTP",
19
19
  "Topic :: Multimedia :: Graphics :: Viewers",
20
20
  "Topic :: Software Development :: Libraries :: Python Modules",
21
21
  ]
22
- requires-python = ">=3.9"
22
+ requires-python = ">=3.11"
23
23
  dependencies = [
24
24
  "blinker",
25
25
  "click",
26
26
  "Jinja2>=2.7",
27
27
  "Markdown",
28
- "Pillow>=8.0.0",
28
+ "Pillow>=10.0.0",
29
29
  "pilkit",
30
30
  "natsort",
31
31
  ]
32
32
  dynamic = ["version"]
33
33
 
34
34
  [project.optional-dependencies]
35
- all = ["brotli", "feedgenerator", "zopfli", "cryptography", "pillow-heif"]
35
+ all = ["brotli", "feedgenerator", "zopfli", "cryptography", "pillow-heif", "pdf2image"]
36
36
  tests = ["pytest", "pytest-cov"]
37
37
  docs = ["Sphinx>=4.1.0", "furo", "cryptography"]
38
38
 
@@ -41,11 +41,10 @@ sigal = "sigal.__main__:main"
41
41
 
42
42
  [project.urls]
43
43
  repository = "https://github.com/saimn/sigal"
44
- documentation = "http://sigal.saimon.org/en/latest/"
44
+ documentation = "https://sigal.readthedocs.io/en/latest/"
45
45
 
46
46
  [tool.setuptools]
47
47
  include-package-data = true
48
- license-files = ["LICENSE"]
49
48
 
50
49
  [tool.setuptools.packages.find]
51
50
  where = ["src"]
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2023 - Simon Conseil
1
+ # Copyright (c) 2009-2026 - Simon Conseil
2
2
 
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2023 - Simon Conseil
1
+ # Copyright (c) 2009-2026 - Simon Conseil
2
2
 
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to
@@ -237,15 +237,17 @@ def serve(destination, port, config, browser):
237
237
  settings = read_settings(config)
238
238
  destination = settings.get("destination")
239
239
  if not os.path.exists(destination):
240
- sys.stderr.write(
240
+ click.echo(
241
241
  f"The '{destination}' directory doesn't exist, maybe try building"
242
- " first?\n"
242
+ " first?",
243
+ err=True,
243
244
  )
244
245
  sys.exit(1)
245
246
  else:
246
- sys.stderr.write(
247
+ click.echo(
247
248
  f"The {destination} directory doesn't exist "
248
- f"and the config file ({config}) could not be read.\n"
249
+ f"and the config file ({config}) could not be read.",
250
+ err=True,
249
251
  )
250
252
  sys.exit(2)
251
253
 
@@ -284,10 +286,10 @@ def set_meta(target, keys, overwrite=False):
284
286
  """
285
287
 
286
288
  if not os.path.exists(target):
287
- sys.stderr.write(f"The target {target} does not exist.\n")
289
+ click.echo(f"The target {target} does not exist.", err=True)
288
290
  sys.exit(1)
289
291
  if len(keys) < 2 or len(keys) % 2 > 0:
290
- sys.stderr.write("Need an even number of arguments.\n")
292
+ click.echo("Need an even number of arguments.", err=True)
291
293
  sys.exit(1)
292
294
 
293
295
  if os.path.isdir(target):
@@ -295,9 +297,10 @@ def set_meta(target, keys, overwrite=False):
295
297
  else:
296
298
  descfile = os.path.splitext(target)[0] + ".md"
297
299
  if os.path.exists(descfile) and not overwrite:
298
- sys.stderr.write(
300
+ click.echo(
299
301
  f"Description file '{descfile}' already exists. "
300
- "Use --overwrite to overwrite it.\n"
302
+ "Use --overwrite to overwrite it.",
303
+ err=True,
301
304
  )
302
305
  sys.exit(2)
303
306
 
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2023 - Simon Conseil
1
+ # Copyright (c) 2009-2026 - Simon Conseil
2
2
  # Copyright (c) 2013 - Christophe-Marie Duquesne
3
3
  # Copyright (c) 2014 - Jonas Kaufmann
4
4
  # Copyright (c) 2015 - François D.
@@ -52,7 +52,7 @@ from .image import (
52
52
  get_size,
53
53
  process_image,
54
54
  )
55
- from .settings import Status, get_thumb
55
+ from .settings import IMG_EXTENSIONS, Status, get_thumb
56
56
  from .utils import (
57
57
  Devnull,
58
58
  check_or_create_dir,
@@ -246,15 +246,10 @@ class Image(Media):
246
246
  super().__init__(filename, path, settings)
247
247
  imgformat = settings.get("img_format")
248
248
 
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():
253
250
  # 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()]
256
252
  self.dst_filename = self.basename + ext
257
- self.thumb_name = get_thumb(self.settings, self.dst_filename)
258
253
 
259
254
  @cached_property
260
255
  def date(self):
@@ -593,76 +588,76 @@ class Album:
593
588
  # Test the thumbnail from the Markdown file.
594
589
  thumbnail = self.meta.get("thumbnail", [""])[0]
595
590
 
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))
600
597
  self.logger.debug("Thumbnail for %r : %s", self, self._thumbnail)
601
598
  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:
616
631
  try:
617
- self._thumbnail = url_quote(self.name) + "/" + f.thumbnail
632
+ self._thumbnail = url_quote(self.name) + "/" + media.thumbnail
618
633
  except Exception as e:
619
634
  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,
621
638
  )
622
639
  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
649
644
 
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
666
661
 
667
662
  self.logger.error("Thumbnail not found for %r", self)
668
663
 
@@ -918,14 +913,6 @@ class Gallery:
918
913
  ) as albums:
919
914
  for album in albums:
920
915
  if album.albums:
921
- if album.medias:
922
- self.logger.warning(
923
- "Album '%s' contains sub-albums and images. "
924
- "Please move images to their own sub-album. "
925
- "Images in album %s will not be visible.",
926
- album.title,
927
- album.title,
928
- )
929
916
  album_list_writer.write(album)
930
917
  else:
931
918
  album_writer.write(album)
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2023 - Simon Conseil
1
+ # Copyright (c) 2009-2026 - Simon Conseil
2
2
  # Copyright (c) 2015 - François D.
3
3
  # Copyright (c) 2018 - Edwin Steele
4
4
 
@@ -99,6 +99,7 @@ def generate_image(source, outname, settings, options=None):
99
99
 
100
100
  img = _read_image(source)
101
101
  original_format = img.format
102
+ logger.debug("Read %s: %dx%d (%s)", source, *img.size, original_format)
102
103
 
103
104
  if settings["copy_exif_data"] and settings["autorotate_images"]:
104
105
  logger.warning(
@@ -149,7 +150,7 @@ def generate_image(source, outname, settings, options=None):
149
150
  # format, or fall back to JPEG
150
151
  outformat = settings.get("img_format") or img.format or original_format or "JPEG"
151
152
 
152
- logger.debug("Save resized image to %s (%s)", outname, outformat)
153
+ logger.debug("Save resized image: %s, %dx%d (%s)", outname, *img.size, outformat)
153
154
  save_image(img, outname, outformat, options=options, autoconvert=True)
154
155
 
155
156
 
@@ -162,6 +163,7 @@ def generate_thumbnail(
162
163
  img = _read_image(source)
163
164
  img = Transpose().process(img)
164
165
  original_format = img.format
166
+ logger.debug("Read %s: %dx%d (%s)", source, *img.size, original_format)
165
167
 
166
168
  try:
167
169
  method = PILImage.Resampling.LANCZOS
@@ -175,7 +177,7 @@ def generate_thumbnail(
175
177
  img.thumbnail(box, method)
176
178
 
177
179
  outformat = img.format or original_format or "JPEG"
178
- logger.debug("Save thumnail image: %s (%s)", outname, outformat)
180
+ logger.debug("Save thumbnail image: %s, %dx%d (%s)", outname, *img.size, outformat)
179
181
  save_image(img, outname, outformat, options=options, autoconvert=True)
180
182
 
181
183
 
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2013-2023 - Simon Conseil
1
+ # Copyright (c) 2013-2026 - Simon Conseil
2
2
 
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to
@@ -70,6 +70,7 @@ def generate_feed(gallery, medias, feed_type=None, feed_url="", nb_items=0):
70
70
  feed.add_item(
71
71
  title=Markup.escape(item.title or item.url),
72
72
  link=link,
73
+ content=None,
73
74
  # unique_id='tag:%s,%s:%s' % (urlparse(link).netloc,
74
75
  # item.date.date(),
75
76
  # urlparse(link).path.lstrip('/')),
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2023 - Simon Conseil
1
+ # Copyright (c) 2009-2026 - Simon Conseil
2
2
  # Copyright (c) 2014 - Jamie Starke
3
3
 
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -4,6 +4,9 @@ This plugin will copy the files into the build tree and generate generic
4
4
  thumbnails for the files. In-browser previews will likely fail, and
5
5
  it is up to the theme to provide correct support for downloads.
6
6
 
7
+ If the `pdf2image <https://pypi.org/project/pdf2image/>`_ optional dependency is installed,
8
+ it will be used to generate thumbnails for PDF files.
9
+
7
10
  Settings available as dictionary in ``nonmedia_files_options``:
8
11
 
9
12
  - ``ext_as_thumb``: Enable simple thumbnail showing ext. Default to ``True``
@@ -26,6 +29,10 @@ Settings available as dictionary in ``nonmedia_files_options``:
26
29
  import logging
27
30
  import os
28
31
 
32
+ try: # Optional dependency:
33
+ from pdf2image import convert_from_path as pdf2img
34
+ except ImportError:
35
+ pdf2img = None
29
36
  from PIL import Image as PILImage
30
37
  from PIL import ImageDraw, ImageFont
31
38
  from pilkit.utils import save_image
@@ -102,9 +109,7 @@ def generate_thumbnail(
102
109
  logger.info(f"kwargs: {kwargs}")
103
110
  d.text(anchor, text, anchor="mm", **kwargs)
104
111
 
105
- outformat = "JPEG"
106
- logger.info("Save thumnail image: %s (%s)", outname, outformat)
107
- save_image(img, outname, outformat, options=options, autoconvert=True)
112
+ save_image(img, outname, "JPEG", options=options, autoconvert=True)
108
113
 
109
114
 
110
115
  def process_thumb(media):
@@ -113,18 +118,24 @@ def process_thumb(media):
113
118
  utils.copy(media.src_path, media.dst_path, symlink=settings["orig_link"])
114
119
 
115
120
  if plugin_settings.get("ext_as_thumb", DEFAULT_CONFIG["ext_as_thumb"]):
116
- logger.info("plugin_settings: %r", plugin_settings)
117
- kwargs = {}
118
- for key in ("bg_color", "font", "font_color", "font_size"):
119
- if f"thumb_{key}" in plugin_settings:
120
- kwargs[key] = plugin_settings[f"thumb_{key}"]
121
- generate_thumbnail(
122
- media.src_ext[1:].upper(),
123
- media.thumb_path,
124
- settings["thumb_size"],
125
- options=settings["jpg_options"],
126
- **kwargs,
127
- )
121
+ if pdf2img and media.src_ext.lower() == ".pdf":
122
+ images = pdf2img(
123
+ media.src_path, single_file=True, size=settings["thumb_size"]
124
+ )
125
+ images[0].save(media.thumb_path)
126
+ else:
127
+ kwargs = {}
128
+ for key in ("bg_color", "font", "font_color", "font_size"):
129
+ if f"thumb_{key}" in plugin_settings:
130
+ kwargs[key] = plugin_settings[f"thumb_{key}"]
131
+ generate_thumbnail(
132
+ media.src_ext[1:].upper(),
133
+ media.thumb_path,
134
+ settings["thumb_size"],
135
+ options=settings["jpg_options"],
136
+ **kwargs,
137
+ )
138
+ logger.info("Saved thumbnail image: %s", media.thumb_path)
128
139
 
129
140
 
130
141
  def process_nonmedia(media):