feat: Add WebP support for image renditions#3061
feat: Add WebP support for image renditions#3061srebastian wants to merge 2 commits intosuperdesk:developfrom
Conversation
- Add WebP to supported formats in renditions.py to prevent conversion to PNG - Add RENDITION_FORMAT config setting to enable WebP conversion for JPEG/PNG/GIF - Add documentation for RENDITION_FORMAT setting in settings.rst - Add tests for WebP format conversion and preservation - Original images always preserved regardless of rendition format
|
ping @IvanJelicSF |
There was a problem hiding this comment.
Pull request overview
This PR adds WebP support to Superdesk's image rendition system, enabling more efficient image storage and delivery. The changes allow WebP images to remain in their native format instead of being automatically converted to PNG, and provide an opt-in configuration to convert other image formats (JPEG, PNG, GIF, TIFF) to WebP for renditions while preserving originals in their source format.
- Added WebP to the list of supported image formats to prevent automatic conversion
- Introduced RENDITION_FORMAT configuration setting for optional WebP conversion
- Added comprehensive test coverage and documentation
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| superdesk/media/renditions.py | Added "webp" to supported formats tuple and implemented RENDITION_FORMAT config check to enable WebP conversion for renditions |
| superdesk/default_settings.py | Added RENDITION_FORMAT configuration setting with environment variable support |
| docs/settings.rst | Documented the new RENDITION_FORMAT setting with usage examples and WebP requirements |
| tests/media/renditions_tests.py | Added two test cases to validate WebP format conversion and preservation behavior |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| inserted = [] | ||
| renditions = get_renditions_spec() | ||
| # Create a simple WebP image for testing | ||
| from io import BytesIO |
There was a problem hiding this comment.
The BytesIO import should be moved to the top of the file with other imports rather than being imported inside the test method. This follows Python best practices and makes dependencies clearer.
| self.app.config["RENDITION_FORMAT"] = "webp" | ||
| inserted = [] | ||
| renditions = get_renditions_spec() | ||
| with open(IMG_PATH, "rb") as original: | ||
| generated = generate_renditions( | ||
| original, "id", inserted, "image", "image/jpeg", renditions, self.app.media.url_for_media | ||
| ) | ||
|
|
||
| # Original stays JPEG | ||
| self.assertIn("original", generated) | ||
| self.assertEqual("image/jpeg", generated["original"]["mimetype"]) | ||
|
|
||
| # All other renditions should be WebP | ||
| self.assertIn("thumbnail", generated) | ||
| self.assertEqual("image/webp", generated["thumbnail"]["mimetype"]) | ||
|
|
||
| self.assertIn("viewImage", generated) | ||
| self.assertEqual("image/webp", generated["viewImage"]["mimetype"]) | ||
|
|
||
| self.assertIn("baseImage", generated) | ||
| self.assertEqual("image/webp", generated["baseImage"]["mimetype"]) | ||
|
|
There was a problem hiding this comment.
The test modifies the app configuration (self.app.config["RENDITION_FORMAT"] = "webp") but doesn't clean it up after the test completes. This could cause side effects on other tests. Consider adding a teardown method or using a context manager to restore the original configuration value after the test.
| self.app.config["RENDITION_FORMAT"] = "webp" | |
| inserted = [] | |
| renditions = get_renditions_spec() | |
| with open(IMG_PATH, "rb") as original: | |
| generated = generate_renditions( | |
| original, "id", inserted, "image", "image/jpeg", renditions, self.app.media.url_for_media | |
| ) | |
| # Original stays JPEG | |
| self.assertIn("original", generated) | |
| self.assertEqual("image/jpeg", generated["original"]["mimetype"]) | |
| # All other renditions should be WebP | |
| self.assertIn("thumbnail", generated) | |
| self.assertEqual("image/webp", generated["thumbnail"]["mimetype"]) | |
| self.assertIn("viewImage", generated) | |
| self.assertEqual("image/webp", generated["viewImage"]["mimetype"]) | |
| self.assertIn("baseImage", generated) | |
| self.assertEqual("image/webp", generated["baseImage"]["mimetype"]) | |
| orig_format = self.app.config.get("RENDITION_FORMAT") | |
| self.app.config["RENDITION_FORMAT"] = "webp" | |
| try: | |
| inserted = [] | |
| renditions = get_renditions_spec() | |
| with open(IMG_PATH, "rb") as original: | |
| generated = generate_renditions( | |
| original, "id", inserted, "image", "image/jpeg", renditions, self.app.media.url_for_media | |
| ) | |
| # Original stays JPEG | |
| self.assertIn("original", generated) | |
| self.assertEqual("image/jpeg", generated["original"]["mimetype"]) | |
| # All other renditions should be WebP | |
| self.assertIn("thumbnail", generated) | |
| self.assertEqual("image/webp", generated["thumbnail"]["mimetype"]) | |
| self.assertIn("viewImage", generated) | |
| self.assertEqual("image/webp", generated["viewImage"]["mimetype"]) | |
| self.assertIn("baseImage", generated) | |
| self.assertEqual("image/webp", generated["baseImage"]["mimetype"]) | |
| finally: | |
| if orig_format is None: | |
| # Remove test-specific override if there was no original value | |
| self.app.config.pop("RENDITION_FORMAT", None) | |
| else: | |
| self.app.config["RENDITION_FORMAT"] = orig_format |
| elif ext not in ("jpeg", "gif", "tiff", "png", "webp"): | ||
| ext = "png" | ||
|
|
||
| if get_app_config("RENDITION_FORMAT") == "webp": |
There was a problem hiding this comment.
The configuration value comparison is case-sensitive, which could lead to unexpected behavior if users set the environment variable to "WEBP" or "WebP" instead of "webp". Consider normalizing the configuration value by converting it to lowercase before comparison for better user experience.
| if get_app_config("RENDITION_FORMAT") == "webp": | |
| rendition_format = str(get_app_config("RENDITION_FORMAT") or "").lower() | |
| if rendition_format == "webp": |
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Purpose
This PR adds WebP support to Superdesk's image rendition system to enable more efficient image storage and delivery.
Currently, WebP images uploaded to Superdesk are automatically converted to PNG during rendition generation, which defeats the purpose of using WebP. Additionally, there's no way to convert other formats (JPEG, PNG, GIF) to WebP for renditions.
What has changed
Format Recognition Fix: Added webp to the tuple of supported image formats in renditions.py (line 89) to prevent automatic conversion of WebP images to PNG.
Configurable WebP Conversion: Added new RENDITION_FORMAT configuration setting that enables conversion of JPEG/PNG/GIF/TIFF images to WebP format for renditions (lines 92-93 in renditions.py).
Configuration Setting: Added RENDITION_FORMAT to default_settings.py with environment variable support.
Documentation: Added documentation for the RENDITION_FORMAT setting in settings.rst, including usage examples and WebP requirements.
Test Coverage: Added two test cases in renditions_tests.py:
Important: Original images are always preserved in their source format regardless of the rendition format setting.
Steps to test
Note: This is an opt-in feature. Without setting RENDITION_FORMAT=webp, behavior remains unchanged from current production.