This is a gentle fork from https://framagit.org/marienfressinaud/photos.marienfressinaud.fr with a responsive and optimized mindset. https://media.larlet.fr/
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/env python3
  2. import os
  3. import shutil
  4. import re
  5. from operator import itemgetter
  6. from jinja2 import Environment, PackageLoader, select_autoescape
  7. from PIL import Image, ExifTags
  8. from configuration import SITE_TITLE, SITE_AUTHOR, SITE_AUTHOR_WEBSITE, THEME
  9. PICTURES_DIR_NAME = "photos"
  10. OUTPUT_DIR_NAME = "output"
  11. def list_galleries_in(path):
  12. gallery_dirs = [f for f in os.scandir(path) if f.is_dir()]
  13. for gallery_dir in gallery_dirs:
  14. photos = list(list_photos_in(gallery_dir))
  15. if len(photos) == 0:
  16. continue
  17. photos.sort(key=itemgetter("name"))
  18. cover_index = (len(gallery_dir.name) + 42) % len(photos)
  19. gallery = {
  20. "name": gallery_dir.name,
  21. "path": gallery_dir.path,
  22. "url": gallery_dir.name,
  23. "num_photos": len(photos),
  24. "photos": photos,
  25. "cover_photo": photos[cover_index],
  26. }
  27. yield gallery
  28. def list_photos_in(gallery_dir):
  29. photo_files = [
  30. f for f in os.scandir(gallery_dir) if re.match(".+\.jpg", f.name, re.I)
  31. ]
  32. for photo_file in photo_files:
  33. url = os.path.join(gallery_dir.name, photo_file.name)
  34. thumb_url = os.path.join(gallery_dir.name, f"thumb_{photo_file.name}")
  35. photo = {
  36. "name": photo_file.name,
  37. "path": photo_file.path,
  38. "url": url,
  39. "thumb_url": thumb_url,
  40. }
  41. yield photo
  42. def generate_output_dir():
  43. output_path = os.path.join(os.curdir, OUTPUT_DIR_NAME)
  44. if not os.path.isdir(output_path):
  45. os.mkdir(output_path)
  46. return output_path
  47. def generate_style(output_path):
  48. style_path = os.path.join(os.curdir, THEME, "style")
  49. style_output_path = os.path.join(output_path, "style")
  50. if os.path.isdir(style_output_path):
  51. shutil.rmtree(style_output_path)
  52. shutil.copytree(style_path, style_output_path)
  53. def generate_index(output_path, galleries):
  54. index_path = os.path.join(output_path, "index.html")
  55. theme_path = os.path.join(os.curdir, THEME)
  56. jinja_env = Environment(
  57. loader=PackageLoader("boop", theme_path), autoescape=select_autoescape(["html"])
  58. )
  59. index_template = jinja_env.get_template("index.html.j2")
  60. with open(index_path, "w") as index_file:
  61. index_file.write(
  62. index_template.render(
  63. galleries=galleries,
  64. site_title=SITE_TITLE,
  65. site_author=SITE_AUTHOR,
  66. site_author_website=SITE_AUTHOR_WEBSITE,
  67. )
  68. )
  69. def generate_gallery(output_path, gallery):
  70. generate_gallery_index(output_path, gallery)
  71. generate_gallery_dir(output_path, gallery)
  72. def generate_gallery_index(output_path, gallery):
  73. gallery_index_path = os.path.join(output_path, f"{gallery['name']}.html")
  74. theme_path = os.path.join(os.curdir, THEME)
  75. jinja_env = Environment(
  76. loader=PackageLoader("boop", theme_path), autoescape=select_autoescape(["html"])
  77. )
  78. gallery_template = jinja_env.get_template("gallery.html.j2")
  79. with open(gallery_index_path, "w") as gallery_file:
  80. gallery_file.write(
  81. gallery_template.render(
  82. gallery=gallery,
  83. site_title=SITE_TITLE,
  84. site_author=SITE_AUTHOR,
  85. site_author_website=SITE_AUTHOR_WEBSITE,
  86. )
  87. )
  88. def generate_gallery_dir(output_path, gallery):
  89. gallery_output_path = os.path.join(output_path, gallery["name"])
  90. if not os.path.isdir(gallery_output_path):
  91. os.mkdir(gallery_output_path)
  92. for photo in gallery["photos"]:
  93. photo_output_path = os.path.join(output_path, photo["url"])
  94. if not os.path.exists(photo_output_path):
  95. shutil.copyfile(photo["path"], photo_output_path)
  96. thumb_output_path = os.path.join(output_path, photo["thumb_url"])
  97. if not os.path.exists(thumb_output_path):
  98. generate_thumb_file(thumb_output_path, photo)
  99. def generate_thumb_file(output_path, photo):
  100. orientation_key = get_orientation_exif_key()
  101. size = (440, 264)
  102. with Image.open(photo["path"]) as image:
  103. # First, make sure image is correctly oriented
  104. exif = image._getexif()
  105. if exif[orientation_key] == 3:
  106. image = image.rotate(180, expand=True)
  107. elif exif[orientation_key] == 6:
  108. image = image.rotate(270, expand=True)
  109. elif exif[orientation_key] == 8:
  110. image = image.rotate(90, expand=True)
  111. w, h = image.size
  112. if w > size[0] and h > size[1]:
  113. # If the original file is larger in width AND height, we resize
  114. # first the image to the lowest size accepted (both width and
  115. # height stays greater or equal to requested size).
  116. # E.g. 1200x900 is resized to 440x330
  117. # 1200x600 is resized to 528x264
  118. if size[0] / size[1] <= w / h:
  119. w = int(max(size[1] * w / h, 1))
  120. h = 264
  121. else:
  122. h = int(max(size[0] * h / w, 1))
  123. w = 440
  124. new_size = (w, h)
  125. image.draft(None, new_size)
  126. image = image.resize(new_size, Image.BICUBIC)
  127. # We now have an image with at least w = 440 OR h = 264 (unless one of
  128. # the size is smaller). But the image can still be larger than
  129. # requested size, so we have to crop the image in the middle.
  130. crop_box = None
  131. if w > size[0]:
  132. left = (w - size[0]) / 2
  133. right = left + size[0]
  134. crop_box = (left, 0, right, h)
  135. elif h > size[1]:
  136. upper = (h - size[1]) / 2
  137. lower = upper + size[1]
  138. crop_box = (0, upper, w, lower)
  139. if crop_box is not None:
  140. image = image.crop(crop_box)
  141. # And we save the final image.
  142. image.save(output_path)
  143. def get_orientation_exif_key():
  144. for (key, tag) in ExifTags.TAGS.items():
  145. if tag == "Orientation":
  146. return key
  147. def main():
  148. pictures_folder = os.path.join(os.curdir, PICTURES_DIR_NAME)
  149. galleries = list(list_galleries_in(pictures_folder))
  150. if len(galleries) == 0:
  151. return
  152. galleries.sort(key=itemgetter("name"))
  153. output_path = generate_output_dir()
  154. generate_style(output_path)
  155. generate_index(output_path, galleries)
  156. for gallery in galleries:
  157. generate_gallery(output_path, gallery)
  158. if __name__ == "__main__":
  159. main()