Normalize strings
This commit is contained in:
@@ -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='<revision>..<revision>')
|
||||
parser.add_argument("token", help="github access token")
|
||||
parser.add_argument("revision_range", help="<revision>..<revision>")
|
||||
args = parser.parse_args()
|
||||
main(args.token, args.revision_range)
|
||||
|
||||
44
docs/conf.py
44
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"
|
||||
|
||||
@@ -8,4 +8,3 @@ write_to = "src/sigal/version.py"
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py38']
|
||||
skip-string-normalization = true
|
||||
|
||||
@@ -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. "
|
||||
|
||||
@@ -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 <imagename>.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 <imagename>.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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 ...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
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.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 == "<p>This is a funny description of this image</p>"
|
||||
|
||||
@@ -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')
|
||||
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.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 == "<p>This is a funny description of this image</p>"
|
||||
|
||||
file_path = join(path, '11.tiff')
|
||||
file_path = join(path, "11.tiff")
|
||||
assert repr(m) == f"<Image>('{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']
|
||||
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_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"] = "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'])
|
||||
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 '<title>Sigal test gallery - Sigal test gallery ☺</title>' in html
|
||||
assert "<title>Sigal test gallery - Sigal test gallery ☺</title>" in html
|
||||
assert '<link rel="stylesheet" href="static/my.css">' 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"))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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'] == "<p>This is a funny description of this image</p>"
|
||||
assert m["title"] == "Foo Bar"
|
||||
assert m["meta"]["location"][0] == "Bavaria"
|
||||
assert m["description"] == "<p>This is a funny description of this image</p>"
|
||||
|
||||
|
||||
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'] == '<p>content</p>'
|
||||
assert "title" not in m
|
||||
assert m["meta"] == {}
|
||||
assert m["description"] == "<p>content</p>"
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user