imager-client 1.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.
- imager/Imager.py +238 -0
- imager/ImagerData.py +46 -0
- imager/ImagerEncode.py +58 -0
- imager/ImagerFormat.py +39 -0
- imager/__init__.py +39 -0
- imager_client-1.0.0.dist-info/LICENSE +674 -0
- imager_client-1.0.0.dist-info/METADATA +941 -0
- imager_client-1.0.0.dist-info/RECORD +11 -0
- imager_client-1.0.0.dist-info/WHEEL +5 -0
- imager_client-1.0.0.dist-info/entry_points.txt +2 -0
- imager_client-1.0.0.dist-info/top_level.txt +1 -0
imager/Imager.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from .ImagerData import ImagerData
|
|
2
|
+
from .ImagerFormat import GetFormat
|
|
3
|
+
from .ImagerEncode import encode
|
|
4
|
+
from typing import Mapping, Any, List, Union
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Imager:
|
|
9
|
+
"""Imager
|
|
10
|
+
Горячее создание миниатюр для картинок
|
|
11
|
+
@see https://github.com/pkg-ru/imager
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
thumb (str | Mapping[str, Any], optional): устанавливаем thumb или настройки по умолчанию. Defaults to "".
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
instance = {}
|
|
18
|
+
|
|
19
|
+
def __init__(self, thumb: Union[str, Mapping[str, Any]] = "") -> Self:
|
|
20
|
+
self.instance = ImagerData()
|
|
21
|
+
|
|
22
|
+
if isinstance(thumb, str):
|
|
23
|
+
self.thumb(thumb)
|
|
24
|
+
else:
|
|
25
|
+
self.setting(thumb)
|
|
26
|
+
|
|
27
|
+
def setting(self, setting: Mapping[str, Any]) -> Self:
|
|
28
|
+
"""Настройки
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Imager: Imager
|
|
32
|
+
"""
|
|
33
|
+
for key in setting:
|
|
34
|
+
if key in self.instance:
|
|
35
|
+
self.instance[key] = setting[key]
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def clone(self) -> "Imager":
|
|
39
|
+
"""Клонируем, чтобы не вносить изменения в общий экземпляр
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Imager: Imager
|
|
43
|
+
"""
|
|
44
|
+
my = Imager(self.instance)
|
|
45
|
+
my.instance = self.instance.copy()
|
|
46
|
+
return my
|
|
47
|
+
|
|
48
|
+
def copy(self) -> "Imager":
|
|
49
|
+
"""Клонируем, чтобы не вносить изменения в общий экземпляр
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Imager: Imager
|
|
53
|
+
"""
|
|
54
|
+
return self.clone()
|
|
55
|
+
|
|
56
|
+
def size(self, width: int = 0, height: int = 0) -> Self:
|
|
57
|
+
"""Изменяем размер
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
self: self
|
|
61
|
+
"""
|
|
62
|
+
self.width(width)
|
|
63
|
+
return self.height(height)
|
|
64
|
+
|
|
65
|
+
def width(self, width: int = 0) -> Self:
|
|
66
|
+
"""Ширина
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
self: self
|
|
70
|
+
"""
|
|
71
|
+
self.instance["width"] = width
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def height(self, height: int = 0) -> Self:
|
|
75
|
+
"""Высота
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
self: self
|
|
79
|
+
"""
|
|
80
|
+
self.instance["height"] = height
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def quality(self, quality: int) -> Self:
|
|
84
|
+
"""Качество
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
self: self
|
|
88
|
+
"""
|
|
89
|
+
self.instance["quality"] = quality
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
def crop(self, crop: bool) -> Self:
|
|
93
|
+
"""Кроп
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
self: self
|
|
97
|
+
"""
|
|
98
|
+
self.instance["crop"] = crop
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def color(self, r: int, g: int, b: int) -> Self:
|
|
102
|
+
"""Цвет фона
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
self: self
|
|
106
|
+
"""
|
|
107
|
+
self.instance["color"] = [r, g, b]
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def loop(self, loop: bool) -> Self:
|
|
111
|
+
"""Зацикливание анимации
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
self: self
|
|
115
|
+
"""
|
|
116
|
+
self.instance["loop"] = loop
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def thumb(self, thumb: str) -> Self:
|
|
120
|
+
"""Шаблон настроек на сервере Imager
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
self: self
|
|
124
|
+
"""
|
|
125
|
+
self.instance["thumb"] = thumb
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def trim(self, active: bool, rate: int = 0, color: List[List[int]] = []) -> Self:
|
|
129
|
+
"""Обрезать по краям прозрачные пиксели/рамку/и т.д.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
self: self
|
|
133
|
+
"""
|
|
134
|
+
self.trimActive(active)
|
|
135
|
+
self.trimRate(rate)
|
|
136
|
+
return self.trimColors(color)
|
|
137
|
+
|
|
138
|
+
def trimActive(self, active: bool) -> Self:
|
|
139
|
+
"""активность фильтра: Обрезать по краям прозрачные пиксели/рамку/и т.д.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
self: self
|
|
143
|
+
"""
|
|
144
|
+
self.instance["trimActive"] = active
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def trimRate(self, rate: int = 0) -> Self:
|
|
148
|
+
"""огрешность при сравнении цветов: Обрезать по краям прозрачные пиксели/рамку/и т.д.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
self: self
|
|
152
|
+
"""
|
|
153
|
+
self.instance["trimRate"] = rate
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def trimColors(self, color: List[List[int]]) -> Self:
|
|
157
|
+
"""список цветов: Обрезать по краям прозрачные пиксели/рамку/и т.д.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
self: self
|
|
161
|
+
"""
|
|
162
|
+
if len(color) > 0:
|
|
163
|
+
newColors = []
|
|
164
|
+
for item in color:
|
|
165
|
+
if len(item) == 3:
|
|
166
|
+
newColors.append(item)
|
|
167
|
+
else:
|
|
168
|
+
break
|
|
169
|
+
self.instance["trimColor"] = newColors
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def convert(
|
|
173
|
+
self, file: str, format: str, setting: Union[None, Mapping[str, Any]] = None
|
|
174
|
+
) -> str:
|
|
175
|
+
"""Получаем ассет на миниатюру в указаном формате
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
file (str): путь к исходной картинке
|
|
179
|
+
format (str): формат файла миниатюры
|
|
180
|
+
setting (None | Mapping[str, Any], optional): Настройки. Defaults to None.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
str: asset path
|
|
184
|
+
"""
|
|
185
|
+
if setting != None:
|
|
186
|
+
instance = self.clone().setting(setting).instance
|
|
187
|
+
else:
|
|
188
|
+
instance = self.instance
|
|
189
|
+
|
|
190
|
+
file_arr = file.split(".")
|
|
191
|
+
lastIndex = len(file_arr) - 1
|
|
192
|
+
instance["format"] = file_arr[lastIndex]
|
|
193
|
+
if GetFormat(instance["format"]) == False:
|
|
194
|
+
return file
|
|
195
|
+
|
|
196
|
+
if format == "":
|
|
197
|
+
format = str(instance["format"]).lower()
|
|
198
|
+
|
|
199
|
+
nf = GetFormat(format)
|
|
200
|
+
if nf == False:
|
|
201
|
+
return file
|
|
202
|
+
|
|
203
|
+
instance["formatTo"] = nf
|
|
204
|
+
if instance["format"] == format:
|
|
205
|
+
# если запрашиваемый формат совпадает с текущим то не пишем в данные
|
|
206
|
+
instance["format"] = ""
|
|
207
|
+
|
|
208
|
+
if (
|
|
209
|
+
instance["format"] != ""
|
|
210
|
+
and instance["format"] == str(instance["format"]).lower()
|
|
211
|
+
):
|
|
212
|
+
# если формат файла в нижнем регистре, пишем в данные только 1 байт
|
|
213
|
+
nf = GetFormat(instance["format"])
|
|
214
|
+
if nf != False:
|
|
215
|
+
instance["formatFrom"] = nf
|
|
216
|
+
instance["format"] = ""
|
|
217
|
+
|
|
218
|
+
if instance["trimActive"] != True:
|
|
219
|
+
instance["trimColor"] = []
|
|
220
|
+
instance["trimRate"] = 0
|
|
221
|
+
|
|
222
|
+
code = encode(instance)
|
|
223
|
+
if code == "":
|
|
224
|
+
return file
|
|
225
|
+
|
|
226
|
+
return ".".join(file_arr[0:lastIndex]) + "/" + code + "." + format
|
|
227
|
+
|
|
228
|
+
def get(self, file: str, setting: Union[None, Mapping[str, Any]] = None) -> str:
|
|
229
|
+
"""Получаем ассет на миниатюру в формате исходного файла
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
file (str): путь к исходной картинке
|
|
233
|
+
setting (None | Mapping[str, Any], optional): Настройки. Defaults to None.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
str: asset path
|
|
237
|
+
"""
|
|
238
|
+
return self.convert(file, "", setting)
|
imager/ImagerData.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Mapping, Any, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def ImagerData() -> Mapping[str, Any]:
|
|
5
|
+
return {
|
|
6
|
+
"trimColor": [],
|
|
7
|
+
"format": "",
|
|
8
|
+
"thumb": "",
|
|
9
|
+
"color": [],
|
|
10
|
+
"width": 0,
|
|
11
|
+
"height": 0,
|
|
12
|
+
"formatTo": 0,
|
|
13
|
+
"formatFrom": 0,
|
|
14
|
+
"quality": 0,
|
|
15
|
+
"trimRate": False,
|
|
16
|
+
"loop": True,
|
|
17
|
+
"trimActive": False,
|
|
18
|
+
"crop": False,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
Flags: Mapping[str, int] = {
|
|
23
|
+
"width": 1 << 0,
|
|
24
|
+
"height": 1 << 1,
|
|
25
|
+
"quality": 1 << 2,
|
|
26
|
+
"format": 1 << 3,
|
|
27
|
+
"color": 1 << 4,
|
|
28
|
+
"loop": 1 << 5,
|
|
29
|
+
"thumb": 1 << 6,
|
|
30
|
+
"trimActive": 1 << 7,
|
|
31
|
+
"trimColor": 1 << 8,
|
|
32
|
+
"trimRate": 1 << 9,
|
|
33
|
+
"formatTo": 1 << 10,
|
|
34
|
+
"formatFrom": 1 << 11,
|
|
35
|
+
"crop": 1 << 12,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__flagsSorted = []
|
|
40
|
+
|
|
41
|
+
def FlagsSorted() -> List[str]:
|
|
42
|
+
"""Отсортированные ключи"""
|
|
43
|
+
global __flagsSorted
|
|
44
|
+
if len(__flagsSorted) == 0:
|
|
45
|
+
__flagsSorted = list({key: Flags[key] for key in sorted(Flags)}.keys())
|
|
46
|
+
return __flagsSorted
|
imager/ImagerEncode.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Mapping, Any
|
|
2
|
+
from base64 import urlsafe_b64encode
|
|
3
|
+
import struct
|
|
4
|
+
from .ImagerData import Flags, FlagsSorted
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def encode(instance: Mapping[str, Any]) -> str:
|
|
8
|
+
if instance["thumb"] == "default":
|
|
9
|
+
instance["thumb"] = ""
|
|
10
|
+
|
|
11
|
+
myFlag = 0
|
|
12
|
+
newData = b""
|
|
13
|
+
|
|
14
|
+
for name in FlagsSorted():
|
|
15
|
+
flag = Flags[name] | 0
|
|
16
|
+
|
|
17
|
+
if name == "color" and len(list(instance[name])) > 0:
|
|
18
|
+
# [3]uint8
|
|
19
|
+
myFlag |= flag
|
|
20
|
+
newData += struct.pack(
|
|
21
|
+
">BBB",
|
|
22
|
+
instance["color"][0],
|
|
23
|
+
instance["color"][1],
|
|
24
|
+
instance["color"][2],
|
|
25
|
+
)
|
|
26
|
+
elif name == "trimColor" and len(instance[name]) > 0:
|
|
27
|
+
# [][3]uint8
|
|
28
|
+
myFlag |= flag
|
|
29
|
+
newData += struct.pack(">B", len(instance["trimColor"]) * 3)
|
|
30
|
+
for color in instance["trimColor"]:
|
|
31
|
+
newData += struct.pack(
|
|
32
|
+
">BBB", color[0], color[1], color[2]
|
|
33
|
+
)
|
|
34
|
+
elif name in ["loop", "trimActive", "crop"] and instance[name]:
|
|
35
|
+
# bool
|
|
36
|
+
myFlag |= flag
|
|
37
|
+
elif (
|
|
38
|
+
name in ["formatTo", "formatFrom", "quality", "trimRate"]
|
|
39
|
+
and instance[name] > 0
|
|
40
|
+
):
|
|
41
|
+
# uint8
|
|
42
|
+
myFlag |= flag
|
|
43
|
+
newData += struct.pack(">B", instance[name])
|
|
44
|
+
elif name in ["width", "height"] and instance[name] > 0:
|
|
45
|
+
# uint16
|
|
46
|
+
myFlag |= flag
|
|
47
|
+
newData += struct.pack(">H", instance[name])
|
|
48
|
+
elif name in ["format", "thumb"] and instance[name] and instance[name] != "":
|
|
49
|
+
# string
|
|
50
|
+
myFlag |= flag
|
|
51
|
+
newData += struct.pack(">B", len(str(instance[name])))
|
|
52
|
+
newData += str(instance[name]).encode("utf-8")
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
urlsafe_b64encode(struct.pack(">H", myFlag) + newData)
|
|
56
|
+
.rstrip(b"=")
|
|
57
|
+
.decode("ascii")
|
|
58
|
+
)
|
imager/ImagerFormat.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
FORMAT_JPG = 1
|
|
4
|
+
FORMAT_JPEG = 2
|
|
5
|
+
FORMAT_GIF = 3
|
|
6
|
+
FORMAT_PNG = 4
|
|
7
|
+
FORMAT_APNG = 5
|
|
8
|
+
FORMAT_JPE = 6
|
|
9
|
+
FORMAT_JIF = 7
|
|
10
|
+
FORMAT_JFIF = 8
|
|
11
|
+
FORMAT_JFI = 9
|
|
12
|
+
FORMAT_WEBP = 10
|
|
13
|
+
FORMAT_AVIF = 11
|
|
14
|
+
FORMAT_HEIF = 12
|
|
15
|
+
FORMAT_HEIC = 13
|
|
16
|
+
|
|
17
|
+
FormatList = {
|
|
18
|
+
FORMAT_JPG: "jpg",
|
|
19
|
+
FORMAT_JPEG: "jpeg",
|
|
20
|
+
FORMAT_GIF: "gif",
|
|
21
|
+
FORMAT_PNG: "png",
|
|
22
|
+
FORMAT_APNG: "apng",
|
|
23
|
+
FORMAT_JPE: "jpe",
|
|
24
|
+
FORMAT_JIF: "jif",
|
|
25
|
+
FORMAT_JFIF: "jfif",
|
|
26
|
+
FORMAT_JFI: "jfi",
|
|
27
|
+
FORMAT_WEBP: "webp",
|
|
28
|
+
FORMAT_AVIF: "avif",
|
|
29
|
+
FORMAT_HEIF: "heif",
|
|
30
|
+
FORMAT_HEIC: "heic",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def GetFormat(format: str) -> Union[int, bool]:
|
|
35
|
+
format = format.lower()
|
|
36
|
+
for key in FormatList:
|
|
37
|
+
if FormatList[key] == format:
|
|
38
|
+
return key
|
|
39
|
+
return False
|
imager/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Mapping, Any, Union
|
|
2
|
+
from .Imager import Imager
|
|
3
|
+
from .ImagerFormat import FormatList
|
|
4
|
+
from .ImagerData import Flags, FlagsSorted
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ImagerGet(file: str, setting: Union[None, Mapping[str, Any]] = None) -> str:
|
|
8
|
+
"""Получаем ассет на миниатюру в формате исходного файла
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
file (str): путь к исходной картинке
|
|
12
|
+
setting (None | Mapping[str, Any], optional): Настройки. Defaults to None.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: asset path
|
|
16
|
+
"""
|
|
17
|
+
img = Imager()
|
|
18
|
+
if setting != None:
|
|
19
|
+
img.setting(setting)
|
|
20
|
+
return img.get(file)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ImagerConvert(
|
|
24
|
+
file: str, format: str, setting: Union[None, Mapping[str, Any]] = None
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Получаем ассет на миниатюру в указаном формате
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
file (str): путь к исходной картинке
|
|
30
|
+
format (str): формат файла миниатюры
|
|
31
|
+
setting (None | Mapping[str, Any], optional): Настройки. Defaults to None.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
str: asset path
|
|
35
|
+
"""
|
|
36
|
+
img = Imager()
|
|
37
|
+
if setting != None:
|
|
38
|
+
img.setting(setting)
|
|
39
|
+
return img.convert(file, format)
|