django-img-optimizer 0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_img_optimizer/__init__.py +0 -0
- django_img_optimizer/apps.py +6 -0
- django_img_optimizer/management/__init__.py +0 -0
- django_img_optimizer/management/commands/__init__.py +0 -0
- django_img_optimizer/management/commands/optimize_images.py +23 -0
- django_img_optimizer/templatetags/image_optimizer.py +50 -0
- django_img_optimizer/utils.py +73 -0
- django_img_optimizer-0.0.dist-info/LICENSE +21 -0
- django_img_optimizer-0.0.dist-info/METADATA +142 -0
- django_img_optimizer-0.0.dist-info/RECORD +12 -0
- django_img_optimizer-0.0.dist-info/WHEEL +5 -0
- django_img_optimizer-0.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# https://docs.djangoproject.com/en/5.0/howto/custom-management-commands/
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from glob import iglob
|
|
6
|
+
import os
|
|
7
|
+
from django_img_optimizer.utils import delete_optimized_images, optimize_images
|
|
8
|
+
|
|
9
|
+
class Command(BaseCommand):
|
|
10
|
+
help = 'Optimizes images by converting them to WebP and AVIF'
|
|
11
|
+
|
|
12
|
+
def add_arguments(self, parser):
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--delete",
|
|
15
|
+
action="store_true",
|
|
16
|
+
help="Delete all optimized images from static folder",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def handle(self, *args, **options):
|
|
20
|
+
if options["delete"]:
|
|
21
|
+
delete_optimized_images()
|
|
22
|
+
else:
|
|
23
|
+
optimize_images()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from django import template
|
|
2
|
+
from django.templatetags.static import static
|
|
3
|
+
from django.utils.safestring import mark_safe
|
|
4
|
+
from django.contrib.staticfiles.finders import find
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
register = template.Library()
|
|
8
|
+
|
|
9
|
+
@register.simple_tag()
|
|
10
|
+
def optimized_image(*args):
|
|
11
|
+
"""
|
|
12
|
+
Returns:
|
|
13
|
+
str: A html picture tag with an optimized webp image if it exists, or just the original image if no optimized image is found.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
*args: Variable number of arguments representing the attributes for the image tag.
|
|
17
|
+
"""
|
|
18
|
+
attributes = ' '.join(args)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Get src attribute value
|
|
22
|
+
match_src = re.search(r'src="([^"]+)"', attributes)
|
|
23
|
+
# print(match_src)
|
|
24
|
+
# print('************')
|
|
25
|
+
relative_image_path = match_src.group(1) if match_src.group(1) is not None else "src could not be found"
|
|
26
|
+
|
|
27
|
+
# Get django static path
|
|
28
|
+
image_static_src = static(relative_image_path)
|
|
29
|
+
|
|
30
|
+
# Replace src in attributes with django static src
|
|
31
|
+
attributes_output = re.sub(r'(src=")([^"]+)(")(?=\s|>)', fr'\g<1>{image_static_src}\g<3>', attributes)
|
|
32
|
+
|
|
33
|
+
# Check if optimized image exists
|
|
34
|
+
relative_image_path_without_extension = relative_image_path.rsplit('.', 1)[0]
|
|
35
|
+
optimized_image_webp_absolute_path = find(f'{relative_image_path_without_extension}.webp')
|
|
36
|
+
|
|
37
|
+
if optimized_image_webp_absolute_path:
|
|
38
|
+
optimized_image_static_src = static(f'{relative_image_path_without_extension}.webp')
|
|
39
|
+
optimized_image_webp = f'<source srcset="{optimized_image_static_src}" type="image/webp">'
|
|
40
|
+
else:
|
|
41
|
+
optimized_image_webp = ''
|
|
42
|
+
|
|
43
|
+
# Create html picture tag
|
|
44
|
+
picture_tag = f'''
|
|
45
|
+
<picture>
|
|
46
|
+
{optimized_image_webp}
|
|
47
|
+
<img {attributes_output}>
|
|
48
|
+
</picture>
|
|
49
|
+
'''
|
|
50
|
+
return mark_safe(picture_tag)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from glob import iglob
|
|
3
|
+
import os
|
|
4
|
+
from PIL import Image
|
|
5
|
+
|
|
6
|
+
def get_settings():
|
|
7
|
+
""" Retrieves the optimization settings from the Django settings """
|
|
8
|
+
OPTIMIZE_IMAGE_ROOT = getattr(settings, 'OPTIMIZE_IMAGE_ROOT', None)
|
|
9
|
+
if not OPTIMIZE_IMAGE_ROOT:
|
|
10
|
+
raise Exception("Set OPTIMIZE_IMAGE_ROOT in settings.py")
|
|
11
|
+
|
|
12
|
+
OPTIMIZE_IMAGE_QUALITY = getattr(settings, 'OPTIMIZE_IMAGE_QUALITY', 100)
|
|
13
|
+
if OPTIMIZE_IMAGE_QUALITY < 0 or OPTIMIZE_IMAGE_QUALITY > 100:
|
|
14
|
+
raise Exception("Image Quality needs to be between 0 and 100")
|
|
15
|
+
|
|
16
|
+
OPTIMIZE_IMAGE_TYPES = getattr(settings, 'OPTIMIZE_IMAGE_TYPES', ['jpg', 'jpeg', 'png'])
|
|
17
|
+
|
|
18
|
+
OPTIMIZE_IMAGE_EXCLUDED_FOLDERS = getattr(settings, 'OPTIMIZE_IMAGE_EXCLUDED_FOLDERS', [])
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
'OPTIMIZE_IMAGE_ROOT': OPTIMIZE_IMAGE_ROOT,
|
|
22
|
+
'OPTIMIZE_IMAGE_TYPES': OPTIMIZE_IMAGE_TYPES,
|
|
23
|
+
'OPTIMIZE_IMAGE_QUALITY': OPTIMIZE_IMAGE_QUALITY,
|
|
24
|
+
'OPTIMIZE_IMAGE_EXCLUDED_FOLDERS': OPTIMIZE_IMAGE_EXCLUDED_FOLDERS
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def delete_optimized_images():
|
|
28
|
+
""" Delete all optimized images. """
|
|
29
|
+
image_optimizer_settings = get_settings()
|
|
30
|
+
OPTIMIZE_IMAGE_ROOT = image_optimizer_settings.get('OPTIMIZE_IMAGE_ROOT')
|
|
31
|
+
|
|
32
|
+
for file in iglob(f'{OPTIMIZE_IMAGE_ROOT}/**', recursive=True):
|
|
33
|
+
if os.path.isfile(file):
|
|
34
|
+
file_extension = file.rsplit('.', 1)[-1]
|
|
35
|
+
if file_extension in ['webp']:
|
|
36
|
+
os.remove(file)
|
|
37
|
+
|
|
38
|
+
print('Optimized images have been deleted.')
|
|
39
|
+
|
|
40
|
+
def delete_if_optimized_image_is_larger(original_file: str, output_file: str):
|
|
41
|
+
""" Compares the original image to the optimized image and deletes it they it is larger in size. """
|
|
42
|
+
file_size = os.path.getsize(original_file)
|
|
43
|
+
output_file_size = os.path.getsize(output_file)
|
|
44
|
+
|
|
45
|
+
if output_file_size >= file_size:
|
|
46
|
+
os.remove(output_file)
|
|
47
|
+
|
|
48
|
+
def optimize_images():
|
|
49
|
+
"""
|
|
50
|
+
Optimize images by converting them to WebP format with a specified quality (100 by default).
|
|
51
|
+
Deletes the optimized image if it is larger than the original.
|
|
52
|
+
"""
|
|
53
|
+
image_optimizer_settings = get_settings()
|
|
54
|
+
OPTIMIZE_IMAGE_ROOT = image_optimizer_settings.get('OPTIMIZE_IMAGE_ROOT')
|
|
55
|
+
OPTIMIZE_IMAGE_TYPES = image_optimizer_settings.get('OPTIMIZE_IMAGE_TYPES')
|
|
56
|
+
OPTIMIZE_IMAGE_QUALITY = image_optimizer_settings.get('OPTIMIZE_IMAGE_QUALITY')
|
|
57
|
+
OPTIMIZE_IMAGE_EXCLUDED_FOLDERS = image_optimizer_settings.get('OPTIMIZE_IMAGE_EXCLUDED_FOLDERS')
|
|
58
|
+
|
|
59
|
+
for file in iglob(f'{OPTIMIZE_IMAGE_ROOT}/**', recursive=True):
|
|
60
|
+
folder_name = os.path.basename(os.path.dirname(file))
|
|
61
|
+
|
|
62
|
+
if os.path.isfile(file) and (folder_name not in OPTIMIZE_IMAGE_EXCLUDED_FOLDERS):
|
|
63
|
+
split_file_name = file.rsplit('.', 1)
|
|
64
|
+
file_path_and_name = split_file_name[0]
|
|
65
|
+
file_extension = split_file_name[-1]
|
|
66
|
+
|
|
67
|
+
if file_extension in OPTIMIZE_IMAGE_TYPES:
|
|
68
|
+
image = Image.open(file)
|
|
69
|
+
output_file = f'{file_path_and_name}.webp'
|
|
70
|
+
image.save(output_file, 'webp', quality=OPTIMIZE_IMAGE_QUALITY)
|
|
71
|
+
delete_if_optimized_image_is_larger(file, output_file)
|
|
72
|
+
|
|
73
|
+
print('Image optimization complete.')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Peter Stavrou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-img-optimizer
|
|
3
|
+
Version: 0.0
|
|
4
|
+
Summary: Image optimizer for Django
|
|
5
|
+
Author: Peter Stavrou
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/peterstavrou/django-img-optimizer
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pillow >=10.2.0
|
|
13
|
+
|
|
14
|
+
# Django Image Optimizer
|
|
15
|
+
|
|
16
|
+
[](https://github.com/peterstavrou/django-img-optimizer/actions) [](https://test.pypi.org/project/django-img-optimizer/) [](https://github.com/peterstavrou/django-img-optimizer/blob/main/LICENSE)
|
|
17
|
+
|
|
18
|
+
Django Image Optimizer converts images to WebP format with a specified quality and automatically deletes the optimized image if it's larger than the original. This ensures efficient image optimization.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
pip install django-img-optimizer
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Add django_img_optimizer to `INSTALLED_APPS` in your projects `settings.py` file:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
INSTALLED_APPS = [
|
|
29
|
+
...
|
|
30
|
+
'django_img_optimizer',
|
|
31
|
+
...
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Set `OPTIMIZE_IMAGE_ROOT` to the top-level folder which contains the images that you would like to optimize. django_img_optimizer will traverse through all of the subfolders looking for the specified image types.
|
|
36
|
+
|
|
37
|
+
For example, if you have a folder inside static called images:
|
|
38
|
+
|
|
39
|
+
OPTIMIZE_IMAGE_ROOT = os.path.join(PROJECT_DIR, 'static', 'images')
|
|
40
|
+
|
|
41
|
+
### Optional Settings
|
|
42
|
+
|
|
43
|
+
#### Image Quality
|
|
44
|
+
|
|
45
|
+
`OPTIMIZE_IMAGE_QUALITY` specifies the optimization image quality on a scale from 0 (worst) to 100 (best) The lower the quality the smaller the size of the file.
|
|
46
|
+
|
|
47
|
+
Default is `100`.
|
|
48
|
+
|
|
49
|
+
**Example:**
|
|
50
|
+
|
|
51
|
+
OPTIMIZE_IMAGE_QUALITY = 100
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
#### Image Types
|
|
56
|
+
|
|
57
|
+
`OPTIMIZE_IMAGE_TYPES` specifies a list of image types that you want to optimize.
|
|
58
|
+
|
|
59
|
+
Default is `['jpg', 'jpeg', 'png']`.
|
|
60
|
+
|
|
61
|
+
**Example:**
|
|
62
|
+
|
|
63
|
+
OPTIMIZE_IMAGE_TYPES = ['jpg', 'jpeg', 'png']
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
#### Exclude Folders
|
|
68
|
+
|
|
69
|
+
`OPTIMIZE_IMAGE_EXCLUDED_FOLDERS` is used to exclude specific folders from the optimization process.
|
|
70
|
+
|
|
71
|
+
Default is `None`.
|
|
72
|
+
|
|
73
|
+
**Example:**
|
|
74
|
+
|
|
75
|
+
OPTIMIZE_IMAGE_EXCLUDED_FOLDERS = ['backup-larger-versions']
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Command line
|
|
81
|
+
|
|
82
|
+
Optimize all images:
|
|
83
|
+
|
|
84
|
+
python manage.py optimize_images
|
|
85
|
+
|
|
86
|
+
Delete all optimized images:
|
|
87
|
+
|
|
88
|
+
python manage.py optimize_images --delete
|
|
89
|
+
|
|
90
|
+
### Django templates
|
|
91
|
+
|
|
92
|
+
Load the optimized images in Django templates:
|
|
93
|
+
|
|
94
|
+
{% load image_optimizer %}
|
|
95
|
+
|
|
96
|
+
{% optimized_image 'loading="lazy"' 'decoding="async"' 'src="images/logo.jpg"' 'alt="Logo"' 'class="img-fluid"' %}
|
|
97
|
+
|
|
98
|
+
This will render:
|
|
99
|
+
|
|
100
|
+
<picture>
|
|
101
|
+
<source srcset="/static/images/logo.webp" type="image/webp">
|
|
102
|
+
<img loading="lazy" decoding="async" src="/static/images/logo.jpg" alt="Logo" class="img-fluid">
|
|
103
|
+
</picture>
|
|
104
|
+
|
|
105
|
+
Pass in any `<img>` attribute by wrapping it between single quotes `'`.
|
|
106
|
+
|
|
107
|
+
You can use variables with the `optimize_image` template tag that have been passed through `include` by using the built-in Django filter [add](https://docs.djangoproject.com/en/5.0/ref/templates/builtins/#add).
|
|
108
|
+
|
|
109
|
+
**Example:**
|
|
110
|
+
|
|
111
|
+
In index.html
|
|
112
|
+
|
|
113
|
+
{% include 'partial/example.html' with image_name='logo' %}
|
|
114
|
+
|
|
115
|
+
In example.html
|
|
116
|
+
|
|
117
|
+
{% with src='"images/'|add:image_name|add:'.jpg"' %}
|
|
118
|
+
{% optimized_image 'src='|add:src %}
|
|
119
|
+
{% endwith %}
|
|
120
|
+
|
|
121
|
+
This will render:
|
|
122
|
+
|
|
123
|
+
<picture>
|
|
124
|
+
<source srcset="/static/images/logo.webp" type="image/webp">
|
|
125
|
+
<img src="/static/images/logo.jpg">
|
|
126
|
+
</picture>
|
|
127
|
+
|
|
128
|
+
#### Further explanation of example using variables with include:
|
|
129
|
+
|
|
130
|
+
src is set to: `src='"images/'`
|
|
131
|
+
|
|
132
|
+
The single quotes `'` around `"images/` indicates that it is a string.
|
|
133
|
+
|
|
134
|
+
`|add:image_name` adds the `image_name` variable to the string, resulting in: `src="images/logo`
|
|
135
|
+
|
|
136
|
+
`|add:'.jpg"'` adds `.jpg"` to the string, resulting in: `src="images/logo.jpg"`
|
|
137
|
+
|
|
138
|
+
### AVIF Image Support
|
|
139
|
+
|
|
140
|
+
django_img_optimizer utilizes `python-pillow`, which currently lacks official support for AVIF files.
|
|
141
|
+
|
|
142
|
+
However, there is a [pull request](https://github.com/python-pillow/Pillow/pull/5201) in progress to enable this support.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
django_img_optimizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
django_img_optimizer/apps.py,sha256=3wP5TDGW1YPwCEe954pwJTdXMezqehNVXeH2AOA4Hs8,166
|
|
3
|
+
django_img_optimizer/utils.py,sha256=dI6MLCzRUVL6AU0f0owd-5DoeNBxS6QadlDoj6x5r5M,3166
|
|
4
|
+
django_img_optimizer/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
django_img_optimizer/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
django_img_optimizer/management/commands/optimize_images.py,sha256=c7JhzWv4PfrsZwyiJT8M2TOBr_0NVQapxb0ePh9nuSc,740
|
|
7
|
+
django_img_optimizer/templatetags/image_optimizer.py,sha256=mwoYlJNfrxyAneo4gaB0V-UHYZK7zR__ksCQddMQJW0,1740
|
|
8
|
+
django_img_optimizer-0.0.dist-info/LICENSE,sha256=doHY_QMZxbBq8b5kMkzUntcHrzgSHnGXMvj_qHhBupA,1069
|
|
9
|
+
django_img_optimizer-0.0.dist-info/METADATA,sha256=DKdKi94V7dy5pvvJJRdZc6OxYaF-4ITVcQ4Mzx489Ek,4386
|
|
10
|
+
django_img_optimizer-0.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
11
|
+
django_img_optimizer-0.0.dist-info/top_level.txt,sha256=vSf4W52nTSHmoJo2Vbk17Td1RhS8YNlWy4gUWo_kpsA,21
|
|
12
|
+
django_img_optimizer-0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_img_optimizer
|