thumbnailer.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import os
  2. import os.path as path
  3. import re
  4. from pelican import signals
  5. import logging
  6. logger = logging.getLogger(__name__)
  7. try:
  8. from PIL import Image, ImageOps
  9. enabled = True
  10. except ImportError:
  11. logging.warning("Unable to load PIL, disabling thumbnailer")
  12. enabled = False
  13. DEFAULT_IMAGE_DIR = "pictures"
  14. DEFAULT_THUMBNAIL_DIR = "thumbnails"
  15. DEFAULT_THUMBNAIL_SIZES = {
  16. 'thumbnail_square': '150',
  17. 'thumbnail_wide': '150x?',
  18. 'thumbnail_tall': '?x150',
  19. }
  20. class _resizer(object):
  21. """ Resizes based on a text specification, see readme """
  22. REGEX = re.compile(r'(\d+|\?)x(\d+|\?)')
  23. def __init__(self, name, spec):
  24. self._name = name
  25. self._spec = spec
  26. def _null_resize(self, w, h, image):
  27. return image
  28. def _exact_resize(self, w, h, image):
  29. retval = ImageOps.fit(image, (w,h), Image.BICUBIC)
  30. return retval
  31. def _aspect_resize(self, w, h, image):
  32. retval = image.copy()
  33. retval.thumbnail((w, h), Image.ANTIALIAS)
  34. return retval
  35. def resize(self, image):
  36. resizer = self._null_resize
  37. # Square resize and crop
  38. if 'x' not in self._spec:
  39. resizer = self._exact_resize
  40. targetw = int(self._spec)
  41. targeth = targetw
  42. else:
  43. matches = self.REGEX.search(self._spec)
  44. tmpw = matches.group(1)
  45. tmph = matches.group(2)
  46. # Full Size
  47. if tmpw == '?' and tmph == '?':
  48. targetw = image.size[0]
  49. targeth = image.size[1]
  50. resizer = self._null_resize
  51. # Set Height Size
  52. if tmpw == '?':
  53. targetw = image.size[0]
  54. targeth = int(tmph)
  55. resizer = self._aspect_resize
  56. # Set Width Size
  57. elif tmph == '?':
  58. targetw = int(tmpw)
  59. targeth = image.size[1]
  60. resizer = self._aspect_resize
  61. # Scale and Crop
  62. else:
  63. targetw = int(tmpw)
  64. targeth = int(tmph)
  65. resizer = self._exact_resize
  66. logging.debug("Using resizer {0}".format(resizer.__name__))
  67. return resizer(targetw, targeth, image)
  68. def _adjust_filename(self, in_path):
  69. new_filename = path.basename(in_path)
  70. (basename, ext) = path.splitext(new_filename)
  71. basename = "{0}_{1}".format(basename, self._name)
  72. new_filename = "{0}{1}".format(basename, ext)
  73. return new_filename
  74. def resize_file_to(self, in_path, out_path):
  75. """ Given a filename, resize and save the image per the specification into out_path
  76. :param in_path: path to image file to save. Must be supposed by PIL
  77. :param out_path: path to the directory root for the outputted thumbnails to be stored
  78. :return: None
  79. """
  80. filename = path.join(out_path, self._adjust_filename(in_path))
  81. if not path.exists(out_path):
  82. os.makedirs(out_path)
  83. if not path.exists(filename):
  84. image = Image.open(in_path)
  85. thumbnail = self.resize(image)
  86. thumbnail.save(filename)
  87. logger.info("Generated Thumbnail {0}".format(path.basename(filename)))
  88. def resize_thumbnails(pelican):
  89. """ Resize a directory tree full of images into thumbnails
  90. :param pelican: The pelican instance
  91. :return: None
  92. """
  93. global enabled
  94. if not enabled:
  95. return
  96. in_path = path.join(pelican.settings['PATH'],
  97. pelican.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR))
  98. out_path = path.join(pelican.settings['OUTPUT_PATH'],
  99. pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR))
  100. sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES)
  101. resizers = dict((k, _resizer(k, v)) for k,v in sizes.items())
  102. logger.debug("Thumbnailer Started")
  103. for dirpath, _, filenames in os.walk(in_path):
  104. for filename in filenames:
  105. for name, resizer in resizers.items():
  106. in_filename = path.join(dirpath, filename)
  107. logger.debug("Processing thumbnail {0}=>{1}".format(filename, name))
  108. resizer.resize_file_to(in_filename, out_path)
  109. def register():
  110. signals.finalized.connect(resize_thumbnails)