diff --git a/sigal/gallery.py b/sigal/gallery.py index ce738c8..1052a11 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -38,6 +38,7 @@ from urllib.parse import quote as url_quote from click import get_terminal_size, progressbar from natsort import natsort_keygen, ns +from PIL import Image as PILImage from . import image, signals, video from .image import get_exif_tags, get_image_metadata, get_size, process_image @@ -55,7 +56,7 @@ class Media: Attributes: :var Media.type: ``"image"`` or ``"video"``. - :var Media.filename: Filename of the resized image. + :var Media.dst_filename: Filename of the resized image. :var Media.thumbnail: Location of the corresponding thumbnail image. :var Media.big: If not None, location of the unmodified image. :var Media.big_url: If not None, url of the unmodified image. @@ -66,21 +67,23 @@ class Media: """Type of media, e.g. ``"image"`` or ``"video"``.""" def __init__(self, filename, path, settings): - self.filename = filename + self.path = path + self.settings = settings + + self.basename = os.path.splitext(filename)[0] + + self.dst_filename = filename """Filename of the resized image.""" self.src_filename = filename - """Filename of the resized image.""" + """Filename of the input image.""" - self.path = path - self.settings = settings - self.ext = os.path.splitext(filename)[1].lower() + self.src_ext = os.path.splitext(filename)[1].lower() + """Input extension.""" - self.src_path = join(settings['source'], path, filename) - self.dst_path = join(settings['destination'], path, filename) + self.src_path = join(settings['source'], path, self.src_filename) - self.thumb_name = get_thumb(self.settings, self.filename) - self.thumb_path = join(settings['destination'], path, self.thumb_name) + self.thumb_name = get_thumb(self.settings, self.dst_filename) self.logger = logging.getLogger(__name__) @@ -89,19 +92,38 @@ class Media: # default: title is the filename if not self.title: - self.title = self.filename + self.title = self.basename signals.media_initialized.send(self) def __repr__(self): return "<{}>({!r})".format(self.__class__.__name__, str(self)) def __str__(self): - return join(self.path, self.filename) + return join(self.path, self.src_filename) + + def __getstate__(self): + state = self.__dict__.copy() + # remove un-pickable objects + state['logger'] = None + return state + + def __setstate__(self, state): + for slot, value in state.items(): + setattr(self, slot, value) + self.logger = logging.getLogger(__name__) + + @property + def dst_path(self): + return join(self.settings['destination'], self.path, self.dst_filename) + + @property + def thumb_path(self): + return join(self.settings['destination'], self.path, self.thumb_name) @property def url(self): """URL of the media.""" - return url_from_path(self.filename) + return url_from_path(self.dst_filename) @property def big(self): @@ -112,7 +134,7 @@ class Media: s = self.settings if s['use_orig']: # The image *is* the original, just use it - return self.filename + return self.src_filename orig_path = join(s['destination'], self.path, s['orig_dir']) check_or_create_dir(orig_path) big_path = join(orig_path, self.src_filename) @@ -180,6 +202,17 @@ class Image(Media): type = 'image' + def __init__(self, filename, path, settings): + super().__init__(filename, path, settings) + imgformat = settings.get('img_format') + + if imgformat and PILImage.EXTENSION[self.src_ext] != imgformat.upper(): + # Find the extension that should match img_format + extensions = {v: k for k, v in PILImage.EXTENSION.items()} + ext = extensions[imgformat.upper()] + self.dst_filename = self.basename + ext + self.thumb_name = get_thumb(self.settings, self.dst_filename) + @cached_property def date(self): """The date from the EXIF DateTimeOriginal metadata if available, or @@ -194,7 +227,7 @@ class Image(Media): """ datetime_format = self.settings['datetime_format'] return (get_exif_tags(self.raw_exif, datetime_format=datetime_format) - if self.raw_exif and self.ext in ('.jpg', '.jpeg') else None) + if self.raw_exif and self.src_ext in ('.jpg', '.jpeg') else None) def _get_metadata(self): super()._get_metadata() @@ -215,7 +248,7 @@ class Image(Media): @cached_property def raw_exif(self): """If not `None`, contains the raw EXIF tags.""" - if self.ext in ('.jpg', '.jpeg'): + if self.src_ext in ('.jpg', '.jpeg'): return self.file_metadata['exif'] @cached_property @@ -223,6 +256,11 @@ class Image(Media): """The dimensions of the resized image.""" return get_size(self.dst_path) + @cached_property + def input_size(self): + """The dimensions of the input image.""" + return get_size(self.src_path) + @cached_property def thumb_size(self): """The dimensions of the thumbnail image.""" @@ -240,17 +278,15 @@ class Video(Media): def __init__(self, filename, path, settings): super().__init__(filename, path, settings) - base, ext = splitext(filename) - self.src_filename = filename self.date = self._get_file_date() - if not settings['use_orig'] or not is_valid_html5_video(ext): + + if not settings['use_orig'] or not is_valid_html5_video(self.src_ext): video_format = settings['video_format'] ext = '.' + video_format - self.filename = base + ext + self.dst_filename = self.basename + ext self.mime = get_mime(ext) - self.dst_path = join(settings['destination'], path, base + ext) else: - self.mime = get_mime(ext) + self.mime = get_mime(self.src_ext) class Album: @@ -393,6 +429,9 @@ class Album: def sort_medias(self, medias_sort_attr): if self.medias: + if medias_sort_attr == 'filename': + medias_sort_attr = 'dst_filename' + if medias_sort_attr == 'date': key = lambda s: s.date or datetime.now() elif medias_sort_attr.startswith('meta.'): @@ -458,13 +497,13 @@ class Album: else: # find and return the first landscape image for f in self.medias: - ext = splitext(f.filename)[1] + ext = splitext(f.dst_filename)[1] if ext.lower() not in self.settings['img_extensions']: continue # Use f.size if available as it is quicker (in cache), but # fallback to the size of src_path if dst_path is missing - size = f.size + size = f.input_size if size is None: size = f.file_metadata['size'] @@ -474,7 +513,7 @@ class Album: f.thumbnail) except Exception as e: self.logger.info("Failed to get thumbnail for %s: %s", - f.filename, e) + f.dst_filename, e) else: self.logger.debug( "Use 1st landscape image as thumbnail for %r : %s", @@ -491,7 +530,7 @@ class Album: except Exception as e: self.logger.info( "Failed to get thumbnail for %s: %s", - media.filename, e + media.dst_filename, e ) else: break @@ -698,14 +737,13 @@ class Gallery: bar_opt = {'label': "Processing files", 'show_pos': True, 'file': self.progressbar_target} - failed_files = [] if self.pool: + result = [] try: with progressbar(length=len(media_list), **bar_opt) as bar: - for res in self.pool.imap_unordered(worker, media_list): - if res: - failed_files.append(res) + for status in self.pool.imap_unordered(worker, media_list): + result.append(status) bar.update(1) self.pool.close() self.pool.join() @@ -721,12 +759,11 @@ class Gallery: sys.exit('Abort') else: with progressbar(media_list, **bar_opt) as medias: - for media_item in medias: - res = process_file(media_item) - if res: - failed_files.append(res) + result = [process_file(media_item) for media_item in medias] - if failed_files: + if any(result): + failed_files = [media for status, media in zip(result, media_list) + if status != 0] self.remove_files(failed_files) if self.settings['write_html']: @@ -754,13 +791,13 @@ class Gallery: signals.gallery_build.send(self) - def remove_files(self, files): + def remove_files(self, medias): self.logger.error('Some files have failed to be processed:') - for path, filename in files: - self.logger.error(' - %s/%s', path, filename) - album = self.albums[path] + for media in medias: + self.logger.error(' - %s', media.dst_filename) + album = self.albums[media.path] for f in album.medias: - if f.filename == filename: + if f.dst_filename == media.dst_filename: self.stats[f.type + '_failed'] += 1 album.medias.remove(f) break @@ -771,21 +808,16 @@ class Gallery: """Process a list of images in a directory.""" for f in album: if isfile(f.dst_path) and not force: - self.logger.info("%s exists - skipping", f.filename) + self.logger.info("%s exists - skipping", f.dst_filename) self.stats[f.type + '_skipped'] += 1 else: self.stats[f.type] += 1 - yield (f.type, f.path, f.filename, f.src_path, album.dst_path, - self.settings) + yield f -def process_file(args): - # args => ftype, path, filename, src_path, dst_path, settings - processor = process_image if args[0] == 'image' else process_video - ret = processor(*args[3:]) - # If the processor return an error (ret != 0), then we return the path and - # filename of the failed file to the parent process. - return args[1:3] if ret else None +def process_file(media): + processor = process_image if media.type == 'image' else process_video + return processor(media) def worker(args): diff --git a/sigal/image.py b/sigal/image.py index 3c60dcd..3e05334 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -143,7 +143,9 @@ def generate_image(source, outname, settings, options=None): # first, use hard-coded output format, or PIL format, or original image # format, or fall back to JPEG - outformat = settings.get('img_format') or img.format or original_format or 'JPEG' + outformat = (settings.get('img_format') or img.format or + original_format or 'JPEG') + logger.debug('Save resized image to %s (%s)', outname, outformat) save_image(img, outname, outformat, options=options, autoconvert=True) @@ -168,31 +170,32 @@ def generate_thumbnail(source, outname, box, fit=True, options=None, save_image(img, outname, outformat, options=options, autoconvert=True) -def process_image(filepath, outpath, settings): +def process_image(media): """Process one image: resize, create thumbnail.""" logger = logging.getLogger(__name__) - logger.info('Processing %s', filepath) - filename = os.path.split(filepath)[1] - outname = os.path.join(outpath, filename) - ext = os.path.splitext(filename)[1] + logger.info('Processing %s', media.src_path) - if ext in ('.jpg', '.jpeg', '.JPG', '.JPEG'): - options = settings['jpg_options'] - elif ext == '.png': + if media.src_ext in ('.jpg', '.jpeg', '.JPG', '.JPEG'): + options = media.settings['jpg_options'] + elif media.src_ext == '.png': options = {'optimize': True} else: options = {} try: - generate_image(filepath, outname, settings, options=options) + generate_image(media.src_path, media.dst_path, media.settings, + options=options) - if settings['make_thumbs']: - thumb_name = os.path.join(outpath, get_thumb(settings, filename)) + if media.settings['make_thumbs']: generate_thumbnail( - outname, thumb_name, settings['thumb_size'], - fit=settings['thumb_fit'], options=options, - thumb_fit_centering=settings["thumb_fit_centering"]) + media.dst_path, + media.thumb_path, + media.settings['thumb_size'], + fit=media.settings['thumb_fit'], + options=options, + thumb_fit_centering=media.settings["thumb_fit_centering"] + ) except Exception as e: logger.info('Failed to process: %r', e) if logger.getEffectiveLevel() == logging.DEBUG: diff --git a/sigal/plugins/encrypt/encrypt.py b/sigal/plugins/encrypt/encrypt.py index 27ca094..8a12c1b 100644 --- a/sigal/plugins/encrypt/encrypt.py +++ b/sigal/plugins/encrypt/encrypt.py @@ -30,20 +30,25 @@ from click import progressbar from sigal import signals from sigal.settings import get_thumb -from sigal.utils import copy, url_from_path +from sigal.utils import copy from .endec import encrypt, kdf_gen_key logger = logging.getLogger(__name__) -ASSETS_PATH = os.path.normpath(os.path.join( - os.path.abspath(os.path.dirname(__file__)), 'static')) +ASSETS_PATH = os.path.normpath( + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static')) + class Abort(Exception): pass + def gen_rand_string(length=16): - return "".join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=length)) + return "".join(random.SystemRandom().choices(string.ascii_letters + + string.digits, + k=length)) + def get_options(settings, cache): if "encrypt_options" not in settings: @@ -58,7 +63,7 @@ def get_options(settings, cache): table = str.maketrans({'"': r'\"', '\\': r'\\'}) if "password" not in settings["encrypt_options"] \ - or len(settings["encrypt_options"]["password"]) == 0: + or len(settings["encrypt_options"]["password"]) == 0: logger.error("Encrypt: no password provided") raise ValueError("no password provided") else: @@ -66,8 +71,10 @@ def get_options(settings, cache): options["escaped_password"] = options["password"].translate(table) if "ask_password" not in options: - options["ask_password"] = settings["encrypt_options"].get("ask_password", False) - options["filtered_password"] = "" if options["ask_password"] else options["escaped_password"] + options["ask_password"] = settings["encrypt_options"].get( + "ask_password", False) + options["filtered_password"] = "" if options["ask_password"] else options[ + "escaped_password"] if "gcm_tag" not in options: options["gcm_tag"] = gen_rand_string() @@ -83,7 +90,8 @@ def get_options(settings, cache): if "kdf_iters" not in options: options["kdf_iters"] = 10000 - # in case any of the credentials are newly generated, write them back to cache + # in case any of the credentials are newly generated, write them back + # to cache cache["credentials"] = { "gcm_tag": options["gcm_tag"], "kdf_salt": options["kdf_salt"], @@ -93,8 +101,10 @@ def get_options(settings, cache): return options + def cache_key(media): - return os.path.join(media.path, media.filename) + return os.path.join(media.path, media.dst_filename) + def save_property(cache, media): key = cache_key(media) @@ -104,16 +114,20 @@ def save_property(cache, media): cache[key]["thumb_size"] = media.thumb_size cache[key]["encrypted"] = set() + def get_encrypt_list(settings, media): to_encrypt = [] - to_encrypt.append(media.filename) #resized image or in case of "use_orig", the original + # resized image or in case of "use_orig", the original + to_encrypt.append(media.dst_filename) if settings["make_thumbs"]: - to_encrypt.append(get_thumb(settings, media.filename)) #thumbnail + to_encrypt.append(get_thumb(settings, media.dst_filename)) # thumbnail if media.big is not None and not settings["use_orig"]: - to_encrypt.append(media.big) #original image - to_encrypt = list(map(lambda path: os.path.join(media.path, path), to_encrypt)) + to_encrypt.append(media.big) # original image + to_encrypt = list( + map(lambda path: os.path.join(media.path, path), to_encrypt)) return to_encrypt + def load_property(album): gallery = album.gallery cache = load_cache(gallery.settings) @@ -125,21 +139,25 @@ def load_property(album): media.size = cache[key]["size"] media.thumb_size = cache[key]["thumb_size"] + def load_cache(settings): cachePath = os.path.join(settings["destination"], ".encryptCache") try: with open(cachePath, "rb") as cacheFile: encryptCache = pickle.load(cacheFile) - logger.debug("Loaded encryption cache with %d entries", len(encryptCache)) + logger.debug("Loaded encryption cache with %d entries", + len(encryptCache)) return encryptCache except FileNotFoundError: encryptCache = {} return encryptCache except Exception as e: logger.error("Could not load encryption cache: %s", e) - logger.error("Giving up encryption. You may have to delete and rebuild the entire gallery.") + logger.error("Giving up encryption. You may have to delete and " + "rebuild the entire gallery.") raise Abort + def save_cache(settings, cache): cachePath = os.path.join(settings["destination"], ".encryptCache") try: @@ -150,6 +168,7 @@ def save_cache(settings, cache): logger.warning("Could not store encryption cache: %s", e) logger.warning("Next build of the gallery is likely to fail!") + def encrypt_gallery(gallery): albums = gallery.albums settings = gallery.settings @@ -163,19 +182,26 @@ def encrypt_gallery(gallery): encrypt_files(settings, config, cache, albums, gallery.progressbar_target) save_cache(settings, cache) + def encrypt_files(settings, config, cache, albums, progressbar_target): if settings["keep_orig"] and settings["orig_link"]: - logger.warning("Original images are symlinked! Encryption is aborted. Please set \"orig_link\" to False and restart gallery build.") + logger.warning( + "Original images are symlinked! Encryption is aborted. " + "Please set 'orig_link' to False and restart gallery build.") raise Abort - key = kdf_gen_key(config["password"], config["kdf_salt"], config["kdf_iters"]) + key = kdf_gen_key(config["password"], config["kdf_salt"], + config["kdf_iters"]) gcm_tag = config["gcm_tag"].encode("utf-8") medias = list(chain.from_iterable(albums.values())) - with progressbar(medias, label="%16s" % "Encrypting files", file=progressbar_target, show_eta=True) as medias: + with progressbar(medias, + label="%16s" % "Encrypting files", + file=progressbar_target, + show_eta=True) as medias: for media in medias: if media.type != "image": - logger.info("Skipping non-image file %s", media.filename) + logger.info("Skipping non-image file %s", media.src_filename) continue save_property(cache, media) @@ -196,9 +222,11 @@ def encrypt_files(settings, config, cache, albums, progressbar_target): save_cache(settings, cache) raise Abort - key_check_path = os.path.join(settings["destination"], 'static', 'keycheck.txt') + key_check_path = os.path.join(settings["destination"], 'static', + 'keycheck.txt') encrypt_file("keycheck.txt", key_check_path, key, gcm_tag) + def encrypt_file(filename, full_path, key, gcm_tag): with BytesIO() as outBuffer: try: @@ -217,16 +245,28 @@ def encrypt_file(filename, full_path, key, gcm_tag): return False return True + def copy_assets(settings): theme_path = os.path.join(settings["destination"], 'static') - copy(os.path.join(ASSETS_PATH, "decrypt.js"), theme_path, symlink=False, rellink=False) - copy(os.path.join(ASSETS_PATH, "keycheck.txt"), theme_path, symlink=False, rellink=False) - copy(os.path.join(ASSETS_PATH, "sw.js"), settings["destination"], symlink=False, rellink=False) + copy(os.path.join(ASSETS_PATH, "decrypt.js"), + theme_path, + symlink=False, + rellink=False) + copy(os.path.join(ASSETS_PATH, "keycheck.txt"), + theme_path, + symlink=False, + rellink=False) + copy(os.path.join(ASSETS_PATH, "sw.js"), + settings["destination"], + symlink=False, + rellink=False) + def inject_scripts(context): cache = load_cache(context['settings']) context["encrypt_options"] = get_options(context['settings'], cache) + def register(settings): signals.gallery_build.connect(encrypt_gallery) signals.album_initialized.connect(load_property) diff --git a/sigal/plugins/extended_caching.py b/sigal/plugins/extended_caching.py index 562218d..47b0025 100644 --- a/sigal/plugins/extended_caching.py +++ b/sigal/plugins/extended_caching.py @@ -46,7 +46,7 @@ def load_exif(album): for media in album.medias: if media.type == "image": - key = os.path.join(media.path, media.filename) + key = os.path.join(media.path, media.dst_filename) if key in cache: media.exif = cache[key] @@ -76,7 +76,7 @@ def save_cache(gallery): for album in gallery.albums.values(): for image in album.images: - cache[os.path.join(image.path, image.filename)] = image.exif + cache[os.path.join(image.path, image.dst_filename)] = image.exif cachePath = os.path.join(gallery.settings["destination"], ".exif_cache") diff --git a/sigal/plugins/media_page.py b/sigal/plugins/media_page.py index 9d3143a..1d5fc92 100644 --- a/sigal/plugins/media_page.py +++ b/sigal/plugins/media_page.py @@ -45,7 +45,7 @@ class PageWriter(AbstractWriter): ''' Generate the media page and save it ''' from sigal import __url__ as sigal_link - file_path = os.path.join(album.dst_path, media_group[0].filename) + file_path = os.path.join(album.dst_path, media_group[0].dst_filename) page = self.template.render({ 'album': album, diff --git a/sigal/settings.py b/sigal/settings.py index 2d655df..f00a170 100644 --- a/sigal/settings.py +++ b/sigal/settings.py @@ -39,7 +39,7 @@ _DEFAULT_CONFIG = { 'google_tag_manager': '', 'ignore_directories': [], 'ignore_files': [], - 'img_extensions': ['.jpg', '.jpeg', '.png', '.gif'], + 'img_extensions': ['.jpg', '.jpeg', '.png', '.gif', '.tif', '.tiff'], 'img_processor': 'ResizeToFit', 'img_size': (640, 480), 'img_format': None, diff --git a/sigal/video.py b/sigal/video.py index a595e8a..b3f92d4 100644 --- a/sigal/video.py +++ b/sigal/video.py @@ -157,17 +157,16 @@ def generate_thumbnail(source, outname, box, delay, fit=True, options=None, os.unlink(tmpfile) -def process_video(filepath, outpath, settings): +def process_video(media): """Process a video: resize, create thumbnail.""" logger = logging.getLogger(__name__) - filename = os.path.split(filepath)[1] - basename, ext = splitext(filename) + settings = media.settings try: - if settings['use_orig'] and is_valid_html5_video(ext): - outname = os.path.join(outpath, filename) - utils.copy(filepath, outname, symlink=settings['orig_link']) + if settings['use_orig'] and is_valid_html5_video(media.src_ext): + utils.copy(media.src_path, media.dst_path, + symlink=settings['orig_link']) else: valid_formats = ['mp4', 'webm'] video_format = settings['video_format'] @@ -177,8 +176,7 @@ def process_video(filepath, outpath, settings): valid_formats) raise ValueError - outname = os.path.join(outpath, basename + '.' + video_format) - generate_video(filepath, outname, settings, + generate_video(media.src_path, media.dst_path, settings, options=settings.get(video_format + '_options')) except Exception: if logger.getEffectiveLevel() == logging.DEBUG: @@ -187,13 +185,16 @@ def process_video(filepath, outpath, settings): return Status.FAILURE if settings['make_thumbs']: - thumb_name = os.path.join(outpath, get_thumb(settings, filename)) try: generate_thumbnail( - outname, thumb_name, settings['thumb_size'], - settings['thumb_video_delay'], fit=settings['thumb_fit'], + media.dst_path, + media.thumb_path, + settings['thumb_size'], + settings['thumb_video_delay'], + fit=settings['thumb_fit'], options=settings['jpg_options'], - converter=settings['video_converter']) + converter=settings['video_converter'] + ) except Exception: if logger.getEffectiveLevel() == logging.DEBUG: raise diff --git a/tests/sample/pictures/dir1/test2/21.jpg b/tests/sample/pictures/dir1/test2/21.jpg deleted file mode 100644 index 0b60190..0000000 Binary files a/tests/sample/pictures/dir1/test2/21.jpg and /dev/null differ diff --git a/tests/sample/pictures/dir1/test2/21.tiff b/tests/sample/pictures/dir1/test2/21.tiff new file mode 100644 index 0000000..12c9022 Binary files /dev/null and b/tests/sample/pictures/dir1/test2/21.tiff differ diff --git a/tests/sample/pictures/dir1/test2/CMB_Timeline300_no_WMAP.md b/tests/sample/pictures/dir1/test2/CMB_Timeline300_no_WMAP.md new file mode 100644 index 0000000..88db3c4 --- /dev/null +++ b/tests/sample/pictures/dir1/test2/CMB_Timeline300_no_WMAP.md @@ -0,0 +1,2 @@ +Order: 03 + diff --git a/tests/sample/sigal.conf.py b/tests/sample/sigal.conf.py index cf20d4b..e30b0c2 100644 --- a/tests/sample/sigal.conf.py +++ b/tests/sample/sigal.conf.py @@ -4,6 +4,7 @@ source = 'pictures' thumb_suffix = '.tn' keep_orig = True thumb_video_delay = 5 +# img_format = 'jpeg' links = [('Example link', 'http://example.org'), ('Another link', 'http://example.org')] diff --git a/tests/test_gallery.py b/tests/test_gallery.py index 785dcdc..4255a3f 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -2,7 +2,6 @@ import datetime import logging import os from os.path import join -from urllib.parse import quote import pytest @@ -31,9 +30,9 @@ REF = { 'dir1/test2': { 'title': 'test2', 'name': 'test2', - 'thumbnail': 'test2/thumbnails/21.tn.jpg', + 'thumbnail': 'test2/thumbnails/21.tn.tiff', 'subdirs': [], - 'medias': ['21.jpg', '22.jpg', 'CMB_Timeline300_no_WMAP.jpg'], + 'medias': ['21.tiff', '22.jpg', 'CMB_Timeline300_no_WMAP.jpg'], }, 'dir1/test3': { 'title': '01 First title alphabetically', @@ -76,7 +75,7 @@ def test_media(settings): file_path = join(path, '11.jpg') thumb = join('thumbnails', '11.tn.jpg') - assert m.filename == '11.jpg' + assert m.dst_filename == '11.jpg' assert m.src_path == join(settings['source'], file_path) assert m.dst_path == join(settings['destination'], file_path) assert m.thumb_name == thumb @@ -100,7 +99,7 @@ def test_media_orig(settings, tmpdir): assert m.big == 'original/11.jpg' m = Video('example video.ogv', 'video', settings) - assert m.filename == 'example video.webm' + assert m.dst_filename == 'example video.webm' assert m.big_url == 'original/example%20video.ogv' assert os.path.isfile(join(settings['destination'], m.path, m.big)) @@ -116,14 +115,34 @@ def test_media_iptc_override(settings): # Markdown parsing adds formatting. Let's just focus on content assert "Markdown description beats iptc" in img_with_md.description img_no_md = Image('1.jpg', 'iptcTest', settings) - assert img_no_md.title == 'Haemostratulus clouds over Canberra - ' + \ - '2005-12-28 at 03-25-07' - assert img_no_md.description == \ - '"Haemo" because they look like haemoglobin ' + \ - 'cells and "stratulus" because I can\'t work out whether ' + \ - 'they\'re Stratus or Cumulus clouds.\nWe\'re driving down ' + \ - 'the main drag in Canberra so it\'s Parliament House that ' + \ - 'you can see at the end of the road.' + assert img_no_md.title == ('Haemostratulus clouds over Canberra - ' + '2005-12-28 at 03-25-07') + assert img_no_md.description == ( + '"Haemo" because they look like haemoglobin ' + 'cells and "stratulus" because I can\'t work out whether ' + 'they\'re Stratus or Cumulus clouds.\nWe\'re driving down ' + 'the main drag in Canberra so it\'s Parliament House that ' + 'you can see at the end of the road.' + ) + + +def test_media_img_format(settings): + settings['img_format'] = 'jpeg' + m = Image('11.tiff', 'dir1/test1', settings) + path = join('dir1', 'test1') + thumb = join('thumbnails', '11.tn.jpeg') + + assert m.dst_filename == '11.jpeg' + assert m.src_path == join(settings['source'], path, '11.tiff') + assert m.dst_path == join(settings['destination'], path, '11.jpeg') + assert m.thumb_name == thumb + assert m.thumb_path == join(settings['destination'], path, thumb) + assert m.title == "Foo Bar" + assert m.description == "

This is a funny description of this image

" + + file_path = join(path, '11.tiff') + assert repr(m) == f"('{file_path}')" + assert str(m) == file_path def test_image(settings, tmpdir): @@ -141,8 +160,11 @@ def test_image(settings, tmpdir): def test_video(settings, tmpdir): settings['destination'] = str(tmpdir) m = Video('example video.ogv', 'video', settings) + + src_path = join('video', 'example video.ogv') + assert str(m) == src_path + file_path = join('video', 'example video.webm') - assert str(m) == file_path assert m.dst_path == join(settings['destination'], file_path) os.makedirs(join(settings['destination'], 'video', 'thumbnails')) @@ -161,11 +183,11 @@ def test_album(path, album, settings, tmpdir): assert a.thumbnail == album['thumbnail'] if path == 'video': assert list(a.images) == [] - assert [m.filename for m in a.medias] == \ + assert [m.dst_filename for m in a.medias] == \ [album['medias'][0].replace('.ogv', '.webm')] else: assert list(a.videos) == [] - assert [m.filename for m in a.medias] == album['medias'] + assert [m.dst_filename for m in a.medias] == album['medias'] assert len(a) == len(album['medias']) @@ -216,21 +238,20 @@ def test_medias_sort(settings): settings['medias_sort_reverse'] = True a = Album('dir1/test2', settings, album['subdirs'], album['medias'], gal) a.sort_medias(settings['medias_sort_attr']) - assert [im.filename for im in a.images] == list(reversed(album['medias'])) + assert [im.dst_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) a.sort_medias(settings['medias_sort_attr']) - assert [im.filename for im in a.images] == ['22.jpg', '21.jpg', - 'CMB_Timeline300_no_WMAP.jpg'] + assert a.medias[0].src_filename == '22.jpg' settings['medias_sort_attr'] = 'meta.order' settings['medias_sort_reverse'] = False a = Album('dir1/test2', settings, album['subdirs'], album['medias'], gal) a.sort_medias(settings['medias_sort_attr']) - assert [im.filename for im in a.images] == [ - 'CMB_Timeline300_no_WMAP.jpg', '21.jpg', '22.jpg'] + assert [im.dst_filename for im in a.images] == [ + '21.tiff', '22.jpg', 'CMB_Timeline300_no_WMAP.jpg'] def test_gallery(settings, tmpdir): diff --git a/tests/test_image.py b/tests/test_image.py index 0377e87..2d20156 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -2,32 +2,37 @@ import os from unittest.mock import patch import pytest -from PIL import Image +from PIL import Image as PILImage from sigal import init_logging from sigal.image import (generate_image, generate_thumbnail, get_exif_data, get_exif_tags, get_iptc_data, get_size, process_image) +from sigal.gallery import Image from sigal.settings import Status, create_settings CURRENT_DIR = os.path.dirname(__file__) +SRCDIR = os.path.join(CURRENT_DIR, 'sample', 'pictures') TEST_IMAGE = 'KeckObservatory20071020.jpg' -SRCFILE = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'dir2', TEST_IMAGE) +SRCFILE = os.path.join(SRCDIR, 'dir2', TEST_IMAGE) TEST_GIF_IMAGE = 'example.gif' -SRC_GIF_FILE = os.path.join(CURRENT_DIR, 'sample', 'pictures', - 'dir1', 'test1', TEST_GIF_IMAGE) +SRC_GIF_FILE = os.path.join(SRCDIR, 'dir1', 'test1', TEST_GIF_IMAGE) def test_process_image(tmpdir): "Test the process_image function." - status = process_image('foo.txt', 'none.txt', {}) + status = process_image(Image('foo.txt', 'bar', create_settings())) assert status == Status.FAILURE - settings = create_settings(img_processor='ResizeToFill', make_thumbs=False) - status = process_image(SRCFILE, str(tmpdir), settings) + settings = create_settings(img_processor='ResizeToFill', + make_thumbs=False, + source=os.path.join(SRCDIR, 'dir2'), + destination=str(tmpdir)) + image = Image(TEST_IMAGE, '.', settings) + status = process_image(image) assert status == Status.SUCCESS - im = Image.open(os.path.join(str(tmpdir), TEST_IMAGE)) + im = PILImage.open(os.path.join(str(tmpdir), TEST_IMAGE)) assert im.size == settings['img_size'] @@ -40,21 +45,25 @@ def test_generate_image(tmpdir): copy_exif_data=True) options = None if i == 0 else {'quality': 85} generate_image(SRCFILE, dstfile, settings, options=options) - im = Image.open(dstfile) + im = PILImage.open(dstfile) assert im.size == size + def test_generate_image_imgformat(tmpdir): "Test the effects of the img_format setting on generate_image." dstfile = str(tmpdir.join(TEST_IMAGE)) for i, outfmt in enumerate(["JPEG", "PNG", "TIFF"]): - settings = create_settings(img_size=(300,300), img_processor='ResizeToFill', - copy_exif_data=True, img_format=outfmt) + settings = create_settings(img_size=(300, 300), + img_processor='ResizeToFill', + copy_exif_data=True, + img_format=outfmt) options = {'quality': 85} generate_image(SRCFILE, dstfile, settings, options=options) - im = Image.open(dstfile) + im = PILImage.open(dstfile) assert im.format == outfmt + def test_resize_image_portrait(tmpdir): """Test that the area is the same regardless of aspect ratio.""" size = (300, 200) @@ -65,7 +74,7 @@ def test_resize_image_portrait(tmpdir): portrait_dst = str(tmpdir.join(portrait_image)) generate_image(portrait_src, portrait_dst, settings) - im = Image.open(portrait_dst) + im = PILImage.open(portrait_dst) # In the default mode, PILKit resizes in a way to never make an image # smaller than either of the lengths, the other is scaled accordingly. @@ -77,7 +86,7 @@ def test_resize_image_portrait(tmpdir): landscape_dst = str(tmpdir.join(landscape_image)) generate_image(landscape_src, landscape_dst, settings) - im = Image.open(landscape_dst) + im = PILImage.open(landscape_dst) assert im.size[1] == 200 @@ -129,13 +138,13 @@ def test_generate_thumbnail(tmpdir, image, path, wide_size, high_size): dstfile = str(tmpdir.join(image)) for size in [(200, 150), (150, 200)]: generate_thumbnail(path, dstfile, size) - im = Image.open(dstfile) + im = PILImage.open(dstfile) assert im.size == size for size, thumb_size in [((200, 150), wide_size), ((150, 200), high_size)]: generate_thumbnail(path, dstfile, size, fit=False) - im = Image.open(dstfile) + im = PILImage.open(dstfile) assert im.size == thumb_size diff --git a/tests/test_video.py b/tests/test_video.py index 4b17b6c..bff29ee 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -2,13 +2,15 @@ import os import pytest +from sigal.gallery import Video from sigal.settings import Status, create_settings from sigal.video import (generate_thumbnail, generate_video, process_video, video_size) CURRENT_DIR = os.path.dirname(__file__) +SRCDIR = os.path.join(CURRENT_DIR, 'sample', 'pictures') TEST_VIDEO = 'example video.ogv' -SRCFILE = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'video', TEST_VIDEO) +SRCFILE = os.path.join(SRCDIR, 'video', TEST_VIDEO) def test_video_size(): @@ -27,17 +29,20 @@ def test_generate_thumbnail(tmpdir): def test_process_video(tmpdir): base, ext = os.path.splitext(TEST_VIDEO) - settings = create_settings(video_format='ogv', use_orig=True, - orig_link=True) - process_video(SRCFILE, str(tmpdir), settings) + settings = create_settings(video_format='ogv', + use_orig=True, orig_link=True, + source=os.path.join(SRCDIR, 'video'), + destination=str(tmpdir)) + video = Video(TEST_VIDEO, '.', settings) + process_video(video) dstfile = str(tmpdir.join(base + '.ogv')) assert os.path.realpath(dstfile) == SRCFILE settings = create_settings(video_format='mjpg') - assert process_video(SRCFILE, str(tmpdir), settings) == Status.FAILURE + assert process_video(video) == Status.FAILURE settings = create_settings(thumb_video_delay=-1) - assert process_video(SRCFILE, str(tmpdir), settings) == Status.FAILURE + assert process_video(video) == Status.FAILURE @pytest.mark.parametrize("fmt", ['webm', 'mp4'])