-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimagehandler.py
executable file
·301 lines (253 loc) · 13.8 KB
/
imagehandler.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#!/usr/bin/env python
import os
import time
import sys
import logging.handlers
import logging
import base64
from PIL import Image
from PIL import ImageFilter
from PIL import ImageEnhance
from configparser import ConfigParser
from configparser import ExtendedInterpolation
from pkg_resources import resource_stream, Requirement
from io import BytesIO
from io import StringIO
class imagehandler(object):
'''the class is used for processing of the image dynamically and accepts the name of the original image
the screensize and the bandwidth. This provides a byte image output
Based on these values, the program will do simple logic of sending the most optimal image as ByteArray.
THis may need conversin to Base64 if someone intends to leverage as string
the optimal PIL-SIMD based Image generation which is faster than most other tools including Imagemagica by approx 10-15times
'''
'''All configured bandwidth, screens will be initialized with a weightage'''
bandwidth = dict()
screens = dict()
dynamicqualityvalues =dict()
bandqualityvalues = dict()
scales = dict()
'''Default screensize, band will be set once initialized'''
defaultscreen = {}
defaultband = {}
'''loginfo level from config'''
loginfo =""
'''The root path for all images'''
imagepath = {}
sumup=0
'''This will be used to resize and set quality of image'''
qualityscore =60
'''Initialize the logger to log information'''
mylogger = logging.getLogger('Imagehandler')
handler = logging.handlers.RotatingFileHandler('/var/www/imgprocessor/imghandler.log','a',maxBytes=10000000,backupCount=5)
mylogger.addHandler(handler)
mylogger.setLevel("ERROR")
iswidthheightset = False
'''
This method Generates an output of byte of an image
filename - provide the path to the file. The core mount path is configured in configuration. This config property and the filename
value given here will be joined to get a fullpath or real path
screensize - in case we are sending screensize parameter. if width and height of image is sent then this property becomes irrelevant
band - is the bandwidth or network type. This can be 2g,3g,4g or *. This will be used purely to optimize quality of image and hence size
width - the image width
height - the image height which is required.
forcesize - if given True, the imagesize given is resized without any aspect ratio consideration. Default is False i.e.
aspect ratio is considered in resize which can be overriden if this is given as 1 or True
'''
def generate(self,filename,ssize="1080",band="*",width=0,height=0,forcesize=False):
try:
'''To avoid issues with zerolength params, default such items'''
if ssize == "None" or len(str(ssize))==0 : ssize = "1080"
if band == "None" or len(str(band))==0 :band="*"
'''if weight is given but height not given or other way around'''
width = int(width)
height = int(height)
#if (width >0 and height==0) : height = width
#if (height>0 and width==0): width = height
self.mylogger.info(msg="Starttime is {}".format(time.time()))
config = ConfigParser()
config._interpolation = ExtendedInterpolation()
'''get executionpath'''
dirname = os.path.dirname(os.path.realpath(__file__))
config.read(os.path.join (dirname ,"config.cfg"))
'''set logger level from config'''
loglevel = config.get(section="config",option="loglevel")
self.mylogger.setLevel(level=loglevel)
self.mylogger.info(config.sections())
self.mylogger.info(msg="Dirname is in {}".format(os.path.join (dirname ,"config.cfg")))
'''Initialize all the configurations like screensizes, bandconfig etc'''
screenconfig = config.get(section="config",option="screensize")
bandconfig = config.get(section="config",option="band")
dynamicqualityconfig = config.get(section="config",option="dynamicqualityscore")
bandqualityconfig = config.get(section="config",option="bandqualityscore")
scaleconfig = config.get(section="config",option="scale")
defaultscreen = config.get(section="config",option="defaultscreen")
defaultband = config.get(section="config",option="defaultbandwidth")
imagepath = config.get(section="config",option="path")
'''Populate all the dict values from configuration'''
self.screens = dict(screenmix.split(",") for screenmix in screenconfig.split(";"))
self.bandwidth = dict(band.split(",") for band in bandconfig.split(";"))
self.dynamicqualityvalues = dict(quality.split(",") for quality in dynamicqualityconfig.split(";"))
self.bandqualityvalues = dict(quality.split(",") for quality in bandqualityconfig.split(";"))
self.scales = dict(scale.split(",") for scale in scaleconfig.split(";"))
if (width >0 and height >0): self.iswidthheightset = True
self.mylogger.info(msg="Done initialization")
imgfile = BytesIO()
self.mylogger.info('Generating file {}'.format(filename))
'''Expected name of the file to be generated'''
'''A consistent logic is used to generate name of image'''
savefilename = self._createimageid(filename.split(".")[0],ssize,band,width,height) + filename.split(".")[1]
savefilename = os.path.join(imagepath, savefilename )
'''if filealready exist return file name'''
if os.path.isfile(savefilename):
img = Image.open(savefilename)
img.save(imgfile,format="JPEG")
'''encode it using base64 and return in asciformat'''
#base64encode= base64.encodebytes(imgfile.getvalue())
#return base64encode.decode("ascii")
return imgfile.getvalue()
else:
'''Open the file if it isnt there and resize, send back the path'''
'''Check if input fullpath leads to file, if not throw exception'''
fullpath = os.path.join(imagepath, filename)
self.mylogger.info("Image {} is generated from path {}".format(filename, fullpath))
if not os.path.isfile(fullpath):
raise NameError('File not found')
'''if fullpath is file, then open the same'''
img = self._getimage(fullpath,ssize,band,width,height,forcesize)
'''load from bytes too'''
'''Sharpen by 1.5, detail out and set subsampling to 0'''
sharp = ImageEnhance.Sharpness(img)
img = sharp.enhance(1.5)
img = img.filter(ImageFilter.DETAIL)
img.save(imgfile, format="JPEG" ,subsampling=0, quality=int(self.qualityscore))
self.mylogger.info("Image {} is generated with force {}".format(filename,forcesize))
'''encode it using base64 and return in asciformat'''
#base64encode = base64.encodebytes(imgfile.getvalue())
#return base64encode.decode('ascii')
return imgfile.getvalue()
except NameError as filenotfound:
raise NameError('File {} doesnt exist. Exception - {}'.format(filename,str(filenotfound)))
self.mylogger.exception("Exception occurred in image generation for {}".format(filename))
finally:
self.mylogger.info("Completed image for {} in finally".format(filename))
'''Creates a consistent naming for file given the options available'''
def _createimageid(self,filename,ssize,band,width,height):
if self.iswidthheightset:
filename = "{}_{}x{}_{}".format(filename,width,height,band)
else:
self._calculatescreenandbandscore(ssize,band)
filename = "{}_{}".format(filename,self.sumup)
#print (filename)
return filename
def _getimage(self,fullpath, size,band,width,height,forcesize):
img = Image.open(fullpath, 'r')
self.mylogger.info("Size of image is {}".format(img.size))
if self.iswidthheightset:
'''since we will use width/height given, resolve quality of network to set the scale factor'''
self._resolvequality(band)
'''if forcesize is true then use resize to forcefit else use aspectratio preserving thumbnail'''
if forcesize == True:
img = img.resize((int(width), int(height)), Image.LANCZOS)
self.mylogger.info("Using image resize method - WXH = {}_{}".format(img.width,img.height))
else:
img.thumbnail((int(width), int(height)), Image.LANCZOS)
self.mylogger.info("Using image thumbnail method - WXH = {}_{}".format(img.width, img.height))
else:
'''since we will use screensize and width/height is not explicit, resolve quality of network to set the scale factor
and return the scale based on combination of screen and network configured in config'''
scale = self._resolvequalityandscale()
'''if forcesize is true then use resize to forcefit else use aspectratio preserving thumbnail'''
if forcesize == True:
img = img.resize((int(img.size[0] * float(scale)), int(img.size[1] * float(scale))), Image.LANCZOS)
self.mylogger.info("Using image resize method - WXH = {}_{}".format(img.width, img.height))
else:
img.thumbnail((int(img.size[0] * float(scale)), int(img.size[1] * float(scale))), Image.LANCZOS)
self.mylogger.info("Using image resize method - WXH = {}_{}".format(img.width, img.height))
'''saved locally'''
return img
'''We will set the optimized value for image generation based on size & bandwidth'''
def _calculatescreenandbandscore(self,size,band):
'''If the sizeinfo is not configured, send the highest sizeinfo available'''
self.mylogger.info(msg='Size & bandwidth is {} & {}'.format(size,band))
#print(self.screens)
try:
size = self.screens[str(size)]
except Exception:
size = max({val:key for key, val in self.screens.items()})
#mylogger.info('Size was not found and defaulting to size {}'.format(size))
'''if input value isnt configured, send the highest bandwidth configured'''
try:
band = self.bandwidth[band.lower()]
except Exception:
band = max({val:key for key, val in self.bandwidth.items()})
#mylogger.info('Bandwidth was not found and defaulting to size {}'.format(bandwidth))
self.sumup = int(size) + int(band)
self.mylogger.info('Sumup value is {}'.format(self.sumup))
'''Based on the network type and screensize the application itself does some approx scaling and returns the scale factor'''
def _resolvequalityandscale(self):
if float(self.sumup) <= 4:
self.sumup = 4
self.qualityscore = self.dynamicqualityvalues.get("4")
return self.scales.get("4")
elif float(self.sumup) <= 7:
'''I am sure screen is of medium size and bandwidth is around 2g'''
self.sumup = 7
self.qualityscore = self.dynamicqualityvalues.get("7")
return self.scales.get("7")
elif float(self.sumup) <=10:
'''I know this is of medium resolution and high bandwidth or high res with low bandwidth'''
self.sumup = 10
self.qualityscore = self.dynamicqualityvalues.get("10")
return self.scales.get("10")
else:
'''I am sure if the value is > 4, its either 3g, 4g etc with higher screensize'''
self.sumup = 11
self.qualityscore = self.dynamicqualityvalues.get("*")
return self.scales.get("*")
'''Resolves only quality setting here and not the screensize of requesting. Use this when we know absolute sizeof image to resize
but need to set the quality of image depending on network being used'''
def _resolvequality(self,band):
try:
band = self.bandwidth[band.lower()]
except Exception:
band = max({val: key for key, val in selfbicu.bandwidth.items()})
# mylogger.info('Bandwidth was not found and defaulting to size {}'.format(bandwidth))
self.sumup = int(band)
if float(self.sumup) <= 1:
'''I am sure this is 2g'''
self.qualityscore = self.bandqualityvalues.get("1")
elif float(self.sumup) < 3:
self.qualityscore = self.bandqualityvalues.get("2")
elif float(self.sumup) < 5:
self.qualityscore = self.bandqualityvalues.get("3")
else:
self.qualityscore = self.bandqualityvalues.get("*")
'''We will execute this more to test when running individual script'''
if __name__ == '__main__':
'''Handle or get the values here from the request handler and set the same'''
'''Replace this with input from handler'''
import cgi
formdata = cgi.FieldStorage()
filename=""
size=""
band=""
width ="0"
height = "0"
forcesize = 0
if formdata.length <= 0:
filename = "nasa.jpeg"
width = "200"
height = "300"
size= '720'
band='*'
else:
filename=""
'''the output automatically will be of json with content type and values set
the output would have 2 keys.
path is imagename & key is unique key for the image & screen, size combination to handle in edge servers like varnish
'''
print('content-type:image/jpg\n')
img = imagehandler()
#i = img.generate(filename,size,band)
i = img.generate(filename, ssize=size,band= band,width=width ,height=height,forcesize=forcesize)
print(i)