diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | setup.py | 35 | ||||
-rw-r--r-- | thumbnail/__init__.py | 4 | ||||
-rw-r--r-- | thumbnail/action/Thumbnail.py | 140 | ||||
-rw-r--r-- | thumbnail/action/__init__.py | 5 | ||||
-rw-r--r-- | thumbnail/macro/Thumbnail.py | 40 | ||||
-rw-r--r-- | thumbnail/macro/ThumbnailGallery.py | 47 | ||||
-rw-r--r-- | thumbnail/macro/__init__.py | 5 |
8 files changed, 279 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b116e4 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1,3 @@ | |||
1 | /*.egg-info | ||
2 | *.pyc | ||
3 | .DS_Store | ||
diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..dddb52c --- /dev/null +++ b/setup.py | |||
@@ -0,0 +1,35 @@ | |||
1 | from setuptools import setup, find_packages | ||
2 | |||
3 | setup( | ||
4 | name="CruteMoinThumbnail", | ||
5 | version="1.0", | ||
6 | description="", | ||
7 | author="Michael Crute <mcrute@gmail.com>", | ||
8 | license="MIT", | ||
9 | packages=find_packages(), | ||
10 | entry_points={ | ||
11 | "moin.plugins.action": [ | ||
12 | "Thumbnail = thumbnail.action.Thumbnail:execute", | ||
13 | ], | ||
14 | # "moin.plugins.converter": [ | ||
15 | # ], | ||
16 | # "moin.plugins.events": [ | ||
17 | # ], | ||
18 | # "moin.plugins.filter": [ | ||
19 | # ], | ||
20 | # "moin.plugins.formatter": [ | ||
21 | # ], | ||
22 | "moin.plugins.macro": [ | ||
23 | "Thumbnail = thumbnail.macro.Thumbnail:macro_Thumbnail", | ||
24 | "ThumbnailGallery = thumbnail.macro.ThumbnailGallery:macro_ThumbnailGallery", | ||
25 | ], | ||
26 | # "moin.plugins.parser": [ | ||
27 | # ], | ||
28 | # "moin.plugins.theme": [ | ||
29 | # ], | ||
30 | # "moin.plugins.userprefs": [ | ||
31 | # ], | ||
32 | # "moin.plugins.xmlrpc": [ | ||
33 | # ], | ||
34 | } | ||
35 | ) | ||
diff --git a/thumbnail/__init__.py b/thumbnail/__init__.py new file mode 100644 index 0000000..f2d8200 --- /dev/null +++ b/thumbnail/__init__.py | |||
@@ -0,0 +1,4 @@ | |||
1 | # *** Do not remove this! *** | ||
2 | # Although being empty, the presence of this file is important for plugins | ||
3 | # working correctly. | ||
4 | |||
diff --git a/thumbnail/action/Thumbnail.py b/thumbnail/action/Thumbnail.py new file mode 100644 index 0000000..3ff1f42 --- /dev/null +++ b/thumbnail/action/Thumbnail.py | |||
@@ -0,0 +1,140 @@ | |||
1 | import os | ||
2 | import hashlib | ||
3 | from PIL import Image, ImageOps | ||
4 | from PIL.ImageFileIO import ImageFileIO | ||
5 | |||
6 | from MoinMoin import log | ||
7 | from MoinMoin import wikiutil | ||
8 | from MoinMoin.Page import Page | ||
9 | from MoinMoin.action import AttachFile | ||
10 | from MoinMoin.caching import CacheEntry | ||
11 | |||
12 | |||
13 | logging = log.getLogger(__name__) | ||
14 | |||
15 | |||
16 | def crop(img, width, height): | ||
17 | src_width, src_height = img.size | ||
18 | src_ratio = float(src_width) / float(src_height) | ||
19 | dst_width, dst_height = int(width), int(height) | ||
20 | dst_ratio = float(dst_width) / float(dst_height) | ||
21 | |||
22 | if dst_ratio < src_ratio: | ||
23 | crop_height = src_height | ||
24 | crop_width = crop_height * dst_ratio | ||
25 | x_offset = float(src_width - crop_width) / 2 | ||
26 | y_offset = 0 | ||
27 | else: | ||
28 | crop_width = src_width | ||
29 | crop_height = crop_width / dst_ratio | ||
30 | x_offset = 0 | ||
31 | y_offset = float(src_height - crop_height) / 3 | ||
32 | |||
33 | return img.crop(( | ||
34 | x_offset, | ||
35 | y_offset, | ||
36 | x_offset + int(crop_width), | ||
37 | y_offset + int(crop_height) | ||
38 | )) | ||
39 | |||
40 | |||
41 | def thumbnail(img, long_side): | ||
42 | long_side = int(long_side) | ||
43 | width, height = [float(d) for d in img.size] | ||
44 | |||
45 | if height > width: | ||
46 | width = (width / height) * long_side | ||
47 | height = long_side | ||
48 | else: | ||
49 | height = (height / width) * long_side | ||
50 | width = long_side | ||
51 | |||
52 | img.thumbnail((width, height), Image.ANTIALIAS) | ||
53 | return img | ||
54 | |||
55 | |||
56 | def thumbnail_constrain(img, size, dimension): | ||
57 | size = int(size) | ||
58 | width, height = [float(d) for d in img.size] | ||
59 | |||
60 | if dimension.lower() == "h": | ||
61 | height, width = size, ((width * size) / height) | ||
62 | elif dimension.lower() == "w": | ||
63 | width, height = size, ((height * size) / width) | ||
64 | else: | ||
65 | raise Exception("Must contrain valid dimension") | ||
66 | |||
67 | img.thumbnail((int(width), int(height)), Image.ANTIALIAS) | ||
68 | return img | ||
69 | |||
70 | |||
71 | def get_cache_key(request, filename): | ||
72 | ops = hashlib.md5(":".join(request.values.getlist("do"))) | ||
73 | |||
74 | key = filename.split(".") | ||
75 | key.insert(0, "thumbnail") | ||
76 | key.insert(-1, ops.hexdigest()[:8]) | ||
77 | |||
78 | return ".".join(key) | ||
79 | |||
80 | |||
81 | def execute(pagename, request): | ||
82 | _ = request.getText | ||
83 | |||
84 | if not request.user.may.read(pagename): | ||
85 | return _('You are not allowed to view attachments of this page.') | ||
86 | |||
87 | page = Page(request, pagename) | ||
88 | pagename, filename, fpath = AttachFile._access_file(pagename, request) | ||
89 | |||
90 | if not filename: | ||
91 | request.status_code = 404 | ||
92 | return | ||
93 | |||
94 | cache = CacheEntry(request, page, | ||
95 | get_cache_key(request, filename), scope="item") | ||
96 | |||
97 | if cache.exists() and (cache.mtime() >= os.path.getmtime(fpath)): | ||
98 | logging.info("Using cache for %s", fpath) | ||
99 | cache.open(mode="r") | ||
100 | request.write(cache.read()) | ||
101 | cache.close() | ||
102 | return | ||
103 | |||
104 | action_map = { | ||
105 | 'ds': ImageOps.grayscale, | ||
106 | 'cr': crop, | ||
107 | 'th': thumbnail, | ||
108 | 'tc': thumbnail_constrain, | ||
109 | } | ||
110 | |||
111 | with open(fpath) as fp: | ||
112 | img = Image.open(fp) | ||
113 | |||
114 | for action in request.values.getlist("do"): | ||
115 | action = action.split(":") | ||
116 | args = action[1].split(",") if len(action) > 1 else [] | ||
117 | |||
118 | try: | ||
119 | img = action_map[action[0]](img, *args) | ||
120 | except Exception, e: | ||
121 | request.status_code = 400 | ||
122 | request.write("Error: {}".format(e)) | ||
123 | return | ||
124 | |||
125 | mt = wikiutil.MimeType(filename=filename) | ||
126 | request.headers['Content-Type'] = mt.content_type() | ||
127 | data = img.tostring('jpeg', img.mode) | ||
128 | |||
129 | try: | ||
130 | cache.lock("w") | ||
131 | cache.open(mode="w") | ||
132 | cache.write(data) | ||
133 | cache.close() | ||
134 | except Exception, e: | ||
135 | request.status_code = 500 | ||
136 | request.write("Error: {}".format(e)) | ||
137 | return | ||
138 | finally: | ||
139 | cache.unlock() | ||
140 | request.write(data) | ||
diff --git a/thumbnail/action/__init__.py b/thumbnail/action/__init__.py new file mode 100644 index 0000000..e4ed3b6 --- /dev/null +++ b/thumbnail/action/__init__.py | |||
@@ -0,0 +1,5 @@ | |||
1 | # -*- coding: iso-8859-1 -*- | ||
2 | |||
3 | from MoinMoin.util import pysupport | ||
4 | |||
5 | modules = pysupport.getPackageModules(__file__) | ||
diff --git a/thumbnail/macro/Thumbnail.py b/thumbnail/macro/Thumbnail.py new file mode 100644 index 0000000..0603435 --- /dev/null +++ b/thumbnail/macro/Thumbnail.py | |||
@@ -0,0 +1,40 @@ | |||
1 | from MoinMoin import wikiutil | ||
2 | from MoinMoin.action import AttachFile | ||
3 | |||
4 | |||
5 | def macro_Thumbnail(macro, attachment, height=200, width=None, link=True): | ||
6 | _ = macro.request.getText | ||
7 | formatter = macro.formatter | ||
8 | |||
9 | pagename, filename = AttachFile.absoluteName(attachment, formatter.page.page_name) | ||
10 | fname = wikiutil.taintfilename(filename) | ||
11 | |||
12 | if not macro.request.user.may.read(pagename): | ||
13 | return _('You are not allowed to view attachments of this page.') | ||
14 | |||
15 | if width: | ||
16 | size, dimension = width, "w" | ||
17 | else: | ||
18 | size, dimension = height, "h" | ||
19 | |||
20 | if AttachFile.exists(macro.request, pagename, fname): | ||
21 | url = AttachFile.getAttachUrl(pagename, fname, macro.request, do='get') | ||
22 | |||
23 | output = [ | ||
24 | formatter.url(True, url) if link else "", | ||
25 | formatter.image( | ||
26 | macro.request.href(pagename, { | ||
27 | "target": fname, | ||
28 | "action": "Thumbnail", | ||
29 | "do": "tc:{},{}".format(size, dimension) | ||
30 | })), | ||
31 | formatter.url(False) if link else "", | ||
32 | ] | ||
33 | else: | ||
34 | output = [ | ||
35 | formatter.span(True, style="color: red"), | ||
36 | "No Image: {}".format(attachment), | ||
37 | formatter.span(False), | ||
38 | ] | ||
39 | |||
40 | return "".join(output) | ||
diff --git a/thumbnail/macro/ThumbnailGallery.py b/thumbnail/macro/ThumbnailGallery.py new file mode 100644 index 0000000..038c3bc --- /dev/null +++ b/thumbnail/macro/ThumbnailGallery.py | |||
@@ -0,0 +1,47 @@ | |||
1 | import re | ||
2 | from MoinMoin import wikiutil | ||
3 | from MoinMoin.action import AttachFile | ||
4 | |||
5 | |||
6 | def macro_ThumbnailGallery(macro, page=None, regex=None, height=200, width=None, link=True): | ||
7 | _ = macro.request.getText | ||
8 | formatter = macro.formatter | ||
9 | pagename = page or macro.formatter.page.page_name | ||
10 | output = [] | ||
11 | |||
12 | if not regex: | ||
13 | regex = re.compile(".*\.(jpg|jpeg|png|gif)$") | ||
14 | else: | ||
15 | regex = re.compile(regex) | ||
16 | |||
17 | if not macro.request.user.may.read(pagename): | ||
18 | return _('You are not allowed to view attachments of this page.') | ||
19 | |||
20 | for attachment in AttachFile._get_files(macro.request, pagename): | ||
21 | if regex.match(attachment) is None: | ||
22 | continue | ||
23 | |||
24 | pagename, filename = AttachFile.absoluteName(attachment, pagename) | ||
25 | fname = wikiutil.taintfilename(filename) | ||
26 | |||
27 | if width: | ||
28 | size, dimension = width, "w" | ||
29 | else: | ||
30 | size, dimension = height, "h" | ||
31 | |||
32 | if AttachFile.exists(macro.request, pagename, fname): | ||
33 | url = AttachFile.getAttachUrl(pagename, fname, macro.request, do='get') | ||
34 | |||
35 | output.extend([ | ||
36 | macro.formatter.url(True, url) if link else "", | ||
37 | macro.formatter.image( | ||
38 | macro.request.href(pagename, { | ||
39 | "target": fname, | ||
40 | "action": "Thumbnail", | ||
41 | "do": "tc:{},{}".format(size, dimension) | ||
42 | })), | ||
43 | macro.formatter.url(False) if link else "", | ||
44 | " ", | ||
45 | ]) | ||
46 | |||
47 | return "".join(output) | ||
diff --git a/thumbnail/macro/__init__.py b/thumbnail/macro/__init__.py new file mode 100644 index 0000000..e4ed3b6 --- /dev/null +++ b/thumbnail/macro/__init__.py | |||
@@ -0,0 +1,5 @@ | |||
1 | # -*- coding: iso-8859-1 -*- | ||
2 | |||
3 | from MoinMoin.util import pysupport | ||
4 | |||
5 | modules = pysupport.getPackageModules(__file__) | ||