diff --git a/docs/changelog.rst b/docs/changelog.rst index 4a723ee..161fda9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,9 @@ Released on 2014-xx-xx. ``album.title``, ``media.file`` => ``media.filename``, ``media.thumb`` => ``media.thumbnail``, ``zip_gallery`` => ``album.zip`` +- New settings to define the sort order for albums and medias: + ``albums_sort_reverse``, ``medias_sort_attr``, ``medias_sort_reverse``. + Version 0.6.0 ~~~~~~~~~~~~~ diff --git a/sigal/compat.py b/sigal/compat.py index 0bed15d..7716781 100644 --- a/sigal/compat.py +++ b/sigal/compat.py @@ -9,9 +9,7 @@ if not PY2: text_type = str string_types = (str,) unichr = chr - - from functools import cmp_to_key - alpha_sort = {'key': cmp_to_key(locale.strcoll)} + strxfrm = locale.strxfrm from http import server import socketserver @@ -19,7 +17,9 @@ else: text_type = unicode # NOQA string_types = (str, unicode) # NOQA unichr = unichr - alpha_sort = {'cmp': locale.strcoll} + + def strxfrm(s): + return locale.strxfrm(s.encode('utf-8')) import SimpleHTTPServer as server import SocketServer as socketserver diff --git a/sigal/gallery.py b/sigal/gallery.py index 408d56b..d924e25 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -32,11 +32,12 @@ import sys import zipfile from collections import defaultdict -from os.path import isfile, join +from datetime import datetime +from os.path import isfile, join, splitext from PIL import Image as PILImage from . import image, video -from .compat import UnicodeMixin, alpha_sort +from .compat import UnicodeMixin, strxfrm from .image import process_image, get_exif_tags from .log import colored, BLUE from .settings import get_thumb, get_orig @@ -76,6 +77,7 @@ class Media(UnicodeMixin): self.logger = logging.getLogger(__name__) self.raw_exif = None self.exif = None + self.date = None def __repr__(self): return "<%s>(%r)" % (self.__class__.__name__, self.file_path) @@ -120,6 +122,8 @@ class Image(Media): def __init__(self, filename, path, settings): super(Image, self).__init__(filename, path, settings) self.raw_exif, self.exif = get_exif_tags(self.src_path) + if self.exif is not None and 'dateobj' in self.exif: + self.date = self.exif['dateobj'] class Video(Media): @@ -130,7 +134,7 @@ class Video(Media): def __init__(self, filename, path, settings): super(Video, self).__init__(filename, path, settings) - base = os.path.splitext(filename)[0] + base = splitext(filename)[0] self.file_path = join(path, base + '.webm') self.dst_path = join(settings['destination'], path, base + '.webm') @@ -188,19 +192,17 @@ class Album(UnicodeMixin): self.index_url = os.path.relpath(settings['destination'], self.dst_path) + '/' + self.url_ext - # sort images and sub-albums by name - filenames.sort(**alpha_sort) - dirnames.sort(**alpha_sort) - + # sort sub-albums + dirnames.sort(key=strxfrm, reverse=settings['albums_sort_reverse']) self.subdirs = dirnames #: List of all medias in the album (:class:`~sigal.gallery.Image` and #: :class:`~sigal.gallery.Video`). - self.medias = [] + self.medias = medias = [] self.medias_count = defaultdict(int) for f in filenames: - ext = os.path.splitext(f)[1] + ext = splitext(f)[1] if ext in Image.extensions: media = Image(f, self.path, settings) elif ext in Video.extensions: @@ -209,7 +211,16 @@ class Album(UnicodeMixin): continue self.medias_count[media.type] += 1 - self.medias.append(media) + medias.append(media) + + # sort images + medias_sort_attr = settings['medias_sort_attr'] + if medias_sort_attr == 'date': + key = lambda s: s.date or datetime.now() + else: + key = lambda s: strxfrm(getattr(s, medias_sort_attr)) + + medias.sort(key=key, reverse=settings['medias_sort_reverse']) def __repr__(self): return "<%s>(%r)" % (self.__class__.__name__, self.path) @@ -292,7 +303,7 @@ class Album(UnicodeMixin): else: # find and return the first landscape image for f in self.medias: - ext = os.path.splitext(f.filename)[1] + ext = splitext(f.filename)[1] if ext in Image.extensions: im = PILImage.open(f.src_path) if im.size[0] > im.size[1]: diff --git a/sigal/image.py b/sigal/image.py index 6905ad3..a6aa2a1 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -218,8 +218,8 @@ def get_exif_tags(source): try: # Remove null bytes at the end if necessary date = data['DateTimeOriginal'].rsplit('\x00')[0] - dt = datetime.strptime(date, '%Y:%m:%d %H:%M:%S') - dt = dt.strftime('%A, %d. %B %Y') + simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S') + dt = simple['dateobj'].strftime('%A, %d. %B %Y') if compat.PY2: simple['datetime'] = dt.decode('utf8') diff --git a/sigal/settings.py b/sigal/settings.py index d913bdc..396b736 100644 --- a/sigal/settings.py +++ b/sigal/settings.py @@ -31,6 +31,7 @@ from .compat import PY2 _DEFAULT_CONFIG = { 'adjust_options': {'color': 1.0, 'brightness': 1.0, 'contrast': 1.0, 'sharpness': 1.0}, + 'albums_sort_reverse': False, 'copy_exif_data': True, 'copyright': '', 'destination': '_build', @@ -44,6 +45,8 @@ _DEFAULT_CONFIG = { 'keep_orig': False, 'links': '', 'locale': '', + 'medias_sort_attr': 'filename', + 'medias_sort_reverse': False, 'make_thumbs': True, 'orig_dir': 'original', 'orig_link': False, diff --git a/sigal/templates/sigal.conf.py b/sigal/templates/sigal.conf.py index 7b04e7b..1cd0b07 100644 --- a/sigal/templates/sigal.conf.py +++ b/sigal/templates/sigal.conf.py @@ -61,6 +61,16 @@ thumb_size = (280, 210) # Use symbolic links instead of copying the original images # orig_link = False +# Reverse sort for albums +albums_sort_reverse = False + +# Attribute of Media objects which is used to sort medias. 'date' can be used +# to sort with EXIF dates. +medias_sort_attr = 'filename' + +# Reverse sort for medias +medias_sort_reverse = False + # Jpeg options # jpg_options = {'quality': 85, # 'optimize': True, diff --git a/tests/sample/pictures/dir1/test2/archlinux-kiss-1024x640.png b/tests/sample/pictures/dir1/test2/archlinux-kiss-1024x640.png new file mode 100644 index 0000000..828f84d Binary files /dev/null and b/tests/sample/pictures/dir1/test2/archlinux-kiss-1024x640.png differ diff --git a/tests/test_gallery.py b/tests/test_gallery.py index 94c49f0..493e7be 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -1,5 +1,6 @@ # -*- coding:utf-8 -*- +import locale import os import pytest @@ -31,7 +32,7 @@ REF = { 'name': 'test2', 'thumbnail': 'test2/thumbnails/21.tn.jpg', 'subdirs': [], - 'medias': ['21.jpg', '22.jpg'], + 'medias': ['21.jpg', '22.jpg', 'archlinux-kiss-1024x640.png'], }, 'dir2': { 'title': 'Another example gallery with a very long name', @@ -39,9 +40,9 @@ REF = { 'thumbnail': 'dir2/thumbnails/m57_the_ring_nebula-587px.tn.jpg', 'subdirs': [], 'medias': ['exo20101028-b-full.jpg', - 'm57_the_ring_nebula-587px.jpg', + 'Hubble Interacting Galaxy NGC 5257.jpg', 'Hubble ultra deep field.jpg', - 'Hubble Interacting Galaxy NGC 5257.jpg'], + 'm57_the_ring_nebula-587px.jpg'], }, u'accentué': { 'title': u'Accentué', @@ -118,6 +119,7 @@ def test_video(settings, tmpdir): @pytest.mark.parametrize("path,album", REF.items()) def test_album(path, album, settings, tmpdir): + locale.setlocale(locale.LC_ALL, 'fr_FR') gal = Gallery(settings, ncpu=1) a = Album(path, settings, album['subdirs'], album['medias'], gal) @@ -129,7 +131,7 @@ def test_album(path, album, settings, tmpdir): assert len(a) == len(album['medias']) -def test_album_medias(settings, tmpdir): +def test_album_medias(settings): gal = Gallery(settings, ncpu=1) album = REF['dir1/test1'] @@ -143,6 +145,30 @@ def test_album_medias(settings, tmpdir): assert list(a.images) == [] +def test_albums_sort(settings): + gal = Gallery(settings, ncpu=1) + album = REF['dir1'] + + settings['albums_sort_reverse'] = True + a = Album('dir1', settings, album['subdirs'], album['medias'], gal) + assert [im.filename for im in a.images] == list(reversed(album['medias'])) + + +def test_medias_sort(settings): + gal = Gallery(settings, ncpu=1) + album = REF['dir1/test2'] + + settings['medias_sort_reverse'] = True + a = Album('dir1/test2', settings, album['subdirs'], album['medias'], gal) + assert [im.filename for im in a.images] == list(reversed(album['medias'])) + + settings['medias_sort_attr'] = 'date' + settings['medias_sort_reverse'] = False + a = Album('dir1/test2', settings, album['subdirs'], album['medias'], gal) + assert [im.filename for im in a.images] == ['22.jpg', '21.jpg', + 'archlinux-kiss-1024x640.png'] + + def test_gallery(settings, tmpdir): "Test the Gallery class."