├── .gitignore
├── .pylintrc
├── .vscode
└── settings.json
├── README.md
├── comepress.py
├── comepress.spec
├── dist
└── res
│ ├── checked.svg
│ ├── folder.png
│ ├── inbox_tray_3d.ico
│ ├── logo.png
│ └── unchecked.svg
├── requirements.txt
└── res
├── checked.svg
├── comepress.ui
├── folder.png
├── inbox_tray_3d.ico
├── logo.png
└── unchecked.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/*.zip
3 | dist/comepress
4 | env/
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | extension-pkg-whitelist=PyQt5
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.flake8Enabled": false,
3 | "python.linting.pydocstyleEnabled": false,
4 | "python.linting.pylintEnabled": true,
5 | "python.linting.enabled": true
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 📥 Comepress
4 | #### Super trivial app to optimize your web project by converting all PNG, JPG and JPEG images to Next-Gen WebP format
5 | Just drag and drop your files or folders and that's it!
6 | 
7 | 
8 |
9 |
10 |
11 | # Reduce your image bundle size by 40-80%
12 | #### That means faster site loading for users and smaller project size!
13 |
14 | ## Here's a small comparison:
15 | | Without Comepress | With Comepress |
16 | |-------------------|----------------|
17 | |||
18 | # With Comepress I easily got over 43% space gain!
19 | The images generated by Comepress are currently lossy but the quality is indistinguishable, so you can use this in production without any worries.
20 |
21 | # Installation 👨💻
22 |
23 | ## Linux 🐧
24 |
25 | 1. Download the Linux version from here: https://github.com/NayamAmarshe/comepress/releases/latest
26 | 2. Extract the 7z file.
27 | 3. Double click and run the `comepress` executable file.
28 |
29 | ## MacOS 🍎
30 |
31 | Coming Soon
32 |
33 | ## Windows 🔳
34 |
35 | Coming Soon
36 |
37 | # Build Instructions 🛠
38 |
39 | Please help me generate builds for MacOS and Windows.
40 |
41 | 1. Install dependencies:
42 | ```bash
43 | pip3 install pyqt5 pyinstaller
44 | ```
45 | 2. To run comepress as is, run:
46 | ```bash
47 | python3 comepress.py
48 | ```
49 | 3. To build comepress:
50 | ```bash
51 | pyinstaller comepress.py --add-data "./res/*:res" --onefile --icon='res/inbox_tray_3d.ico' --windowed
52 | ```
53 |
54 | An executable file will be created in `dist` folder.
55 |
56 | # Credits ⛑
57 | - [@TGS963](https://github.com/TGS963) for helping me with the project.
58 | - Microsoft for their open source Fluent 3D Emoji Icons.
59 | - Python and Qt5
60 |
--------------------------------------------------------------------------------
/comepress.py:
--------------------------------------------------------------------------------
1 | import fnmatch
2 | import os
3 | import shutil
4 | import sys
5 | from os.path import isdir, join
6 |
7 | from PIL import Image
8 | from PyQt5 import QtCore, QtGui, QtWidgets, uic
9 | from PyQt5.QtGui import QCursor
10 | from PyQt5.QtWidgets import *
11 |
12 |
13 | bundle_dir = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
14 |
15 |
16 | def restart_program():
17 | python = sys.executable
18 | os.execl(python, python, *sys.argv)
19 |
20 |
21 | def include_patterns(*patterns):
22 | def _ignore_patterns(path, all_names):
23 | # Determine names which match one or more patterns (that shouldn't be
24 | # ignored).
25 | keep = (
26 | name for pattern in patterns for name in fnmatch.filter(all_names, pattern)
27 | )
28 | # Ignore file names which *didn't* match any of the patterns given that
29 | # aren't directory names.
30 | dir_names = (name for name in all_names if isdir(join(path, name)))
31 | return set(all_names) - set(keep) - set(dir_names)
32 |
33 | return _ignore_patterns
34 |
35 |
36 | def ignore_list(path, files):
37 | filesToIgnore = []
38 | for fileName in files:
39 | fullFileName = os.path.join(os.path.normpath(path), fileName)
40 | if (
41 | not os.path.isdir(fullFileName)
42 | and not fileName.endswith("jpg")
43 | and not fileName.endswith("jpeg")
44 | and not fileName.endswith("png")
45 | and not fileName.endswith("mp4")
46 | ):
47 | filesToIgnore.append(fileName)
48 | return filesToIgnore
49 |
50 |
51 | class MyGUI(QMainWindow):
52 | def __init__(self):
53 | super(MyGUI, self).__init__()
54 | uic.loadUi(os.path.abspath(os.path.join(bundle_dir, "res/comepress.ui")), self)
55 |
56 | # Remove Titlebar and background
57 | self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
58 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
59 | self.setStyleSheet(
60 | """QToolTip {
61 | border: none;
62 | color: white;
63 | }"""
64 | )
65 | self.setWindowIcon(QtGui.QIcon("res/inbox_tray_3d.ico"))
66 | # BUTTONS
67 | self.pushButton.clicked.connect(self.browse)
68 |
69 | self.checkBox.clicked.connect(self.checked)
70 | self.checkBox.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
71 |
72 | self.closeButton.clicked.connect(self.close)
73 | self.closeButton.setToolTip("Close")
74 | self.closeButton.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
75 |
76 | self.minimizeButton.clicked.connect(self.showMinimized)
77 | self.minimizeButton.setToolTip("Minimize")
78 | self.minimizeButton.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
79 |
80 | self.comboBox.activated.connect(self.comboBoxSelected)
81 |
82 | self.horizontalSlider.valueChanged.connect(self.horizontalSliderChanged)
83 | self.setAcceptDrops(True)
84 |
85 | # DEFAULT VARIABLES
86 | self.backup = True
87 | self.dragPos = QtCore.QPoint()
88 | self.quality = 100
89 | self.percentageLabel.setText(f"{self.quality}")
90 | self.format = "JPG"
91 |
92 | self.show()
93 |
94 | def mousePressEvent(self, event):
95 | self.dragPos = event.globalPos()
96 |
97 | def mouseMoveEvent(self, event):
98 | if event.buttons() == QtCore.Qt.LeftButton:
99 | self.move(self.pos() + event.globalPos() - self.dragPos)
100 | self.dragPos = event.globalPos()
101 | event.accept()
102 |
103 | def checked(self):
104 | self.backup = bool(self.checkBox.isChecked())
105 |
106 | def browse(self):
107 | folder_path = QtWidgets.QFileDialog.getExistingDirectory(
108 | self, "Select a Folder"
109 | )
110 | if folder_path == "":
111 | return
112 | self.comepress_folder(folder_path)
113 | alert_dialog = QMessageBox.information(
114 | self, "All good!", "Successfully comepressed all files/folders"
115 | )
116 |
117 | def comboBoxSelected(self):
118 | self.format = self.comboBox.currentText()
119 |
120 | def horizontalSliderChanged(self):
121 | self.percentageLabel.setText(f"{self.horizontalSlider.value()}")
122 | self.quality = self.horizontalSlider.value()
123 |
124 | def dragEnterEvent(self, event):
125 | if event.mimeData().hasUrls():
126 | event.accept()
127 | else:
128 | event.ignore()
129 |
130 | def dropEvent(self, event):
131 | allowed_types = ["image/jpeg", "image/jpg", "image/png", "inode/directory"]
132 | dropped_files_folders = []
133 | db = QtCore.QMimeDatabase()
134 |
135 | for url in event.mimeData().urls():
136 | mimetype = db.mimeTypeForUrl(url)
137 | if mimetype.name() in allowed_types:
138 | dropped_files_folders.append((url.toLocalFile(), mimetype.name()))
139 |
140 | for file_folder_path in dropped_files_folders:
141 | if file_folder_path[1] == "inode/directory":
142 | self.comepress_folder(file_folder_path[0])
143 |
144 | else:
145 | self.comepress_file(file_folder_path[0])
146 | alert_dialog = QMessageBox.information(
147 | self, "All good!", "Successfully comepressed all files/folders"
148 | )
149 |
150 | def comepress_folder(self, folder_path):
151 | # Get parent folder path
152 | # Get folder name
153 | folder_name = os.path.basename(folder_path)
154 | # Destination backup
155 | if os.path.isdir(f"{folder_path}_COMEPRESS"):
156 | shutil.rmtree(f"{folder_path}_COMEPRESS")
157 | backup_destination = os.path.abspath(f"{folder_path}_COMEPRESS")
158 | # Backup folder
159 | print(f"backup: {self.backup}")
160 | if self.backup:
161 | shutil.copytree(
162 | folder_path,
163 | backup_destination,
164 | ignore=include_patterns("*.png", "*.jpg", "*.jpeg"),
165 | )
166 |
167 | # Loop through all the files in the folder
168 | for root, dirs, files in os.walk(
169 | folder_path,
170 | ):
171 | for file in files:
172 | # If file is an image
173 | if file.endswith((".jpg", ".jpeg", ".png", ".webp")):
174 | # Convert to WebP
175 | try:
176 | img = Image.open(f"{root}/{file}")
177 | img.save(
178 | (
179 | f"{root}/{file.rsplit('.', 1)[0]}.{self.format}",
180 | self.format,
181 | ),
182 | quality=self.quality,
183 | optimize=(self.quality < 100),
184 | )
185 | # Remove original file
186 | os.remove(f"{root}/{file}")
187 | except Exception:
188 | alert_dialog = QMessageBox.information(
189 | self,
190 | "Error!",
191 | f"Please check if {root}/{file} is not corrupt",
192 | )
193 |
194 | return
195 |
196 | def comepress_file(self, file_path):
197 | # GET DETAILS
198 | parent_folder = os.path.abspath(os.path.join(file_path, os.pardir))
199 | file_name = os.path.basename(file_path)
200 | destination_path = f"{parent_folder}/ORIGINAL_{file_name}"
201 | # BACKUP
202 | if self.backup:
203 | shutil.copyfile(file_path, destination_path)
204 | # CONVERT
205 | try:
206 | img = Image.open(f"{parent_folder}/{file_name}")
207 | img.save(
208 | ((f"{parent_folder}/" + file_name.rsplit(".", 1)[0]) + ".webp"), "webp"
209 | )
210 | os.remove(f"{parent_folder}/{file_name}")
211 | except Exception:
212 | alert_dialog = QMessageBox.information(
213 | self,
214 | "Error!",
215 | f"Please check if {parent_folder}/{file_name} is not corrupt",
216 | )
217 | return
218 |
219 |
220 | def main():
221 | app = QApplication([])
222 | app.setWindowIcon(QtGui.QIcon("res/inbox_tray_3d.ico"))
223 | window = MyGUI()
224 | app.exec()
225 |
226 |
227 | if __name__ == "__main__":
228 | main()
229 |
--------------------------------------------------------------------------------
/comepress.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python ; coding: utf-8 -*-
2 |
3 |
4 | block_cipher = None
5 |
6 |
7 | a = Analysis(
8 | ['comepress.py'],
9 | pathex=[],
10 | binaries=[],
11 | datas=[('./res/*', 'res')],
12 | hiddenimports=[],
13 | hookspath=[],
14 | hooksconfig={},
15 | runtime_hooks=[],
16 | excludes=[],
17 | win_no_prefer_redirects=False,
18 | win_private_assemblies=False,
19 | cipher=block_cipher,
20 | noarchive=False,
21 | )
22 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
23 |
24 | exe = EXE(
25 | pyz,
26 | a.scripts,
27 | a.binaries,
28 | a.zipfiles,
29 | a.datas,
30 | [],
31 | name='comepress',
32 | debug=False,
33 | bootloader_ignore_signals=False,
34 | strip=False,
35 | upx=True,
36 | upx_exclude=[],
37 | runtime_tmpdir=None,
38 | console=False,
39 | disable_windowed_traceback=False,
40 | argv_emulation=False,
41 | target_arch=None,
42 | codesign_identity=None,
43 | entitlements_file=None,
44 | icon='res/inbox_tray_3d.ico',
45 | )
46 |
--------------------------------------------------------------------------------
/dist/res/checked.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dist/res/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/dist/res/folder.png
--------------------------------------------------------------------------------
/dist/res/inbox_tray_3d.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/dist/res/inbox_tray_3d.ico
--------------------------------------------------------------------------------
/dist/res/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/dist/res/logo.png
--------------------------------------------------------------------------------
/dist/res/unchecked.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Pillow==9.5.0
2 | Pillow==9.5.0
3 | PyQt5==5.15.9
4 | PyQt5_sip==12.12.1
5 |
--------------------------------------------------------------------------------
/res/checked.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/comepress.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 500
10 | 684
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 | 1.000000000000000
18 |
19 |
20 |
21 |
22 |
23 | 10
24 | 10
25 | 480
26 | 661
27 |
28 |
29 |
30 | QFrame {
31 | background: #28262b;
32 | border-radius: 25px;
33 | border: 2px solid #65606C;
34 | }
35 |
36 | QHBoxLayout {
37 | border: none;
38 | }
39 |
40 |
41 | QFrame::StyledPanel
42 |
43 |
44 | QFrame::Raised
45 |
46 |
47 |
48 |
49 | 30
50 | 140
51 | 420
52 | 250
53 |
54 |
55 |
56 | false
57 |
58 |
59 | background: #2a2c2e;
60 | border-radius: 25px;
61 | border: 2px solid #323134;
62 |
63 |
64 | QFrame::StyledPanel
65 |
66 |
67 | QFrame::Raised
68 |
69 |
70 |
71 |
72 | 150
73 | 40
74 | 126
75 | 121
76 |
77 |
78 |
79 | border-image: url(res/folder.png) 0 0 0 0 stretch stretch;
80 |
81 |
82 | false
83 |
84 |
85 |
86 |
87 |
88 | 70
89 | 170
90 | 271
91 | 41
92 |
93 |
94 |
95 |
96 | Montserrat
97 | 50
98 | false
99 |
100 |
101 |
102 | border: none;
103 | border-radius: none;
104 | background: none;
105 | color: #ecedec;
106 |
107 |
108 | Drag and Drop your Files or Folders here or Click to Select Files...
109 |
110 |
111 | Qt::AlignCenter
112 |
113 |
114 | true
115 |
116 |
117 |
118 |
119 | true
120 |
121 |
122 |
123 | 0
124 | 0
125 | 421
126 | 251
127 |
128 |
129 |
130 | PointingHandCursor
131 |
132 |
133 | true
134 |
135 |
136 | false
137 |
138 |
139 | border:none; opacity: 0%; background: transparent;
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | 0
150 | 610
151 | 480
152 | 40
153 |
154 |
155 |
156 |
157 | Montserrat
158 | false
159 | true
160 |
161 |
162 |
163 | border: none;
164 | border-radius: none;
165 | background: none;
166 | color: #ecedec;
167 |
168 |
169 |
170 | 1
171 |
172 |
173 | <html><head/><body><p align="center">Copyright © 2022 <span style=" font-weight:600;">Comepress</span> <br/> Made by <span style=" font-weight:600;">Nayam Amarshe</span> with ⌨ and 🖱</p></body></html>
174 |
175 |
176 |
177 |
178 |
179 | 170
180 | 490
181 | 151
182 | 28
183 |
184 |
185 |
186 |
187 | 0
188 | 0
189 |
190 |
191 |
192 | QCheckBox {
193 | border: none;
194 | background:none;
195 | color: #ecedec;
196 | }
197 |
198 | QCheckBox::indicator {
199 | width: 18px;
200 | height: 18px;
201 | }
202 |
203 | QCheckBox::indicator:checked {
204 | image: url("res/checked.svg")
205 | }
206 |
207 | QCheckBox::indicator:unchecked {
208 | image: url("res/unchecked.svg")
209 | }
210 |
211 |
212 |
213 | Backup Original Files
214 |
215 |
216 | true
217 |
218 |
219 | false
220 |
221 |
222 | false
223 |
224 |
225 |
226 |
227 |
228 | 0
229 | 10
230 | 480
231 | 35
232 |
233 |
234 |
235 | border: none;
236 | border-radius: none;
237 | background: none;
238 | border-image: url(res/logo.png);
239 |
240 |
241 |
242 |
243 |
244 | 450
245 | 15
246 | 16
247 | 16
248 |
249 |
250 |
251 | QPushButton {
252 | font-weight: bold;
253 | background: #2a2c2e;
254 | border: none;
255 | border-radius: 7px;
256 | background: #ff0901;
257 | }
258 |
259 | QPushButton:hover{
260 | background: #A40005;
261 | }
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 | 425
271 | 15
272 | 16
273 | 16
274 |
275 |
276 |
277 | QPushButton {
278 | font-weight: bold;
279 | background: #feba41;
280 | border: none;
281 | border-radius: 7px;
282 | }
283 |
284 | QPushButton:hover {
285 | background: #C08D31;
286 | }
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 | 140
296 | 440
297 | 221
298 | 41
299 |
300 |
301 |
302 | -
303 |
304 |
305 | border: none;
306 | color: #fff;
307 |
308 |
309 | Quality
310 |
311 |
312 |
313 | -
314 |
315 |
316 | QSlider {
317 | border: none;
318 | border-radius:5px;
319 | }
320 | QSlider::groove:horizontal {
321 | height: 10px;
322 | background: #404040;
323 | border-radius:5px;
324 | }
325 |
326 | QSlider::handle:horizontal {
327 | background: #7e22ce;
328 | width: 18px;
329 | margin: -4px 0;
330 | border-radius: 9px;
331 | }
332 |
333 | QSlider::add-page:horizontal {
334 | background: #404040;
335 | border-radius:5px;
336 |
337 | }
338 |
339 | QSlider::sub-page:horizontal {
340 | background: #7e22ce;
341 | border-radius:5px;
342 |
343 | }
344 |
345 |
346 | 1
347 |
348 |
349 | 100
350 |
351 |
352 | 100
353 |
354 |
355 | Qt::Horizontal
356 |
357 |
358 |
359 | -
360 |
361 |
362 | border: none;
363 | color: #fff;
364 |
365 |
366 | 90
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 | 140
376 | 400
377 | 221
378 | 41
379 |
380 |
381 |
382 | -
383 |
384 |
385 | border: none;
386 | border-radius: none;
387 | background: none;
388 | color: #ecedec;
389 |
390 |
391 | Output Format
392 |
393 |
394 |
395 | -
396 |
397 |
398 | QComboBox{
399 | background: #28262b;
400 | border: 2px solid #65606C;
401 | color: #fff;
402 | border-radius:6px;
403 | }
404 |
405 | -
406 |
407 | JPG
408 |
409 |
410 | -
411 |
412 | PNG
413 |
414 |
415 | -
416 |
417 | WEBP
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
--------------------------------------------------------------------------------
/res/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/res/folder.png
--------------------------------------------------------------------------------
/res/inbox_tray_3d.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/res/inbox_tray_3d.ico
--------------------------------------------------------------------------------
/res/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayamAmarshe/comepress/f688fca431b9d1bd89adb3157757496dae51e1ba/res/logo.png
--------------------------------------------------------------------------------
/res/unchecked.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------