]>
Commit | Line | Data |
---|---|---|
9523790a | 1 | #!/usr/bin/python3 |
66862195 | 2 | |
5ef115cd MT |
3 | import PIL.Image |
4 | import PIL.ImageFilter | |
2b72638d | 5 | import PIL.ImageOps |
5ef115cd MT |
6 | import io |
7 | import logging | |
e96e445b | 8 | import random |
9523790a | 9 | import re |
e96e445b | 10 | import string |
75d9b3da | 11 | import unicodedata |
e96e445b | 12 | |
9523790a MT |
13 | def parse_search_query(query): |
14 | q = [] | |
15 | for word in query.split(): | |
16 | # Is this lexeme negated? | |
17 | negated = word.startswith("!") | |
18 | ||
19 | # Remove any special characters | |
20 | word = re.sub(r"\W+", "", word, flags=re.UNICODE) | |
21 | if not word: | |
22 | continue | |
23 | ||
24 | # Restore negation | |
25 | if negated: | |
26 | word = "!%s" % word | |
27 | ||
28 | q.append(word) | |
29 | ||
30 | return " & ".join(q) | |
31 | ||
84604476 MT |
32 | def format_size(s, max_unit=None): |
33 | units = ("B", "kB", "MB", "GB", "TB") | |
66862195 MT |
34 | |
35 | i = 0 | |
36 | while s >= 1024 and i < len(units) - 1: | |
37 | s /= 1024 | |
38 | i += 1 | |
39 | ||
84604476 MT |
40 | if max_unit and units[i] == max_unit: |
41 | break | |
42 | ||
66862195 MT |
43 | return "%.0f%s" % (s, units[i]) |
44 | ||
5ac74b02 | 45 | def format_time(s, shorter=True): |
66862195 MT |
46 | #_ = handler.locale.translate |
47 | _ = lambda x: x | |
48 | ||
49 | hrs, s = divmod(s, 3600) | |
50 | min, s = divmod(s, 60) | |
51 | ||
52 | if s >= 30: | |
53 | min += 1 | |
54 | ||
55 | if shorter and not hrs: | |
56 | return _("%(min)d min") % { "min" : min } | |
57 | ||
58 | return _("%(hrs)d:%(min)02d hrs") % {"hrs" : hrs, "min" : min} | |
e96e445b MT |
59 | |
60 | def random_string(length=8): | |
61 | input_chars = string.ascii_letters + string.digits | |
62 | ||
63 | r = (random.choice(input_chars) for i in range(length)) | |
64 | ||
65 | return "".join(r) | |
75d9b3da MT |
66 | |
67 | def normalize(s): | |
68 | # Remove any non-ASCII characters | |
69 | try: | |
70 | s = unicodedata.normalize("NFKD", s) | |
71 | except TypeError: | |
72 | pass | |
73 | ||
74 | # Remove excessive whitespace | |
75 | s = re.sub(r"[^\w]+", " ", s) | |
76 | ||
77 | return "-".join(s.split()) | |
5ef115cd | 78 | |
2de3dacc | 79 | def generate_thumbnail(data, size, square=False, **args): |
5ef115cd MT |
80 | assert data, "No image data received" |
81 | ||
82 | image = PIL.Image.open(io.BytesIO(data)) | |
83 | ||
84 | # Save image format | |
85 | format = image.format | |
86 | ||
87 | # Remove any alpha-channels | |
88 | if image.format == "JPEG" and not image.mode == "RGB": | |
89 | # Make a white background | |
90 | background = PIL.Image.new("RGBA", image.size, (255,255,255)) | |
91 | ||
92 | # Convert image to RGBA if not in RGBA, yet | |
93 | if not image.mode == "RGBA": | |
94 | image = image.convert("RGBA") | |
95 | ||
96 | # Flatten both images together | |
97 | flattened_image = PIL.Image.alpha_composite(background, image) | |
98 | ||
99 | # Remove the alpha channel | |
100 | image = flattened_image.convert("RGB") | |
101 | ||
102 | # Resize the image to the desired resolution | |
2de3dacc | 103 | if square: |
7e222133 | 104 | image = PIL.ImageOps.fit(image, (size, size), PIL.Image.LANCZOS) |
2de3dacc MT |
105 | else: |
106 | image.thumbnail((size, size), PIL.Image.LANCZOS) | |
5ef115cd MT |
107 | |
108 | if image.format == "JPEG": | |
109 | # Apply a gaussian blur to make compression easier | |
110 | image = image.filter(PIL.ImageFilter.GaussianBlur(radius=0.05)) | |
111 | ||
112 | # Arguments to optimise the compression | |
113 | args.update({ | |
114 | "subsampling" : "4:2:0", | |
115 | "quality" : 70, | |
116 | }) | |
117 | ||
118 | with io.BytesIO() as f: | |
119 | # If writing out the image does not work with optimization, | |
120 | # we try to write it out without any optimization. | |
121 | try: | |
122 | image.save(f, format, optimize=True, **args) | |
123 | except: | |
124 | image.save(f, format, **args) | |
125 | ||
126 | return f.getvalue() |