Skip to content

Commit

Permalink
Add GIF Functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnGrubba committed Oct 2, 2024
1 parent 9d8e77c commit 856cec6
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 13 deletions.
5 changes: 4 additions & 1 deletion config/configtemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@
"width": 200,
"height": 200
},
"quality": 80
"quality": 80,
"allow_gif": true,
"max_gif_frames": 200,
"max_size_mb": 15
}
},
"security": {
Expand Down
71 changes: 60 additions & 11 deletions src/api/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import bson
import json
import io
from PIL import Image
from PIL import Image, ImageSequence

router = APIRouter(prefix="/profile", tags=["Profile"])

Expand Down Expand Up @@ -54,9 +54,11 @@ async def update_profile(
## Description
This endpoint is used to update the profile information of the user.
"""
return bson_to_json(update_public_user(
user["_id"], update_data.model_dump(exclude_none=True), background_tasks
))
return bson_to_json(
update_public_user(
user["_id"], update_data.model_dump(exclude_none=True), background_tasks
)
)


@router.post("/confirm-email-change", status_code=204)
Expand Down Expand Up @@ -155,13 +157,60 @@ async def upload_profile_picture(
# Check if the uploaded file is an image
if not pic.content_type.startswith("image/"):
raise HTTPException(
status_code=400, detail="Invalid file type. Only images are allowed."
status_code=400, detail="Invalid file type. Only images / gifs are allowed."
)

# Check file size (limit to 10 MB)
if pic.size > AccountFeaturesConfig.max_size_mb * 1024 * 1024:
raise HTTPException(
status_code=400,
detail=f"File size exceeds the {AccountFeaturesConfig.max_size_mb} MB limit.",
)

try:
image = Image.open(io.BytesIO(await pic.read()))
except Exception:
raise HTTPException(status_code=400, detail="Invalid Image File.")

if image.format == "GIF" and not AccountFeaturesConfig.allow_gif:
raise HTTPException(
status_code=400, detail="GIFs are not allowed for profile pictures."
)

# Handle GIFs
if image.format == "GIF" and AccountFeaturesConfig.allow_gif:
frames = [frame.copy() for frame in ImageSequence.Iterator(image)]
if len(frames) > AccountFeaturesConfig.max_gif_frames:
raise HTTPException(
status_code=400,
detail="GIFs with more than 200 frames are not allowed.",
)
resized_frames = []
for frame in frames:
frame = frame.convert("RGBA")
frame = resize_and_crop_image(frame)
resized_frames.append(frame)
image = resized_frames[0]
image.save(
f"/uploads/{user['_id']}.webp",
save_all=True,
append_images=resized_frames[1:],
format="webp",
optimize=True,
quality=AccountFeaturesConfig.profile_picture_quality,
)
else:
image = resize_and_crop_image(image)
save_path = f"/uploads/{user['_id']}.webp"
image.save(
save_path,
"webp",
optimize=True,
quality=AccountFeaturesConfig.profile_picture_quality,
)


def resize_and_crop_image(image):
# Crop the image to a square from the center
width, height = image.size
min_dim = min(width, height)
Expand All @@ -173,10 +222,10 @@ async def upload_profile_picture(

# Resize the image to 128x128
image = image.resize(
(AccountFeaturesConfig.profile_picture_resize_width, AccountFeaturesConfig.profile_picture_resize_height),
Image.Resampling.NEAREST
(
AccountFeaturesConfig.profile_picture_resize_width,
AccountFeaturesConfig.profile_picture_resize_height,
),
Image.Resampling.NEAREST,
)

# Save the image
save_path = f"/uploads/{user["_id"]}.webp"
image.save(save_path, "webp", optimize=True, quality=AccountFeaturesConfig.profile_picture_quality)
return image
40 changes: 40 additions & 0 deletions src/tools/conf/AccountFeaturesConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class AccountFeaturesConfig:
profile_picture_quality: int = config["account_features"]["profile_picture"][
"quality"
]
allow_gif: bool = config["account_features"]["profile_picture"]["allow_gif"]
max_size_mb: float = config["account_features"]["profile_picture"]["max_size_mb"]
max_gif_frames: int = config["account_features"]["profile_picture"][
"max_gif_frames"
]

def validate_types(self) -> bool:
"""This is to Type Check the Configuration"""
Expand Down Expand Up @@ -117,6 +122,27 @@ def validate_types(self) -> bool:
)
)

if not isinstance(self.allow_gif, bool):
raise ValueError(
"account_features.profile_picture.allow_gif must be a boolean (got type {})".format(
type(self.allow_gif)
)
)

if not isinstance(self.max_size_mb, (float, int)):
raise ValueError(
"account_features.profile_picture.max_size_mb must be a float (got type {})".format(
type(self.max_size_mb)
)
)

if not isinstance(self.max_gif_frames, int):
raise ValueError(
"account_features.profile_picture.max_gif_frames must be an integer (got type {})".format(
type(self.max_gif_frames)
)
)

def validate_values(self) -> bool:
"""This is to Value Check the Configuration"""
if not self.issuer_name_2fa:
Expand Down Expand Up @@ -149,6 +175,20 @@ def validate_values(self) -> bool:
)
)

if not self.max_size_mb > 0:
raise ValueError(
"account_features.profile_picture.max_size_mb must be a positive float (got {})".format(
self.max_size_mb
)
)

if not self.max_gif_frames > 0:
raise ValueError(
"account_features.profile_picture.max_gif_frames must be a positive integer (got {})".format(
self.max_gif_frames
)
)


AccountFeaturesConfig().validate_types()
AccountFeaturesConfig().validate_values()
5 changes: 4 additions & 1 deletion src/tools/conf/testing_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
"width": 200,
"height": 200
},
"quality": 100
"quality": 100,
"allow_gif": true,
"max_gif_frames": 200,
"max_size_mb": 15
}
},
"security": {
Expand Down

0 comments on commit 856cec6

Please sign in to comment.