Skip to content

Commit be5a019

Browse files
committed
Added metadata reponse header.
- Added "Dirpy-Data" response header with various metadata. Useful when using `save:noshow`. - De-obfuscated some internal variable names. - Version bump to 0.6
1 parent cbec5ae commit be5a019

File tree

1 file changed

+98
-74
lines changed

1 file changed

+98
-74
lines changed

dirpy/__init__.py

+98-74
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import datetime
66
import errno
77
import io
8+
import json
89
import logging
910
import multiprocessing
1011
import os
@@ -52,14 +53,19 @@ def __init__(self, http_root):
5253
self.gravity = None
5354
self.im_in = None
5455
self.in_fmt = None
55-
self.in_info = {}
56-
self.out_fmt = None
56+
self.in_size = 0
5757
self.out_buf = None
5858
self.out_size = 0
59+
self.out_x = 0
60+
self.out_y = 0
61+
self.in_x = 0
62+
self.in_y = 0
63+
self.out_fmt = None
5964
self.save_opts = {}
6065
self.trans = None
6166
self.modified = False
6267
self.http_root = http_root
68+
self.meta_data = "{}"
6369

6470
# Run a command, provided that it is value
6571
def run(self, cmd, opts):
@@ -98,6 +104,7 @@ def load(self, opts, rel_file, req_post_data): ###########################
98104
self.logger.debug("Loading post data: %s" % str(opts))
99105
file_obj = req_post_data
100106
self.file_path = "POST_file"
107+
self.in_size = len(file_obj.getvalue())
101108
else:
102109
raise DirpyUserError("POST prohibited.")
103110

@@ -111,13 +118,15 @@ def load(self, opts, rel_file, req_post_data): ###########################
111118
headers={"User-Agent": "Dirpy/" + __version__})
112119
proxy_res = urllib2.urlopen(proxy_req)
113120
file_obj = io.BytesIO(proxy_res.read())
121+
self.in_size = len(file_obj.getvalue())
114122

115123
# Otherwise read it locally
116124
else:
117125
self.file_path = self.local_file
118126
self.logger.debug("Loading image %s: %s" %
119127
(self.file_path, str(opts)))
120128
file_obj = open(self.file_path, "rb")
129+
self.in_size = os.fstat(file_obj.fileno()).st_size
121130

122131
self.logger.debug("Serving file: %s" % self.file_path)
123132
except Exception as e:
@@ -131,12 +140,11 @@ def load(self, opts, rel_file, req_post_data): ###########################
131140

132141
# Record our input format and dimensions
133142
self.in_fmt = self.im_in.format.lower()
134-
self.in_info = self.im_in.info
143+
self.out_x, self.out_y = self.im_in.size
135144
self.in_x, self.in_y = self.im_in.size
136-
self.orig_x, self.orig_y = self.im_in.size
137145

138146
# Guard against decompression bombs
139-
if cfg.max_pixels and self.in_x * self.in_y > cfg.max_pixels:
147+
if cfg.max_pixels and self.out_x * self.out_y > cfg.max_pixels:
140148
raise DirpyUserError("Image exceeds maximum pixel limit")
141149

142150
except Exception as e:
@@ -207,44 +215,44 @@ def resize(self, opts): ##################################################
207215
if pct:
208216
resize_ratio = float(pct)/100
209217
elif unlock:
210-
resize_ratio = min(float(req_x)/self.in_x,
211-
float(req_y)/self.in_y)
218+
resize_ratio = min(float(req_x)/self.out_x,
219+
float(req_y)/self.out_y)
212220
new_x, new_y = req_x, req_y
213221
elif landscape:
214-
if self.in_x > self.in_y:
215-
resize_ratio = max(float(req_x)/self.in_x,
216-
float(req_y)/self.in_y)
222+
if self.out_x > self.out_y:
223+
resize_ratio = max(float(req_x)/self.out_x,
224+
float(req_y)/self.out_y)
217225
else:
218-
resize_ratio = min(float(req_x)/self.in_x,
219-
float(req_y)/self.in_y)
226+
resize_ratio = min(float(req_x)/self.out_x,
227+
float(req_y)/self.out_y)
220228
elif portrait:
221-
if self.in_x < self.in_y:
222-
resize_ratio = max(float(req_x)/self.in_x,
223-
float(req_y)/self.in_y)
229+
if self.out_x < self.out_y:
230+
resize_ratio = max(float(req_x)/self.out_x,
231+
float(req_y)/self.out_y)
224232
else:
225-
resize_ratio = min(float(req_x)/self.in_x,
226-
float(req_y)/self.in_y)
233+
resize_ratio = min(float(req_x)/self.out_x,
234+
float(req_y)/self.out_y)
227235
else:
228236
if not req_y:
229-
resize_ratio = float(req_x)/self.in_x
237+
resize_ratio = float(req_x)/self.out_x
230238
elif not req_x:
231-
resize_ratio = float(req_y)/self.in_y
239+
resize_ratio = float(req_y)/self.out_y
232240
elif fill:
233-
resize_ratio = max(float(req_x)/self.in_x,
234-
float(req_y)/self.in_y)
241+
resize_ratio = max(float(req_x)/self.out_x,
242+
float(req_y)/self.out_y)
235243
else:
236-
resize_ratio = min(float(req_x)/self.in_x,
237-
float(req_y)/self.in_y)
244+
resize_ratio = min(float(req_x)/self.out_x,
245+
float(req_y)/self.out_y)
238246

239247
# Evaluate our target dimensions to preserve aspect ratio
240248
if new_x is None:
241-
new_x = int(self.in_x * resize_ratio)
249+
new_x = int(self.out_x * resize_ratio)
242250
if new_y is None:
243-
new_y = int(self.in_y * resize_ratio)
251+
new_y = int(self.out_y * resize_ratio)
244252

245253
self.logger.debug(
246-
"Resize: in_x=%s in_y=%s new_x=%s new_y=%s ratio=%s" %
247-
(self.in_x, self.in_y, new_x, new_y, resize_ratio))
254+
"Resize: out_x=%s out_y=%s new_x=%s new_y=%s ratio=%s" %
255+
(self.out_x, self.out_y, new_x, new_y, resize_ratio))
248256

249257
# Now do the actual resize
250258
try:
@@ -255,7 +263,7 @@ def resize(self, opts): ##################################################
255263
self.im_in.draft(None,(new_x,new_y))
256264
self.im_in = self.im_in.resize((new_x, new_y), filter_type)
257265
self.modified = True
258-
self.in_x, self.in_y = self.im_in.size
266+
self.out_x, self.out_y = self.im_in.size
259267
except Exception as e:
260268
raise DirpyFatalError("Error resizing: %s" % e)
261269

@@ -294,15 +302,15 @@ def crop(self, opts): ####################################################
294302

295303
# Make border cropping symmetric, if requested
296304
if "symmetric" in opts:
297-
if new_dims[0] > self.in_x - new_dims[2]:
298-
new_dims[0] = self.in_x - new_dims[2]
299-
elif new_dims[2] < self.in_x - new_dims[0]:
300-
new_dims[2] = self.in_x - new_dims[0]
305+
if new_dims[0] > self.out_x - new_dims[2]:
306+
new_dims[0] = self.out_x - new_dims[2]
307+
elif new_dims[2] < self.out_x - new_dims[0]:
308+
new_dims[2] = self.out_x - new_dims[0]
301309

302-
if new_dims[1] > self.in_y - new_dims[3]:
303-
new_dims[1] = self.in_y - new_dims[3]
304-
elif new_dims[3] < self.in_y - new_dims[1]:
305-
new_dims[3] = self.in_y - new_dims[1]
310+
if new_dims[1] > self.out_y - new_dims[3]:
311+
new_dims[1] = self.out_y - new_dims[3]
312+
elif new_dims[3] < self.out_y - new_dims[1]:
313+
new_dims[3] = self.out_y - new_dims[1]
306314

307315

308316
# Handle gravity crop (i.e. a dimension-based crop)
@@ -312,15 +320,15 @@ def crop(self, opts): ####################################################
312320
# from previous commands which may have shrank the image
313321
# boundary below the specified dimensions
314322

315-
if self.req_dims[0] > self.in_x:
316-
self.req_dims[0] = self.in_x
317-
if self.req_dims[1] > self.in_y:
318-
self.req_dims[1] = self.in_y
323+
if self.req_dims[0] > self.out_x:
324+
self.req_dims[0] = self.out_x
325+
if self.req_dims[1] > self.out_y:
326+
self.req_dims[1] = self.out_y
319327

320328
# Don't bother cropping if our image is the same size as our
321329
# requested crop size
322-
if (self.req_dims[1] == self.in_y
323-
and self.req_dims[0] == self.in_x):
330+
if (self.req_dims[1] == self.out_y
331+
and self.req_dims[0] == self.out_x):
324332
return
325333

326334
new_dims = self._get_new_dims(opts)
@@ -344,8 +352,8 @@ def crop(self, opts): ####################################################
344352
str(self.req_dims))
345353

346354
if (self.req_dims[0] < 0 or self.req_dims[1] < 0 or
347-
self.req_dims[2] > self.in_x or
348-
self.req_dims[3] > self.in_y):
355+
self.req_dims[2] > self.out_x or
356+
self.req_dims[3] > self.out_y):
349357
raise DirpyUserError(
350358
"Crop corners must be inside source image border: %s" %
351359
str(self.req_dims))
@@ -356,14 +364,14 @@ def crop(self, opts): ####################################################
356364
else:
357365
raise DirpyUserError("Crop requires dimensions or coordinates")
358366

359-
self.logger.debug("Crop: in_x=%s in_y=%s crop_box=%s, grav=%s" %
360-
(self.in_x, self.in_y, str(new_dims), self.gravity))
367+
self.logger.debug("Crop: out_x=%s out_y=%s crop_box=%s, grav=%s" %
368+
(self.out_x, self.out_y, str(new_dims), self.gravity))
361369

362370
# Now crop the image
363371
try:
364372
self.im_in = self.im_in.crop(new_dims)
365373
self.im_in.load()
366-
self.in_x, self.in_y = self.im_in.size
374+
self.out_x, self.out_y = self.im_in.size
367375
self.modified = True
368376
except Exception as e:
369377
raise DirpyFatalError("Error cropping: %s" % e)
@@ -381,11 +389,11 @@ def pad(self, opts): #####################################################
381389
raise DirpyUserError("Pad requires no more than 2 dimensions")
382390

383391
# Sanity check
384-
if (self.req_dims[0] < self.in_x or
385-
self.req_dims[1] < self.in_y):
392+
if (self.req_dims[0] < self.out_x or
393+
self.req_dims[1] < self.out_y):
386394
raise DirpyUserError(
387395
"Pad area must be larger than source image: %s < [%s,%s]" %
388-
(str(self.req_dims), self.in_x, self.in_y))
396+
(str(self.req_dims), self.out_x, self.out_y))
389397

390398
# Process transparency request
391399
if "trans" in opts:
@@ -434,7 +442,7 @@ def pad(self, opts): #####################################################
434442
im_pad.putalpha(im_mask)
435443

436444
self.im_in = im_pad
437-
self.in_x, self.in_y = self.im_in.size
445+
self.out_x, self.out_y = self.im_in.size
438446
self.modified = True
439447
except Exception as e:
440448
raise DirpyFatalError(
@@ -472,7 +480,7 @@ def transpose(self, opts): ###############################################
472480
# Now rotate
473481
try:
474482
self.im_in = self.im_in.transpose(method)
475-
self.in_x, self.in_y = self.im_in.size
483+
self.out_x, self.out_y = self.im_in.size
476484
self.modified = True
477485
except Exception as e:
478486
raise DirpyFatalError(
@@ -533,9 +541,9 @@ def save(self, opts): ####################################################
533541
raise Exception("Invalid quality")
534542

535543
# Dont recompress input images that are less than this size
536-
if self.in_x * self.in_y < cfg.min_recompress_pixels:
544+
if self.out_x * self.out_y < cfg.min_recompress_pixels:
537545
self.logger.debug("Not recompressing image: %s < %s" %
538-
(self.in_x * self.in_y,
546+
(self.out_x * self.out_y,
539547
cfg.min_recompress_pixels))
540548
qual_val=95
541549

@@ -580,8 +588,8 @@ def save(self, opts): ####################################################
580588
self.save_opts["format"] = self.out_fmt
581589
if progressive or optimize:
582590
ImageFile.MAXBLOCK = max(
583-
self.orig_x * self.orig_y,
584-
self.in_x * self.in_y, 2097152)
591+
self.in_x * self.in_y,
592+
self.out_x * self.out_y, 2097152)
585593
self.logger.debug("MAXBLOCK set to: %s" % ImageFile.MAXBLOCK)
586594
if optimize:
587595
self.save_opts["optimize"] = True
@@ -639,15 +647,6 @@ def save(self, opts): ####################################################
639647
except Exception as e:
640648
raise DirpyFatalError("Can't save image to disk: %s" % e)
641649

642-
# If the user has requested "noshow", we don't want to return the
643-
# image back to them (presumably because we have saved it to disk
644-
# and that is all they care about, so we don't have to waste
645-
# network overhead sending the image back to them)
646-
if noshow:
647-
logger.debug("Not showing %s, as requested" % self.file_path)
648-
self.out_buf = io.BytesIO()
649-
650-
651650
# Seek to the end of the buffer so we can get our content
652651
# size without allocating to a string (which we don't want
653652
# to do if this is a HEAD request). Then seek back to the
@@ -656,6 +655,25 @@ def save(self, opts): ####################################################
656655
self.out_size = self.out_buf.tell()
657656
self.out_buf.seek(0)
658657

658+
# If the user has requested "noshow", we don't want to return the
659+
# image back to them (presumably because we have saved it to disk
660+
# and that is all they care about, so we don't have to waste
661+
# network overhead sending the image back to them)
662+
if noshow:
663+
logger.debug("Not showing %s, as requested" % self.file_path)
664+
self.out_buf = io.BytesIO()
665+
666+
# Put together some image metadata in JSON format
667+
self.meta_data = json.dumps({
668+
"in_width" : self.in_x,
669+
"in_height" : self.in_y,
670+
"out_width" : self.out_x,
671+
"out_height" : self.out_y,
672+
"in_fmt" : self.in_fmt,
673+
"out_fmt" : self.out_fmt,
674+
"in_bytes" : self.in_size,
675+
"out_bytes" : self.out_size
676+
})
659677

660678
except DirpyFatalError:
661679
raise
@@ -716,26 +734,26 @@ def _get_new_dims(self,opts): ############################################
716734
new_dims = [None, None, None, None]
717735

718736
# Account for requested dimensions with only a single value set
719-
req_x = self.req_dims[0] or self.in_x
720-
req_y = self.req_dims[1] or self.in_y
737+
req_x = self.req_dims[0] or self.out_x
738+
req_y = self.req_dims[1] or self.out_y
721739

722740
# Now calculate dimensions based on gravity
723741
if "w" in self.gravity:
724742
new_dims[0] = 0
725743
elif "e" in self.gravity:
726-
new_dims[0] = abs(self.in_x - req_x)
744+
new_dims[0] = abs(self.out_x - req_x)
727745
else:
728-
new_dims[0] = abs(self.in_x - req_x)/2
746+
new_dims[0] = abs(self.out_x - req_x)/2
729747

730748
if "n" in self.gravity:
731749
new_dims[1] = 0
732750
elif "s" in self.gravity:
733-
new_dims[1] = abs(self.in_y - req_y)
751+
new_dims[1] = abs(self.out_y - req_y)
734752
else:
735-
new_dims[1] = abs(self.in_y - req_y)/2
753+
new_dims[1] = abs(self.out_y - req_y)/2
736754

737-
new_dims[2] = new_dims[0] + min(req_x, self.in_x)
738-
new_dims[3] = new_dims[1] + min(req_y, self.in_y)
755+
new_dims[2] = new_dims[0] + min(req_x, self.out_x)
756+
new_dims[3] = new_dims[1] + min(req_y, self.out_y)
739757

740758
return new_dims
741759

@@ -898,12 +916,14 @@ def http_worker(req, method="GET"): ##########################################
898916

899917
# Throw an error if required
900918
if result.http_code == 204:
919+
req.send_header("Dirpy-Data", result.dirpy_obj.meta_data)
901920
return req.send_response(204)
902921
elif result.err_msg is not None:
903922
return req.send_error(result.http_code, result.err_msg)
904923

905924
# Now fire off a response to our client
906925
req.send_response(200)
926+
req.send_header("Dirpy-Data", result.dirpy_obj.meta_data)
907927
req.send_header("Content-Type", "image/%s" % result.dirpy_obj.out_fmt)
908928
req.send_header("Content-Length", result.dirpy_obj.out_size)
909929
req.end_headers()
@@ -953,14 +973,18 @@ def application(env, resp): ##################################################
953973

954974
# Throw an error if required
955975
if result.http_code == 204:
956-
resp(http_res.resultTxt, [("Content-Type","text/html")])
976+
resp(http_res.resultTxt, [
977+
("Dirpy-Data", result.dirpy_obj.meta_data),
978+
("Content-Type","text/html")
979+
])
957980
return ""
958981
elif result.err_msg is not None:
959982
resp(http_res.resultTxt, [("Content-Type","text/html")])
960983
return result.err_msg
961984

962985
# Now fire off a response to our client
963986
resp("200 OK", [
987+
("Dirpy-Data", result.dirpy_obj.meta_data),
964988
("Content-Type", "image/%s" % result.dirpy_obj.out_fmt),
965989
("Content-Length", str(result.dirpy_obj.out_size)) ]
966990
)

0 commit comments

Comments
 (0)