diff --git a/docs/changelog.py b/docs/changelog.py index c96ff0b..350cef4 100644 --- a/docs/changelog.py +++ b/docs/changelog.py @@ -52,15 +52,15 @@ A total of %d pull requests were merged for this release. def get_authors(revision_range): - pat = '^.*\\t(.*)$' - lst_release, cur_release = (r.strip() for r in revision_range.split('..')) + pat = "^.*\\t(.*)$" + lst_release, cur_release = (r.strip() for r in revision_range.split("..")) # authors, in current release and previous to current release. - cur = set(re.findall(pat, this_repo.git.shortlog('-s', revision_range), re.M)) - pre = set(re.findall(pat, this_repo.git.shortlog('-s', lst_release), re.M)) + cur = set(re.findall(pat, this_repo.git.shortlog("-s", revision_range), re.M)) + pre = set(re.findall(pat, this_repo.git.shortlog("-s", lst_release), re.M)) # Append '+' to new authors. - authors = [s + ' +' for s in cur - pre] + [s for s in cur & pre] + authors = [s + " +" for s in cur - pre] + [s for s in cur & pre] authors.sort() return authors @@ -69,7 +69,7 @@ def get_pull_requests(repo, revision_range): prnums = [] # From regular merges - merges = this_repo.git.log('--oneline', '--merges', revision_range) + merges = this_repo.git.log("--oneline", "--merges", revision_range) issues = re.findall("Merge pull request \\#(\\d*)", merges) prnums.extend(int(s) for s in issues) @@ -79,9 +79,9 @@ def get_pull_requests(repo, revision_range): # From fast forward squash-merges commits = this_repo.git.log( - '--oneline', '--no-merges', '--first-parent', revision_range + "--oneline", "--no-merges", "--first-parent", revision_range ) - issues = re.findall('^.*\\(\\#(\\d+)\\)$', commits, re.M) + issues = re.findall("^.*\\(\\#(\\d+)\\)$", commits, re.M) prnums.extend(int(s) for s in issues) # get PR data from github repo @@ -91,10 +91,10 @@ def get_pull_requests(repo, revision_range): def main(token, revision_range): - lst_release, cur_release = (r.strip() for r in revision_range.split('..')) + lst_release, cur_release = (r.strip() for r in revision_range.split("..")) github = Github(token) - github_repo = github.get_repo('saimn/sigal') + github_repo = github.get_repo("saimn/sigal") # document authors authors = get_authors(revision_range) @@ -105,7 +105,7 @@ def main(token, revision_range): print(author_msg % len(authors)) for s in authors: - print('* ' + s) + print("* " + s) # document pull requests pull_requests = get_pull_requests(github_repo, revision_range) @@ -132,7 +132,7 @@ if __name__ == "__main__": from argparse import ArgumentParser parser = ArgumentParser(description="Generate author/pr lists for release") - parser.add_argument('token', help='github access token') - parser.add_argument('revision_range', help='..') + parser.add_argument("token", help="github access token") + parser.add_argument("revision_range", help="..") args = parser.parse_args() main(args.token, args.revision_range) diff --git a/docs/conf.py b/docs/conf.py index 197acbe..98de6d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,7 +7,7 @@ from pkg_resources import get_distribution # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('..')) +sys.path.append(os.path.abspath("..")) # -- General configuration ---------------------------------------------------- @@ -16,34 +16,34 @@ sys.path.append(os.path.abspath('..')) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.extlinks', 'alabaster'] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.extlinks", "alabaster"] -extlinks = {'issue': ('https://github.com/saimn/sigal/issues/%s', '#%s')} +extlinks = {"issue": ("https://github.com/saimn/sigal/issues/%s", "#%s")} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Sigal' -copyright = '2012-2020, Simon Conseil' +project = "Sigal" +copyright = "2012-2020, Simon Conseil" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -release = get_distribution('sigal').version +release = get_distribution("sigal").version # for example take major/minor -version = '.'.join(release.split('.')[:2]) +version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation @@ -58,7 +58,7 @@ version = '.'.join(release.split('.')[:2]) # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True @@ -72,7 +72,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -81,20 +81,20 @@ pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- html_theme_path = [alabaster.get_path()] -html_theme = 'alabaster' +html_theme = "alabaster" html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "searchbox.html", + "donate.html", ] } html_theme_options = { # 'logo': 'logo.png', - 'github_user': 'saimn', - 'github_repo': 'sigal', - 'description': "Yet another simple static gallery generator.", + "github_user": "saimn", + "github_repo": "sigal", + "description": "Yet another simple static gallery generator.", # 'analytics_id': 'UA-18486793-2', # 'travis_button': True, } @@ -165,4 +165,4 @@ html_theme_options = { # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'Sigaldoc' +htmlhelp_basename = "Sigaldoc" diff --git a/pyproject.toml b/pyproject.toml index b740522..8cb0670 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,4 +8,3 @@ write_to = "src/sigal/version.py" [tool.black] line-length = 88 target-version = ['py38'] -skip-string-normalization = true diff --git a/src/sigal/__init__.py b/src/sigal/__init__.py index de22a55..9e01997 100644 --- a/src/sigal/__init__.py +++ b/src/sigal/__init__.py @@ -41,9 +41,9 @@ except ImportError: # package is not installed __version__ = None -__url__ = 'https://github.com/saimn/sigal' +__url__ = "https://github.com/saimn/sigal" -_DEFAULT_CONFIG_FILE = 'sigal.conf.py' +_DEFAULT_CONFIG_FILE = "sigal.conf.py" @click.group() @@ -59,7 +59,7 @@ def main(): @main.command() -@argument('path', default=_DEFAULT_CONFIG_FILE) +@argument("path", default=_DEFAULT_CONFIG_FILE) def init(path): """Copy a sample config file in the current directory (default to 'sigal.conf.py'), or use the provided 'path'.""" @@ -70,44 +70,44 @@ def init(path): from pkg_resources import resource_string - conf = resource_string(__name__, 'templates/sigal.conf.py') + conf = resource_string(__name__, "templates/sigal.conf.py") - with open(path, 'w', encoding='utf-8') as f: - f.write(conf.decode('utf8')) + with open(path, "w", encoding="utf-8") as f: + f.write(conf.decode("utf8")) print(f"Sample config file created: {path}") @main.command() -@argument('source', required=False) -@argument('destination', required=False) -@option('-f', '--force', is_flag=True, help="Force the reprocessing of existing images") -@option('-v', '--verbose', is_flag=True, help="Show all messages") +@argument("source", required=False) +@argument("destination", required=False) +@option("-f", "--force", is_flag=True, help="Force the reprocessing of existing images") +@option("-v", "--verbose", is_flag=True, help="Show all messages") @option( - '-d', - '--debug', + "-d", + "--debug", is_flag=True, help=( "Show all messages, including debug messages. Also raise " "exception if an error happen when processing files." ), ) -@option('-q', '--quiet', is_flag=True, help="Show only error messages") +@option("-q", "--quiet", is_flag=True, help="Show only error messages") @option( - '-c', - '--config', + "-c", + "--config", default=_DEFAULT_CONFIG_FILE, show_default=True, help="Configuration file", ) @option( - '-t', - '--theme', + "-t", + "--theme", help=( "Specify a theme directory, or a theme name for the themes included with Sigal" ), ) -@option('--title', help="Title of the gallery (overrides the title setting.") -@option('-n', '--ncpu', help="Number of cpu to use (default: all)") +@option("--title", help="Title of the gallery (overrides the title setting.") +@option("-n", "--ncpu", help="Number of cpu to use (default: all)") def build( source, destination, debug, verbose, quiet, force, config, theme, title, ncpu ): @@ -118,7 +118,7 @@ def build( """ if sum([debug, verbose, quiet]) > 1: - sys.exit('Only one option of debug, verbose and quiet should be used') + sys.exit("Only one option of debug, verbose and quiet should be used") if debug: level = logging.DEBUG @@ -139,14 +139,14 @@ def build( start_time = time.time() settings = read_settings(config) - for key in ('source', 'destination', 'theme'): + for key in ("source", "destination", "theme"): arg = locals()[key] if arg is not None: settings[key] = os.path.abspath(arg) logger.info("%12s : %s", key.capitalize(), settings[key]) - if not settings['source'] or not os.path.isdir(settings['source']): - logger.error("Input directory not found: %s", settings['source']) + if not settings["source"] or not os.path.isdir(settings["source"]): + logger.error("Input directory not found: %s", settings["source"]) sys.exit(1) # on windows os.path.relpath raises a ValueError if the two paths are on @@ -155,8 +155,8 @@ def build( relative_check = True try: relative_check = os.path.relpath( - settings['destination'], settings['source'] - ).startswith('..') + settings["destination"], settings["source"] + ).startswith("..") except ValueError: pass @@ -165,75 +165,75 @@ def build( sys.exit(1) if title: - settings['title'] = title + settings["title"] = title - locale.setlocale(locale.LC_ALL, settings['locale']) + locale.setlocale(locale.LC_ALL, settings["locale"]) init_plugins(settings) gal = Gallery(settings, ncpu=ncpu, quiet=quiet) gal.build(force=force) # copy extra files - for src, dst in settings['files_to_copy']: - src = os.path.join(settings['source'], src) - dst = os.path.join(settings['destination'], dst) - logger.debug('Copy %s to %s', src, dst) - copy(src, dst, symlink=settings['orig_link'], rellink=settings['rel_link']) + for src, dst in settings["files_to_copy"]: + src = os.path.join(settings["source"], src) + dst = os.path.join(settings["destination"], dst) + logger.debug("Copy %s to %s", src, dst) + copy(src, dst, symlink=settings["orig_link"], rellink=settings["rel_link"]) stats = gal.stats def format_stats(_type): opt = [ - "{} {}".format(stats[_type + '_' + subtype], subtype) - for subtype in ('skipped', 'failed') - if stats[_type + '_' + subtype] > 0 + "{} {}".format(stats[_type + "_" + subtype], subtype) + for subtype in ("skipped", "failed") + if stats[_type + "_" + subtype] > 0 ] - opt = ' ({})'.format(', '.join(opt)) if opt else '' - return f'{stats[_type]} {_type}s{opt}' + opt = " ({})".format(", ".join(opt)) if opt else "" + return f"{stats[_type]} {_type}s{opt}" if not quiet: - stats_str = '' - types = sorted({t.rsplit('_', 1)[0] for t in stats}) + stats_str = "" + types = sorted({t.rsplit("_", 1)[0] for t in stats}) for t in types[:-1]: - stats_str += f'{format_stats(t)} and ' - stats_str += f'{format_stats(types[-1])}' + stats_str += f"{format_stats(t)} and " + stats_str += f"{format_stats(types[-1])}" end_time = time.time() - start_time - print(f'Done, processed {stats_str} in {end_time:.2f} seconds.') + print(f"Done, processed {stats_str} in {end_time:.2f} seconds.") def init_plugins(settings): """Load plugins and call register().""" logger = logging.getLogger(__name__) - logger.debug('Plugin paths: %s', settings['plugin_paths']) + logger.debug("Plugin paths: %s", settings["plugin_paths"]) - for path in settings['plugin_paths']: + for path in settings["plugin_paths"]: sys.path.insert(0, path) - for plugin in settings['plugins']: + for plugin in settings["plugins"]: try: if isinstance(plugin, str): mod = importlib.import_module(plugin) mod.register(settings) else: plugin.register(settings) - logger.debug('Registered plugin %s', plugin) + logger.debug("Registered plugin %s", plugin) except Exception as e: - logger.error('Failed to load plugin %s: %r', plugin, e) + logger.error("Failed to load plugin %s: %r", plugin, e) - for path in settings['plugin_paths']: + for path in settings["plugin_paths"]: sys.path.remove(path) @main.command() -@argument('destination', default='_build') -@option('-p', '--port', help="Port to use", default=8000) +@argument("destination", default="_build") +@option("-p", "--port", help="Port to use", default=8000) @option( - '-c', - '--config', + "-c", + "--config", default=_DEFAULT_CONFIG_FILE, show_default=True, - help='Configuration file', + help="Configuration file", ) def serve(destination, port, config): """Run a simple web server.""" @@ -241,7 +241,7 @@ def serve(destination, port, config): pass elif os.path.exists(config): settings = read_settings(config) - destination = settings.get('destination') + destination = settings.get("destination") if not os.path.exists(destination): sys.stderr.write( f"The '{destination}' directory doesn't exist, maybe try building" @@ -255,7 +255,7 @@ def serve(destination, port, config): ) sys.exit(2) - print(f'DESTINATION : {destination}') + print(f"DESTINATION : {destination}") os.chdir(destination) Handler = server.SimpleHTTPRequestHandler httpd = socketserver.TCPServer(("", port), Handler, False) @@ -267,14 +267,14 @@ def serve(destination, port, config): httpd.server_activate() httpd.serve_forever() except KeyboardInterrupt: - print('\nAll done!') + print("\nAll done!") @main.command() -@argument('target') -@argument('keys', nargs=-1) +@argument("target") +@argument("keys", nargs=-1) @option( - '-o', '--overwrite', default=False, is_flag=True, help='Overwrite existing .md file' + "-o", "--overwrite", default=False, is_flag=True, help="Overwrite existing .md file" ) def set_meta(target, keys, overwrite=False): """Write metadata keys to .md file. @@ -294,9 +294,9 @@ def set_meta(target, keys, overwrite=False): sys.exit(1) if os.path.isdir(target): - descfile = os.path.join(target, 'index.md') + descfile = os.path.join(target, "index.md") else: - descfile = os.path.splitext(target)[0] + '.md' + descfile = os.path.splitext(target)[0] + ".md" if os.path.exists(descfile) and not overwrite: sys.stderr.write( f"Description file '{descfile}' already exists. " diff --git a/src/sigal/gallery.py b/src/sigal/gallery.py index a976657..daa04a8 100644 --- a/src/sigal/gallery.py +++ b/src/sigal/gallery.py @@ -73,7 +73,7 @@ class Media: """ - type = '' + type = "" """Type of media, e.g. ``"image"`` or ``"video"``.""" def __init__(self, filename, path, settings): @@ -91,7 +91,7 @@ class Media: self.src_ext = os.path.splitext(filename)[1].lower() """Input extension.""" - self.src_path = join(settings['source'], path, self.src_filename) + self.src_path = join(settings["source"], path, self.src_filename) self.thumb_name = get_thumb(self.settings, self.dst_filename) @@ -108,7 +108,7 @@ class Media: def __getstate__(self): state = self.__dict__.copy() # remove un-pickable objects - state['logger'] = None + state["logger"] = None return state def __setstate__(self, state): @@ -118,11 +118,11 @@ class Media: @property def dst_path(self): - return join(self.settings['destination'], self.path, self.dst_filename) + 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) + return join(self.settings["destination"], self.path, self.thumb_name) @property def url(self): @@ -134,22 +134,22 @@ class Media: """Path to the original image, if ``keep_orig`` is set (relative to the album directory). Copy the file if needed. """ - if self.settings['keep_orig']: + if self.settings["keep_orig"]: s = self.settings - if s['use_orig']: + if s["use_orig"]: # The image *is* the original, just use it return self.src_filename - orig_path = join(s['destination'], self.path, s['orig_dir']) + orig_path = join(s["destination"], self.path, s["orig_dir"]) check_or_create_dir(orig_path) big_path = join(orig_path, self.src_filename) if not isfile(big_path): copy( self.src_path, big_path, - symlink=s['orig_link'], - rellink=self.settings['rel_link'], + symlink=s["orig_link"], + rellink=self.settings["rel_link"], ) - return join(s['orig_dir'], self.src_filename) + return join(s["orig_dir"], self.src_filename) @property def big_url(self): @@ -162,47 +162,47 @@ class Media: """Path to the thumbnail image (relative to the album directory).""" if not isfile(self.thumb_path): - self.logger.debug('Generating thumbnail for %r', self) + self.logger.debug("Generating thumbnail for %r", self) path = self.dst_path if os.path.exists(self.dst_path) else self.src_path try: # if thumbnail is missing (if settings['make_thumbs'] is False) s = self.settings - if self.type == 'image': + if self.type == "image": image.generate_thumbnail( - path, self.thumb_path, s['thumb_size'], fit=s['thumb_fit'] + path, self.thumb_path, s["thumb_size"], fit=s["thumb_fit"] ) - elif self.type == 'video': + elif self.type == "video": video.generate_thumbnail( path, self.thumb_path, - s['thumb_size'], - s['thumb_video_delay'], - fit=s['thumb_fit'], - converter=s['video_converter'], - black_retries=s['thumb_video_black_retries'], - black_offset=s['thumb_video_black_retry_offset'], - black_max_colors=s['thumb_video_black_max_colors'], + s["thumb_size"], + s["thumb_video_delay"], + fit=s["thumb_fit"], + converter=s["video_converter"], + black_retries=s["thumb_video_black_retries"], + black_offset=s["thumb_video_black_retry_offset"], + black_max_colors=s["thumb_video_black_max_colors"], ) except Exception as e: - self.logger.error('Failed to generate thumbnail: %s', e) + self.logger.error("Failed to generate thumbnail: %s", e) return return url_from_path(self.thumb_name) @cached_property def description(self): """Description extracted from the Markdown .md file.""" - return self.markdown_metadata.get('description', '') + return self.markdown_metadata.get("description", "") @cached_property def title(self): """Title extracted from the metadata, or defaults to the filename.""" - title = self.markdown_metadata.get('title', '') + title = self.markdown_metadata.get("title", "") return title if title else self.basename @cached_property def meta(self): """Other metadata extracted from the Markdown .md file.""" - return self.markdown_metadata.get('meta', {}) + return self.markdown_metadata.get("meta", {}) @cached_property def markdown_metadata(self): @@ -211,11 +211,11 @@ class Media: @property def markdown_metadata_filepath(self): - return splitext(self.src_path)[0] + '.md' + return splitext(self.src_path)[0] + ".md" def _get_markdown_metadata(self): """Get metadata from filename.md.""" - meta = {'title': '', 'description': '', 'meta': {}} + meta = {"title": "", "description": "", "meta": {}} if isfile(self.markdown_metadata_filepath): meta.update(read_markdown(self.markdown_metadata_filepath)) return meta @@ -232,11 +232,11 @@ class Media: class Image(Media): """Gather all informations on an image file.""" - type = 'image' + type = "image" def __init__(self, filename, path, settings): super().__init__(filename, path, settings) - imgformat = settings.get('img_format') + imgformat = settings.get("img_format") # Register all formats PILImage.init() @@ -252,17 +252,17 @@ class Image(Media): def date(self): """The date from the EXIF DateTimeOriginal metadata if available, or from the file date.""" - return self.exif and self.exif.get('dateobj', None) or self._get_file_date() + return self.exif and self.exif.get("dateobj", None) or self._get_file_date() @cached_property def exif(self): """If not `None` contains a dict with the most common tags. For more information, see :ref:`simple-exif-data`. """ - datetime_format = self.settings['datetime_format'] + datetime_format = self.settings["datetime_format"] return ( get_exif_tags(self.raw_exif, datetime_format=datetime_format) - if self.raw_exif and self.src_ext in ('.jpg', '.jpeg') + if self.raw_exif and self.src_ext in (".jpg", ".jpeg") else None ) @@ -277,18 +277,18 @@ class Image(Media): # If a title or description hasn't been obtained by other means, look # for the information in IPTC fields - if not meta['title']: - meta['title'] = self.file_metadata['iptc'].get('title', '') - if not meta['description']: - meta['description'] = self.file_metadata['iptc'].get('description', '') + if not meta["title"]: + meta["title"] = self.file_metadata["iptc"].get("title", "") + if not meta["description"]: + meta["description"] = self.file_metadata["iptc"].get("description", "") return meta @cached_property def raw_exif(self): """If not `None`, contains the raw EXIF tags.""" - if self.src_ext in ('.jpg', '.jpeg'): - return self.file_metadata['exif'] + if self.src_ext in (".jpg", ".jpeg"): + return self.file_metadata["exif"] @cached_property def size(self): @@ -307,20 +307,20 @@ class Image(Media): def has_location(self): """True if location information is available for EXIF GPSInfo.""" - return self.exif is not None and 'gps' in self.exif + return self.exif is not None and "gps" in self.exif class Video(Media): """Gather all informations on a video file.""" - type = 'video' + type = "video" def __init__(self, filename, path, settings): super().__init__(filename, path, settings) - if not settings['use_orig'] or not is_valid_html5_video(self.src_ext): - video_format = settings['video_format'] - ext = '.' + video_format + if not settings["use_orig"] or not is_valid_html5_video(self.src_ext): + video_format = settings["video_format"] + ext = "." + video_format self.dst_filename = self.basename + ext self.mime = get_mime(ext) else: @@ -329,12 +329,12 @@ class Video(Media): @cached_property def date(self): """The date from the Date metadata if available, or from the file date.""" - if 'date' in self.meta: + if "date" in self.meta: try: self.logger.debug( "Reading date from image metadata : %s", self.src_filename ) - return datetime.fromisoformat(self.meta['date'][0]) + return datetime.fromisoformat(self.meta["date"][0]) except Exception: self.logger.debug( "Reading date from image metadata failed : %s", self.src_filename @@ -368,24 +368,24 @@ class Album: self.gallery = gallery self.settings = settings self.subdirs = dirnames - self.output_file = settings['output_filename'] + self.output_file = settings["output_filename"] self._thumbnail = None - if path == '.': - self.src_path = settings['source'] - self.dst_path = settings['destination'] + if path == ".": + self.src_path = settings["source"] + self.dst_path = settings["destination"] else: - self.src_path = join(settings['source'], path) - self.dst_path = join(settings['destination'], path) + self.src_path = join(settings["source"], path) + self.dst_path = join(settings["destination"], path) self.logger = logging.getLogger(__name__) # optionally add index.html to the URLs - self.url_ext = self.output_file if settings['index_in_url'] else '' + self.url_ext = self.output_file if settings["index_in_url"] else "" self.index_url = ( - url_from_path(os.path.relpath(settings['destination'], self.dst_path)) - + '/' + url_from_path(os.path.relpath(settings["destination"], self.dst_path)) + + "/" + self.url_ext ) @@ -397,9 +397,9 @@ class Album: for f in filenames: ext = splitext(f)[1] media = None - if ext.lower() in settings['img_extensions']: + if ext.lower() in settings["img_extensions"]: media = Image(f, self.path, settings) - elif ext.lower() in settings['video_extensions']: + elif ext.lower() in settings["video_extensions"]: media = Video(f, self.path, settings) # Allow modification of the media, including overriding the class @@ -421,8 +421,8 @@ class Album: ) def __str__(self): - return f'{self.path} : ' + ', '.join( - f'{count} {_type}s' for _type, count in self.medias_count.items() + return f"{self.path} : " + ", ".join( + f"{count} {_type}s" for _type, count in self.medias_count.items() ) def __len__(self): @@ -434,27 +434,27 @@ class Album: @cached_property def description(self): """Description extracted from the Markdown index.md file.""" - return self.markdown_metadata.get('description', '') + return self.markdown_metadata.get("description", "") @cached_property def title(self): """Title extracted from the Markdown index.md file.""" - title = self.markdown_metadata.get('title', '') - path = self.path if self.path != '.' else self.src_path + title = self.markdown_metadata.get("title", "") + path = self.path if self.path != "." else self.src_path return title if title else os.path.basename(path) @cached_property def meta(self): """Other metadata extracted from the Markdown index.md file.""" - return self.markdown_metadata.get('meta', {}) + return self.markdown_metadata.get("meta", {}) @cached_property def author(self): """Author extracted from the Markdown index.md file or settings.""" try: - return self.meta['author'][0] + return self.meta["author"][0] except KeyError: - return self.settings.get('author') + return self.settings.get("author") @property def markdown_metadata_filepath(self): @@ -463,7 +463,7 @@ class Album: @cached_property def markdown_metadata(self): """Get metadata from filename.md: title, description, meta.""" - meta = {'title': '', 'description': '', 'meta': {}} + meta = {"title": "", "description": "", "meta": {}} if isfile(self.markdown_metadata_filepath): meta.update(read_markdown(self.markdown_metadata_filepath)) return meta @@ -473,27 +473,27 @@ class Album: check_or_create_dir(self.dst_path) if self.medias: - check_or_create_dir(join(self.dst_path, self.settings['thumb_dir'])) + check_or_create_dir(join(self.dst_path, self.settings["thumb_dir"])) - if self.medias and self.settings['keep_orig']: - self.orig_path = join(self.dst_path, self.settings['orig_dir']) + if self.medias and self.settings["keep_orig"]: + self.orig_path = join(self.dst_path, self.settings["orig_dir"]) check_or_create_dir(self.orig_path) def sort_subdirs(self, albums_sort_attr): if self.subdirs: if not albums_sort_attr: - albums_sort_attr = self.settings['albums_sort_attr'] - reverse = self.settings['albums_sort_reverse'] + albums_sort_attr = self.settings["albums_sort_attr"] + reverse = self.settings["albums_sort_reverse"] - if 'sort' in self.meta: - albums_sort_attr = self.meta['sort'][0] - if albums_sort_attr[0] == '-': + if "sort" in self.meta: + albums_sort_attr = self.meta["sort"][0] + if albums_sort_attr[0] == "-": albums_sort_attr = albums_sort_attr[1:] reverse = True else: reverse = False - root_path = self.path if self.path != '.' else '' + root_path = self.path if self.path != "." else "" def sort_key(s): sort_attr = albums_sort_attr @@ -513,7 +513,7 @@ class Album: continue except TypeError: continue - return '' + return "" key = natsort_keygen(key=sort_key, alg=ns.LOCALE) self.subdirs.sort(key=key, reverse=reverse) @@ -522,22 +522,22 @@ 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 == "filename": + medias_sort_attr = "dst_filename" - if medias_sort_attr == 'date': + if medias_sort_attr == "date": key = lambda s: s.date or datetime.now() - elif medias_sort_attr.startswith('meta.'): + elif medias_sort_attr.startswith("meta."): meta_key = medias_sort_attr.split(".", 1)[1] key = natsort_keygen( - key=lambda s: s.meta.get(meta_key, [''])[0], alg=ns.LOCALE + key=lambda s: s.meta.get(meta_key, [""])[0], alg=ns.LOCALE ) else: key = natsort_keygen( key=lambda s: getattr(s, medias_sort_attr), alg=ns.LOCALE ) - self.medias.sort(key=key, reverse=self.settings['medias_sort_reverse']) + self.medias.sort(key=key, reverse=self.settings["medias_sort_reverse"]) signals.medias_sorted.send(self) @@ -545,14 +545,14 @@ class Album: def images(self): """List of images (:class:`~sigal.gallery.Image`).""" for media in self.medias: - if media.type == 'image': + if media.type == "image": yield media @property def videos(self): """List of videos (:class:`~sigal.gallery.Video`).""" for media in self.medias: - if media.type == 'video': + if media.type == "video": yield media @property @@ -560,7 +560,7 @@ class Album: """List of :class:`~sigal.gallery.Album` objects for each sub-directory. """ - root_path = self.path if self.path != '.' else '' + root_path = self.path if self.path != "." else "" return [self.gallery.albums[join(root_path, path)] for path in self.subdirs] @property @@ -570,8 +570,8 @@ class Album: @property def url(self): """URL of the album, relative to its parent.""" - url = self.name.encode('utf-8') - return url_quote(url) + '/' + self.url_ext + url = self.name.encode("utf-8") + return url_quote(url) + "/" + self.url_ext @property def thumbnail(self): @@ -582,7 +582,7 @@ class Album: return self._thumbnail # Test the thumbnail from the Markdown file. - thumbnail = self.meta.get('thumbnail', [''])[0] + thumbnail = self.meta.get("thumbnail", [""])[0] if thumbnail and isfile(join(self.src_path, thumbnail)): self._thumbnail = url_from_path( @@ -594,18 +594,18 @@ class Album: # find and return the first landscape image for f in self.medias: ext = splitext(f.dst_filename)[1] - if ext.lower() not in self.settings['img_extensions']: + 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.input_size if size is None: - size = f.file_metadata['size'] + size = f.file_metadata["size"] - if size['width'] > size['height']: + if size["width"] > size["height"]: try: - self._thumbnail = url_quote(self.name) + '/' + f.thumbnail + self._thumbnail = url_quote(self.name) + "/" + f.thumbnail except Exception as e: self.logger.info( "Failed to get thumbnail for %s: %s", f.dst_filename, e @@ -624,7 +624,7 @@ class Album: if media.thumbnail is not None: try: self._thumbnail = ( - url_quote(self.name) + '/' + media.thumbnail + url_quote(self.name) + "/" + media.thumbnail ) except Exception as e: self.logger.info( @@ -647,7 +647,7 @@ class Album: if not self._thumbnail: for path, album in self.gallery.get_albums(self.path): if album.thumbnail: - self._thumbnail = url_quote(self.name) + '/' + album.thumbnail + self._thumbnail = url_quote(self.name) + "/" + album.thumbnail self.logger.debug( "Using thumbnail from sub-directory for %r : %s", self, @@ -655,7 +655,7 @@ class Album: ) return self._thumbnail - self.logger.error('Thumbnail not found for %r', self) + self.logger.error("Thumbnail not found for %r", self) @property def random_thumbnail(self): @@ -669,18 +669,18 @@ class Album: """List of ``(url, title)`` tuples defining the current breadcrumb path. """ - if self.path == '.': + if self.path == ".": return [] path = self.path - breadcrumb = [((self.url_ext or '.'), self.title)] + breadcrumb = [((self.url_ext or "."), self.title)] while True: - path = os.path.normpath(os.path.join(path, '..')) - if path == '.': + path = os.path.normpath(os.path.join(path, "..")) + if path == ".": break - url = url_from_path(os.path.relpath(path, self.path)) + '/' + self.url_ext + url = url_from_path(os.path.relpath(path, self.path)) + "/" + self.url_ext breadcrumb.append((url, self.gallery.albums[path].title)) breadcrumb.reverse() @@ -704,17 +704,17 @@ class Gallery: self.logger = logging.getLogger(__name__) self.stats = defaultdict(int) self.init_pool(ncpu) - check_or_create_dir(settings['destination']) + check_or_create_dir(settings["destination"]) - if settings['max_img_pixels']: - PILImage.MAX_IMAGE_PIXELS = settings['max_img_pixels'] + if settings["max_img_pixels"]: + PILImage.MAX_IMAGE_PIXELS = settings["max_img_pixels"] # Build the list of directories with images albums = self.albums = {} - src_path = self.settings['source'] + src_path = self.settings["source"] - ignore_dirs = settings['ignore_directories'] - ignore_files = settings['ignore_files'] + ignore_dirs = settings["ignore_directories"] + ignore_files = settings["ignore_files"] progressChars = cycle(["/", "-", "\\", "|"]) show_progress = ( @@ -733,7 +733,7 @@ class Gallery: if ignore_dirs and any( fnmatch.fnmatch(relpath, ignore) for ignore in ignore_dirs ): - self.logger.info('Ignoring %s', relpath) + self.logger.info("Ignoring %s", relpath) continue # Remove files that match the ignore_files settings @@ -742,22 +742,22 @@ class Gallery: for ignore in ignore_files: files_path -= set(fnmatch.filter(files_path, ignore)) - self.logger.debug('Files before filtering: %r', files) + self.logger.debug("Files before filtering: %r", files) files = [os.path.split(f)[1] for f in files_path] - self.logger.debug('Files after filtering: %r', files) + self.logger.debug("Files after filtering: %r", files) # Remove sub-directories that have been ignored in a previous # iteration (as topdown=False, sub-directories are processed before # their parent for d in dirs[:]: - path = join(relpath, d) if relpath != '.' else d + path = join(relpath, d) if relpath != "." else d if path not in albums.keys(): dirs.remove(d) album = Album(relpath, settings, dirs, files, self) if not album.medias and not album.albums: - self.logger.info('Skip empty album: %r', album) + self.logger.info("Skip empty album: %r", album) else: album.create_output_directories() albums[relpath] = album @@ -771,7 +771,7 @@ class Gallery: file=self.progressbar_target, ) as progress_albums: for album in progress_albums: - album.sort_subdirs(settings['albums_sort_attr']) + album.sort_subdirs(settings["albums_sort_attr"]) with progressbar( albums.values(), @@ -779,15 +779,15 @@ class Gallery: file=self.progressbar_target, ) as progress_albums: for album in progress_albums: - album.sort_medias(settings['medias_sort_attr']) + album.sort_medias(settings["medias_sort_attr"]) - self.logger.debug('Albums:\n%r', albums.values()) + self.logger.debug("Albums:\n%r", albums.values()) signals.gallery_initialized.send(self) @property def title(self): """Title of the gallery.""" - return self.settings['title'] or self.albums['.'].title + return self.settings["title"] or self.albums["."].title def init_pool(self, ncpu): try: @@ -801,7 +801,7 @@ class Gallery: try: ncpu = int(ncpu) except ValueError: - self.logger.error('ncpu should be an integer value') + self.logger.error("ncpu should be an integer value") ncpu = cpu_count self.logger.info("Using %s cores", ncpu) @@ -809,7 +809,7 @@ class Gallery: self.pool = multiprocessing.Pool( processes=ncpu, initializer=pool_init, - initargs=(self.settings['max_img_pixels'],), + initargs=(self.settings["max_img_pixels"],), ) else: self.pool = None @@ -850,12 +850,12 @@ class Gallery: f for album in albums for f in self.process_dir(album, force=force) ] except KeyboardInterrupt: - sys.exit('Interrupted') + sys.exit("Interrupted") bar_opt = { - 'label': "Processing files", - 'show_pos': True, - 'file': self.progressbar_target, + "label": "Processing files", + "show_pos": True, + "file": self.progressbar_target, } if self.pool: @@ -867,7 +867,7 @@ class Gallery: bar.update(1) except KeyboardInterrupt: self.pool.terminate() - sys.exit('Interrupted') + sys.exit("Interrupted") except pickle.PicklingError: self.logger.critical( "Failed to process files with the multiprocessing feature." @@ -875,7 +875,7 @@ class Gallery: "defined in the settings file, which can't be serialized.", exc_info=True, ) - sys.exit('Abort') + sys.exit("Abort") finally: self.pool.close() self.pool.join() @@ -889,7 +889,7 @@ class Gallery: ] self.remove_files(failed_files) - if self.settings['write_html']: + if self.settings["write_html"]: album_writer = AlbumPageWriter(self.settings, index_title=self.title) album_list_writer = AlbumListPageWriter( self.settings, index_title=self.title @@ -914,23 +914,23 @@ class Gallery: album_list_writer.write(album) else: album_writer.write(album) - print('') + print("") signals.gallery_build.send(self) def remove_files(self, medias): - self.logger.error('Some files have failed to be processed:') + self.logger.error("Some files have failed to be processed:") for media in medias: - self.logger.error(' - %s', media.dst_filename) + self.logger.error(" - %s", media.dst_filename) album = self.albums[media.path] for f in album.medias: if f.dst_filename == media.dst_filename: - self.stats[f.type + '_failed'] += 1 + self.stats[f.type + "_failed"] += 1 album.medias.remove(f) break self.logger.error( 'You can run "sigal build" in verbose (--verbose) or' - ' debug (--debug) mode to get more details.' + " debug (--debug) mode to get more details." ) def process_dir(self, album, force=False): @@ -938,7 +938,7 @@ class Gallery: for f in album: if isfile(f.dst_path) and not force: self.logger.info("%s exists - skipping", f.dst_filename) - self.stats[f.type + '_skipped'] += 1 + self.stats[f.type + "_skipped"] += 1 else: self.stats[f.type] += 1 yield f @@ -951,9 +951,9 @@ def pool_init(max_img_pixels): def process_file(media): processor = None - if media.type == 'image': + if media.type == "image": processor = process_image - elif media.type == 'video': + elif media.type == "video": processor = process_video # Allow overriding of the processor @@ -965,7 +965,7 @@ def process_file(media): if processor: return processor(media) else: - logging.warning('Processor not found for media %s', media.path) + logging.warning("Processor not found for media %s", media.path) return Status.FAILURE diff --git a/src/sigal/image.py b/src/sigal/image.py index ac65cc6..c1031ed 100644 --- a/src/sigal/image.py +++ b/src/sigal/image.py @@ -57,7 +57,7 @@ ImageFile.LOAD_TRUNCATED_IMAGES = True def _has_exif_tags(img): - return hasattr(img, 'info') and 'exif' in img.info + return hasattr(img, "info") and "exif" in img.info def _read_image(file_path): @@ -72,8 +72,8 @@ def _read_image(file_path): for w in caught_warnings: logger.warning( - f'PILImage reported a warning for file {file_path}\n' - f'{w.category}: {w.message}' + f"PILImage reported a warning for file {file_path}\n" + f"{w.category}: {w.message}" ) return im @@ -90,14 +90,14 @@ def generate_image(source, outname, settings, options=None): logger = logging.getLogger(__name__) - if settings['use_orig'] or source.endswith('.gif'): - utils.copy(source, outname, symlink=settings['orig_link']) + if settings["use_orig"] or source.endswith(".gif"): + utils.copy(source, outname, symlink=settings["orig_link"]) return img = _read_image(source) original_format = img.format - if settings['copy_exif_data'] and settings['autorotate_images']: + if settings["copy_exif_data"] and settings["autorotate_images"]: logger.warning( "The 'autorotate_images' and 'copy_exif_data' settings " "are not compatible because Sigal can't save the " @@ -105,30 +105,30 @@ def generate_image(source, outname, settings, options=None): ) # Preserve EXIF data - if settings['copy_exif_data'] and _has_exif_tags(img): + if settings["copy_exif_data"] and _has_exif_tags(img): if options is not None: options = deepcopy(options) else: options = {} - options['exif'] = img.info['exif'] + options["exif"] = img.info["exif"] # Rotate the img, and catch IOError when PIL fails to read EXIF - if settings['autorotate_images']: + if settings["autorotate_images"]: try: img = Transpose().process(img) except (OSError, IndexError): pass # Resize the image - if settings['img_processor']: + if settings["img_processor"]: try: - logger.debug('Processor: %s', settings['img_processor']) - processor_cls = getattr(pilkit.processors, settings['img_processor']) + logger.debug("Processor: %s", settings["img_processor"]) + processor_cls = getattr(pilkit.processors, settings["img_processor"]) except AttributeError: - logger.error('Wrong processor name: %s', settings['img_processor']) + logger.error("Wrong processor name: %s", settings["img_processor"]) sys.exit() - width, height = settings['img_size'] + width, height = settings["img_size"] if img.size[0] < img.size[1]: # swap target size if image is in portrait mode @@ -144,9 +144,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) + logger.debug("Save resized image to %s (%s)", outname, outformat) save_image(img, outname, outformat, options=options, autoconvert=True) @@ -171,8 +171,8 @@ def generate_thumbnail( else: img.thumbnail(box, method) - outformat = img.format or original_format or 'JPEG' - logger.debug('Save thumnail image: %s (%s)', outname, outformat) + outformat = img.format or original_format or "JPEG" + logger.debug("Save thumnail image: %s (%s)", outname, outformat) save_image(img, outname, outformat, options=options, autoconvert=True) @@ -180,24 +180,24 @@ def process_image(media): """Process one image: resize, create thumbnail.""" logger = logging.getLogger(__name__) - logger.info('Processing %s', media.src_path) + logger.info("Processing %s", media.src_path) - if media.src_ext in ('.jpg', '.jpeg', '.JPG', '.JPEG'): - options = media.settings['jpg_options'] - elif media.src_ext == '.png': - options = {'optimize': True} + if media.src_ext in (".jpg", ".jpeg", ".JPG", ".JPEG"): + options = media.settings["jpg_options"] + elif media.src_ext == ".png": + options = {"optimize": True} else: options = {} with utils.raise_if_debug() as status: generate_image(media.src_path, media.dst_path, media.settings, options=options) - if media.settings['make_thumbs']: + if media.settings["make_thumbs"]: generate_thumbnail( media.dst_path, media.thumb_path, - media.settings['thumb_size'], - fit=media.settings['thumb_fit'], + media.settings["thumb_size"], + fit=media.settings["thumb_fit"], options=options, thumb_fit_centering=media.settings["thumb_fit_centering"], ) @@ -214,7 +214,7 @@ def get_size(file_path): logger.error("Could not read size of %s due to %r", file_path, e) else: width, height = im.size - return {'width': width, 'height': height} + return {"width": width, "height": height} def get_exif_data(filename): @@ -228,25 +228,25 @@ def get_exif_data(filename): with warnings.catch_warnings(record=True) as caught_warnings: exif = img._getexif() or {} except ZeroDivisionError: - logger.warning('Failed to read EXIF data.') + logger.warning("Failed to read EXIF data.") return None for w in caught_warnings: fname = filename.filename if isinstance(filename, PILImage.Image) else filename logger.warning( - f'PILImage reported a warning for file {fname}\n{w.category}: {w.message}' + f"PILImage reported a warning for file {fname}\n{w.category}: {w.message}" ) data = {TAGS.get(tag, tag): value for tag, value in exif.items()} - if 'GPSInfo' in data: + if "GPSInfo" in data: try: - data['GPSInfo'] = { - GPSTAGS.get(tag, tag): value for tag, value in data['GPSInfo'].items() + data["GPSInfo"] = { + GPSTAGS.get(tag, tag): value for tag, value in data["GPSInfo"].items() } except AttributeError: - logger.info('Failed to get GPS Info') - del data['GPSInfo'] + logger.info("Failed to get GPS Info") + del data["GPSInfo"] return data @@ -266,21 +266,21 @@ def get_iptc_data(filename): img = _read_image(filename) raw_iptc = IptcImagePlugin.getiptcinfo(img) except SyntaxError: - logger.info('IPTC Error in %s', filename) + logger.info("IPTC Error in %s", filename) # IPTC fields are catalogued in: # https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata # 2:05 is the IPTC title property if raw_iptc and (2, 5) in raw_iptc: - iptc_data["title"] = raw_iptc[(2, 5)].decode('utf-8', errors='replace') + iptc_data["title"] = raw_iptc[(2, 5)].decode("utf-8", errors="replace") # 2:120 is the IPTC description property if raw_iptc and (2, 120) in raw_iptc: - iptc_data["description"] = raw_iptc[(2, 120)].decode('utf-8', errors='replace') + iptc_data["description"] = raw_iptc[(2, 120)].decode("utf-8", errors="replace") # 2:105 is the IPTC headline property if raw_iptc and (2, 105) in raw_iptc: - iptc_data["headline"] = raw_iptc[(2, 105)].decode('utf-8', errors='replace') + iptc_data["headline"] = raw_iptc[(2, 105)].decode("utf-8", errors="replace") return iptc_data @@ -292,25 +292,25 @@ def get_image_metadata(filename): try: img = _read_image(filename) except Exception as e: - logger.error('Could not open image %s metadata: %s', filename, e) + logger.error("Could not open image %s metadata: %s", filename, e) else: try: - if os.path.splitext(filename)[1].lower() in ('.jpg', '.jpeg'): + if os.path.splitext(filename)[1].lower() in (".jpg", ".jpeg"): exif = get_exif_data(img) except Exception as e: - logger.warning('Could not read EXIF data from %s: %s', filename, e) + logger.warning("Could not read EXIF data from %s: %s", filename, e) try: iptc = get_iptc_data(img) except Exception as e: - logger.warning('Could not read IPTC data from %s: %s', filename, e) + logger.warning("Could not read IPTC data from %s: %s", filename, e) try: size = get_size(img) except Exception as e: - logger.warning('Could not read size from %s: %s', filename, e) + logger.warning("Could not read size from %s: %s", filename, e) - return {'exif': exif, 'iptc': iptc, 'size': size} + return {"exif": exif, "iptc": iptc, "size": size} def dms_to_degrees(v): @@ -327,81 +327,81 @@ def dms_to_degrees(v): return d + (m / 60.0) + (s / 3600.0) -def get_exif_tags(data, datetime_format='%c'): +def get_exif_tags(data, datetime_format="%c"): """Make a simplified version with common tags from raw EXIF data.""" logger = logging.getLogger(__name__) simple = {} - for tag in ('Model', 'Make', 'LensModel'): + for tag in ("Model", "Make", "LensModel"): if tag in data: val = data[tag][0] if isinstance(data[tag], tuple) else data[tag] simple[tag] = str(val).strip() - if 'FNumber' in data: - fnumber = data['FNumber'] + if "FNumber" in data: + fnumber = data["FNumber"] try: if IFDRational and isinstance(fnumber, IFDRational): - simple['fstop'] = float(fnumber) + simple["fstop"] = float(fnumber) else: - simple['fstop'] = float(fnumber[0]) / fnumber[1] + simple["fstop"] = float(fnumber[0]) / fnumber[1] except Exception: - logger.debug('Skipped invalid FNumber: %r', fnumber, exc_info=True) + logger.debug("Skipped invalid FNumber: %r", fnumber, exc_info=True) - if 'FocalLength' in data: - focal = data['FocalLength'] + if "FocalLength" in data: + focal = data["FocalLength"] try: if IFDRational and isinstance(focal, IFDRational): - simple['focal'] = round(float(focal)) + simple["focal"] = round(float(focal)) else: - simple['focal'] = round(float(focal[0]) / focal[1]) + simple["focal"] = round(float(focal[0]) / focal[1]) except Exception: - logger.debug('Skipped invalid FocalLength: %r', focal, exc_info=True) + logger.debug("Skipped invalid FocalLength: %r", focal, exc_info=True) - if 'ExposureTime' in data: - exptime = data['ExposureTime'] + if "ExposureTime" in data: + exptime = data["ExposureTime"] if IFDRational and isinstance(exptime, IFDRational): - simple['exposure'] = f'{exptime.numerator}/{exptime.denominator}' + simple["exposure"] = f"{exptime.numerator}/{exptime.denominator}" elif isinstance(exptime, tuple): try: - simple['exposure'] = str(fractions.Fraction(exptime[0], exptime[1])) + simple["exposure"] = str(fractions.Fraction(exptime[0], exptime[1])) except ZeroDivisionError: - logger.info('Invalid ExposureTime: %r', exptime) + logger.info("Invalid ExposureTime: %r", exptime) elif isinstance(exptime, int): - simple['exposure'] = str(exptime) + simple["exposure"] = str(exptime) else: - logger.info('Unknown format for ExposureTime: %r', exptime) + logger.info("Unknown format for ExposureTime: %r", exptime) - if data.get('ISOSpeedRatings'): - simple['iso'] = data['ISOSpeedRatings'] + if data.get("ISOSpeedRatings"): + simple["iso"] = data["ISOSpeedRatings"] - if 'DateTimeOriginal' in data: + if "DateTimeOriginal" in data: # Remove null bytes at the end if necessary - date = data['DateTimeOriginal'].rsplit('\x00')[0] + date = data["DateTimeOriginal"].rsplit("\x00")[0] try: - simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S') - simple['datetime'] = simple['dateobj'].strftime(datetime_format) + simple["dateobj"] = datetime.strptime(date, "%Y:%m:%d %H:%M:%S") + simple["datetime"] = simple["dateobj"].strftime(datetime_format) except (ValueError, TypeError) as e: - logger.info('Could not parse DateTimeOriginal: %s', e) + logger.info("Could not parse DateTimeOriginal: %s", e) - if 'GPSInfo' in data: - info = data['GPSInfo'] - lat_info = info.get('GPSLatitude') - lon_info = info.get('GPSLongitude') - lat_ref_info = info.get('GPSLatitudeRef') - lon_ref_info = info.get('GPSLongitudeRef') + if "GPSInfo" in data: + info = data["GPSInfo"] + lat_info = info.get("GPSLatitude") + lon_info = info.get("GPSLongitude") + lat_ref_info = info.get("GPSLatitudeRef") + lon_ref_info = info.get("GPSLongitudeRef") if lat_info and lon_info and lat_ref_info and lon_ref_info: try: lat = dms_to_degrees(lat_info) lon = dms_to_degrees(lon_info) except (ZeroDivisionError, ValueError, TypeError): - logger.info('Failed to read GPS info') + logger.info("Failed to read GPS info") else: - simple['gps'] = { - 'lat': -lat if lat_ref_info != 'N' else lat, - 'lon': -lon if lon_ref_info != 'E' else lon, + simple["gps"] = { + "lat": -lat if lat_ref_info != "N" else lat, + "lon": -lon if lon_ref_info != "E" else lon, } return simple diff --git a/src/sigal/log.py b/src/sigal/log.py index 8938549..4ef678a 100644 --- a/src/sigal/log.py +++ b/src/sigal/log.py @@ -28,11 +28,11 @@ from logging import Formatter BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = (30 + i for i in range(8)) COLORS = { - 'DEBUG': BLUE, - 'INFO': GREEN, - 'WARNING': YELLOW, - 'ERROR': RED, - 'CRITICAL': MAGENTA, + "DEBUG": BLUE, + "INFO": GREEN, + "WARNING": YELLOW, + "ERROR": RED, + "CRITICAL": MAGENTA, } # These are the sequences need to get colored ouput @@ -48,7 +48,7 @@ def colored(text, color): class ColoredFormatter(Formatter): def format(self, record): level = record.levelname - return colored(level, COLORS[level]) + ': ' + record.getMessage() + return colored(level, COLORS[level]) + ": " + record.getMessage() def init_logging(name, level=logging.INFO): @@ -61,15 +61,15 @@ def init_logging(name, level=logging.INFO): logger.setLevel(level) try: - if os.isatty(sys.stdout.fileno()) and not sys.platform.startswith('win'): + if os.isatty(sys.stdout.fileno()) and not sys.platform.startswith("win"): formatter = ColoredFormatter() elif level == logging.DEBUG: - formatter = Formatter('%(levelname)s - %(message)s') + formatter = Formatter("%(levelname)s - %(message)s") else: - formatter = Formatter('%(message)s') + formatter = Formatter("%(message)s") except Exception: # This fails when running tests with click (test_build) - formatter = Formatter('%(message)s') + formatter = Formatter("%(message)s") handler = logging.StreamHandler() handler.setFormatter(formatter) diff --git a/src/sigal/plugins/adjust.py b/src/sigal/plugins/adjust.py index 8202841..91b97de 100644 --- a/src/sigal/plugins/adjust.py +++ b/src/sigal/plugins/adjust.py @@ -26,12 +26,12 @@ logger = logging.getLogger(__name__) def adjust(img, settings=None): - logger.debug('Adjust image %r', img) - return Adjust(**settings['adjust_options']).process(img) + logger.debug("Adjust image %r", img) + return Adjust(**settings["adjust_options"]).process(img) def register(settings): - if settings.get('adjust_options'): + if settings.get("adjust_options"): signals.img_resized.connect(adjust) else: - logger.warning('Adjust options are not set') + logger.warning("Adjust options are not set") diff --git a/src/sigal/plugins/compress_assets.py b/src/sigal/plugins/compress_assets.py index 53f481c..226cace 100644 --- a/src/sigal/plugins/compress_assets.py +++ b/src/sigal/plugins/compress_assets.py @@ -39,8 +39,8 @@ from sigal import signals logger = logging.getLogger(__name__) DEFAULT_SETTINGS = { - 'suffixes': ['htm', 'html', 'css', 'js', 'svg'], - 'method': 'gzip', + "suffixes": ["htm", "html", "css", "js", "svg"], + "method": "gzip", } @@ -49,7 +49,7 @@ class BaseCompressor: def __init__(self, settings): self.suffixes_to_compress = settings.get( - 'suffixes', DEFAULT_SETTINGS['suffixes'] + "suffixes", DEFAULT_SETTINGS["suffixes"] ) def do_compress(self, filename, compressed_filename): @@ -85,7 +85,7 @@ class BaseCompressor: file_stats = None compressed_stats = None - compressed_filename = f'{filename}.{self.suffix}' + compressed_filename = f"{filename}.{self.suffix}" try: file_stats = os.stat(filename) compressed_stats = os.stat(compressed_filename) @@ -103,63 +103,63 @@ class BaseCompressor: class GZipCompressor(BaseCompressor): - suffix = 'gz' + suffix = "gz" def do_compress(self, filename, compressed_filename): - with open(filename, 'rb') as f_in, gzip.open( - compressed_filename, 'wb' + with open(filename, "rb") as f_in, gzip.open( + compressed_filename, "wb" ) as f_out: shutil.copyfileobj(f_in, f_out) class ZopfliCompressor(BaseCompressor): - suffix = 'gz' + suffix = "gz" def do_compress(self, filename, compressed_filename): import zopfli.gzip - with open(filename, 'rb') as f_in, open(compressed_filename, 'wb') as f_out: + with open(filename, "rb") as f_in, open(compressed_filename, "wb") as f_out: f_out.write(zopfli.gzip.compress(f_in.read())) class BrotliCompressor(BaseCompressor): - suffix = 'br' + suffix = "br" def do_compress(self, filename, compressed_filename): import brotli - with open(filename, 'rb') as f_in, open(compressed_filename, 'wb') as f_out: + with open(filename, "rb") as f_in, open(compressed_filename, "wb") as f_out: f_out.write(brotli.compress(f_in.read(), mode=brotli.MODE_TEXT)) def get_compressor(settings): - name = settings.get('method', DEFAULT_SETTINGS['method']) - if name == 'gzip': + name = settings.get("method", DEFAULT_SETTINGS["method"]) + if name == "gzip": return GZipCompressor(settings) - elif name == 'zopfli': + elif name == "zopfli": try: import zopfli.gzip # noqa return ZopfliCompressor(settings) except ImportError: - logging.error('Unable to import zopfli module') + logging.error("Unable to import zopfli module") - elif name == 'brotli': + elif name == "brotli": try: import brotli # noqa return BrotliCompressor(settings) except ImportError: - logger.error('Unable to import brotli module') + logger.error("Unable to import brotli module") else: - logger.error(f'No such compressor {name}') + logger.error(f"No such compressor {name}") def compress_gallery(gallery): - logging.info('Compressing assets for %s', gallery.title) + logging.info("Compressing assets for %s", gallery.title) compress_settings = gallery.settings.get( - 'compress_assets_options', DEFAULT_SETTINGS + "compress_assets_options", DEFAULT_SETTINGS ) compressor = get_compressor(compress_settings) @@ -169,13 +169,13 @@ def compress_gallery(gallery): # Collecting theme assets theme_assets = [] for current_directory, _, filenames in os.walk( - os.path.join(gallery.settings['destination'], 'static') + os.path.join(gallery.settings["destination"], "static") ): for filename in filenames: theme_assets.append(os.path.join(current_directory, filename)) with progressbar( - length=len(gallery.albums) + len(theme_assets), label='Compressing static files' + length=len(gallery.albums) + len(theme_assets), label="Compressing static files" ) as bar: for album in gallery.albums.values(): compressor.compress(os.path.join(album.dst_path, album.output_file)) @@ -187,5 +187,5 @@ def compress_gallery(gallery): def register(settings): - if settings['write_html']: + if settings["write_html"]: signals.gallery_build.connect(compress_gallery) diff --git a/src/sigal/plugins/copyright.py b/src/sigal/plugins/copyright.py index ac4fff8..6c8c5b7 100644 --- a/src/sigal/plugins/copyright.py +++ b/src/sigal/plugins/copyright.py @@ -25,13 +25,13 @@ logger = logging.getLogger(__name__) def add_copyright(img, settings=None): - logger.debug('Adding copyright to %r', img) + logger.debug("Adding copyright to %r", img) draw = ImageDraw.Draw(img) - text = settings['copyright'] - font = settings.get('copyright_text_font', None) - font_size = settings.get('copyright_text_font_size', 10) + text = settings["copyright"] + font = settings.get("copyright_text_font", None) + font_size = settings.get("copyright_text_font_size", 10) assert font_size >= 0 - color = settings.get('copyright_text_color', (0, 0, 0)) + color = settings.get("copyright_text_color", (0, 0, 0)) bottom_margin = 3 # bottom margin for text text_height = bottom_margin + 12 # default text height (of 15) if font: @@ -43,13 +43,13 @@ def add_copyright(img, settings=None): font = ImageFont.load_default() else: font = ImageFont.load_default() - left, top = settings.get('copyright_text_position', (5, img.size[1] - text_height)) + left, top = settings.get("copyright_text_position", (5, img.size[1] - text_height)) draw.text((left, top), text, fill=color, font=font) return img def register(settings): - if settings.get('copyright'): + if settings.get("copyright"): signals.img_resized.connect(add_copyright) else: - logger.warning('Copyright text is not set') + logger.warning("Copyright text is not set") diff --git a/src/sigal/plugins/encrypt/encrypt.py b/src/sigal/plugins/encrypt/encrypt.py index b2d8f14..a3a5a9f 100644 --- a/src/sigal/plugins/encrypt/encrypt.py +++ b/src/sigal/plugins/encrypt/encrypt.py @@ -37,7 +37,7 @@ 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') + os.path.join(os.path.abspath(os.path.dirname(__file__)), "static") ) @@ -62,7 +62,7 @@ def get_options(settings, cache): except KeyError: options = settings["encrypt_options"] - table = str.maketrans({'"': r'\"', '\\': r'\\'}) + table = str.maketrans({'"': r"\"", "\\": r"\\"}) if ( "password" not in settings["encrypt_options"] or len(settings["encrypt_options"]["password"]) == 0 @@ -227,7 +227,7 @@ 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) @@ -251,7 +251,7 @@ def encrypt_file(filename, full_path, key, gcm_tag): def copy_assets(settings): - theme_path = os.path.join(settings["destination"], 'static') + theme_path = os.path.join(settings["destination"], "static") copy( os.path.join(ASSETS_PATH, "decrypt.js"), theme_path, @@ -273,8 +273,8 @@ def copy_assets(settings): def inject_scripts(context): - cache = load_cache(context['settings']) - context["encrypt_options"] = get_options(context['settings'], cache) + cache = load_cache(context["settings"]) + context["encrypt_options"] = get_options(context["settings"], cache) def register(settings): diff --git a/src/sigal/plugins/extended_caching.py b/src/sigal/plugins/extended_caching.py index 951351c..54682a0 100644 --- a/src/sigal/plugins/extended_caching.py +++ b/src/sigal/plugins/extended_caching.py @@ -45,7 +45,7 @@ def load_metadata(album): cache = album.gallery.metadataCache # load album metadata - key = os.path.join(album.path, '_index') + key = os.path.join(album.path, "_index") if key in cache: data = cache[key] @@ -55,10 +55,10 @@ def load_metadata(album): except FileNotFoundError: pass else: - if data.get('mod_date', -1) >= mod_date: + if data.get("mod_date", -1) >= mod_date: # cache is good - if 'markdown_metadata' in data: - album.markdown_metadata = data['markdown_metadata'] + if "markdown_metadata" in data: + album.markdown_metadata = data["markdown_metadata"] # load media metadata for media in album.medias: @@ -71,23 +71,23 @@ def load_metadata(album): mod_date = int(get_mod_date(media.src_path)) except FileNotFoundError: continue - if data.get('mod_date', -1) < mod_date: + if data.get("mod_date", -1) < mod_date: continue # file_metadata needs updating - if 'file_metadata' in data: - media.file_metadata = data['file_metadata'] - if 'exif' in data: - media.exif = data['exif'] + if "file_metadata" in data: + media.file_metadata = data["file_metadata"] + if "exif" in data: + media.exif = data["exif"] try: mod_date = int(get_mod_date(media.markdown_metadata_filepath)) except FileNotFoundError: continue - if data.get('meta_mod_date', -1) < mod_date: + if data.get("meta_mod_date", -1) < mod_date: continue # markdown_metadata needs updating - if 'markdown_metadata' in data: - media.markdown_metadata = data['markdown_metadata'] + if "markdown_metadata" in data: + media.markdown_metadata = data["markdown_metadata"] def _restore_cache(gallery): @@ -116,10 +116,10 @@ def save_cache(gallery): for album in gallery.albums.values(): try: data = { - 'mod_date': int(get_mod_date(album.markdown_metadata_filepath)), - 'markdown_metadata': album.markdown_metadata, + "mod_date": int(get_mod_date(album.markdown_metadata_filepath)), + "markdown_metadata": album.markdown_metadata, } - cache[os.path.join(album.path, '_index')] = data + cache[os.path.join(album.path, "_index")] = data except FileNotFoundError: pass @@ -130,18 +130,18 @@ def save_cache(gallery): except FileNotFoundError: continue else: - data['mod_date'] = mod_date - data['file_metadata'] = media.file_metadata - if hasattr(media, 'exif'): - data['exif'] = media.exif + data["mod_date"] = mod_date + data["file_metadata"] = media.file_metadata + if hasattr(media, "exif"): + data["exif"] = media.exif try: meta_mod_date = int(get_mod_date(media.markdown_metadata_filepath)) except FileNotFoundError: pass else: - data['meta_mod_date'] = meta_mod_date - data['markdown_metadata'] = media.markdown_metadata + data["meta_mod_date"] = meta_mod_date + data["markdown_metadata"] = media.markdown_metadata cache[os.path.join(media.path, media.dst_filename)] = data diff --git a/src/sigal/plugins/feeds.py b/src/sigal/plugins/feeds.py index 8f5fe2e..f3625ba 100644 --- a/src/sigal/plugins/feeds.py +++ b/src/sigal/plugins/feeds.py @@ -38,34 +38,34 @@ def generate_feeds(gallery): medias.sort(key=lambda m: m.date, reverse=True) settings = gallery.settings - if settings.get('rss_feed'): - generate_feed(gallery, medias, feed_type='rss', **settings['rss_feed']) - if settings.get('atom_feed'): - generate_feed(gallery, medias, feed_type='atom', **settings['atom_feed']) + if settings.get("rss_feed"): + generate_feed(gallery, medias, feed_type="rss", **settings["rss_feed"]) + if settings.get("atom_feed"): + generate_feed(gallery, medias, feed_type="atom", **settings["atom_feed"]) -def generate_feed(gallery, medias, feed_type=None, feed_url='', nb_items=0): +def generate_feed(gallery, medias, feed_type=None, feed_url="", nb_items=0): from feedgenerator import Atom1Feed, Rss201rev2Feed - root_album = gallery.albums['.'] - cls = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed + root_album = gallery.albums["."] + cls = Rss201rev2Feed if feed_type == "rss" else Atom1Feed feed = cls( title=Markup.escape(root_album.title), - link='/', + link="/", feed_url=feed_url, description=Markup.escape(root_album.description).striptags(), ) - theme = gallery.settings['theme'] + theme = gallery.settings["theme"] nb_medias = len(medias) nb_items = min(nb_items, nb_medias) if nb_items > 0 else nb_medias - base_url = feed_url.rsplit('/', maxsplit=1)[0] + base_url = feed_url.rsplit("/", maxsplit=1)[0] for item in medias[:nb_items]: - if theme == 'galleria': - link = f'{base_url}/{item.path}/#{item.url}' + if theme == "galleria": + link = f"{base_url}/{item.path}/#{item.url}" else: - link = f'{base_url}/{item.path}/' + link = f"{base_url}/{item.path}/" feed.add_item( title=Markup.escape(item.title or item.url), @@ -77,14 +77,14 @@ def generate_feed(gallery, medias, feed_type=None, feed_url='', nb_items=0): base_url, item.path, item.thumbnail ), # categories=item.tags if hasattr(item, 'tags') else None, - author_name=getattr(item, 'author', ''), + author_name=getattr(item, "author", ""), pubdate=item.date or datetime.now(), ) - output_file = os.path.join(root_album.dst_path, feed_url.split('/')[-1]) - logger.info('Generate %s feeds: %s', feed_type.upper(), output_file) - with open(output_file, 'w', encoding='utf8') as f: - feed.write(f, 'utf-8') + output_file = os.path.join(root_album.dst_path, feed_url.split("/")[-1]) + logger.info("Generate %s feeds: %s", feed_type.upper(), output_file) + with open(output_file, "w", encoding="utf8") as f: + feed.write(f, "utf-8") def register(settings): diff --git a/src/sigal/plugins/nonmedia_files.py b/src/sigal/plugins/nonmedia_files.py index 6dfb043..b263af9 100644 --- a/src/sigal/plugins/nonmedia_files.py +++ b/src/sigal/plugins/nonmedia_files.py @@ -37,37 +37,37 @@ logger = logging.getLogger(__name__) DEFAULT_CONFIG = { - 'ext_as_thumb': True, - 'ignore_ext': ['.md'], - 'thumb_bg_color': (255, 255, 255), - 'thumb_font': None, - 'thumb_font_color': (0, 0, 0), - 'thumb_font_size': 40, + "ext_as_thumb": True, + "ignore_ext": [".md"], + "thumb_bg_color": (255, 255, 255), + "thumb_font": None, + "thumb_font_color": (0, 0, 0), + "thumb_font_size": 40, } COMMON_MIME_TYPES = { - '.azw': 'application/vnd.amazon.ebook', - '.csv': 'text/csv', - '.epub': 'application/epub+zip', - '.pdf': 'application/pdf', - '.svg': 'image/svg+xml', - '.txt': 'text/plain', - '.zip': 'application/zip', + ".azw": "application/vnd.amazon.ebook", + ".csv": "text/csv", + ".epub": "application/epub+zip", + ".pdf": "application/pdf", + ".svg": "image/svg+xml", + ".txt": "text/plain", + ".zip": "application/zip", } class NonMedia(Media): """Gather all informations on a non-media file.""" - type = 'nonmedia' + type = "nonmedia" def __init__(self, filename, path, settings): super().__init__(filename, path, settings) - self.thumb_name = os.path.splitext(self.thumb_name)[0] + '.jpg' + self.thumb_name = os.path.splitext(self.thumb_name)[0] + ".jpg" self.date = self._get_file_date() - self.mime = COMMON_MIME_TYPES.get(self.src_ext, 'application/octet-stream') - logger.debug('mime type %s', self.mime) + self.mime = COMMON_MIME_TYPES.get(self.src_ext, "application/octet-stream") + logger.debug("mime type %s", self.mime) @property def thumbnail(self): @@ -81,55 +81,55 @@ def generate_thumbnail( text, outname, box, - bg_color=DEFAULT_CONFIG['thumb_bg_color'], - font=DEFAULT_CONFIG['thumb_font'], - font_color=DEFAULT_CONFIG['thumb_font_color'], - font_size=DEFAULT_CONFIG['thumb_font_size'], + bg_color=DEFAULT_CONFIG["thumb_bg_color"], + font=DEFAULT_CONFIG["thumb_font"], + font_color=DEFAULT_CONFIG["thumb_font_color"], + font_size=DEFAULT_CONFIG["thumb_font_size"], options=None, ): """Create a thumbnail image.""" kwargs = {} if font: - kwargs['font'] = ImageFont.truetype(font, font_size) + kwargs["font"] = ImageFont.truetype(font, font_size) if font_color: - kwargs['fill'] = font_color + kwargs["fill"] = font_color img = PILImage.new("RGB", box, bg_color) anchor = (box[0] // 2, box[1] // 2) d = ImageDraw.Draw(img) logger.info(f"kwargs: {kwargs}") - d.text(anchor, text, anchor='mm', **kwargs) + d.text(anchor, text, anchor="mm", **kwargs) - outformat = 'JPEG' - logger.info('Save thumnail image: %s (%s)', outname, outformat) + outformat = "JPEG" + logger.info("Save thumnail image: %s (%s)", outname, outformat) save_image(img, outname, outformat, options=options, autoconvert=True) def process_thumb(media): settings = media.settings - plugin_settings = settings.get('nonmedia_files_options', {}) - utils.copy(media.src_path, media.dst_path, symlink=settings['orig_link']) + plugin_settings = settings.get("nonmedia_files_options", {}) + utils.copy(media.src_path, media.dst_path, symlink=settings["orig_link"]) - if plugin_settings.get('ext_as_thumb', DEFAULT_CONFIG['ext_as_thumb']): - logger.info('plugin_settings: %r', plugin_settings) + if plugin_settings.get("ext_as_thumb", DEFAULT_CONFIG["ext_as_thumb"]): + logger.info("plugin_settings: %r", plugin_settings) kwargs = {} - for key in ('bg_color', 'font', 'font_color', 'font_size'): - if f'thumb_{key}' in plugin_settings: - kwargs[key] = plugin_settings[f'thumb_{key}'] + for key in ("bg_color", "font", "font_color", "font_size"): + if f"thumb_{key}" in plugin_settings: + kwargs[key] = plugin_settings[f"thumb_{key}"] generate_thumbnail( media.src_ext[1:].upper(), media.thumb_path, - settings['thumb_size'], - options=settings['jpg_options'], + settings["thumb_size"], + options=settings["jpg_options"], **kwargs, ) def process_nonmedia(media): """Process a non-media file: copy and create thumbnail.""" - logger.info('Processing non-media file: %s', media.dst_filename) + logger.info("Processing non-media file: %s", media.dst_filename) with utils.raise_if_debug() as status: process_thumb(media) @@ -140,18 +140,18 @@ def process_nonmedia(media): def album_file(album, filename, media=None): if not media: ext = os.path.splitext(filename)[1] - ext_ignore = album.settings.get('nonmedia_files_options', {}).get( - 'ignore_ext', DEFAULT_CONFIG['ignore_ext'] + ext_ignore = album.settings.get("nonmedia_files_options", {}).get( + "ignore_ext", DEFAULT_CONFIG["ignore_ext"] ) if ext in ext_ignore: - logger.info('Ignoring non-media file: %s', filename) + logger.info("Ignoring non-media file: %s", filename) else: - logger.info('Registering non-media file: %s', filename) + logger.info("Registering non-media file: %s", filename) return NonMedia(filename, album.path, album.settings) def process_file(media, processor=None): - if media.type == 'nonmedia': + if media.type == "nonmedia": return process_nonmedia diff --git a/src/sigal/plugins/titleregexp.py b/src/sigal/plugins/titleregexp.py index 9814ad6..057725c 100644 --- a/src/sigal/plugins/titleregexp.py +++ b/src/sigal/plugins/titleregexp.py @@ -67,24 +67,24 @@ logger = logging.getLogger(__name__) def titleregexp(album): """Create a title by regexping name""" - cfg = album.settings.get('titleregexp') + cfg = album.settings.get("titleregexp") n = 0 total = 0 album_title_org = album.title - for r in cfg.get('regexp'): + for r in cfg.get("regexp"): album.title, n = re.subn( - r.get('search'), r.get('replace'), album.title, r.get('count', 0) + r.get("search"), r.get("replace"), album.title, r.get("count", 0) ) total += n if n > 0: - for s in r.get('substitute', []): + for s in r.get("substitute", []): album.title = album.title.replace(s[0], s[1]) - if r.get('break', '') != '': + if r.get("break", "") != "": break - for r in cfg.get('substitute', []): + for r in cfg.get("substitute", []): album.title = album.title.replace(r[0], r[1]) if total > 0: @@ -92,7 +92,7 @@ def titleregexp(album): def register(settings): - if settings.get('titleregexp'): + if settings.get("titleregexp"): signals.album_initialized.connect(titleregexp) else: logger.warning("'titleregexp' setting not available!") diff --git a/src/sigal/plugins/watermark.py b/src/sigal/plugins/watermark.py index ffd150d..8f2290e 100644 --- a/src/sigal/plugins/watermark.py +++ b/src/sigal/plugins/watermark.py @@ -47,8 +47,8 @@ from sigal import signals def reduce_opacity(im, opacity): """Returns an image with reduced opacity.""" assert opacity >= 0 and opacity <= 1 - if im.mode != 'RGBA': - im = im.convert('RGBA') + if im.mode != "RGBA": + im = im.convert("RGBA") else: im = im.copy() alpha = im.split()[3] @@ -61,16 +61,16 @@ def watermark(im, mark, position, opacity=1): """Adds a watermark to an image.""" if opacity < 1: mark = reduce_opacity(mark, opacity) - if im.mode != 'RGBA': - im = im.convert('RGBA') + if im.mode != "RGBA": + im = im.convert("RGBA") # create a transparent layer the size of the image and draw the # watermark in that layer. - layer = Image.new('RGBA', im.size, (0, 0, 0, 0)) - if position == 'tile': + layer = Image.new("RGBA", im.size, (0, 0, 0, 0)) + if position == "tile": for y in range(0, im.size[1], mark.size[1]): for x in range(0, im.size[0], mark.size[0]): layer.paste(mark, (x, y)) - elif position == 'scale': + elif position == "scale": # scale, but preserve the aspect ratio ratio = min(float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1]) w = int(mark.size[0] * ratio) @@ -85,16 +85,16 @@ def watermark(im, mark, position, opacity=1): def add_watermark(img, settings=None): logger = logging.getLogger(__name__) - logger.debug('Adding watermark to %r', img) - mark = Image.open(settings['watermark']) - position = settings.get('watermark_position', 'scale') + logger.debug("Adding watermark to %r", img) + mark = Image.open(settings["watermark"]) + position = settings.get("watermark_position", "scale") opacity = settings.get("watermark_opacity", 1) return watermark(img, mark, position, opacity) def register(settings): logger = logging.getLogger(__name__) - if settings.get('watermark'): + if settings.get("watermark"): signals.img_resized.connect(add_watermark) else: - logger.warning('Watermark image is not set') + logger.warning("Watermark image is not set") diff --git a/src/sigal/plugins/zip_gallery.py b/src/sigal/plugins/zip_gallery.py index 5901d50..4996f26 100644 --- a/src/sigal/plugins/zip_gallery.py +++ b/src/sigal/plugins/zip_gallery.py @@ -56,18 +56,18 @@ def _generate_album_zip(album): archive with all original images of the corresponding directory. """ - zip_gallery = album.settings['zip_gallery'] + zip_gallery = album.settings["zip_gallery"] if zip_gallery and len(album) > 0: zip_gallery = zip_gallery.format(album=album) archive_path = join(album.dst_path, zip_gallery) - if album.settings.get('zip_skip_if_exists', False) and isfile(archive_path): + if album.settings.get("zip_skip_if_exists", False) and isfile(archive_path): logger.debug("Archive %s already created, passing", archive_path) return zip_gallery - archive = zipfile.ZipFile(archive_path, 'w', allowZip64=True) + archive = zipfile.ZipFile(archive_path, "w", allowZip64=True) attr = ( - 'src_path' if album.settings['zip_media_format'] == 'orig' else 'dst_path' + "src_path" if album.settings["zip_media_format"] == "orig" else "dst_path" ) for p in album: @@ -75,10 +75,10 @@ def _generate_album_zip(album): try: archive.write(path, os.path.split(path)[1]) except OSError as e: - logger.warn('Failed to add %s to the ZIP: %s', p, e) + logger.warn("Failed to add %s to the ZIP: %s", p, e) archive.close() - logger.debug('Created ZIP archive %s', archive_path) + logger.debug("Created ZIP archive %s", archive_path) return zip_gallery return False @@ -108,15 +108,15 @@ def generate_album_zip(album): def nozip_gallery_file(album, settings=None): """Filesystem based switch to disable ZIP generation for an Album""" Album.zip = cached_property(generate_album_zip) - Album.zip.__set_name__(Album, 'zip') + Album.zip.__set_name__(Album, "zip") def check_settings(gallery): - if gallery.settings['zip_gallery'] and not isinstance( - gallery.settings['zip_gallery'], str + if gallery.settings["zip_gallery"] and not isinstance( + gallery.settings["zip_gallery"], str ): logger.error("'zip_gallery' should be set to a filename") - gallery.settings['zip_gallery'] = False + gallery.settings["zip_gallery"] = False def register(settings): diff --git a/src/sigal/settings.py b/src/sigal/settings.py index e6d4994..fbdf39c 100644 --- a/src/sigal/settings.py +++ b/src/sigal/settings.py @@ -27,72 +27,72 @@ from os.path import abspath, isabs, join, normpath from pprint import pformat _DEFAULT_CONFIG = { - 'albums_sort_attr': 'name', - 'albums_sort_reverse': False, - 'autorotate_images': True, - 'colorbox_column_size': 3, - 'copy_exif_data': False, - 'datetime_format': '%c', - 'destination': '_build', - 'files_to_copy': (), - 'galleria_theme': 'classic', - 'google_analytics': '', - 'google_tag_manager': '', - 'ignore_directories': [], - 'ignore_files': [], - 'img_extensions': ['.jpg', '.jpeg', '.png', '.gif', '.tif', '.tiff', '.webp'], - 'img_processor': 'ResizeToFit', - 'img_size': (640, 480), - 'img_format': None, - 'index_in_url': False, - 'jpg_options': {'quality': 85, 'optimize': True, 'progressive': True}, - 'keep_orig': False, - 'html_language': 'en', - 'leaflet_provider': 'OpenStreetMap.Mapnik', - 'links': '', - 'locale': '', - 'make_thumbs': True, - 'max_img_pixels': None, - 'map_height': '500px', - 'medias_sort_attr': 'filename', - 'medias_sort_reverse': False, - 'mp4_options': ['-crf', '23', '-strict', '-2'], - 'mp4_options_second_pass': None, - 'orig_dir': 'original', - 'orig_link': False, - 'rel_link': False, - 'output_filename': 'index.html', - 'piwik': {'tracker_url': '', 'site_id': 0}, - 'plugin_paths': [], - 'plugins': [], - 'site_logo': '', - 'show_map': False, - 'source': '', - 'theme': 'colorbox', - 'thumb_dir': 'thumbnails', - 'thumb_fit': True, - 'thumb_fit_centering': (0.5, 0.5), - 'thumb_prefix': '', - 'thumb_size': (200, 150), - 'thumb_suffix': '', - 'thumb_video_delay': 0, - 'thumb_video_black_retries': 0, - 'thumb_video_black_retry_offset': 1, - 'thumb_video_black_max_colors': 4, - 'title': '', - 'use_orig': False, - 'user_css': None, - 'video_converter': 'ffmpeg', - 'video_extensions': ['.3gp', '.avi', '.mkv', '.mov', '.mp4', '.ogv', '.webm'], - 'video_format': 'webm', - 'video_always_convert': False, - 'video_size': (480, 360), - 'watermark': '', - 'webm_options': ['-crf', '10', '-b:v', '1.6M', '-qmin', '4', '-qmax', '63'], - 'webm_options_second_pass': None, - 'write_html': True, - 'zip_gallery': False, - 'zip_media_format': 'resized', + "albums_sort_attr": "name", + "albums_sort_reverse": False, + "autorotate_images": True, + "colorbox_column_size": 3, + "copy_exif_data": False, + "datetime_format": "%c", + "destination": "_build", + "files_to_copy": (), + "galleria_theme": "classic", + "google_analytics": "", + "google_tag_manager": "", + "ignore_directories": [], + "ignore_files": [], + "img_extensions": [".jpg", ".jpeg", ".png", ".gif", ".tif", ".tiff", ".webp"], + "img_processor": "ResizeToFit", + "img_size": (640, 480), + "img_format": None, + "index_in_url": False, + "jpg_options": {"quality": 85, "optimize": True, "progressive": True}, + "keep_orig": False, + "html_language": "en", + "leaflet_provider": "OpenStreetMap.Mapnik", + "links": "", + "locale": "", + "make_thumbs": True, + "max_img_pixels": None, + "map_height": "500px", + "medias_sort_attr": "filename", + "medias_sort_reverse": False, + "mp4_options": ["-crf", "23", "-strict", "-2"], + "mp4_options_second_pass": None, + "orig_dir": "original", + "orig_link": False, + "rel_link": False, + "output_filename": "index.html", + "piwik": {"tracker_url": "", "site_id": 0}, + "plugin_paths": [], + "plugins": [], + "site_logo": "", + "show_map": False, + "source": "", + "theme": "colorbox", + "thumb_dir": "thumbnails", + "thumb_fit": True, + "thumb_fit_centering": (0.5, 0.5), + "thumb_prefix": "", + "thumb_size": (200, 150), + "thumb_suffix": "", + "thumb_video_delay": 0, + "thumb_video_black_retries": 0, + "thumb_video_black_retry_offset": 1, + "thumb_video_black_max_colors": 4, + "title": "", + "use_orig": False, + "user_css": None, + "video_converter": "ffmpeg", + "video_extensions": [".3gp", ".avi", ".mkv", ".mov", ".mp4", ".ogv", ".webm"], + "video_format": "webm", + "video_always_convert": False, + "video_size": (480, 360), + "watermark": "", + "webm_options": ["-crf", "10", "-b:v", "1.6M", "-qmin", "4", "-qmax", "63"], + "webm_options_second_pass": None, + "write_html": True, + "zip_gallery": False, + "zip_media_format": "resized", } @@ -119,12 +119,12 @@ def get_thumb(settings, filename): path, filen = os.path.split(filename) name, ext = os.path.splitext(filen) - if ext.lower() in settings['video_extensions']: - ext = '.jpg' + if ext.lower() in settings["video_extensions"]: + ext = ".jpg" return join( path, - settings['thumb_dir'], - settings['thumb_prefix'] + name + settings['thumb_suffix'] + ext, + settings["thumb_dir"], + settings["thumb_prefix"] + name + settings["thumb_suffix"] + ext, ) @@ -141,20 +141,20 @@ def read_settings(filename=None): tempdict = {} with open(filename) as f: - code = compile(f.read(), filename, 'exec') + code = compile(f.read(), filename, "exec") exec(code, tempdict) settings.update( - (k, v) for k, v in tempdict.items() if k not in ['__builtins__'] + (k, v) for k, v in tempdict.items() if k not in ["__builtins__"] ) # Make the paths relative to the settings file - paths = ['source', 'destination', 'watermark'] + paths = ["source", "destination", "watermark"] - if os.path.isdir(join(settings_path, settings['theme'])) and os.path.isdir( - join(settings_path, settings['theme'], 'templates') + if os.path.isdir(join(settings_path, settings["theme"])) and os.path.isdir( + join(settings_path, settings["theme"], "templates") ): - paths.append('theme') + paths.append("theme") for p in paths: path = settings[p] @@ -162,7 +162,7 @@ def read_settings(filename=None): settings[p] = abspath(normpath(join(settings_path, path))) logger.debug("Rewrite %s : %s -> %s", p, path, settings[p]) - for key in ('img_size', 'thumb_size', 'video_size'): + for key in ("img_size", "thumb_size", "video_size"): if settings[key]: w, h = settings[key] if h > w: @@ -172,10 +172,10 @@ def read_settings(filename=None): key, ) - if not settings['img_processor']: - logger.info('No Processor, images will not be resized') + if not settings["img_processor"]: + logger.info("No Processor, images will not be resized") - logger.debug('Settings:\n%s', pformat(settings, width=120)) + logger.debug("Settings:\n%s", pformat(settings, width=120)) return settings diff --git a/src/sigal/signals.py b/src/sigal/signals.py index bf923de..a0af225 100644 --- a/src/sigal/signals.py +++ b/src/sigal/signals.py @@ -1,13 +1,13 @@ from blinker import signal -img_resized = signal('img_resized') +img_resized = signal("img_resized") -album_initialized = signal('album_initialized') -gallery_initialized = signal('gallery_initialized') -gallery_build = signal('gallery_build') -media_initialized = signal('media_initialized') -albums_sorted = signal('albums_sorted') -medias_sorted = signal('medias_sorted') -before_render = signal('before_render') -album_file = signal('album_file') -process_file = signal('process_file') +album_initialized = signal("album_initialized") +gallery_initialized = signal("gallery_initialized") +gallery_build = signal("gallery_build") +media_initialized = signal("media_initialized") +albums_sorted = signal("albums_sorted") +medias_sorted = signal("medias_sorted") +before_render = signal("before_render") +album_file = signal("album_file") +process_file = signal("process_file") diff --git a/src/sigal/templates/sigal.conf.py b/src/sigal/templates/sigal.conf.py index b6aa5f4..c1a5612 100644 --- a/src/sigal/templates/sigal.conf.py +++ b/src/sigal/templates/sigal.conf.py @@ -13,7 +13,7 @@ # Source directory. Can be set here or as the first argument of the `sigal # build` command -source = 'pictures' +source = "pictures" # Destination directory. Can be set here or as the second argument of the # `sigal build` command (default: '_build') @@ -22,7 +22,7 @@ source = 'pictures' # Theme : # - colorbox (default), galleria, photoswipe, or the path to a custom theme # directory -theme = 'galleria' +theme = "galleria" # Theme for galleria (https://galleriajs.github.io/themes/) # galleria_theme = 'classic' diff --git a/src/sigal/utils.py b/src/sigal/utils.py index a3c4474..851b929 100644 --- a/src/sigal/utils.py +++ b/src/sigal/utils.py @@ -31,7 +31,7 @@ from sigal.settings import Status logger = logging.getLogger(__name__) MD = None -VIDEO_MIMES = {'.mp4': 'video/mp4', '.webm': 'video/webm', '.ogv': 'video/ogg'} +VIDEO_MIMES = {".mp4": "video/mp4", ".webm": "video/webm", ".ogv": "video/ogg"} class Devnull: @@ -77,8 +77,8 @@ def get_mod_date(path): def url_from_path(path): """Transform path to url, converting backslashes to slashes if needed.""" - if os.sep != '/': - path = '/'.join(path.split(os.sep)) + if os.sep != "/": + path = "/".join(path.split(os.sep)) return quote(path) @@ -90,17 +90,17 @@ def read_markdown(filename): # Use utf-8-sig codec to remove BOM if it is present. This is only possible # this way prior to feeding the text to the markdown parser (which would # also default to pure utf-8) - with open(filename, encoding='utf-8-sig') as f: + with open(filename, encoding="utf-8-sig") as f: text = f.read() if MD is None: MD = Markdown( extensions=[ - 'markdown.extensions.extra', - 'markdown.extensions.meta', - 'markdown.extensions.tables', + "markdown.extensions.extra", + "markdown.extensions.meta", + "markdown.extensions.tables", ], - output_format='html5', + output_format="html5", ) else: MD.reset() @@ -109,16 +109,16 @@ def read_markdown(filename): MD.Meta = {} # Mark HTML with Markup to prevent jinja2 autoescaping - output = {'description': Markup(MD.convert(text))} + output = {"description": Markup(MD.convert(text))} try: meta = MD.Meta.copy() except AttributeError: pass else: - output['meta'] = meta + output["meta"] = meta try: - output['title'] = MD.Meta['title'][0] + output["title"] = MD.Meta["title"][0] except KeyError: pass @@ -144,7 +144,7 @@ class raise_if_debug: def __exit__(self, exc_type, exc_value, traceback): if exc_type: - logger.info('Failed to process: %r', exc_value) + logger.info("Failed to process: %r", exc_value) if logger.getEffectiveLevel() == logging.DEBUG: # propagate the exception return False diff --git a/src/sigal/video.py b/src/sigal/video.py index 2899c00..3699d43 100644 --- a/src/sigal/video.py +++ b/src/sigal/video.py @@ -256,9 +256,9 @@ def process_video(media): fit=settings["thumb_fit"], options=settings["jpg_options"], converter=settings["video_converter"], - black_retries=settings['thumb_video_black_retries'], - black_offset=settings['thumb_video_black_retry_offset'], - black_max_colors=settings['thumb_video_black_max_colors'], + black_retries=settings["thumb_video_black_retries"], + black_offset=settings["thumb_video_black_retry_offset"], + black_max_colors=settings["thumb_video_black_max_colors"], ) return status.value diff --git a/src/sigal/writer.py b/src/sigal/writer.py index 97a0a38..c19ef4f 100644 --- a/src/sigal/writer.py +++ b/src/sigal/writer.py @@ -76,10 +76,10 @@ class AbstractWriter: # handle optional filters.py filters_py = os.path.join(self.theme, "filters.py") if os.path.exists(filters_py): - self.logger.info('Loading filters file: %s', filters_py) - module_spec = importlib.util.spec_from_file_location('filters', filters_py) + self.logger.info("Loading filters file: %s", filters_py) + module_spec = importlib.util.spec_from_file_location("filters", filters_py) mod = importlib.util.module_from_spec(module_spec) - sys.modules['filters'] = mod + sys.modules["filters"] = mod module_spec.loader.exec_module(mod) for name in dir(mod): if isinstance(getattr(mod, name), types.FunctionType): @@ -101,8 +101,8 @@ class AbstractWriter: shutil.rmtree(self.theme_path) for static_path in ( - os.path.join(THEMES_PATH, 'default', 'static'), - os.path.join(self.theme, 'static'), + os.path.join(THEMES_PATH, "default", "static"), + os.path.join(self.theme, "static"), ): shutil.copytree(static_path, self.theme_path, dirs_exist_ok=True) diff --git a/tests/conftest.py b/tests/conftest.py index b0060b5..b7426c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,10 +9,10 @@ from sigal import signals from sigal.settings import read_settings CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) -BUILD_DIR = os.path.join(CURRENT_DIR, 'sample', '_build') +BUILD_DIR = os.path.join(CURRENT_DIR, "sample", "_build") -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def remove_build(): """Ensure that build directory does not exists before each test.""" if os.path.exists(BUILD_DIR): @@ -22,7 +22,7 @@ def remove_build(): @pytest.fixture def settings(): """Read the sample config file.""" - return read_settings(os.path.join(CURRENT_DIR, 'sample', 'sigal.conf.py')) + return read_settings(os.path.join(CURRENT_DIR, "sample", "sigal.conf.py")) @pytest.fixture() @@ -30,7 +30,7 @@ def disconnect_signals(): # Reset plugins yield None for name in dir(signals): - if not name.startswith('_'): + if not name.startswith("_"): try: sig = getattr(signals, name) if isinstance(sig, blinker.Signal): diff --git a/tests/test_cli.py b/tests/test_cli.py index 7191572..601129f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -6,15 +6,15 @@ from click.testing import CliRunner from sigal import build, init, serve, set_meta -TESTGAL = join(os.path.abspath(os.path.dirname(__file__)), 'sample') +TESTGAL = join(os.path.abspath(os.path.dirname(__file__)), "sample") def test_init(tmpdir): - config_file = str(tmpdir.join('sigal.conf.py')) + config_file = str(tmpdir.join("sigal.conf.py")) runner = CliRunner() result = runner.invoke(init, [config_file]) assert result.exit_code == 0 - assert result.output.startswith('Sample config file created:') + assert result.output.startswith("Sample config file created:") assert os.path.isfile(config_file) result = runner.invoke(init, [config_file]) @@ -26,29 +26,29 @@ def test_init(tmpdir): def test_build(tmpdir, disconnect_signals): runner = CliRunner() - config_file = str(tmpdir.join('sigal.conf.py')) - tmpdir.mkdir('pictures') + config_file = str(tmpdir.join("sigal.conf.py")) + tmpdir.mkdir("pictures") tmpdir = str(tmpdir) cwd = os.getcwd() try: result = runner.invoke(init, [config_file]) assert result.exit_code == 0 - os.symlink(join(TESTGAL, 'watermark.png'), join(tmpdir, 'watermark.png')) + os.symlink(join(TESTGAL, "watermark.png"), join(tmpdir, "watermark.png")) os.symlink( - join(TESTGAL, 'pictures', 'dir2', 'KeckObservatory20071020.jpg'), - join(tmpdir, 'pictures', 'KeckObservatory20071020.jpg'), + join(TESTGAL, "pictures", "dir2", "KeckObservatory20071020.jpg"), + join(tmpdir, "pictures", "KeckObservatory20071020.jpg"), ) - result = runner.invoke(build, ['-n', 1, '--debug']) + result = runner.invoke(build, ["-n", 1, "--debug"]) assert result.exit_code == 1 os.chdir(tmpdir) - result = runner.invoke(build, ['foo', '-n', 1, '--debug']) + result = runner.invoke(build, ["foo", "-n", 1, "--debug"]) assert result.exit_code == 1 - result = runner.invoke(build, ['pictures', 'pictures/out', '-n', 1, '--debug']) + result = runner.invoke(build, ["pictures", "pictures/out", "-n", 1, "--debug"]) assert result.exit_code == 1 with open(config_file) as f: @@ -70,29 +70,29 @@ rss_feed = {'feed_url': 'http://example.org/feed.rss', 'nb_items': 10} atom_feed = {'feed_url': 'http://example.org/feed.atom', 'nb_items': 10} """ - with open(config_file, 'w') as f: + with open(config_file, "w") as f: f.write(text) result = runner.invoke( - build, ['pictures', 'build', '--title', 'Testing build', '-n', 1, '--debug'] + build, ["pictures", "build", "--title", "Testing build", "-n", 1, "--debug"] ) assert result.exit_code == 0 assert os.path.isfile( - join(tmpdir, 'build', 'thumbnails', 'KeckObservatory20071020.jpg') + join(tmpdir, "build", "thumbnails", "KeckObservatory20071020.jpg") ) - assert os.path.isfile(join(tmpdir, 'build', 'feed.atom')) - assert os.path.isfile(join(tmpdir, 'build', 'feed.rss')) - assert os.path.isfile(join(tmpdir, 'build', 'watermark.png')) + assert os.path.isfile(join(tmpdir, "build", "feed.atom")) + assert os.path.isfile(join(tmpdir, "build", "feed.rss")) + assert os.path.isfile(join(tmpdir, "build", "watermark.png")) finally: os.chdir(cwd) # Reset logger - logger = logging.getLogger('sigal') + logger = logging.getLogger("sigal") logger.handlers[:] = [] logger.setLevel(logging.INFO) def test_serve(tmpdir): - config_file = str(tmpdir.join('sigal.conf.py')) + config_file = str(tmpdir.join("sigal.conf.py")) runner = CliRunner() result = runner.invoke(init, [config_file]) assert result.exit_code == 0 @@ -100,7 +100,7 @@ def test_serve(tmpdir): result = runner.invoke(serve) assert result.exit_code == 2 - result = runner.invoke(serve, ['-c', config_file]) + result = runner.invoke(serve, ["-c", config_file]) assert result.exit_code == 1 diff --git a/tests/test_compress_assets_plugin.py b/tests/test_compress_assets_plugin.py index dcaa306..889e78b 100644 --- a/tests/test_compress_assets_plugin.py +++ b/tests/test_compress_assets_plugin.py @@ -12,18 +12,18 @@ CURRENT_DIR = os.path.dirname(__file__) def make_gallery(settings, tmpdir, method): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) # Really speed up testing - settings['use_orig'] = True + settings["use_orig"] = True if "sigal.plugins.compress_assets" not in settings["plugins"]: - settings['plugins'] += ["sigal.plugins.compress_assets"] + settings["plugins"] += ["sigal.plugins.compress_assets"] # Set method - settings.setdefault('compress_assets_options', {})['method'] = method + settings.setdefault("compress_assets_options", {})["method"] = method compress_options = compress_assets.DEFAULT_SETTINGS.copy() # The key was created by the previous setdefault if needed - compress_options.update(settings['compress_assets_options']) + compress_options.update(settings["compress_assets_options"]) init_plugins(settings) gal = Gallery(settings) @@ -36,7 +36,7 @@ def walk_destination(destination, suffixes, compress_suffix): for path, dirs, files in os.walk(destination): for file in files: original_filename = os.path.join(path, file) - compressed_filename = '{}.{}'.format( + compressed_filename = "{}.{}".format( os.path.join(path, file), compress_suffix ) path_exists = os.path.exists(compressed_filename) @@ -53,7 +53,7 @@ def walk_destination(destination, suffixes, compress_suffix): @pytest.mark.parametrize( "method,compress_suffix,test_import", - [('gzip', 'gz', None), ('zopfli', 'gz', 'zopfli.gzip'), ('brotli', 'br', 'brotli')], + [("gzip", "gz", None), ("zopfli", "gz", "zopfli.gzip"), ("brotli", "br", "brotli")], ) def test_compress( disconnect_signals, settings, tmpdir, method, compress_suffix, test_import @@ -65,16 +65,16 @@ def test_compress( for _ in range(2): compress_options = make_gallery(settings, tmpdir, method) walk_destination( - settings['destination'], compress_options['suffixes'], compress_suffix + settings["destination"], compress_options["suffixes"], compress_suffix ) @pytest.mark.parametrize( "method,compress_suffix,mask", [ - ('zopfli', 'gz', 'zopfli.gzip'), - ('brotli', 'br', 'brotli'), - ('__does_not_exist__', 'br', None), + ("zopfli", "gz", "zopfli.gzip"), + ("brotli", "br", "brotli"), + ("__does_not_exist__", "br", None), ], ) def test_failed_compress( @@ -84,5 +84,5 @@ def test_failed_compress( with mock.patch.dict(sys.modules, {mask: None}): make_gallery(settings, tmpdir, method) walk_destination( - settings['destination'], [], compress_suffix # No file should be compressed + settings["destination"], [], compress_suffix # No file should be compressed ) diff --git a/tests/test_encrypt.py b/tests/test_encrypt.py index 3ea83c7..a305f85 100644 --- a/tests/test_encrypt.py +++ b/tests/test_encrypt.py @@ -22,9 +22,9 @@ def get_key_tag(settings): def test_encrypt(settings, tmpdir, disconnect_signals, caplog): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) if "sigal.plugins.encrypt" not in settings["plugins"]: - settings['plugins'] += ["sigal.plugins.encrypt"] + settings["plugins"] += ["sigal.plugins.encrypt"] init_plugins(settings) gal = Gallery(settings) @@ -32,17 +32,17 @@ def test_encrypt(settings, tmpdir, disconnect_signals, caplog): with pytest.raises(ValueError, match="no encrypt_options in settings"): gal.build() - settings['encrypt_options'] = {} + settings["encrypt_options"] = {} gal = Gallery(settings) with pytest.raises(ValueError, match="no password provided"): gal.build() - settings['encrypt_options'] = { - 'password': 'password', - 'ask_password': True, - 'encrypt_symlinked_originals': False, + settings["encrypt_options"] = { + "password": "password", + "ask_password": True, + "encrypt_symlinked_originals": False, } gal = Gallery(settings) @@ -80,20 +80,20 @@ def test_encrypt(settings, tmpdir, disconnect_signals, caplog): endec.decrypt(key, infile, outfile, tag) # check static files have been copied - static = os.path.join(settings["destination"], 'static') + static = os.path.join(settings["destination"], "static") assert os.path.isfile(os.path.join(static, "decrypt.js")) assert os.path.isfile(os.path.join(static, "keycheck.txt")) assert os.path.isfile(os.path.join(settings["destination"], "sw.js")) # check keycheck file with open( - os.path.join(settings["destination"], 'static', "keycheck.txt"), "rb" + os.path.join(settings["destination"], "static", "keycheck.txt"), "rb" ) as infile: with BytesIO() as outfile: endec.decrypt(key, infile, outfile, tag) caplog.clear() - caplog.set_level('DEBUG') + caplog.set_level("DEBUG") gal = Gallery(settings) gal.build() # Doesn't work on Actions ... diff --git a/tests/test_extended_caching.py b/tests/test_extended_caching.py index 537c0cf..4617600 100644 --- a/tests/test_extended_caching.py +++ b/tests/test_extended_caching.py @@ -8,11 +8,11 @@ CURRENT_DIR = os.path.dirname(__file__) def test_save_cache(settings, tmpdir): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) gal = Gallery(settings, ncpu=1) extended_caching.save_cache(gal) - cachePath = os.path.join(settings['destination'], ".metadata_cache") + cachePath = os.path.join(settings["destination"], ".metadata_cache") assert os.path.isfile(cachePath) @@ -23,17 +23,17 @@ def test_save_cache(settings, tmpdir): album = gal.albums["exifTest"] cache_img = cache["exifTest/21.jpg"] assert cache_img["exif"] == album.medias[0].exif - assert 'markdown_metadata' not in cache_img + assert "markdown_metadata" not in cache_img assert cache_img["file_metadata"] == album.medias[0].file_metadata cache_img = cache["exifTest/22.jpg"] assert cache_img["exif"] == album.medias[1].exif - assert 'markdown_metadata' not in cache_img + assert "markdown_metadata" not in cache_img assert cache_img["file_metadata"] == album.medias[1].file_metadata cache_img = cache["exifTest/noexif.png"] assert cache_img["exif"] == album.medias[2].exif - assert 'markdown_metadata' not in cache_img + assert "markdown_metadata" not in cache_img assert cache_img["file_metadata"] == album.medias[2].file_metadata # test iptc and md @@ -42,7 +42,7 @@ def test_save_cache(settings, tmpdir): cache_img = cache["iptcTest/1.jpg"] assert cache_img["file_metadata"] == album.medias[0].file_metadata - assert 'markdown_metadata' not in cache_img + assert "markdown_metadata" not in cache_img cache_img = cache["iptcTest/2.jpg"] assert cache_img["markdown_metadata"] == album.medias[1].markdown_metadata @@ -56,7 +56,7 @@ def test_save_cache(settings, tmpdir): def test_restore_cache(settings, tmpdir): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) gal1 = Gallery(settings, ncpu=1) gal2 = Gallery(settings, ncpu=1) extended_caching.save_cache(gal1) @@ -64,16 +64,16 @@ def test_restore_cache(settings, tmpdir): assert gal1.metadataCache == gal2.metadataCache # test bad cache - cachePath = os.path.join(settings['destination'], ".metadata_cache") - with open(cachePath, 'w') as f: - f.write('bad pickle file') + cachePath = os.path.join(settings["destination"], ".metadata_cache") + with open(cachePath, "w") as f: + f.write("bad pickle file") extended_caching._restore_cache(gal2) assert gal2.metadataCache == {} def test_load_exif(settings, tmpdir): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) gal1 = Gallery(settings, ncpu=1) gal1.albums["exifTest"].medias[2].exif = "blafoo" # set mod_date in future, to force these values @@ -99,7 +99,7 @@ def test_load_exif(settings, tmpdir): def test_load_metadata_missing(settings, tmpdir): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) gal = Gallery(settings, ncpu=1) extended_caching.save_cache(gal) assert gal.metadataCache diff --git a/tests/test_gallery.py b/tests/test_gallery.py index e823a24..72ff9a5 100644 --- a/tests/test_gallery.py +++ b/tests/test_gallery.py @@ -13,86 +13,86 @@ from sigal.video import SubprocessException CURRENT_DIR = os.path.dirname(__file__) REF = { - 'dir1': { - 'title': 'An example gallery', - 'name': 'dir1', - 'thumbnail': 'dir1/test1/thumbnails/11.tn.jpg', - 'subdirs': ['test1', 'test2', 'test3'], - 'medias': [], + "dir1": { + "title": "An example gallery", + "name": "dir1", + "thumbnail": "dir1/test1/thumbnails/11.tn.jpg", + "subdirs": ["test1", "test2", "test3"], + "medias": [], }, - 'dir1/test1': { - 'title': 'An example sub-category', - 'name': 'test1', - 'thumbnail': 'test1/thumbnails/11.tn.jpg', - 'subdirs': [], - 'medias': [ - '11.jpg', - 'CMB_Timeline300_no_WMAP.jpg', - 'flickr_jerquiaga_2394751088_cc-by-nc.jpg', - 'example.gif', + "dir1/test1": { + "title": "An example sub-category", + "name": "test1", + "thumbnail": "test1/thumbnails/11.tn.jpg", + "subdirs": [], + "medias": [ + "11.jpg", + "CMB_Timeline300_no_WMAP.jpg", + "flickr_jerquiaga_2394751088_cc-by-nc.jpg", + "example.gif", ], }, - 'dir1/test2': { - 'title': 'test2', - 'name': 'test2', - 'thumbnail': 'test2/thumbnails/21.tn.tiff', - 'subdirs': [], - 'medias': ['21.tiff', '22.jpg', 'CMB_Timeline300_no_WMAP.jpg'], + "dir1/test2": { + "title": "test2", + "name": "test2", + "thumbnail": "test2/thumbnails/21.tn.tiff", + "subdirs": [], + "medias": ["21.tiff", "22.jpg", "CMB_Timeline300_no_WMAP.jpg"], }, - 'dir1/test3': { - 'title': '01 First title alphabetically', - 'name': 'test3', - 'thumbnail': 'test3/thumbnails/3.tn.jpg', - 'subdirs': [], - 'medias': ['3.jpg'], + "dir1/test3": { + "title": "01 First title alphabetically", + "name": "test3", + "thumbnail": "test3/thumbnails/3.tn.jpg", + "subdirs": [], + "medias": ["3.jpg"], }, - 'dir2': { - 'title': 'Another example gallery with a very long name', - 'name': 'dir2', - 'thumbnail': 'dir2/thumbnails/m57_the_ring_nebula-587px.tn.jpg', - 'subdirs': [], - 'medias': [ - 'KeckObservatory20071020.jpg', - 'Hubble Interacting Galaxy NGC 5257.jpg', - 'Hubble ultra deep field.jpg', - 'm57_the_ring_nebula-587px.jpg', + "dir2": { + "title": "Another example gallery with a very long name", + "name": "dir2", + "thumbnail": "dir2/thumbnails/m57_the_ring_nebula-587px.tn.jpg", + "subdirs": [], + "medias": [ + "KeckObservatory20071020.jpg", + "Hubble Interacting Galaxy NGC 5257.jpg", + "Hubble ultra deep field.jpg", + "m57_the_ring_nebula-587px.jpg", ], }, - 'accentué': { - 'title': 'accentué', - 'name': 'accentué', - 'thumbnail': 'accentu%C3%A9/thumbnails/h%C3%A9lico%C3%AFde.tn.jpg', - 'subdirs': [], - 'medias': ['hélicoïde.jpg', '11.jpg'], + "accentué": { + "title": "accentué", + "name": "accentué", + "thumbnail": "accentu%C3%A9/thumbnails/h%C3%A9lico%C3%AFde.tn.jpg", + "subdirs": [], + "medias": ["hélicoïde.jpg", "11.jpg"], }, - 'video': { - 'title': 'video', - 'name': 'video', - 'thumbnail': 'video/thumbnails/example%20video.tn.jpg', - 'subdirs': [], - 'medias': ['example video.ogv'], + "video": { + "title": "video", + "name": "video", + "thumbnail": "video/thumbnails/example%20video.tn.jpg", + "subdirs": [], + "medias": ["example video.ogv"], }, - 'webp': { - 'title': 'webp', - 'name': 'webp', - 'thumbnail': 'webp/thumbnails/_MG_7805_lossy80.tn.webp', - 'subdirs': [], - 'medias': ['_MG_7805_lossy80.webp', '_MG_7808_lossy80.webp'], + "webp": { + "title": "webp", + "name": "webp", + "thumbnail": "webp/thumbnails/_MG_7805_lossy80.tn.webp", + "subdirs": [], + "medias": ["_MG_7805_lossy80.webp", "_MG_7808_lossy80.webp"], }, } def test_media(settings): - m = Media('11.jpg', 'dir1/test1', settings) - path = join('dir1', 'test1') - file_path = join(path, '11.jpg') - thumb = join('thumbnails', '11.tn.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) + m = Media("11.jpg", "dir1/test1", settings) + path = join("dir1", "test1") + file_path = join(path, "11.jpg") + thumb = join("thumbnails", "11.tn.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 - assert m.thumb_path == join(settings['destination'], path, 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

" @@ -101,223 +101,223 @@ def test_media(settings): def test_media_orig(settings, tmpdir): - settings['keep_orig'] = False - m = Media('11.jpg', 'dir1/test1', settings) + settings["keep_orig"] = False + m = Media("11.jpg", "dir1/test1", settings) assert m.big is None - settings['keep_orig'] = True - settings['destination'] = str(tmpdir) + settings["keep_orig"] = True + settings["destination"] = str(tmpdir) - m = Image('11.jpg', 'dir1/test1', settings) - assert m.big == 'original/11.jpg' + m = Image("11.jpg", "dir1/test1", settings) + assert m.big == "original/11.jpg" - m = Video('example video.ogv', 'video', settings) - 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)) + m = Video("example video.ogv", "video", settings) + 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)) - settings['use_orig'] = True + settings["use_orig"] = True - m = Image('21.jpg', 'dir1/test2', settings) - assert m.big == '21.jpg' + m = Image("21.jpg", "dir1/test2", settings) + assert m.big == "21.jpg" def test_media_iptc_override(settings): - img_with_md = Image('2.jpg', 'iptcTest', settings) + img_with_md = Image("2.jpg", "iptcTest", settings) assert img_with_md.title == "Markdown title beats iptc" # 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) + img_no_md = Image("1.jpg", "iptcTest", settings) assert ( img_no_md.title - == 'Haemostratulus clouds over Canberra - 2005-12-28 at 03-25-07' + == "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.' + "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') + 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.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') + file_path = join(path, "11.tiff") assert repr(m) == f"('{file_path}')" assert str(m) == file_path def test_image(settings, tmpdir): - settings['destination'] = str(tmpdir) - settings['datetime_format'] = '%d/%m/%Y' - m = Image('11.jpg', 'dir1/test1', settings) + settings["destination"] = str(tmpdir) + settings["datetime_format"] = "%d/%m/%Y" + m = Image("11.jpg", "dir1/test1", settings) assert m.date == datetime.datetime(2006, 1, 22, 10, 32, 42) - assert m.exif['datetime'] == '22/01/2006' + assert m.exif["datetime"] == "22/01/2006" - os.makedirs(join(settings['destination'], 'dir1', 'test1', 'thumbnails')) - assert m.thumbnail == join('thumbnails', '11.tn.jpg') + os.makedirs(join(settings["destination"], "dir1", "test1", "thumbnails")) + assert m.thumbnail == join("thumbnails", "11.tn.jpg") assert os.path.isfile(m.thumb_path) def test_video(settings, tmpdir): - settings['destination'] = str(tmpdir) - m = Video('example video.ogv', 'video', settings) + settings["destination"] = str(tmpdir) + m = Video("example video.ogv", "video", settings) - src_path = join('video', 'example video.ogv') + src_path = join("video", "example video.ogv") assert str(m) == src_path - file_path = join('video', 'example video.webm') - assert m.dst_path == join(settings['destination'], file_path) + file_path = join("video", "example video.webm") + assert m.dst_path == join(settings["destination"], file_path) - os.makedirs(join(settings['destination'], 'video', 'thumbnails')) - assert m.thumbnail == join('thumbnails', 'example%20video.tn.jpg') + os.makedirs(join(settings["destination"], "video", "thumbnails")) + assert m.thumbnail == join("thumbnails", "example%20video.tn.jpg") assert os.path.isfile(m.thumb_path) @pytest.mark.parametrize("path,album", REF.items()) def test_album(path, album, settings, tmpdir): gal = Gallery(settings, ncpu=1) - a = Album(path, settings, album['subdirs'], album['medias'], gal) + a = Album(path, settings, album["subdirs"], album["medias"], gal) - assert a.title == album['title'] - assert a.name == album['name'] - assert a.subdirs == album['subdirs'] - assert a.thumbnail == album['thumbnail'] - if path == 'video': + assert a.title == album["title"] + assert a.name == album["name"] + assert a.subdirs == album["subdirs"] + assert a.thumbnail == album["thumbnail"] + if path == "video": assert list(a.images) == [] assert [m.dst_filename for m in a.medias] == [ - album['medias'][0].replace('.ogv', '.webm') + album["medias"][0].replace(".ogv", ".webm") ] else: assert list(a.videos) == [] - assert [m.dst_filename for m in a.medias] == album['medias'] - assert len(a) == len(album['medias']) + assert [m.dst_filename for m in a.medias] == album["medias"] + assert len(a) == len(album["medias"]) def test_albums_sort(settings): gal = Gallery(settings, ncpu=1) - album = REF['dir1'] - subdirs = list(album['subdirs']) + album = REF["dir1"] + subdirs = list(album["subdirs"]) - settings['albums_sort_reverse'] = False - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('') + settings["albums_sort_reverse"] = False + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("") assert [alb.name for alb in a.albums] == subdirs - settings['albums_sort_reverse'] = True - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('') + settings["albums_sort_reverse"] = True + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("") assert [alb.name for alb in a.albums] == list(reversed(subdirs)) titles = [im.title for im in a.albums] titles.sort() - settings['albums_sort_reverse'] = False - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('title') + settings["albums_sort_reverse"] = False + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("title") assert [im.title for im in a.albums] == titles - settings['albums_sort_reverse'] = True - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('title') + settings["albums_sort_reverse"] = True + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("title") assert [im.title for im in a.albums] == list(reversed(titles)) - orders = ['01', '02', '03'] + orders = ["01", "02", "03"] orders.sort() - settings['albums_sort_reverse'] = False - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('meta.order') - assert [d.meta['order'][0] for d in a.albums] == orders + settings["albums_sort_reverse"] = False + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("meta.order") + assert [d.meta["order"][0] for d in a.albums] == orders - settings['albums_sort_reverse'] = True - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs('meta.order') - assert [d.meta['order'][0] for d in a.albums] == list(reversed(orders)) + settings["albums_sort_reverse"] = True + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs("meta.order") + assert [d.meta["order"][0] for d in a.albums] == list(reversed(orders)) - settings['albums_sort_reverse'] = False - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs(['meta.partialorder', 'meta.order']) - assert [d.name for d in a.albums] == list(['test1', 'test2', 'test3']) + settings["albums_sort_reverse"] = False + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs(["meta.partialorder", "meta.order"]) + assert [d.name for d in a.albums] == list(["test1", "test2", "test3"]) - settings['albums_sort_reverse'] = False - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs(['meta.partialorderb', 'name']) - assert [d.name for d in a.albums] == list(['test2', 'test3', 'test1']) + settings["albums_sort_reverse"] = False + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs(["meta.partialorderb", "name"]) + assert [d.name for d in a.albums] == list(["test2", "test3", "test1"]) - settings['albums_sort_reverse'] = True - a = Album('dir1', settings, album['subdirs'], album['medias'], gal) - a.sort_subdirs(['meta.partialorderb', 'name']) - assert [d.name for d in a.albums] == list(['test1', 'test3', 'test2']) + settings["albums_sort_reverse"] = True + a = Album("dir1", settings, album["subdirs"], album["medias"], gal) + a.sort_subdirs(["meta.partialorderb", "name"]) + assert [d.name for d in a.albums] == list(["test1", "test3", "test2"]) 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) - a.sort_medias(settings['medias_sort_attr']) - 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 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']) + album = REF["dir1/test2"] + + settings["medias_sort_reverse"] = True + a = Album("dir1/test2", settings, album["subdirs"], album["medias"], gal) + a.sort_medias(settings["medias_sort_attr"]) + 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 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.dst_filename for im in a.images] == [ - '21.tiff', - '22.jpg', - 'CMB_Timeline300_no_WMAP.jpg', + "21.tiff", + "22.jpg", + "CMB_Timeline300_no_WMAP.jpg", ] def test_gallery(settings, tmp_path, caplog): "Test the Gallery class." - caplog.set_level('ERROR') - settings['destination'] = str(tmp_path) - settings['user_css'] = str(tmp_path / 'my.css') - settings['webm_options'] = ['-missing-option', 'foobar'] + caplog.set_level("ERROR") + settings["destination"] = str(tmp_path) + settings["user_css"] = str(tmp_path / "my.css") + settings["webm_options"] = ["-missing-option", "foobar"] gal = Gallery(settings, ncpu=1) gal.build() - assert re.match(r'CSS file .* could not be found', caplog.records[3].message) + assert re.match(r"CSS file .* could not be found", caplog.records[3].message) - with open(tmp_path / 'my.css', mode='w') as f: - f.write('color: red') + with open(tmp_path / "my.css", mode="w") as f: + f.write("color: red") gal.build() - mycss = os.path.join(settings['destination'], 'static', 'my.css') + mycss = os.path.join(settings["destination"], "static", "my.css") assert os.path.isfile(mycss) - out_html = os.path.join(settings['destination'], 'index.html') + out_html = os.path.join(settings["destination"], "index.html") assert os.path.isfile(out_html) with open(out_html) as f: html = f.read() - assert 'Sigal test gallery - Sigal test gallery ☺' in html + assert "Sigal test gallery - Sigal test gallery ☺" in html assert '' in html - logger = logging.getLogger('sigal') + logger = logging.getLogger("sigal") logger.setLevel(logging.DEBUG) try: gal = Gallery(settings, ncpu=1) @@ -328,33 +328,33 @@ def test_gallery(settings, tmp_path, caplog): def test_custom_theme(settings, tmp_path, caplog): - theme_path = tmp_path / 'mytheme' - tpl_path = theme_path / 'templates' + theme_path = tmp_path / "mytheme" + tpl_path = theme_path / "templates" - settings['destination'] = str(tmp_path / 'build') - settings['source'] = os.path.join(settings['source'], 'encryptTest') - settings['theme'] = str(theme_path) - settings['title'] = 'My gallery' + settings["destination"] = str(tmp_path / "build") + settings["source"] = os.path.join(settings["source"], "encryptTest") + settings["theme"] = str(theme_path) + settings["title"] = "My gallery" gal = Gallery(settings, ncpu=1) - with pytest.raises(Exception, match='Impossible to find the theme'): + with pytest.raises(Exception, match="Impossible to find the theme"): gal.build() tpl_path.mkdir(parents=True) - (theme_path / 'static').mkdir(parents=True) + (theme_path / "static").mkdir(parents=True) with pytest.raises(SystemExit): gal.build() assert caplog.records[0].message.startswith( - 'The template album.html was not found in template folder' + "The template album.html was not found in template folder" ) - with open(tpl_path / 'album.html', mode='w') as f: + with open(tpl_path / "album.html", mode="w") as f: f.write(""" {{ settings.title|myfilter }} """) - with open(tpl_path / 'album_list.html', mode='w') as f: + with open(tpl_path / "album_list.html", mode="w") as f: f.write(""" {{ settings.title|myfilter }} """) - with open(theme_path / 'filters.py', mode='w') as f: + with open(theme_path / "filters.py", mode="w") as f: f.write( """ def myfilter(value): @@ -365,13 +365,13 @@ def myfilter(value): gal = Gallery(settings, ncpu=1) gal.build() - out_html = os.path.join(settings['destination'], 'index.html') + out_html = os.path.join(settings["destination"], "index.html") assert os.path.isfile(out_html) with open(out_html) as f: html = f.read() - assert 'My gallery is very nice' in html + assert "My gallery is very nice" in html def test_gallery_max_img_pixels(settings, tmpdir, monkeypatch): @@ -379,23 +379,23 @@ def test_gallery_max_img_pixels(settings, tmpdir, monkeypatch): # monkeypatch is used here to reset the value to the PIL default. # This value does not matter, other than it is "large" # to show that settings['max_img_pixels'] works. - monkeypatch.setattr('PIL.Image.MAX_IMAGE_PIXELS', 100_000_000) + monkeypatch.setattr("PIL.Image.MAX_IMAGE_PIXELS", 100_000_000) - with open(str(tmpdir.join('my.css')), mode='w') as f: - f.write('color: red') + with open(str(tmpdir.join("my.css")), mode="w") as f: + f.write("color: red") - settings['destination'] = str(tmpdir) - settings['user_css'] = str(tmpdir.join('my.css')) - settings['max_img_pixels'] = 5000 + settings["destination"] = str(tmpdir) + settings["user_css"] = str(tmpdir.join("my.css")) + settings["max_img_pixels"] = 5000 - logger = logging.getLogger('sigal') + logger = logging.getLogger("sigal") logger.setLevel(logging.DEBUG) try: with pytest.raises(PILImage.DecompressionBombError): gal = Gallery(settings, ncpu=1) gal.build() - settings['max_img_pixels'] = 100_000_000 + settings["max_img_pixels"] = 100_000_000 gal = Gallery(settings, ncpu=1) gal.build() finally: @@ -404,19 +404,19 @@ def test_gallery_max_img_pixels(settings, tmpdir, monkeypatch): def test_empty_dirs(settings): gal = Gallery(settings, ncpu=1) - assert 'empty' not in gal.albums - assert 'dir1/empty' not in gal.albums + assert "empty" not in gal.albums + assert "dir1/empty" not in gal.albums def test_ignores(settings, tmpdir): tmp = str(tmpdir) - settings['destination'] = tmp - settings['ignore_directories'] = ['*test2', 'accentué'] - settings['ignore_files'] = ['dir2/Hubble*', '*.png', '*CMB_*'] + settings["destination"] = tmp + settings["ignore_directories"] = ["*test2", "accentué"] + settings["ignore_files"] = ["dir2/Hubble*", "*.png", "*CMB_*"] gal = Gallery(settings, ncpu=1) gal.build() - assert 'test2' not in os.listdir(join(tmp, 'dir1')) - assert 'accentué' not in os.listdir(tmp) - assert 'CMB_Timeline300_no_WMAP.jpg' not in os.listdir(join(tmp, 'dir1', 'test1')) - assert 'Hubble Interacting Galaxy NGC 5257.jpg' not in os.listdir(join(tmp, 'dir2')) + assert "test2" not in os.listdir(join(tmp, "dir1")) + assert "accentué" not in os.listdir(tmp) + assert "CMB_Timeline300_no_WMAP.jpg" not in os.listdir(join(tmp, "dir1", "test1")) + assert "Hubble Interacting Galaxy NGC 5257.jpg" not in os.listdir(join(tmp, "dir2")) diff --git a/tests/test_image.py b/tests/test_image.py index 093959c..4bc032c 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -19,31 +19,31 @@ from sigal.image import ( 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(SRCDIR, 'dir2', TEST_IMAGE) +SRCDIR = os.path.join(CURRENT_DIR, "sample", "pictures") +TEST_IMAGE = "KeckObservatory20071020.jpg" +SRCFILE = os.path.join(SRCDIR, "dir2", TEST_IMAGE) -TEST_GIF_IMAGE = 'example.gif' -SRC_GIF_FILE = os.path.join(SRCDIR, 'dir1', 'test1', TEST_GIF_IMAGE) +TEST_GIF_IMAGE = "example.gif" +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(Image('foo.txt', 'bar', create_settings())) + status = process_image(Image("foo.txt", "bar", create_settings())) assert status == Status.FAILURE settings = create_settings( - img_processor='ResizeToFill', + img_processor="ResizeToFill", make_thumbs=False, - source=os.path.join(SRCDIR, 'dir2'), + source=os.path.join(SRCDIR, "dir2"), destination=str(tmpdir), ) - image = Image(TEST_IMAGE, '.', settings) + image = Image(TEST_IMAGE, ".", settings) status = process_image(image) assert status == Status.SUCCESS im = PILImage.open(os.path.join(str(tmpdir), TEST_IMAGE)) - assert im.size == settings['img_size'] + assert im.size == settings["img_size"] def test_generate_image(tmpdir): @@ -52,9 +52,9 @@ def test_generate_image(tmpdir): dstfile = str(tmpdir.join(TEST_IMAGE)) for i, size in enumerate([(600, 600), (300, 200)]): settings = create_settings( - img_size=size, img_processor='ResizeToFill', copy_exif_data=True + img_size=size, img_processor="ResizeToFill", copy_exif_data=True ) - options = None if i == 0 else {'quality': 85} + options = None if i == 0 else {"quality": 85} generate_image(SRCFILE, dstfile, settings, options=options) im = PILImage.open(dstfile) assert im.size == size @@ -67,11 +67,11 @@ def test_generate_image_imgformat(tmpdir): for i, outfmt in enumerate(["JPEG", "PNG", "TIFF"]): settings = create_settings( img_size=(300, 300), - img_processor='ResizeToFill', + img_processor="ResizeToFill", copy_exif_data=True, img_format=outfmt, ) - options = {'quality': 85} + options = {"quality": 85} generate_image(SRCFILE, dstfile, settings, options=options) im = PILImage.open(dstfile) assert im.format == outfmt @@ -82,9 +82,9 @@ def test_resize_image_portrait(tmpdir): size = (300, 200) settings = create_settings(img_size=size) - portrait_image = 'm57_the_ring_nebula-587px.jpg' + portrait_image = "m57_the_ring_nebula-587px.jpg" portrait_src = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir2', portrait_image + CURRENT_DIR, "sample", "pictures", "dir2", portrait_image ) portrait_dst = str(tmpdir.join(portrait_image)) @@ -96,9 +96,9 @@ def test_resize_image_portrait(tmpdir): # Hence we test that the shorter side has the smallest length. assert im.size[0] == 200 - landscape_image = 'KeckObservatory20071020.jpg' + landscape_image = "KeckObservatory20071020.jpg" landscape_src = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir2', landscape_image + CURRENT_DIR, "sample", "pictures", "dir2", landscape_image ) landscape_dst = str(tmpdir.join(landscape_image)) @@ -137,9 +137,9 @@ def test_generate_image_passthrough_symlink(tmpdir): def test_generate_image_processor(tmpdir): "Test generate_image with a wrong processor name." - init_logging('sigal') + init_logging("sigal") dstfile = str(tmpdir.join(TEST_IMAGE)) - settings = create_settings(img_size=(200, 200), img_processor='WrongMethod') + settings = create_settings(img_size=(200, 200), img_processor="WrongMethod") with pytest.raises(SystemExit): generate_image(SRCFILE, dstfile, settings) @@ -168,123 +168,123 @@ def test_generate_thumbnail(tmpdir, image, path, wide_size, high_size): def test_get_exif_tags(): - test_image = '11.jpg' + test_image = "11.jpg" src_file = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir1', 'test1', test_image + CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) data = get_exif_data(src_file) - simple = get_exif_tags(data, datetime_format='%d/%m/%Y') - assert simple['fstop'] == 3.9 - assert simple['focal'] == 12.0 - assert simple['iso'] == 50 - assert simple['Make'] == 'NIKON' - assert simple['datetime'] == '22/01/2006' + simple = get_exif_tags(data, datetime_format="%d/%m/%Y") + assert simple["fstop"] == 3.9 + assert simple["focal"] == 12.0 + assert simple["iso"] == 50 + assert simple["Make"] == "NIKON" + assert simple["datetime"] == "22/01/2006" try: # Pillow 7.2+ - assert simple['exposure'] == '0.00100603' + assert simple["exposure"] == "0.00100603" except Exception: - assert simple['exposure'] == '100603/100000000' + assert simple["exposure"] == "100603/100000000" - data = {'FNumber': [1, 0], 'FocalLength': [1, 0], 'ExposureTime': 10} + data = {"FNumber": [1, 0], "FocalLength": [1, 0], "ExposureTime": 10} simple = get_exif_tags(data) - assert 'fstop' not in simple - assert 'focal' not in simple - assert simple['exposure'] == '10' + assert "fstop" not in simple + assert "focal" not in simple + assert simple["exposure"] == "10" data = { - 'ExposureTime': '--', - 'DateTimeOriginal': '---', - 'GPSInfo': { - 'GPSLatitude': ((34, 0), (1, 0), (4500, 100)), - 'GPSLatitudeRef': 'N', - 'GPSLongitude': ((116, 0), (8, 0), (3900, 100)), - 'GPSLongitudeRef': 'W', + "ExposureTime": "--", + "DateTimeOriginal": "---", + "GPSInfo": { + "GPSLatitude": ((34, 0), (1, 0), (4500, 100)), + "GPSLatitudeRef": "N", + "GPSLongitude": ((116, 0), (8, 0), (3900, 100)), + "GPSLongitudeRef": "W", }, } simple = get_exif_tags(data) - assert 'exposure' not in simple - assert 'datetime' not in simple - assert 'gps' not in simple + assert "exposure" not in simple + assert "datetime" not in simple + assert "gps" not in simple def test_get_iptc_data(caplog): - test_image = '1.jpg' - src_file = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'iptcTest', test_image) + test_image = "1.jpg" + src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "iptcTest", test_image) data = get_iptc_data(src_file) # Title assert ( data["title"] - == 'Haemostratulus clouds over Canberra - ' + '2005-12-28 at 03-25-07' + == "Haemostratulus clouds over Canberra - " + "2005-12-28 at 03-25-07" ) # Description assert ( data["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.' + + "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." ) # This file has no IPTC data - test_image = '21.jpg' - src_file = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'exifTest', test_image) + test_image = "21.jpg" + src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "exifTest", test_image) assert get_iptc_data(src_file) == {} # Headline - test_image = '3.jpg' - src_file = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'iptcTest', test_image) + test_image = "3.jpg" + src_file = os.path.join(CURRENT_DIR, "sample", "pictures", "iptcTest", test_image) data = get_iptc_data(src_file) - assert data["headline"] == 'Ring Nebula, M57' + assert data["headline"] == "Ring Nebula, M57" # Test catching the SyntaxError -- assert output - with patch('sigal.image.IptcImagePlugin.getiptcinfo', side_effect=SyntaxError): + with patch("sigal.image.IptcImagePlugin.getiptcinfo", side_effect=SyntaxError): get_iptc_data(src_file) - assert ['IPTC Error in'] == [log.message[:13] for log in caplog.records] + assert ["IPTC Error in"] == [log.message[:13] for log in caplog.records] def test_get_image_metadata_exceptions(): # image does not exist - test_image = 'bad_image.jpg' - src_file = os.path.join(CURRENT_DIR, 'sample', test_image) + test_image = "bad_image.jpg" + src_file = os.path.join(CURRENT_DIR, "sample", test_image) data = get_image_metadata(src_file) - assert data == {'exif': {}, 'iptc': {}, 'size': {}} + assert data == {"exif": {}, "iptc": {}, "size": {}} def test_iso_speed_ratings(): - data = {'ISOSpeedRatings': ()} + data = {"ISOSpeedRatings": ()} simple = get_exif_tags(data) - assert 'iso' not in simple + assert "iso" not in simple - data = {'ISOSpeedRatings': None} + data = {"ISOSpeedRatings": None} simple = get_exif_tags(data) - assert 'iso' not in simple + assert "iso" not in simple - data = {'ISOSpeedRatings': 125} + data = {"ISOSpeedRatings": 125} simple = get_exif_tags(data) - assert 'iso' in simple + assert "iso" in simple def test_null_exposure_time(): - data = {'ExposureTime': (0, 0)} + data = {"ExposureTime": (0, 0)} simple = get_exif_tags(data) - assert 'exposure' not in simple + assert "exposure" not in simple def test_exif_copy(tmpdir): "Test if EXIF data can transferred copied to the resized image." - test_image = '11.jpg' + test_image = "11.jpg" src_file = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir1', 'test1', test_image + CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) dst_file = str(tmpdir.join(test_image)) settings = create_settings(img_size=(300, 400), copy_exif_data=True) generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) - assert simple['iso'] == 50 + assert simple["iso"] == 50 - settings['copy_exif_data'] = False + settings["copy_exif_data"] = False generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) assert not simple @@ -293,40 +293,40 @@ def test_exif_copy(tmpdir): def test_exif_gps(tmpdir): """Test reading out correct geo tags""" - test_image = 'flickr_jerquiaga_2394751088_cc-by-nc.jpg' + test_image = "flickr_jerquiaga_2394751088_cc-by-nc.jpg" src_file = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir1', 'test1', test_image + CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) dst_file = str(tmpdir.join(test_image)) settings = create_settings(img_size=(400, 300), copy_exif_data=True) generate_image(src_file, dst_file, settings) simple = get_exif_tags(get_exif_data(dst_file)) - assert 'gps' in simple + assert "gps" in simple lat = 34.029167 lon = -116.144167 - assert abs(simple['gps']['lat'] - lat) < 0.0001 - assert abs(simple['gps']['lon'] - lon) < 0.0001 + assert abs(simple["gps"]["lat"] - lat) < 0.0001 + assert abs(simple["gps"]["lon"] - lon) < 0.0001 def test_get_size(tmpdir): """Test reading out image size""" - test_image = 'flickr_jerquiaga_2394751088_cc-by-nc.jpg' + test_image = "flickr_jerquiaga_2394751088_cc-by-nc.jpg" src_file = os.path.join( - CURRENT_DIR, 'sample', 'pictures', 'dir1', 'test1', test_image + CURRENT_DIR, "sample", "pictures", "dir1", "test1", test_image ) result = get_size(src_file) - assert result == {'height': 800, 'width': 600} + assert result == {"height": 800, "width": 600} def test_get_size_with_invalid_path(tmpdir): """Test reading out image size with a missing file""" - test_image = 'missing-file.jpg' + test_image = "missing-file.jpg" src_file = os.path.join(CURRENT_DIR, test_image) result = get_size(src_file) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index c65a3bd..1e464c2 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -7,18 +7,18 @@ CURRENT_DIR = os.path.dirname(__file__) def test_plugins(settings, tmpdir, disconnect_signals): - settings['destination'] = str(tmpdir) + settings["destination"] = str(tmpdir) if "sigal.plugins.nomedia" not in settings["plugins"]: - settings['plugins'] += ["sigal.plugins.nomedia"] + settings["plugins"] += ["sigal.plugins.nomedia"] if "sigal.plugins.media_page" not in settings["plugins"]: - settings['plugins'] += ["sigal.plugins.media_page"] + settings["plugins"] += ["sigal.plugins.media_page"] init_plugins(settings) gal = Gallery(settings) gal.build() out_html = os.path.join( - settings['destination'], 'dir2', 'KeckObservatory20071020.jpg.html' + settings["destination"], "dir2", "KeckObservatory20071020.jpg.html" ) assert os.path.isfile(out_html) @@ -30,30 +30,30 @@ def test_plugins(settings, tmpdir, disconnect_signals): def test_nonmedia_files(settings, tmpdir, disconnect_signals): - settings['destination'] = str(tmpdir) - settings['plugins'] += ['sigal.plugins.nonmedia_files'] - settings['nonmedia_files_options'] = {'thumb_bg_color': 'red'} + settings["destination"] = str(tmpdir) + settings["plugins"] += ["sigal.plugins.nonmedia_files"] + settings["nonmedia_files_options"] = {"thumb_bg_color": "red"} init_plugins(settings) gal = Gallery(settings) gal.build() - outfile = os.path.join(settings['destination'], 'nonmedia_files', 'dummy.pdf') + outfile = os.path.join(settings["destination"], "nonmedia_files", "dummy.pdf") assert os.path.isfile(outfile) outthumb = os.path.join( - settings['destination'], 'nonmedia_files', 'thumbnails', 'dummy.tn.jpg' + settings["destination"], "nonmedia_files", "thumbnails", "dummy.tn.jpg" ) assert os.path.isfile(outthumb) def test_titleregexp(settings, tmpdir, disconnect_signals): if "sigal.plugins.titleregexp" not in settings["plugins"]: - settings['plugins'] += ["sigal.plugins.titleregexp"] + settings["plugins"] += ["sigal.plugins.titleregexp"] init_plugins(settings) gal = Gallery(settings) gal.build() - assert gal.albums.get('dir1').albums[1].title == "titleregexp 02" + assert gal.albums.get("dir1").albums[1].title == "titleregexp 02" diff --git a/tests/test_settings.py b/tests/test_settings.py index 7b179ff..2ee7872 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -7,26 +7,26 @@ CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) def test_read_settings(settings): """Test that the settings are correctly read.""" - assert settings['img_size'] == (640, 480) - assert settings['thumb_size'] == (200, 150) - assert settings['thumb_suffix'] == '.tn' - assert settings['source'] == os.path.join(CURRENT_DIR, 'sample', 'pictures') + assert settings["img_size"] == (640, 480) + assert settings["thumb_size"] == (200, 150) + assert settings["thumb_suffix"] == ".tn" + assert settings["source"] == os.path.join(CURRENT_DIR, "sample", "pictures") def test_get_thumb(settings): """Test the get_thumb function.""" tests = [ - ('example.jpg', 'thumbnails/example.tn.jpg'), - ('test/example.jpg', 'test/thumbnails/example.tn.jpg'), - ('test/t/example.jpg', 'test/t/thumbnails/example.tn.jpg'), + ("example.jpg", "thumbnails/example.tn.jpg"), + ("test/example.jpg", "test/thumbnails/example.tn.jpg"), + ("test/t/example.jpg", "test/t/thumbnails/example.tn.jpg"), ] for src, ref in tests: assert get_thumb(settings, src) == ref tests = [ - ('example.webm', 'thumbnails/example.tn.jpg'), - ('test/example.mp4', 'test/thumbnails/example.tn.jpg'), - ('test/t/example.avi', 'test/t/thumbnails/example.tn.jpg'), + ("example.webm", "thumbnails/example.tn.jpg"), + ("test/example.mp4", "test/thumbnails/example.tn.jpg"), + ("test/t/example.avi", "test/t/thumbnails/example.tn.jpg"), ] for src, ref in tests: assert get_thumb(settings, src) == ref @@ -35,20 +35,20 @@ def test_get_thumb(settings): def test_img_sizes(tmpdir): """Test that image size is swaped if needed.""" - conf = tmpdir.join('sigal.conf.py') + conf = tmpdir.join("sigal.conf.py") conf.write("thumb_size = (150, 200)") settings = read_settings(str(conf)) - assert settings['thumb_size'] == (200, 150) + assert settings["thumb_size"] == (200, 150) def test_theme_path(tmpdir): """Test that image size is swaped if needed.""" - tmpdir.join('theme').mkdir() - tmpdir.join('theme').join('templates').mkdir() - conf = tmpdir.join('sigal.conf.py') + tmpdir.join("theme").mkdir() + tmpdir.join("theme").join("templates").mkdir() + conf = tmpdir.join("sigal.conf.py") conf.write("theme = 'theme'") settings = read_settings(str(conf)) - assert settings['theme'] == tmpdir.join('theme') + assert settings["theme"] == tmpdir.join("theme") diff --git a/tests/test_utils.py b/tests/test_utils.py index e782612..b47f709 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,39 +4,39 @@ from pathlib import Path from sigal import utils CURRENT_DIR = os.path.dirname(__file__) -SAMPLE_DIR = os.path.join(CURRENT_DIR, 'sample') +SAMPLE_DIR = os.path.join(CURRENT_DIR, "sample") def test_copy(tmpdir): - filename = 'KeckObservatory20071020.jpg' - src = os.path.join(SAMPLE_DIR, 'pictures', 'dir2', filename) + filename = "KeckObservatory20071020.jpg" + src = os.path.join(SAMPLE_DIR, "pictures", "dir2", filename) dst = str(tmpdir.join(filename)) utils.copy(src, dst) assert os.path.isfile(dst) - filename = 'm57_the_ring_nebula-587px.jpg' - src = os.path.join(SAMPLE_DIR, 'pictures', 'dir2', filename) + filename = "m57_the_ring_nebula-587px.jpg" + src = os.path.join(SAMPLE_DIR, "pictures", "dir2", filename) dst = str(tmpdir.join(filename)) utils.copy(src, dst, symlink=True) assert os.path.islink(dst) assert os.readlink(dst) == src - filename = 'KeckObservatory20071020.jpg' - src = os.path.join(SAMPLE_DIR, 'pictures', 'dir2', filename) + filename = "KeckObservatory20071020.jpg" + src = os.path.join(SAMPLE_DIR, "pictures", "dir2", filename) utils.copy(src, dst, symlink=True) assert os.path.islink(dst) assert os.readlink(dst) == src - filename = 'KeckObservatory20071020.jpg' - src = os.path.join(SAMPLE_DIR, 'pictures', 'dir2', filename) + filename = "KeckObservatory20071020.jpg" + src = os.path.join(SAMPLE_DIR, "pictures", "dir2", filename) dst = str(tmpdir.join(filename)) utils.copy(src, dst, symlink=True, rellink=True) assert os.path.islink(dst) assert os.path.join(os.path.dirname(CURRENT_DIR)), os.readlink(dst) == src # get absolute path of the current dir plus the relative dir - src = str(tmpdir.join('foo.txt')) - dst = str(tmpdir.join('bar.txt')) + src = str(tmpdir.join("foo.txt")) + dst = str(tmpdir.join("bar.txt")) p = Path(src) p.touch() p.chmod(0o444) @@ -45,48 +45,48 @@ def test_copy(tmpdir): def test_check_or_create_dir(tmpdir): - path = str(tmpdir.join('new_directory')) + path = str(tmpdir.join("new_directory")) utils.check_or_create_dir(path) assert os.path.isdir(path) def test_url_from_path(): - assert utils.url_from_path(os.sep.join(['foo', 'bar'])) == 'foo/bar' + assert utils.url_from_path(os.sep.join(["foo", "bar"])) == "foo/bar" def test_url_from_windows_path(monkeypatch): - monkeypatch.setattr('os.sep', "\\") - path = os.sep.join(['foo', 'bar']) - assert path == r'foo\bar' - assert utils.url_from_path(path) == 'foo/bar' + monkeypatch.setattr("os.sep", "\\") + path = os.sep.join(["foo", "bar"]) + assert path == r"foo\bar" + assert utils.url_from_path(path) == "foo/bar" def test_read_markdown(): - src = os.path.join(SAMPLE_DIR, 'pictures', 'dir1', 'test1', '11.md') + src = os.path.join(SAMPLE_DIR, "pictures", "dir1", "test1", "11.md") m = utils.read_markdown(src) - assert m['title'] == "Foo Bar" - assert m['meta']['location'][0] == "Bavaria" - assert m['description'] == "

This is a funny description of this image

" + assert m["title"] == "Foo Bar" + assert m["meta"]["location"][0] == "Bavaria" + assert m["description"] == "

This is a funny description of this image

" def test_read_markdown_empty_file(tmpdir): src = tmpdir.join("file.txt") src.write("content") m = utils.read_markdown(str(src)) - assert 'title' not in m - assert m['meta'] == {} - assert m['description'] == '

content

' + assert "title" not in m + assert m["meta"] == {} + assert m["description"] == "

content

" src = tmpdir.join("empty.txt") src.write("") m = utils.read_markdown(str(src)) - assert 'title' not in m + assert "title" not in m # See https://github.com/Python-Markdown/markdown/pull/672 # Meta attributes should always be there - assert m['meta'] == {} - assert m['description'] == '' + assert m["meta"] == {} + assert m["description"] == "" def test_is_valid_html5_video(): - assert utils.is_valid_html5_video('.webm') is True - assert utils.is_valid_html5_video('.mpeg') is False + assert utils.is_valid_html5_video(".webm") is True + assert utils.is_valid_html5_video(".mpeg") is False diff --git a/tests/test_video.py b/tests/test_video.py index 7d161e4..f7dedfd 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -9,20 +9,20 @@ 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(SRCDIR, 'video', TEST_VIDEO) +SRCDIR = os.path.join(CURRENT_DIR, "sample", "pictures") +TEST_VIDEO = "example video.ogv" +SRCFILE = os.path.join(SRCDIR, "video", TEST_VIDEO) def test_video_size(): size_src = video_size(SRCFILE) assert size_src == (320, 240) - size_src = video_size('missing/file.mp4') + size_src = video_size("missing/file.mp4") assert size_src == (0, 0) def test_generate_thumbnail(tmpdir): - outname = str(tmpdir.join('test.jpg')) + outname = str(tmpdir.join("test.jpg")) generate_thumbnail(SRCFILE, outname, (50, 50), 5) assert os.path.isfile(outname) @@ -31,18 +31,18 @@ def test_process_video(tmpdir): base, ext = os.path.splitext(TEST_VIDEO) settings = create_settings( - video_format='ogv', + video_format="ogv", use_orig=True, orig_link=True, - source=os.path.join(SRCDIR, 'video'), + source=os.path.join(SRCDIR, "video"), destination=str(tmpdir), ) - video = Video(TEST_VIDEO, '.', settings) + video = Video(TEST_VIDEO, ".", settings) process_video(video) - dstfile = str(tmpdir.join(base + '.ogv')) + dstfile = str(tmpdir.join(base + ".ogv")) assert os.path.realpath(dstfile) == SRCFILE - settings = create_settings(video_format='mjpg') + settings = create_settings(video_format="mjpg") assert process_video(video) == Status.FAILURE settings = create_settings(thumb_video_delay=-1) @@ -53,23 +53,23 @@ def test_metadata(tmpdir): base, ext = os.path.splitext(TEST_VIDEO) settings = create_settings( - video_format='ogv', + video_format="ogv", use_orig=True, orig_link=True, - source=os.path.join(SRCDIR, 'video'), + source=os.path.join(SRCDIR, "video"), destination=str(tmpdir), ) - video = Video(TEST_VIDEO, '.', settings) - assert video.meta == {'date': ['2020-01-01T09:00:00']} + video = Video(TEST_VIDEO, ".", settings) + assert video.meta == {"date": ["2020-01-01T09:00:00"]} assert video.date == datetime(2020, 1, 1, 9, 0) -@pytest.mark.parametrize("fmt", ['webm', 'mp4']) +@pytest.mark.parametrize("fmt", ["webm", "mp4"]) def test_generate_video_fit_height(tmpdir, fmt): """largest fitting dimension is height""" base, ext = os.path.splitext(TEST_VIDEO) - dstfile = str(tmpdir.join(base + '.' + fmt)) + dstfile = str(tmpdir.join(base + "." + fmt)) settings = create_settings(video_size=(80, 100), video_format=fmt) generate_video(SRCFILE, dstfile, settings) @@ -81,12 +81,12 @@ def test_generate_video_fit_height(tmpdir, fmt): assert abs(size_dst[0] / size_dst[1] - size_src[0] / size_src[1]) < 2e-2 -@pytest.mark.parametrize("fmt", ['webm', 'mp4']) +@pytest.mark.parametrize("fmt", ["webm", "mp4"]) def test_generate_video_fit_width(tmpdir, fmt): """largest fitting dimension is width""" base, ext = os.path.splitext(TEST_VIDEO) - dstfile = str(tmpdir.join(base + '.' + fmt)) + dstfile = str(tmpdir.join(base + "." + fmt)) settings = create_settings(video_size=(100, 50), video_format=fmt) generate_video(SRCFILE, dstfile, settings) @@ -98,12 +98,12 @@ def test_generate_video_fit_width(tmpdir, fmt): assert abs(size_dst[0] / size_dst[1] - size_src[0] / size_src[1]) < 2e-2 -@pytest.mark.parametrize("fmt", ['webm', 'mp4', 'ogv']) +@pytest.mark.parametrize("fmt", ["webm", "mp4", "ogv"]) def test_generate_video_dont_enlarge(tmpdir, fmt): """Video dimensions should not be enlarged.""" base, ext = os.path.splitext(TEST_VIDEO) - dstfile = str(tmpdir.join(base + '.' + fmt)) + dstfile = str(tmpdir.join(base + "." + fmt)) settings = create_settings(video_size=(1000, 1000), video_format=fmt) generate_video(SRCFILE, dstfile, settings) size_src = video_size(SRCFILE) @@ -112,19 +112,19 @@ def test_generate_video_dont_enlarge(tmpdir, fmt): assert size_src == size_dst -@patch('sigal.video.generate_video_pass') -@pytest.mark.parametrize("fmt", ['webm', 'mp4']) +@patch("sigal.video.generate_video_pass") +@pytest.mark.parametrize("fmt", ["webm", "mp4"]) def test_second_pass_video(mock_generate_video_pass, fmt, tmpdir): """Video should be run through ffmpeg.""" base, ext = os.path.splitext(TEST_VIDEO) - dstfile = str(tmpdir.join(base + '.' + fmt)) - settings_1 = '-c:v libvpx-vp9 -b:v 0 -crf 30 -pass 1 -an -f null dev/null' - settings_2 = f'-c:v libvpx-vp9 -b:v 0 -crf 30 -pass 2 -f {fmt}' + dstfile = str(tmpdir.join(base + "." + fmt)) + settings_1 = "-c:v libvpx-vp9 -b:v 0 -crf 30 -pass 1 -an -f null dev/null" + settings_2 = f"-c:v libvpx-vp9 -b:v 0 -crf 30 -pass 2 -f {fmt}" settings_opts = { - 'video_size': (100, 50), - 'video_format': fmt, - fmt + '_options': settings_1.split(" "), - fmt + '_options_second_pass': settings_2.split(" "), + "video_size": (100, 50), + "video_format": fmt, + fmt + "_options": settings_1.split(" "), + fmt + "_options_second_pass": settings_2.split(" "), } settings = create_settings(**settings_opts) diff --git a/tests/test_zip.py b/tests/test_zip.py index b37742f..b46ad48 100644 --- a/tests/test_zip.py +++ b/tests/test_zip.py @@ -6,14 +6,14 @@ from sigal.gallery import Gallery from sigal.settings import read_settings CURRENT_DIR = os.path.dirname(__file__) -SAMPLE_DIR = os.path.join(CURRENT_DIR, 'sample') -SAMPLE_SOURCE = os.path.join(SAMPLE_DIR, 'pictures') +SAMPLE_DIR = os.path.join(CURRENT_DIR, "sample") +SAMPLE_SOURCE = os.path.join(SAMPLE_DIR, "pictures") -def make_gallery(source_dir='dir1', **kwargs): - default_conf = os.path.join(SAMPLE_DIR, 'sigal.conf.py') +def make_gallery(source_dir="dir1", **kwargs): + default_conf = os.path.join(SAMPLE_DIR, "sigal.conf.py") settings = read_settings(default_conf) - settings['source'] = os.path.join(SAMPLE_SOURCE, source_dir) + settings["source"] = os.path.join(SAMPLE_SOURCE, source_dir) settings.update(kwargs) init_plugins(settings) return Gallery(settings, ncpu=1) @@ -21,18 +21,18 @@ def make_gallery(source_dir='dir1', **kwargs): def test_zipped_correctly(tmpdir): outpath = str(tmpdir) - gallery = make_gallery(destination=outpath, zip_gallery='archive.zip') + gallery = make_gallery(destination=outpath, zip_gallery="archive.zip") gallery.build() - zipf = os.path.join(outpath, 'test1', 'archive.zip') + zipf = os.path.join(outpath, "test1", "archive.zip") assert os.path.isfile(zipf) - zip_file = zipfile.ZipFile(zipf, 'r') + zip_file = zipfile.ZipFile(zipf, "r") expected = ( - '11.jpg', - 'CMB_Timeline300_no_WMAP.jpg', - 'flickr_jerquiaga_2394751088_cc-by-nc.jpg', - 'example.gif', + "11.jpg", + "CMB_Timeline300_no_WMAP.jpg", + "flickr_jerquiaga_2394751088_cc-by-nc.jpg", + "example.gif", ) for filename in zip_file.namelist(): @@ -40,7 +40,7 @@ def test_zipped_correctly(tmpdir): zip_file.close() - assert os.path.isfile(os.path.join(outpath, 'test2', 'archive.zip')) + assert os.path.isfile(os.path.join(outpath, "test2", "archive.zip")) def test_not_zipped(tmpdir): @@ -48,10 +48,10 @@ def test_not_zipped(tmpdir): # is present outpath = str(tmpdir) gallery = make_gallery( - destination=outpath, zip_gallery='archive.zip', source_dir='dir2' + destination=outpath, zip_gallery="archive.zip", source_dir="dir2" ) gallery.build() - assert not os.path.isfile(os.path.join(outpath, 'archive.zip')) + assert not os.path.isfile(os.path.join(outpath, "archive.zip")) def test_no_archive(tmpdir): @@ -59,16 +59,16 @@ def test_no_archive(tmpdir): gallery = make_gallery(destination=outpath, zip_gallery=False) gallery.build() - assert not os.path.isfile(os.path.join(outpath, 'test1', 'archive.zip')) - assert not os.path.isfile(os.path.join(outpath, 'test2', 'archive.zip')) + assert not os.path.isfile(os.path.join(outpath, "test1", "archive.zip")) + assert not os.path.isfile(os.path.join(outpath, "test2", "archive.zip")) def test_correct_filename(tmpdir, caplog): - caplog.set_level('ERROR') + caplog.set_level("ERROR") outpath = str(tmpdir) gallery = make_gallery(destination=outpath, zip_gallery=True) gallery.build() assert caplog.records[0].message == "'zip_gallery' should be set to a filename" - assert not os.path.isfile(os.path.join(outpath, 'test1', 'archive.zip')) - assert not os.path.isfile(os.path.join(outpath, 'test2', 'archive.zip')) + assert not os.path.isfile(os.path.join(outpath, "test1", "archive.zip")) + assert not os.path.isfile(os.path.join(outpath, "test2", "archive.zip"))