forked from skorokithakis/lektor-thumbnail-generator
/
lektor_thumbnail_generator.py
168 lines (138 loc) · 5.65 KB
/
lektor_thumbnail_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# -*- coding: utf-8 -*-
import os
import shutil
from lektor.build_programs import AttachmentBuildProgram, buildprogram
from lektor.context import get_ctx
from lektor.db import Image
from lektor.imagetools import (compute_dimensions, find_imagemagick,
get_image_info, get_quality)
from lektor.pluginsystem import Plugin
from lektor.reporter import reporter
from lektor.utils import portable_popen, locate_executable
from werkzeug.utils import cached_property
def process_svg_image(
ctx,
source_image,
dst_filename,
width=None,
height=None,
mode=None,
):
if width is None and height is None:
raise ValueError("Must specify at least one of width or height.")
# Because Lektor doesn't have a generic find program (only imagemagick),
# only *nix is supported for simplicity. Imagemagick will be used as the
# fallback on Windows, but it won't work well...
if os.name == 'nt':
return process_image(ctx, source_image, dst_filename, width, height,
mode, 85)
inkscape = locate_executable('inkscape')
cmdline = [inkscape, source_image]
if width is not None:
cmdline += ["-w", str(width)]
if height is not None:
cmdline += ["-h", str(height)]
# FIXME: This will only work with inkscape 1.0+
cmdline += ["--export-filename", dst_filename]
reporter.report_debug_info("inkscape cmd line", cmdline)
portable_popen(cmdline).wait()
# We override process_image here because Lektor does not support adding extra
# parameters yet, but it will soon, and this can be removed when it does.
def process_image(
ctx,
source_image,
dst_filename,
width=None,
height=None,
mode=None,
quality=None,
extra_params=None,
):
"""Build image from source image, optionally compressing and resizing.
"source_image" is the absolute path of the source in the content directory,
"dst_filename" is the absolute path of the target in the output directory.
"""
if width is None and height is None:
raise ValueError("Must specify at least one of width or height.")
im = find_imagemagick(ctx.build_state.config["IMAGEMAGICK_EXECUTABLE"])
if quality is None:
quality = get_quality(source_image)
resize_key = ""
if width is not None:
resize_key += str(width)
if height is not None:
resize_key += "x" + str(height)
cmdline = [im, source_image, "-auto-orient"]
cmdline += ["-resize", resize_key]
if extra_params:
cmdline.extend(extra_params)
cmdline += ["-quality", str(quality), dst_filename]
reporter.report_debug_info("imagemagick cmd line", cmdline)
portable_popen(cmdline).wait()
@buildprogram(Image)
class ResizedImageBuildProgram(AttachmentBuildProgram):
def build_artifact(self, artifact):
ctx = get_ctx()
plugin = ctx.env.plugins["thumbnail-generator"]
config = plugin.config
artifact.ensure_dir()
AttachmentBuildProgram.build_artifact(self, artifact)
if not config:
return
source_img = artifact.source_obj.attachment_filename
with open(source_img, "rb") as f:
_, w, h = get_image_info(f)
# For every section in the config, we need to generate one image.
for item, conf in config.items():
width = int(conf["max_width"])
height = int(conf.get("max_height", "0"))
if not height:
_, height = compute_dimensions(width, None, w, h)
df = artifact.source_obj.url_path
ext_pos = df.rfind(".")
if df[ext_pos + 1 :] == 'svg':
dst_filename = "%s-%s.png" % (df[:ext_pos], item)
else:
dst_filename = "%s-%s.%s" % (df[:ext_pos], item, df[ext_pos + 1 :])
def closure(dst_filename, source_img, width, height, resize_image=True):
# We need this closure, otherwise variables get updated and this
# doesn't work at all.
@ctx.sub_artifact(artifact_name=dst_filename, sources=[source_img])
def build_thumbnail_artifact(artifact):
artifact.ensure_dir()
if not resize_image:
shutil.copy2(source_img, artifact.dst_filename)
elif df[ext_pos + 1 :] == 'svg':
process_svg_image(
ctx,
source_img,
artifact.dst_filename,
width,
height,
)
else:
process_image(
ctx,
source_img,
artifact.dst_filename,
width,
height,
quality=85,
extra_params=[
"-strip",
"-interlace",
"Plane",
],
)
# If the image is larger than the max_width, resize it, otherwise
# just copy it.
resize_image = w > width or h > height
closure(dst_filename, source_img, width, height, resize_image)
class ThumbnailGeneratorPlugin(Plugin):
name = u"thumbnail-generator"
description = u"A configurable way to generate thumbnails."
image_exts = ["png", "jpg", "jpeg", "gif"]
@cached_property
def config(self):
conf = self.get_config()
return {section: conf.section_as_dict(section) for section in conf.sections()}