-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoptimizer.py
More file actions
162 lines (126 loc) · 6.07 KB
/
optimizer.py
File metadata and controls
162 lines (126 loc) · 6.07 KB
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
import os
import re
from pathlib import Path
from typing import Optional, Tuple
import logging
from PIL import Image
import htmlmin
from csscompressor import compress as css_compress
from jsmin import jsmin
import subprocess
import asyncio
import aiofiles
logger = logging.getLogger(__name__)
class ContentOptimizer:
def __init__(self, image_quality: int = 85, max_image_width: int = 1920):
self.image_quality = image_quality
self.max_image_width = max_image_width
async def optimize_html(self, filepath: str) -> int:
"""Minify HTML file"""
try:
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
content = await f.read()
# Remove comments
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
# Minify
minified = htmlmin.minify(content,
remove_comments=True,
remove_empty_space=True,
remove_all_empty_space=True,
reduce_boolean_attributes=True,
remove_optional_attribute_quotes=True)
if minified:
async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
await f.write(minified)
return len(content) - len(minified)
except Exception as e:
logger.error(f"Error optimizing HTML {filepath}: {e}")
return 0
async def optimize_css(self, filepath: str) -> int:
"""Minify CSS file"""
try:
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
content = await f.read()
minified = css_compress(content)
async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
await f.write(minified)
return len(content) - len(minified)
except Exception as e:
logger.error(f"Error optimizing CSS {filepath}: {e}")
return 0
async def optimize_js(self, filepath: str) -> int:
"""Minify JavaScript file"""
try:
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
content = await f.read()
minified = jsmin(content)
async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
await f.write(minified)
return len(content) - len(minified)
except Exception as e:
logger.error(f"Error optimizing JS {filepath}: {e}")
return 0
def optimize_image(self, filepath: str) -> int:
"""Optimize image file"""
try:
original_size = os.path.getsize(filepath)
with Image.open(filepath) as img:
# Convert RGBA to RGB if necessary
if img.mode in ('RGBA', 'LA', 'P'):
rgb_img = Image.new('RGB', img.size, (255, 255, 255))
rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
img = rgb_img
# Resize if too large
if img.width > self.max_image_width:
ratio = self.max_image_width / img.width
new_height = int(img.height * ratio)
img = img.resize((self.max_image_width, new_height), Image.Resampling.LANCZOS)
# Save optimized image
output_path = filepath.replace('.png', '.jpg').replace('.bmp', '.jpg')
# Try to save as WebP if available
try:
webp_path = Path(filepath).with_suffix('.webp')
img.save(webp_path, 'WEBP', quality=self.image_quality, method=6)
# Remove original if WebP is smaller
if os.path.getsize(webp_path) < original_size * 0.9:
os.remove(filepath)
return original_size - os.path.getsize(webp_path)
else:
os.remove(webp_path)
except:
pass
# Fallback to JPEG
img.save(output_path, 'JPEG', quality=self.image_quality, optimize=True)
if output_path != filepath:
os.remove(filepath)
return original_size - os.path.getsize(output_path)
except Exception as e:
logger.error(f"Error optimizing image {filepath}: {e}")
return 0
async def optimize_svg(self, filepath: str) -> int:
"""Optimize SVG file using SVGO if available"""
try:
original_size = os.path.getsize(filepath)
# Try using SVGO if installed
result = await asyncio.create_subprocess_exec(
'svgo', filepath, '-o', filepath,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await result.communicate()
return original_size - os.path.getsize(filepath)
except FileNotFoundError:
# SVGO not installed, do basic optimization
try:
async with aiofiles.open(filepath, 'r', encoding='utf-8') as f:
content = await f.read()
# Remove comments and unnecessary whitespace
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
content = re.sub(r'>\s+<', '><', content)
content = re.sub(r'\s+', ' ', content)
async with aiofiles.open(filepath, 'w', encoding='utf-8') as f:
await f.write(content)
return original_size - os.path.getsize(filepath)
except Exception as e:
logger.error(f"Error optimizing SVG {filepath}: {e}")
return 0