1010import io
1111from PIL import Image as PILImage
1212
13+ def calculate_target_params (content_length ):
14+ """Calculate initial quality and size based on input file size"""
15+ target_size = 350000 # Reduced target for more safety margin
16+ ratio = content_length / target_size
17+
18+ logger .debug ("Calculating target parameters" ,
19+ content_length = content_length ,
20+ ratio = ratio ,
21+ target_size = target_size )
22+
23+ if ratio > 4 :
24+ # Very large images - start very aggressive
25+ return 50 , 600 # Lower initial quality and size
26+ elif ratio > 2 :
27+ return 60 , 800
28+ else :
29+ return 70 , 1000
30+
1331def resize_image (img , max_size ):
1432 """Resize image maintaining aspect ratio"""
1533 original_dimensions = {"width" : img .width , "height" : img .height }
@@ -26,29 +44,37 @@ def resize_image(img, max_size):
2644 logger .debug ("No resize needed" , dimensions = original_dimensions )
2745 return img
2846
29- def optimize_image (img , max_output_bytes = 500000 ): # 500KB limit
30- """Iteratively optimize image until it's under max_output_bytes"""
31- logger .debug ("Starting optimization" ,
32- dimensions = {"width" : img .width , "height" : img .height },
33- mode = img .mode ,
34- max_bytes = max_output_bytes )
47+ def optimize_image (img , content_length , max_output_bytes = 350000 ):
48+ """Iteratively optimize image with aggressive size reduction"""
49+ stats = {
50+ "dimensions" : {"width" : img .width , "height" : img .height },
51+ "mode" : img .mode ,
52+ "estimated_memory" : (img .width * img .height * len (img .getbands ()))
53+ }
3554
36- # Start with higher quality for better color preservation
37- quality = 60
55+ initial_quality , initial_size = calculate_target_params (content_length )
3856
39- # Make initial size relative to input dimensions
40- initial_size = min (800 , max (img .width , img .height ))
57+ logger .debug ("Starting optimization" ,
58+ image_stats = stats ,
59+ content_length = content_length ,
60+ initial_quality = initial_quality ,
61+ initial_size = initial_size ,
62+ max_output_bytes = max_output_bytes )
63+
64+ quality = initial_quality
4165 size = initial_size
4266
43- original_mode = img . mode
67+ # Convert to RGB if needed
4468 if img .mode in ('RGBA' , 'LA' ) or (img .mode == 'P' and 'transparency' in img .info ):
45- # Keep original mode info for logging
4669 img = img .convert ('RGB' )
47- logger .debug ("Converted color mode" ,
48- from_mode = original_mode ,
49- to_mode = 'RGB' )
70+ logger .debug ("Converted to RGB mode" )
71+
72+ iteration = 0
73+ min_size = 300 # Absolute minimum size
74+ min_quality = 20 # Absolute minimum quality
5075
5176 while True :
77+ iteration += 1
5278 buf = io .BytesIO ()
5379 resized = resize_image (img , size )
5480
@@ -59,37 +85,42 @@ def optimize_image(img, max_output_bytes=500000): # 500KB limit
5985 subsampling = '4:2:0' )
6086
6187 output_size = buf .getbuffer ().nbytes
88+ reduction_ratio = output_size / content_length
89+
6290 logger .debug ("Optimization attempt" ,
91+ iteration = iteration ,
6392 quality = quality ,
6493 size = size ,
6594 output_bytes = output_size ,
66- target_bytes = max_output_bytes )
95+ target_bytes = max_output_bytes ,
96+ reduction_ratio = f"{ reduction_ratio :.2f} " )
6797
6898 if output_size < max_output_bytes :
69- compression_ratio = output_size / max_output_bytes
7099 logger .info ("Image optimization complete" ,
71100 final_size = output_size ,
72101 quality = quality ,
73102 dimensions = {"width" : resized .width , "height" : resized .height },
74- compression_ratio = compression_ratio )
103+ reduction_ratio = f" { reduction_ratio :.2f } " )
75104 return buf .getvalue ()
76-
77- # More gradual quality reduction for better color preservation
78- if quality > 30 :
79- quality_step = 5 if quality > 50 else 10
80- quality -= quality_step
81- logger .debug ("Reducing quality" ,
82- new_quality = quality ,
83- step = quality_step )
84- # Smaller size reduction steps
85- elif size > 300 :
86- size_step = 25 if size > 600 else 50
87- size -= size_step
88- logger .debug ("Reducing size" ,
89- new_size = size ,
90- step = size_step )
105+
106+ # Very aggressive reduction for large files
107+ if content_length > 2000000 : # 2MB+
108+ quality = max (min_quality , quality - 20 )
109+ size = max (min_size , int (size * 0.6 ))
110+ elif content_length > 1000000 : # 1MB+
111+ quality = max (min_quality , quality - 15 )
112+ size = max (min_size , int (size * 0.7 ))
91113 else :
92- logger .warning ("Reached minimum optimization parameters" ,
114+ quality = max (min_quality , quality - 10 )
115+ size = max (min_size , int (size * 0.8 ))
116+
117+ logger .debug ("Reducing parameters" ,
118+ new_quality = quality ,
119+ new_size = size )
120+
121+ # If we've hit minimum values and still too big
122+ if quality <= min_quality and size <= min_size :
123+ logger .warning ("Reached minimum parameters" ,
93124 final_size = output_size ,
94125 over_limit_by = output_size - max_output_bytes )
95126 return buf .getvalue ()
@@ -123,7 +154,7 @@ async def read_resource(path: str) -> dict:
123154 elif content_type .startswith ("image/" ):
124155 logger .debug ("Processing image" )
125156 img = PILImage .open (io .BytesIO (response .content ))
126- img_bytes = optimize_image (img )
157+ img_bytes = optimize_image (img , content_length )
127158
128159 return {
129160 "type" : "image" ,
@@ -137,6 +168,13 @@ async def read_resource(path: str) -> dict:
137168 # Handle other file types
138169 else :
139170 logger .debug ("Processing binary resource" )
171+ if content_length > 350000 :
172+ logger .warning ("Document too large for response" ,
173+ size = content_length )
174+ return {
175+ "type" : "error" ,
176+ "error" : f"Document size { content_length } bytes exceeds maximum allowed size"
177+ }
140178 return {
141179 "type" : "document" ,
142180 "source" : {
0 commit comments