imgproxykit 0.1.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.
- imgproxykit/__init__.py +2 -0
- imgproxykit/builders.py +132 -0
- imgproxykit/client.py +167 -0
- imgproxykit/internal/__init__.py +0 -0
- imgproxykit/internal/constants.py +8 -0
- imgproxykit/internal/empty.py +8 -0
- imgproxykit/internal/encoding.py +13 -0
- imgproxykit/internal/paths.py +21 -0
- imgproxykit/internal/serialization.py +34 -0
- imgproxykit/options/__init__.py +62 -0
- imgproxykit/options/auto_rotate.py +39 -0
- imgproxykit/options/background.py +46 -0
- imgproxykit/options/base.py +41 -0
- imgproxykit/options/blur.py +38 -0
- imgproxykit/options/cache_buster.py +40 -0
- imgproxykit/options/crop.py +61 -0
- imgproxykit/options/dpr.py +40 -0
- imgproxykit/options/enforce_thumbnail.py +39 -0
- imgproxykit/options/enlarge.py +38 -0
- imgproxykit/options/expires.py +38 -0
- imgproxykit/options/extend.py +50 -0
- imgproxykit/options/extend_aspect_ratio.py +50 -0
- imgproxykit/options/filename.py +41 -0
- imgproxykit/options/flip.py +44 -0
- imgproxykit/options/format_quality.py +38 -0
- imgproxykit/options/gravity.py +48 -0
- imgproxykit/options/height.py +39 -0
- imgproxykit/options/keep_copyright.py +38 -0
- imgproxykit/options/max_animation_frame_resolution.py +39 -0
- imgproxykit/options/max_animation_frames.py +39 -0
- imgproxykit/options/max_bytes.py +39 -0
- imgproxykit/options/max_result_dimension.py +39 -0
- imgproxykit/options/max_src_file_size.py +39 -0
- imgproxykit/options/max_src_resolution.py +39 -0
- imgproxykit/options/min_height.py +38 -0
- imgproxykit/options/min_width.py +38 -0
- imgproxykit/options/padding.py +50 -0
- imgproxykit/options/pixelate.py +38 -0
- imgproxykit/options/preserve_hdr.py +38 -0
- imgproxykit/options/quality.py +38 -0
- imgproxykit/options/raw.py +39 -0
- imgproxykit/options/resize.py +55 -0
- imgproxykit/options/resizing_type.py +39 -0
- imgproxykit/options/return_attachment.py +39 -0
- imgproxykit/options/rotate.py +38 -0
- imgproxykit/options/sharpen.py +38 -0
- imgproxykit/options/size.py +45 -0
- imgproxykit/options/skip_processing.py +39 -0
- imgproxykit/options/strip_color_profile.py +39 -0
- imgproxykit/options/strip_metadata.py +38 -0
- imgproxykit/options/trim.py +52 -0
- imgproxykit/options/watermark.py +60 -0
- imgproxykit/options/width.py +39 -0
- imgproxykit/options/zoom.py +45 -0
- imgproxykit/py.typed +0 -0
- imgproxykit/signing.py +114 -0
- imgproxykit/transform.py +54 -0
- imgproxykit/types.py +95 -0
- imgproxykit-0.1.0.dist-info/METADATA +150 -0
- imgproxykit-0.1.0.dist-info/RECORD +62 -0
- imgproxykit-0.1.0.dist-info/WHEEL +4 -0
- imgproxykit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, FormatType, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class SkipProcessing(Option):
|
|
11
|
+
"""
|
|
12
|
+
Skip processing listed formats when source and output formats match.
|
|
13
|
+
|
|
14
|
+
Processing is skipped only when the requested output format matches
|
|
15
|
+
the source format. Video thumbnail processing cannot be skipped.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
formats: Source formats eligible for skipping processing.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
>>> option = SkipProcessing(formats=('svg', 'gif'))
|
|
23
|
+
>>> option.arguments
|
|
24
|
+
('svg', 'gif')
|
|
25
|
+
|
|
26
|
+
See `imgproxy skip processing
|
|
27
|
+
<https://docs.imgproxy.net/usage/processing#skip-processing>`_.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
formats: tuple[FormatType, ...]
|
|
32
|
+
|
|
33
|
+
_name: ClassVar[OptionType] = 'skp'
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@override
|
|
37
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
38
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
39
|
+
return self.formats
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class StripColorProfile(Option):
|
|
11
|
+
"""
|
|
12
|
+
Convert the embedded profile to sRGB and remove it.
|
|
13
|
+
|
|
14
|
+
When enabled, imgproxy converts the embedded ICC profile to sRGB
|
|
15
|
+
and removes it. Otherwise, imgproxy tries to preserve the profile.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
value: Whether to convert and remove the embedded color profile.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
>>> option = StripColorProfile(value=True)
|
|
23
|
+
>>> option.arguments
|
|
24
|
+
(True,)
|
|
25
|
+
|
|
26
|
+
See `imgproxy strip color profile
|
|
27
|
+
<https://docs.imgproxy.net/usage/processing#strip-color-profile>`_.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
value: bool
|
|
32
|
+
|
|
33
|
+
_name: ClassVar[OptionType] = 'scp'
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@override
|
|
37
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
38
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
39
|
+
return (self.value,)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class StripMetadata(Option):
|
|
11
|
+
"""
|
|
12
|
+
Remove image metadata.
|
|
13
|
+
|
|
14
|
+
Overrides the server metadata stripping setting for this request.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
value: Whether to remove EXIF, IPTC, and other image metadata.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
|
|
21
|
+
>>> option = StripMetadata(value=True)
|
|
22
|
+
>>> option.arguments
|
|
23
|
+
(True,)
|
|
24
|
+
|
|
25
|
+
See `imgproxy strip metadata
|
|
26
|
+
<https://docs.imgproxy.net/usage/processing#strip-metadata>`_.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
value: bool
|
|
31
|
+
|
|
32
|
+
_name: ClassVar[OptionType] = 'sm'
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@override
|
|
36
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
37
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
38
|
+
return (self.value,)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import EMPTY, Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Trim(Option):
|
|
11
|
+
"""
|
|
12
|
+
Trim the surrounding background with optional color and symmetry.
|
|
13
|
+
|
|
14
|
+
Removes the surrounding background before resizing. Trimming loads
|
|
15
|
+
the full image into memory and is unsupported for animated images.
|
|
16
|
+
Omitted settings remain :data:`~imgproxykit.types.EMPTY`.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
threshold: Color similarity tolerance.
|
|
20
|
+
color: Optional hex background color; ``FF00FF`` selects
|
|
21
|
+
transparent backgrounds. Omission enables automatic detection.
|
|
22
|
+
equal_hor: Trim equal amounts from the left and right sides.
|
|
23
|
+
equal_ver: Trim equal amounts from the top and bottom sides.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
|
|
27
|
+
>>> option = Trim(threshold=10, color='ffffff', equal_hor=True)
|
|
28
|
+
>>> option.arguments
|
|
29
|
+
(10, 'ffffff', True, EMPTY)
|
|
30
|
+
|
|
31
|
+
See `imgproxy trim
|
|
32
|
+
<https://docs.imgproxy.net/usage/processing#trim>`_.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
threshold: float
|
|
37
|
+
color: str | EMPTY = EMPTY
|
|
38
|
+
equal_hor: bool | EMPTY = EMPTY
|
|
39
|
+
equal_ver: bool | EMPTY = EMPTY
|
|
40
|
+
|
|
41
|
+
_name: ClassVar[OptionType] = 't'
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
@override
|
|
45
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
46
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
47
|
+
return (
|
|
48
|
+
self.threshold,
|
|
49
|
+
self.color,
|
|
50
|
+
self.equal_hor,
|
|
51
|
+
self.equal_ver,
|
|
52
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import EMPTY, Argument, OptionType, WatermarkPosition
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Watermark(Option):
|
|
11
|
+
"""
|
|
12
|
+
Apply the watermark image configured on the imgproxy server.
|
|
13
|
+
|
|
14
|
+
Omitted settings remain :data:`~imgproxykit.types.EMPTY` and are
|
|
15
|
+
resolved by the server.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
opacity: Multiplier applied to the configured base opacity.
|
|
19
|
+
position: Watermark anchor, or ``re`` to repeat it across the image.
|
|
20
|
+
offset_x: Horizontal offset, or horizontal spacing when repeating.
|
|
21
|
+
offset_y: Vertical offset, or vertical spacing when repeating.
|
|
22
|
+
scale: Size relative to the resulting image. Zero or omission
|
|
23
|
+
preserves the watermark size.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
|
|
27
|
+
>>> option = Watermark(
|
|
28
|
+
... opacity=0.5, position='soea', offset_x=10, offset_y=10
|
|
29
|
+
... )
|
|
30
|
+
>>> option.arguments
|
|
31
|
+
(0.5, 'soea', 10, 10, EMPTY)
|
|
32
|
+
|
|
33
|
+
See `imgproxy watermark
|
|
34
|
+
<https://docs.imgproxy.net/usage/processing#watermark>`_
|
|
35
|
+
and `watermark image configuration
|
|
36
|
+
<https://docs.imgproxy.net/features/watermark#specifying-watermark-image>`_.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Public API:
|
|
41
|
+
opacity: float
|
|
42
|
+
position: WatermarkPosition | EMPTY = EMPTY
|
|
43
|
+
offset_x: float | EMPTY = EMPTY
|
|
44
|
+
offset_y: float | EMPTY = EMPTY
|
|
45
|
+
scale: float | EMPTY = EMPTY
|
|
46
|
+
|
|
47
|
+
# Private API:
|
|
48
|
+
_name: ClassVar[OptionType] = 'wm'
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
@override
|
|
52
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
53
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
54
|
+
return (
|
|
55
|
+
self.opacity,
|
|
56
|
+
self.position,
|
|
57
|
+
self.offset_x,
|
|
58
|
+
self.offset_y,
|
|
59
|
+
self.scale,
|
|
60
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Width(Option):
|
|
11
|
+
"""
|
|
12
|
+
Set the target width in pixels.
|
|
13
|
+
|
|
14
|
+
Zero lets imgproxy calculate width from height and aspect ratio.
|
|
15
|
+
With force resizing, zero preserves the source width.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
value: Target width in pixels.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
>>> option = Width(value=300)
|
|
23
|
+
>>> option.arguments
|
|
24
|
+
(300,)
|
|
25
|
+
|
|
26
|
+
See `imgproxy width
|
|
27
|
+
<https://docs.imgproxy.net/usage/processing#width>`_.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
value: int
|
|
32
|
+
|
|
33
|
+
_name: ClassVar[OptionType] = 'w'
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@override
|
|
37
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
38
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
39
|
+
return (self.value,)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import EMPTY, Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Zoom(Option):
|
|
11
|
+
"""
|
|
12
|
+
Multiply dimensions by one factor or separate X and Y factors.
|
|
13
|
+
|
|
14
|
+
Unlike DPR, zoom does not scale gravity offsets, watermark offsets,
|
|
15
|
+
or padding. Factors must be greater than zero.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
factor_x: Horizontal multiplier, also used vertically when
|
|
19
|
+
``factor_y`` is omitted.
|
|
20
|
+
factor_y: Optional vertical multiplier.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
|
|
24
|
+
>>> option = Zoom(factor_x=2)
|
|
25
|
+
>>> option.arguments
|
|
26
|
+
(2, EMPTY)
|
|
27
|
+
>>> option = Zoom(factor_x=2, factor_y=1.5)
|
|
28
|
+
>>> option.arguments
|
|
29
|
+
(2, 1.5)
|
|
30
|
+
|
|
31
|
+
See `imgproxy zoom
|
|
32
|
+
<https://docs.imgproxy.net/usage/processing#zoom>`_.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
factor_x: float
|
|
37
|
+
factor_y: float | EMPTY = EMPTY
|
|
38
|
+
|
|
39
|
+
_name: ClassVar[OptionType] = 'z'
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@override
|
|
43
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
44
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
45
|
+
return (self.factor_x, self.factor_y)
|
imgproxykit/py.typed
ADDED
|
File without changes
|
imgproxykit/signing.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import hmac
|
|
3
|
+
|
|
4
|
+
from imgproxykit.internal.constants import SIGNATURE_DIGEST, UNSAFE
|
|
5
|
+
from imgproxykit.internal.encoding import encode_base64
|
|
6
|
+
from imgproxykit.types import EMPTY
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class URLSigner:
|
|
10
|
+
"""
|
|
11
|
+
Sign imgproxy paths using a reusable key and salt pair.
|
|
12
|
+
|
|
13
|
+
Used by :class:`~imgproxykit.Client` after path construction.
|
|
14
|
+
Credentials are decoded once during initialization and reused for
|
|
15
|
+
subsequent signatures. Omitting both credentials enables unsigned mode.
|
|
16
|
+
|
|
17
|
+
Subclass this signer and override :meth:`__call__` to customize signing.
|
|
18
|
+
Set the subclass on :attr:`imgproxykit.Client.signer_cls` to use it
|
|
19
|
+
in a client.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__slots__ = ('_credentials',)
|
|
24
|
+
|
|
25
|
+
# Private API:
|
|
26
|
+
_credentials: tuple[bytes, bytes] | None
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
key: str | EMPTY = EMPTY,
|
|
32
|
+
salt: str | EMPTY = EMPTY,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Configure signing credentials or enable unsigned mode.
|
|
36
|
+
|
|
37
|
+
Supply both credentials to generate signatures. Omit both to return
|
|
38
|
+
the ``unsafe`` marker, for servers with signature checking disabled.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
key: Nonempty hex-encoded signing key matching the server.
|
|
42
|
+
salt: Nonempty hex-encoded signing salt matching the server.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If only one credential is supplied, or either
|
|
46
|
+
credential is empty or contains invalid hexadecimal data.
|
|
47
|
+
|
|
48
|
+
Usage:
|
|
49
|
+
|
|
50
|
+
>>> from imgproxykit.signing import URLSigner
|
|
51
|
+
>>> unsigned = URLSigner()
|
|
52
|
+
>>> signed = URLSigner(
|
|
53
|
+
... key='736563726574',
|
|
54
|
+
... salt='73616c74',
|
|
55
|
+
... )
|
|
56
|
+
|
|
57
|
+
These credentials are examples for local development.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
self._credentials = None
|
|
61
|
+
|
|
62
|
+
# Omitting both credentials selects unsigned mode.
|
|
63
|
+
if key is EMPTY and salt is EMPTY:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
# Signing requires a complete key and salt pair.
|
|
67
|
+
if key is EMPTY or salt is EMPTY:
|
|
68
|
+
raise ValueError('key and salt must be supplied together.')
|
|
69
|
+
|
|
70
|
+
self._credentials = (
|
|
71
|
+
self._decode_hex(key),
|
|
72
|
+
self._decode_hex(salt),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def __call__(self, path: str) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Sign the exact supplied path or return the unsigned marker.
|
|
78
|
+
|
|
79
|
+
The signature is an HMAC-SHA256 digest of the salt followed by
|
|
80
|
+
the UTF-8 encoded path. The path is neither normalized nor modified.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
path: Finalized path beginning with ``/``, including processing
|
|
84
|
+
options, the encoded source, and any output extension.
|
|
85
|
+
Excludes the server address and signature segment.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
URL-safe Base64 signature without padding, or ``unsafe`` when
|
|
89
|
+
signing credentials were omitted.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
if self._credentials is None:
|
|
93
|
+
return UNSAFE
|
|
94
|
+
|
|
95
|
+
key, salt = self._credentials
|
|
96
|
+
signature = hmac.digest(
|
|
97
|
+
key,
|
|
98
|
+
salt + path.encode(),
|
|
99
|
+
SIGNATURE_DIGEST,
|
|
100
|
+
)
|
|
101
|
+
return encode_base64(signature)
|
|
102
|
+
|
|
103
|
+
def _decode_hex(self, value: str) -> bytes:
|
|
104
|
+
"""
|
|
105
|
+
Decode a nonempty hexadecimal credential.
|
|
106
|
+
|
|
107
|
+
Invalid and empty values raise :class:`ValueError`.
|
|
108
|
+
"""
|
|
109
|
+
with contextlib.suppress(ValueError):
|
|
110
|
+
decoded = bytes.fromhex(value)
|
|
111
|
+
if decoded:
|
|
112
|
+
return decoded
|
|
113
|
+
|
|
114
|
+
raise ValueError('Signing credentials must be nonempty hex strings.')
|
imgproxykit/transform.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from imgproxykit.options import Option
|
|
4
|
+
from imgproxykit.types import EMPTY, FormatType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
8
|
+
class Transform:
|
|
9
|
+
"""
|
|
10
|
+
Define reusable image processing options and an optional output format.
|
|
11
|
+
|
|
12
|
+
Instances are immutable and can be reused for multiple source images
|
|
13
|
+
and clients. Pass a transform to :meth:`imgproxykit.Client.url` to
|
|
14
|
+
include its processing options and output format in the generated URL.
|
|
15
|
+
|
|
16
|
+
Options are serialized in the supplied order. imgproxy determines the
|
|
17
|
+
image processing pipeline and resolves omitted option arguments using
|
|
18
|
+
its defaults. Creating a transform does not process an image or make
|
|
19
|
+
a network request.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
options: Tuple of typed processing options. Defaults to an empty
|
|
23
|
+
tuple, which adds no processing option segments to the URL.
|
|
24
|
+
format: Output image format, such as ``webp`` or ``avif``.
|
|
25
|
+
Defaults to :data:`~imgproxykit.types.EMPTY`, leaving format
|
|
26
|
+
selection to imgproxy.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
|
|
30
|
+
>>> from imgproxykit import Client, Transform
|
|
31
|
+
>>> from imgproxykit.options import Quality, Resize
|
|
32
|
+
>>> thumbnail = Transform(
|
|
33
|
+
... options=(
|
|
34
|
+
... Resize(width=300, height=300, mode='fill'),
|
|
35
|
+
... Quality(85),
|
|
36
|
+
... ),
|
|
37
|
+
... format='webp',
|
|
38
|
+
... )
|
|
39
|
+
>>> client = Client('https://images.example.com')
|
|
40
|
+
>>> first_url = client.url(
|
|
41
|
+
... 'https://example.com/first.jpg',
|
|
42
|
+
... transform=thumbnail,
|
|
43
|
+
... )
|
|
44
|
+
>>> second_url = client.url(
|
|
45
|
+
... 'https://example.com/second.jpg',
|
|
46
|
+
... transform=thumbnail,
|
|
47
|
+
... )
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# TODO: Add immutable composition; define duplicate option handling,
|
|
52
|
+
# format overrides, and explicitly clearing an inherited format.
|
|
53
|
+
options: tuple[Option, ...] = ()
|
|
54
|
+
format: FormatType | EMPTY = EMPTY
|
imgproxykit/types.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from imgproxykit.internal.empty import EMPTY as EMPTY # noqa: PLC0414
|
|
4
|
+
|
|
5
|
+
type Argument = str | int | float | bool | EMPTY
|
|
6
|
+
type FormatType = Literal[
|
|
7
|
+
'png',
|
|
8
|
+
'jpg',
|
|
9
|
+
'jxl',
|
|
10
|
+
'webp',
|
|
11
|
+
'avif',
|
|
12
|
+
'gif',
|
|
13
|
+
'ico',
|
|
14
|
+
'svg',
|
|
15
|
+
'heic',
|
|
16
|
+
'bmp',
|
|
17
|
+
'tiff',
|
|
18
|
+
]
|
|
19
|
+
type OptionType = Literal[
|
|
20
|
+
'w',
|
|
21
|
+
'h',
|
|
22
|
+
'mw',
|
|
23
|
+
'mh',
|
|
24
|
+
'rt',
|
|
25
|
+
'el',
|
|
26
|
+
'ar',
|
|
27
|
+
'rot',
|
|
28
|
+
'bl',
|
|
29
|
+
'sh',
|
|
30
|
+
'pix',
|
|
31
|
+
'sm',
|
|
32
|
+
'kcr',
|
|
33
|
+
'scp',
|
|
34
|
+
'ph',
|
|
35
|
+
'eth',
|
|
36
|
+
'mb',
|
|
37
|
+
'raw',
|
|
38
|
+
'exp',
|
|
39
|
+
'att',
|
|
40
|
+
'msr',
|
|
41
|
+
'msfs',
|
|
42
|
+
'maf',
|
|
43
|
+
'mafr',
|
|
44
|
+
'mrd',
|
|
45
|
+
's',
|
|
46
|
+
'z',
|
|
47
|
+
'ex',
|
|
48
|
+
'exar',
|
|
49
|
+
'c',
|
|
50
|
+
't',
|
|
51
|
+
'pd',
|
|
52
|
+
'fl',
|
|
53
|
+
'bg',
|
|
54
|
+
'fq',
|
|
55
|
+
'skp',
|
|
56
|
+
'fn',
|
|
57
|
+
'cb',
|
|
58
|
+
'rs',
|
|
59
|
+
'g',
|
|
60
|
+
'dpr',
|
|
61
|
+
'q',
|
|
62
|
+
'wm',
|
|
63
|
+
]
|
|
64
|
+
type WatermarkPosition = Literal[
|
|
65
|
+
'ce',
|
|
66
|
+
'no',
|
|
67
|
+
'so',
|
|
68
|
+
'ea',
|
|
69
|
+
'we',
|
|
70
|
+
'noea',
|
|
71
|
+
'now',
|
|
72
|
+
'soea',
|
|
73
|
+
'sow',
|
|
74
|
+
're',
|
|
75
|
+
]
|
|
76
|
+
type ResizeType = Literal[
|
|
77
|
+
'fit',
|
|
78
|
+
'fill',
|
|
79
|
+
'fill-down',
|
|
80
|
+
'force',
|
|
81
|
+
'auto',
|
|
82
|
+
]
|
|
83
|
+
type Position = Literal[
|
|
84
|
+
'no',
|
|
85
|
+
'so',
|
|
86
|
+
'ea',
|
|
87
|
+
'we',
|
|
88
|
+
'noea',
|
|
89
|
+
'now',
|
|
90
|
+
'soea',
|
|
91
|
+
'sow',
|
|
92
|
+
'ce',
|
|
93
|
+
'sm',
|
|
94
|
+
'fp',
|
|
95
|
+
]
|