diff options
Diffstat (limited to 'thumbnail/action/Thumbnail.py')
-rw-r--r-- | thumbnail/action/Thumbnail.py | 140 |
1 files changed, 140 insertions, 0 deletions
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) | ||