diff options
Diffstat (limited to 'nikola/plugins/task_render_galleries.py')
| -rw-r--r-- | nikola/plugins/task_render_galleries.py | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/nikola/plugins/task_render_galleries.py b/nikola/plugins/task_render_galleries.py new file mode 100644 index 0000000..27e13ea --- /dev/null +++ b/nikola/plugins/task_render_galleries.py @@ -0,0 +1,305 @@ +import codecs +import datetime +import glob +import os +import uuid + +Image = None +try: + import Image as _Image + import ExifTags + Image = _Image +except ImportError: + try: + from PIL import Image, ExifTags # NOQA + except ImportError: + pass + + +from nikola.plugin_categories import Task +from nikola import utils + + +class Galleries(Task): + """Copy theme assets into output.""" + + name = "render_galleries" + dates = {} + + def gen_tasks(self): + """Render image galleries.""" + + kw = { + 'thumbnail_size': self.site.config['THUMBNAIL_SIZE'], + 'max_image_size': self.site.config['MAX_IMAGE_SIZE'], + 'output_folder': self.site.config['OUTPUT_FOLDER'], + 'default_lang': self.site.config['DEFAULT_LANG'], + 'blog_description': self.site.config['BLOG_DESCRIPTION'], + 'use_filename_as_title': self.site.config['USE_FILENAME_AS_TITLE'], + } + + # FIXME: lots of work is done even when images don't change, + # which should be moved into the task. + + template_name = "gallery.tmpl" + + gallery_list = [] + for root, dirs, files in os.walk('galleries'): + gallery_list.append(root) + if not gallery_list: + yield { + 'basename': 'render_galleries', + 'actions': [], + } + return + + # gallery_path is "gallery/name" + for gallery_path in gallery_list: + # gallery_name is "name" + splitted = gallery_path.split(os.sep)[1:] + if not splitted: + gallery_name = '' + else: + gallery_name = os.path.join(*splitted) + # output_gallery is "output/GALLERY_PATH/name" + output_gallery = os.path.dirname(os.path.join(kw["output_folder"], + self.site.path("gallery", gallery_name, None))) + if not os.path.isdir(output_gallery): + yield { + 'basename': 'render_galleries', + 'name': output_gallery, + 'actions': [(os.makedirs, (output_gallery,))], + 'targets': [output_gallery], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + # image_list contains "gallery/name/image_name.jpg" + image_list = glob.glob(gallery_path + "/*jpg") +\ + glob.glob(gallery_path + "/*JPG") +\ + glob.glob(gallery_path + "/*PNG") +\ + glob.glob(gallery_path + "/*png") + + # Filter ignore images + try: + def add_gallery_path(index): + return "{0}/{1}".format(gallery_path, index) + + exclude_path = os.path.join(gallery_path, "exclude.meta") + try: + f = open(exclude_path, 'r') + excluded_image_name_list = f.read().split() + except IOError: + excluded_image_name_list = [] + + excluded_image_list = map(add_gallery_path, + excluded_image_name_list) + image_set = set(image_list) - set(excluded_image_list) + image_list = list(image_set) + except IOError: + pass + + # List of sub-galleries + folder_list = [x.split(os.sep)[-2] for x in + glob.glob(os.path.join(gallery_path, '*') + os.sep)] + + crumbs = gallery_path.split(os.sep)[:-1] + crumbs.append(os.path.basename(gallery_name)) + # TODO: write this in human + paths = ['/'.join(['..'] * (len(crumbs) - 1 - i)) for i in + range(len(crumbs[:-1]))] + ['#'] + crumbs = zip(paths, crumbs) + + image_list = [x for x in image_list if "thumbnail" not in x] + # Sort by date + image_list.sort(cmp=lambda a, b: cmp( + self.image_date(a), self.image_date(b))) + image_name_list = [os.path.basename(x) for x in image_list] + + thumbs = [] + # Do thumbnails and copy originals + for img, img_name in zip(image_list, image_name_list): + # img is "galleries/name/image_name.jpg" + # img_name is "image_name.jpg" + # fname, ext are "image_name", ".jpg" + fname, ext = os.path.splitext(img_name) + # thumb_path is + # "output/GALLERY_PATH/name/image_name.thumbnail.jpg" + thumb_path = os.path.join(output_gallery, + fname + ".thumbnail" + ext) + # thumb_path is "output/GALLERY_PATH/name/image_name.jpg" + orig_dest_path = os.path.join(output_gallery, img_name) + thumbs.append(os.path.basename(thumb_path)) + yield { + 'basename': 'render_galleries', + 'name': thumb_path, + 'file_dep': [img], + 'targets': [thumb_path], + 'actions': [ + (self.resize_image, + (img, thumb_path, kw['thumbnail_size'])) + ], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + yield { + 'basename': 'render_galleries', + 'name': orig_dest_path, + 'file_dep': [img], + 'targets': [orig_dest_path], + 'actions': [ + (self.resize_image, + (img, orig_dest_path, kw['max_image_size'])) + ], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + + # Remove excluded images + if excluded_image_name_list: + for img, img_name in zip(excluded_image_list, + excluded_image_name_list): + # img_name is "image_name.jpg" + # fname, ext are "image_name", ".jpg" + fname, ext = os.path.splitext(img_name) + excluded_thumb_dest_path = os.path.join(output_gallery, + fname + ".thumbnail" + ext) + excluded_dest_path = os.path.join(output_gallery, img_name) + yield { + 'basename': 'render_galleries', + 'name': excluded_thumb_dest_path, + 'file_dep': [exclude_path], + #'targets': [excluded_thumb_dest_path], + 'actions': [ + (utils.remove_file, (excluded_thumb_dest_path,)) + ], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + yield { + 'basename': 'render_galleries', + 'name': excluded_dest_path, + 'file_dep': [exclude_path], + #'targets': [excluded_dest_path], + 'actions': [ + (utils.remove_file, (excluded_dest_path,)) + ], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + + output_name = os.path.join(output_gallery, "index.html") + context = {} + context["lang"] = kw["default_lang"] + context["title"] = os.path.basename(gallery_path) + context["description"] = kw["blog_description"] + if kw['use_filename_as_title']: + img_titles = ['title="%s"' % utils.unslugify(fn[:-4]) + for fn in image_name_list] + else: + img_titles = [''] * len(image_name_list) + context["images"] = zip(image_name_list, thumbs, img_titles) + context["folders"] = folder_list + context["crumbs"] = crumbs + context["permalink"] = self.site.link( + "gallery", gallery_name, None) + + # Use galleries/name/index.txt to generate a blurb for + # the gallery, if it exists + index_path = os.path.join(gallery_path, "index.txt") + cache_dir = os.path.join('cache', 'galleries') + if not os.path.isdir(cache_dir): + os.makedirs(cache_dir) + index_dst_path = os.path.join(cache_dir, unicode(uuid.uuid1())+'.html') + if os.path.exists(index_path): + compile_html = self.site.get_compiler(index_path) + yield { + 'basename': 'render_galleries', + 'name': index_dst_path.encode('utf-8'), + 'file_dep': [index_path], + 'targets': [index_dst_path], + 'actions': [(compile_html, + [index_path, index_dst_path])], + 'clean': True, + 'uptodate': [utils.config_changed(kw)], + } + + file_dep = self.site.template_system.template_deps( + template_name) + image_list + + def render_gallery(output_name, context, index_dst_path): + if os.path.exists(index_dst_path): + with codecs.open(index_dst_path, "rb", "utf8") as fd: + context['text'] = fd.read() + file_dep.append(index_dst_path) + else: + context['text'] = '' + self.site.render_template(template_name, output_name, context) + + yield { + 'basename': 'render_galleries', + 'name': output_name, + 'file_dep': file_dep, + 'targets': [output_name], + 'actions': [(render_gallery, + (output_name, context, index_dst_path))], + 'clean': True, + 'uptodate': [utils.config_changed({ + 1: kw, + 2: self.site.config['GLOBAL_CONTEXT']})], + } + + def resize_image(self, src, dst, max_size): + """Make a copy of the image in the requested size.""" + if not Image: + utils.copy_file(src, dst) + return + im = Image.open(src) + w, h = im.size + if w > max_size or h > max_size: + size = max_size, max_size + try: + exif = im._getexif() + except Exception: + exif = None + if exif is not None: + for tag, value in exif.items(): + decoded = ExifTags.TAGS.get(tag, tag) + + if decoded == 'Orientation': + if value == 3: + im = im.rotate(180) + elif value == 6: + im = im.rotate(270) + elif value == 8: + im = im.rotate(90) + + break + + im.thumbnail(size, Image.ANTIALIAS) + im.save(dst) + + else: + utils.copy_file(src, dst) + + def image_date(self, src): + """Try to figure out the date of the image.""" + if src not in self.dates: + im = Image.open(src) + try: + exif = im._getexif() + except Exception: + exif = None + if exif is not None: + for tag, value in exif.items(): + decoded = ExifTags.TAGS.get(tag, tag) + if decoded == 'DateTimeOriginal': + try: + self.dates[src] = datetime.datetime.strptime( + value, r'%Y:%m:%d %H:%M:%S') + break + except ValueError: # Invalid EXIF date. + pass + if src not in self.dates: + self.dates[src] = datetime.datetime.fromtimestamp( + os.stat(src).st_mtime) + return self.dates[src] |
