5
5
import datetime
6
6
import errno
7
7
import io
8
+ import json
8
9
import logging
9
10
import multiprocessing
10
11
import os
@@ -52,14 +53,19 @@ def __init__(self, http_root):
52
53
self .gravity = None
53
54
self .im_in = None
54
55
self .in_fmt = None
55
- self .in_info = {}
56
- self .out_fmt = None
56
+ self .in_size = 0
57
57
self .out_buf = None
58
58
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
59
64
self .save_opts = {}
60
65
self .trans = None
61
66
self .modified = False
62
67
self .http_root = http_root
68
+ self .meta_data = "{}"
63
69
64
70
# Run a command, provided that it is value
65
71
def run (self , cmd , opts ):
@@ -98,6 +104,7 @@ def load(self, opts, rel_file, req_post_data): ###########################
98
104
self .logger .debug ("Loading post data: %s" % str (opts ))
99
105
file_obj = req_post_data
100
106
self .file_path = "POST_file"
107
+ self .in_size = len (file_obj .getvalue ())
101
108
else :
102
109
raise DirpyUserError ("POST prohibited." )
103
110
@@ -111,13 +118,15 @@ def load(self, opts, rel_file, req_post_data): ###########################
111
118
headers = {"User-Agent" : "Dirpy/" + __version__ })
112
119
proxy_res = urllib2 .urlopen (proxy_req )
113
120
file_obj = io .BytesIO (proxy_res .read ())
121
+ self .in_size = len (file_obj .getvalue ())
114
122
115
123
# Otherwise read it locally
116
124
else :
117
125
self .file_path = self .local_file
118
126
self .logger .debug ("Loading image %s: %s" %
119
127
(self .file_path , str (opts )))
120
128
file_obj = open (self .file_path , "rb" )
129
+ self .in_size = os .fstat (file_obj .fileno ()).st_size
121
130
122
131
self .logger .debug ("Serving file: %s" % self .file_path )
123
132
except Exception as e :
@@ -131,12 +140,11 @@ def load(self, opts, rel_file, req_post_data): ###########################
131
140
132
141
# Record our input format and dimensions
133
142
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
135
144
self .in_x , self .in_y = self .im_in .size
136
- self .orig_x , self .orig_y = self .im_in .size
137
145
138
146
# 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 :
140
148
raise DirpyUserError ("Image exceeds maximum pixel limit" )
141
149
142
150
except Exception as e :
@@ -207,44 +215,44 @@ def resize(self, opts): ##################################################
207
215
if pct :
208
216
resize_ratio = float (pct )/ 100
209
217
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 )
212
220
new_x , new_y = req_x , req_y
213
221
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 )
217
225
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 )
220
228
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 )
224
232
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 )
227
235
else :
228
236
if not req_y :
229
- resize_ratio = float (req_x )/ self .in_x
237
+ resize_ratio = float (req_x )/ self .out_x
230
238
elif not req_x :
231
- resize_ratio = float (req_y )/ self .in_y
239
+ resize_ratio = float (req_y )/ self .out_y
232
240
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 )
235
243
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 )
238
246
239
247
# Evaluate our target dimensions to preserve aspect ratio
240
248
if new_x is None :
241
- new_x = int (self .in_x * resize_ratio )
249
+ new_x = int (self .out_x * resize_ratio )
242
250
if new_y is None :
243
- new_y = int (self .in_y * resize_ratio )
251
+ new_y = int (self .out_y * resize_ratio )
244
252
245
253
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 ))
248
256
249
257
# Now do the actual resize
250
258
try :
@@ -255,7 +263,7 @@ def resize(self, opts): ##################################################
255
263
self .im_in .draft (None ,(new_x ,new_y ))
256
264
self .im_in = self .im_in .resize ((new_x , new_y ), filter_type )
257
265
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
259
267
except Exception as e :
260
268
raise DirpyFatalError ("Error resizing: %s" % e )
261
269
@@ -294,15 +302,15 @@ def crop(self, opts): ####################################################
294
302
295
303
# Make border cropping symmetric, if requested
296
304
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 ]
301
309
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 ]
306
314
307
315
308
316
# Handle gravity crop (i.e. a dimension-based crop)
@@ -312,15 +320,15 @@ def crop(self, opts): ####################################################
312
320
# from previous commands which may have shrank the image
313
321
# boundary below the specified dimensions
314
322
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
319
327
320
328
# Don't bother cropping if our image is the same size as our
321
329
# 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 ):
324
332
return
325
333
326
334
new_dims = self ._get_new_dims (opts )
@@ -344,8 +352,8 @@ def crop(self, opts): ####################################################
344
352
str (self .req_dims ))
345
353
346
354
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 ):
349
357
raise DirpyUserError (
350
358
"Crop corners must be inside source image border: %s" %
351
359
str (self .req_dims ))
@@ -356,14 +364,14 @@ def crop(self, opts): ####################################################
356
364
else :
357
365
raise DirpyUserError ("Crop requires dimensions or coordinates" )
358
366
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 ))
361
369
362
370
# Now crop the image
363
371
try :
364
372
self .im_in = self .im_in .crop (new_dims )
365
373
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
367
375
self .modified = True
368
376
except Exception as e :
369
377
raise DirpyFatalError ("Error cropping: %s" % e )
@@ -381,11 +389,11 @@ def pad(self, opts): #####################################################
381
389
raise DirpyUserError ("Pad requires no more than 2 dimensions" )
382
390
383
391
# 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 ):
386
394
raise DirpyUserError (
387
395
"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 ))
389
397
390
398
# Process transparency request
391
399
if "trans" in opts :
@@ -434,7 +442,7 @@ def pad(self, opts): #####################################################
434
442
im_pad .putalpha (im_mask )
435
443
436
444
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
438
446
self .modified = True
439
447
except Exception as e :
440
448
raise DirpyFatalError (
@@ -472,7 +480,7 @@ def transpose(self, opts): ###############################################
472
480
# Now rotate
473
481
try :
474
482
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
476
484
self .modified = True
477
485
except Exception as e :
478
486
raise DirpyFatalError (
@@ -533,9 +541,9 @@ def save(self, opts): ####################################################
533
541
raise Exception ("Invalid quality" )
534
542
535
543
# 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 :
537
545
self .logger .debug ("Not recompressing image: %s < %s" %
538
- (self .in_x * self .in_y ,
546
+ (self .out_x * self .out_y ,
539
547
cfg .min_recompress_pixels ))
540
548
qual_val = 95
541
549
@@ -580,8 +588,8 @@ def save(self, opts): ####################################################
580
588
self .save_opts ["format" ] = self .out_fmt
581
589
if progressive or optimize :
582
590
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 )
585
593
self .logger .debug ("MAXBLOCK set to: %s" % ImageFile .MAXBLOCK )
586
594
if optimize :
587
595
self .save_opts ["optimize" ] = True
@@ -639,15 +647,6 @@ def save(self, opts): ####################################################
639
647
except Exception as e :
640
648
raise DirpyFatalError ("Can't save image to disk: %s" % e )
641
649
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
-
651
650
# Seek to the end of the buffer so we can get our content
652
651
# size without allocating to a string (which we don't want
653
652
# to do if this is a HEAD request). Then seek back to the
@@ -656,6 +655,25 @@ def save(self, opts): ####################################################
656
655
self .out_size = self .out_buf .tell ()
657
656
self .out_buf .seek (0 )
658
657
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
+ })
659
677
660
678
except DirpyFatalError :
661
679
raise
@@ -716,26 +734,26 @@ def _get_new_dims(self,opts): ############################################
716
734
new_dims = [None , None , None , None ]
717
735
718
736
# 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
721
739
722
740
# Now calculate dimensions based on gravity
723
741
if "w" in self .gravity :
724
742
new_dims [0 ] = 0
725
743
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 )
727
745
else :
728
- new_dims [0 ] = abs (self .in_x - req_x )/ 2
746
+ new_dims [0 ] = abs (self .out_x - req_x )/ 2
729
747
730
748
if "n" in self .gravity :
731
749
new_dims [1 ] = 0
732
750
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 )
734
752
else :
735
- new_dims [1 ] = abs (self .in_y - req_y )/ 2
753
+ new_dims [1 ] = abs (self .out_y - req_y )/ 2
736
754
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 )
739
757
740
758
return new_dims
741
759
@@ -898,12 +916,14 @@ def http_worker(req, method="GET"): ##########################################
898
916
899
917
# Throw an error if required
900
918
if result .http_code == 204 :
919
+ req .send_header ("Dirpy-Data" , result .dirpy_obj .meta_data )
901
920
return req .send_response (204 )
902
921
elif result .err_msg is not None :
903
922
return req .send_error (result .http_code , result .err_msg )
904
923
905
924
# Now fire off a response to our client
906
925
req .send_response (200 )
926
+ req .send_header ("Dirpy-Data" , result .dirpy_obj .meta_data )
907
927
req .send_header ("Content-Type" , "image/%s" % result .dirpy_obj .out_fmt )
908
928
req .send_header ("Content-Length" , result .dirpy_obj .out_size )
909
929
req .end_headers ()
@@ -953,14 +973,18 @@ def application(env, resp): ##################################################
953
973
954
974
# Throw an error if required
955
975
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
+ ])
957
980
return ""
958
981
elif result .err_msg is not None :
959
982
resp (http_res .resultTxt , [("Content-Type" ,"text/html" )])
960
983
return result .err_msg
961
984
962
985
# Now fire off a response to our client
963
986
resp ("200 OK" , [
987
+ ("Dirpy-Data" , result .dirpy_obj .meta_data ),
964
988
("Content-Type" , "image/%s" % result .dirpy_obj .out_fmt ),
965
989
("Content-Length" , str (result .dirpy_obj .out_size )) ]
966
990
)
0 commit comments