photos.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import os
  4. import re
  5. import logging
  6. from pelican import signals
  7. from pelican.utils import pelican_open
  8. from PIL import Image, ExifTags
  9. from itertools import chain
  10. logger = logging.getLogger(__name__)
  11. queue_resize = dict()
  12. hrefs = None
  13. created_galleries = {}
  14. def initialized(pelican):
  15. p = os.path.expanduser('~/Pictures')
  16. from pelican.settings import DEFAULT_CONFIG
  17. DEFAULT_CONFIG.setdefault('PHOTO_LIBRARY', p)
  18. DEFAULT_CONFIG.setdefault('PHOTO_GALLERY', (1024, 768, 80))
  19. DEFAULT_CONFIG.setdefault('PHOTO_ARTICLE', ( 760, 506, 80))
  20. DEFAULT_CONFIG.setdefault('PHOTO_THUMB', ( 192, 144, 60))
  21. if pelican:
  22. pelican.settings.setdefault('PHOTO_LIBRARY', p)
  23. pelican.settings.setdefault('PHOTO_GALLERY', (1024, 768, 80))
  24. pelican.settings.setdefault('PHOTO_ARTICLE', ( 760, 506, 80))
  25. pelican.settings.setdefault('PHOTO_THUMB', ( 192, 144, 60))
  26. def read_notes(filename, msg=None):
  27. notes = {}
  28. try:
  29. with pelican_open(filename) as text:
  30. for line in text.splitlines():
  31. m = line.split(':', 1)
  32. if len(m) > 1:
  33. pic = m[0].strip()
  34. note = m[1].strip()
  35. if pic and note:
  36. notes[pic] = note
  37. except:
  38. if msg:
  39. logger.warning(msg, filename)
  40. return notes
  41. def enqueue_resize(orig, resized, spec=(640, 480, 80)):
  42. global queue_resize
  43. if resized not in queue_resize:
  44. queue_resize[resized] = (orig, spec)
  45. elif queue_resize[resized] != (orig, spec):
  46. logger.error('photos: resize conflict for {}, {}-{} is not {}-{}',
  47. resized,
  48. queue_resize[resized][0], queue_resize[resized][1],
  49. orig, spec)
  50. def resize_photos(generator, writer):
  51. print('photos: {} photo resizes to consider.'
  52. .format(len(queue_resize.items())))
  53. for resized, what in queue_resize.items():
  54. resized = os.path.join(generator.output_path, resized)
  55. orig, spec = what
  56. if (not os.path.isfile(resized) or
  57. os.path.getmtime(orig) > os.path.getmtime(resized)):
  58. logger.info('photos: make photo %s -> %s', orig, resized)
  59. im = Image.open(orig)
  60. try:
  61. exif = im._getexif()
  62. except Exception:
  63. exif = None
  64. try:
  65. icc_profile = im.info.get("icc_profile")
  66. except Exception:
  67. icc_profile = None
  68. if exif:
  69. for tag, value in exif.items():
  70. decoded = ExifTags.TAGS.get(tag, tag)
  71. if decoded == 'Orientation':
  72. if value == 3: im = im.rotate(180)
  73. elif value == 6: im = im.rotate(270)
  74. elif value == 8: im = im.rotate(90)
  75. break
  76. im.thumbnail((spec[0], spec[1]), Image.ANTIALIAS)
  77. try:
  78. os.makedirs(os.path.split(resized)[0])
  79. except:
  80. pass
  81. im.save(resized, 'JPEG', quality=spec[2], icc_profile=icc_profile)
  82. def detect_content(content):
  83. def replacer(m):
  84. what = m.group('what')
  85. value = m.group('value')
  86. origin = m.group('path')
  87. if what == 'photo':
  88. if value.startswith('/'):
  89. value = value[1:]
  90. path = os.path.join(
  91. os.path.expanduser(settings['PHOTO_LIBRARY']),
  92. value)
  93. if not os.path.isfile(path):
  94. logger.error('photos: No photo %s', path)
  95. else:
  96. photo = os.path.splitext(value)[0].lower() + 'a.jpg'
  97. origin = os.path.join(settings['SITEURL'], 'photos', photo)
  98. enqueue_resize(
  99. path,
  100. os.path.join('photos', photo),
  101. settings['PHOTO_ARTICLE'])
  102. return ''.join((m.group('markup'), m.group('quote'), origin,
  103. m.group('quote')))
  104. global hrefs
  105. if hrefs is None:
  106. regex = r"""
  107. (?P<markup><\s*[^\>]* # match tag with src and href attr
  108. (?:href|src)\s*=)
  109. (?P<quote>["\']) # require value to be quoted
  110. (?P<path>{0}(?P<value>.*?)) # the url value
  111. \2""".format(content.settings['INTRASITE_LINK_REGEX'])
  112. hrefs = re.compile(regex, re.X)
  113. if content._content and '{photo}' in content._content:
  114. settings = content.settings
  115. content._content = hrefs.sub(replacer, content._content)
  116. def process_gallery_photo(generator, article, gallery):
  117. if gallery.startswith('/'):
  118. gallery = gallery[1:]
  119. galleries = gallery.split('{photo}')
  120. article.photo_gallery = []
  121. for gallery in galleries:
  122. # strip whitespaces
  123. gallery = gallery.strip()
  124. if gallery in created_galleries:
  125. article.photo_gallery.append((gallery, created_galleries[gallery]))
  126. continue
  127. dir_gallery = os.path.join(
  128. os.path.expanduser(generator.settings['PHOTO_LIBRARY']),
  129. gallery)
  130. if os.path.isdir(dir_gallery):
  131. logger.info('photos: Gallery detected: %s', gallery)
  132. dir_photo = os.path.join('photos', gallery.lower())
  133. dir_thumb = os.path.join('photos', gallery.lower())
  134. exifs = read_notes(os.path.join(dir_gallery, 'exif.txt'),
  135. msg='photos: No EXIF for gallery %s')
  136. captions = read_notes(os.path.join(dir_gallery, 'captions.txt'))
  137. articleGallery = []
  138. for pic in sorted(os.listdir(dir_gallery)):
  139. if pic.startswith('.'): continue
  140. if pic.endswith('.txt'): continue
  141. photo = os.path.splitext(pic)[0].lower() + '.jpg'
  142. thumb = os.path.splitext(pic)[0].lower() + 't.jpg'
  143. articleGallery.append((
  144. pic,
  145. os.path.join(dir_photo, photo),
  146. os.path.join(dir_thumb, thumb),
  147. exifs.get(pic, ''),
  148. captions.get(pic, '')))
  149. enqueue_resize(
  150. os.path.join(dir_gallery, pic),
  151. os.path.join(dir_photo, photo),
  152. generator.settings['PHOTO_GALLERY'])
  153. enqueue_resize(
  154. os.path.join(dir_gallery, pic),
  155. os.path.join(dir_thumb, thumb),
  156. generator.settings['PHOTO_THUMB'])
  157. article.photo_gallery.append((gallery, articleGallery))
  158. created_galleries[gallery] = articleGallery
  159. else:
  160. logger.error('photos: Gallery does not exist: %s at %s', gallery, dir_gallery)
  161. def process_gallery_filename(generator, article, gallery):
  162. if gallery.startswith('/'):
  163. gallery = gallery[1:]
  164. else:
  165. gallery = os.path.join(article.relative_dir, gallery)
  166. dir_gallery = os.path.join(generator.path, gallery)
  167. if os.path.isdir(dir_gallery):
  168. logger.info('photos: Gallery detected: %s', gallery)
  169. dir_photo = gallery.lower()
  170. dir_thumb = os.path.join('photos', gallery.lower())
  171. exifs = read_notes(os.path.join(dir_gallery, 'exif.txt'),
  172. msg='photos: No EXIF for gallery %s')
  173. captions = read_notes(os.path.join(dir_gallery, 'captions.txt'))
  174. article.photo_gallery = []
  175. for pic in sorted(os.listdir(dir_gallery)):
  176. if pic.startswith('.'): continue
  177. if pic.endswith('.txt'): continue
  178. photo = pic.lower()
  179. thumb = os.path.splitext(pic)[0].lower() + 't.jpg'
  180. article.photo_gallery.append((
  181. pic,
  182. os.path.join(dir_photo, photo),
  183. os.path.join(dir_thumb, thumb),
  184. exifs.get(pic, ''),
  185. captions.get(pic, '')))
  186. enqueue_resize(
  187. os.path.join(dir_gallery, pic),
  188. os.path.join(dir_thumb, thumb),
  189. generator.settings['PHOTO_THUMB'])
  190. else:
  191. logger.error('photos: Gallery does not exist: %s at %s', gallery, dir_gallery)
  192. def detect_gallery(generator):
  193. for article in chain(generator.articles, generator.drafts):
  194. if 'gallery' in article.metadata:
  195. gallery = article.metadata.get('gallery')
  196. if gallery.startswith('{photo}'):
  197. process_gallery_photo(generator, article, gallery[7:])
  198. elif gallery.startswith('{filename}'):
  199. process_gallery_filename(generator, article, gallery[10:])
  200. elif gallery:
  201. logger.error('photos: Gallery tag not recognized: %s', gallery)
  202. def process_image_photo(generator, article, image):
  203. if image.startswith('/'):
  204. image = image[1:]
  205. path = os.path.join(
  206. os.path.expanduser(generator.settings['PHOTO_LIBRARY']),
  207. image)
  208. if os.path.isfile(path):
  209. photo = os.path.splitext(image)[0].lower() + 'a.jpg'
  210. thumb = os.path.splitext(image)[0].lower() + 't.jpg'
  211. article.photo_image = (
  212. os.path.basename(image).lower(),
  213. os.path.join('photos', photo),
  214. os.path.join('photos', thumb))
  215. enqueue_resize(
  216. path,
  217. os.path.join('photos', photo),
  218. generator.settings['PHOTO_ARTICLE'])
  219. enqueue_resize(
  220. path,
  221. os.path.join('photos', thumb),
  222. generator.settings['PHOTO_THUMB'])
  223. else:
  224. logger.error('photo: No photo for %s at %s', article.source_path, path)
  225. def process_image_filename(generator, article, image):
  226. if image.startswith('/'):
  227. image = image[1:]
  228. else:
  229. image = os.path.join(article.relative_dir, image)
  230. path = os.path.join(generator.path, image)
  231. if os.path.isfile(path):
  232. small = os.path.splitext(image)[0].lower() + 't.jpg'
  233. article.photo_image = (
  234. os.path.basename(image),
  235. image.lower(),
  236. os.path.join('photos', small))
  237. enqueue_resize(
  238. path,
  239. os.path.join('photos', small),
  240. generator.settings['PHOTO_THUMB'])
  241. else:
  242. logger.error('photos: No photo at %s', path)
  243. def detect_image(generator):
  244. for article in chain(generator.articles, generator.drafts):
  245. image = article.metadata.get('image', None)
  246. if image:
  247. if image.startswith('{photo}'):
  248. process_image_photo(generator, article, image[7:])
  249. elif image.startswith('{filename}'):
  250. process_image_filename(generator, article, image[10:])
  251. else:
  252. logger.error('photos: Image tag not recognized: %s', image)
  253. def register():
  254. signals.initialized.connect(initialized)
  255. signals.content_object_init.connect(detect_content)
  256. signals.article_generator_finalized.connect(detect_gallery)
  257. signals.article_generator_finalized.connect(detect_image)
  258. signals.article_writer_finalized.connect(resize_photos)