Skip to content

Commit ce531f7

Browse files
committed
Added webp image conversion.
1 parent ce1f6af commit ce531f7

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

DataMgr.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import BmiCalculator
3535
import FtpCalculator
3636
import HeartRateCalculator
37+
import Image
3738
import Importer
3839
import InputChecker
3940
import Keys
@@ -483,9 +484,12 @@ def attach_photo_to_activity(self, user_id, uploaded_file_data, activity_id):
483484
if len(decoded_file_data) > self.config.get_photos_max_file_size():
484485
raise Exception("The file is too large.")
485486

487+
# Convert the image to webp.
488+
webp_file_data = Image.to_webp_scaled_50(decoded_file_data)
489+
486490
# Hash the photo. This will prevent duplicates as well as give us a unique name.
487491
h = hashlib.sha512()
488-
h.update(str(decoded_file_data).encode('utf-8'))
492+
h.update(str(webp_file_data).encode('utf-8'))
489493
hash_str = h.hexdigest()
490494

491495
# Where are we storing photos?
@@ -496,7 +500,7 @@ def attach_photo_to_activity(self, user_id, uploaded_file_data, activity_id):
496500
local_file_name = os.path.join(user_photos_dir, hash_str)
497501
if not os.path.isfile(local_file_name):
498502
with open(local_file_name, 'wb') as local_file:
499-
local_file.write(decoded_file_data)
503+
local_file.write(webp_file_data)
500504
except:
501505
raise Exception("Could not save the photo.")
502506

Image.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# # MIT License
4+
#
5+
# Copyright (c) 2025 Michael J Simms
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
25+
from io import BytesIO
26+
from PIL import Image
27+
28+
def to_webp_scaled_50(image_bytes: bytes, *, quality: int = 80, lossless: bool = False) -> bytes:
29+
"""Converts an in-memory image (bytes) to WebP scaled to 50%. Returns WebP bytes."""
30+
with Image.open(BytesIO(image_bytes)) as img:
31+
32+
# Calculate 50% size
33+
new_w = max(1, img.width // 2)
34+
new_h = max(1, img.height // 2)
35+
36+
# High-quality downscale
37+
img_resized = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
38+
39+
# Write to an in-memory buffer as WebP
40+
out = BytesIO()
41+
img_resized.save(
42+
out,
43+
format="WEBP",
44+
quality=quality, # 0-100 (ignored when lossless=True)
45+
lossless=lossless, # set True for lossless WebP
46+
method=6, # 0-6, slower = better compression
47+
optimize=True
48+
)
49+
return out.getvalue()

0 commit comments

Comments
 (0)