py-img-processor 1.1.0__py3-none-any.whl → 1.2.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.

Potentially problematic release.


This version of py-img-processor might be problematic. Click here for more details.

imgprocessor/__init__.py CHANGED
@@ -5,7 +5,7 @@ import importlib
5
5
 
6
6
 
7
7
  __all__ = ["settings", "VERSION"]
8
- __version__ = "1.1.0"
8
+ __version__ = "1.2.0"
9
9
 
10
10
 
11
11
  VERSION = __version__
@@ -41,6 +41,10 @@ class SettingsProxy(object):
41
41
  PROCESSOR_DEFAULT_QUALITY = 75
42
42
  # 默认字体文件; 默认配置了MacOS系统中的字体
43
43
  PROCESSOR_TEXT_FONT = "Arial Unicode.ttf"
44
+ # 工作目录列表:例如水印文件必须限制在设定的目录下,避免恶意访问文件
45
+ PROCESSOR_WORKSPACES = ()
46
+ # 当资源文件uri使用链接地址时,限制地址域名来源
47
+ PROCESSOR_ALLOW_DOMAINS = ()
44
48
 
45
49
  def __getattribute__(self, attr: str) -> typing.Any:
46
50
  try:
imgprocessor/enums.py CHANGED
@@ -62,6 +62,8 @@ class ArgType(ChoiceEnum):
62
62
  STRING = ("str", "字符串")
63
63
  INTEGER = ("int", "整数")
64
64
  FLOAT = ("float", "浮点数")
65
+ URI = ("uri", "资源路径/链接")
66
+ ACTION = ("action", "对图像的操作")
65
67
 
66
68
 
67
69
  class Geography(ChoiceEnum):
imgprocessor/main.py CHANGED
@@ -7,7 +7,7 @@ import argparse
7
7
  import traceback
8
8
 
9
9
  from imgprocessor import VERSION
10
- from imgprocessor.processor import ProcessParams, process_image_by_path
10
+ from imgprocessor.processor import ProcessParams, process_image
11
11
 
12
12
 
13
13
  def main(argv: typing.Optional[list[str]] = None) -> int:
@@ -95,7 +95,7 @@ def main(argv: typing.Optional[list[str]] = None) -> int:
95
95
  if not os.path.exists(cur_out_dir):
96
96
  os.makedirs(cur_out_dir)
97
97
  try:
98
- process_image_by_path(file_path, out_path, param_str)
98
+ process_image(file_path, out_path, param_str)
99
99
  print(f"{tag}\t 成功", flush=True)
100
100
  except Exception as e:
101
101
  print(f"{tag}\t \033[31m失败:{e}\033[0m", file=sys.stderr, flush=True)
@@ -4,7 +4,8 @@ import typing
4
4
 
5
5
  from imgprocessor import enums
6
6
  from imgprocessor.exceptions import ParamParseException
7
- from .base import BaseParser, ImgSaveParser # noqa: F401
7
+
8
+ from .base import BaseParser, ImgSaveParser
8
9
  from .resize import ResizeParser
9
10
  from .crop import CropParser
10
11
  from .circle import CircleParser
@@ -40,7 +41,7 @@ class ProcessParams(object):
40
41
  ) -> None:
41
42
  self.save_parser: ImgSaveParser = ImgSaveParser.init(kwargs, enable_base64=enable_base64) # type: ignore
42
43
 
43
- _actions = []
44
+ _actions: list[BaseParser] = []
44
45
  for i in actions or []:
45
46
  key = i.get("key")
46
47
  cls = _ACTION_PARASER_MAP.get(key)
@@ -1,11 +1,20 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
1
3
  import typing
2
4
 
5
+ import os
3
6
  import re
7
+ import tempfile
8
+ import urllib.parse
9
+ from urllib.request import urlretrieve
4
10
 
5
11
  from PIL import Image, ImageOps
6
12
 
7
- from imgprocessor import enums, utils
8
- from imgprocessor.exceptions import ParamValidateException, ParamParseException
13
+ from imgprocessor import settings, enums, utils
14
+ from imgprocessor.exceptions import ParamValidateException, ParamParseException, ProcessLimitException
15
+
16
+
17
+ _ALLOW_SCHEMES = ("http", "https")
9
18
 
10
19
 
11
20
  class BaseParser(object):
@@ -64,6 +73,11 @@ class BaseParser(object):
64
73
  value = cls._validate_number(value, use_float=True, **config)
65
74
  elif _type == enums.ArgType.STRING:
66
75
  value = cls._validate_str(value, enable_base64=enable_base64, **config)
76
+ elif _type == enums.ArgType.URI:
77
+ value = cls._validate_uri(value, enable_base64=enable_base64, **config)
78
+ elif _type == enums.ArgType.ACTION:
79
+ if value and isinstance(value, str):
80
+ value = cls._validate_str(value, enable_base64=enable_base64, **config)
67
81
 
68
82
  choices = config.get("choices")
69
83
  if choices and value not in choices:
@@ -82,7 +96,7 @@ class BaseParser(object):
82
96
  regex: typing.Optional[str] = None,
83
97
  base64_encode: bool = False,
84
98
  max_length: typing.Optional[int] = None,
85
- **kwargs: dict,
99
+ **kwargs: typing.Any,
86
100
  ) -> str:
87
101
  if not isinstance(value, str):
88
102
  raise ParamValidateException("参数类型不符合要求,必须是字符串类型")
@@ -101,7 +115,7 @@ class BaseParser(object):
101
115
  min: typing.Optional[int] = None,
102
116
  max: typing.Optional[int] = None,
103
117
  use_float: bool = False,
104
- **kwargs: dict,
118
+ **kwargs: typing.Any,
105
119
  ) -> typing.Union[int, float]:
106
120
  if isinstance(value, int) or (use_float and isinstance(value, (int, float))):
107
121
  v = value
@@ -125,6 +139,47 @@ class BaseParser(object):
125
139
 
126
140
  return v
127
141
 
142
+ @classmethod
143
+ def _validate_uri(cls, value: typing.Any, **kwargs: typing.Any) -> str:
144
+ """校验输入的资源,转换为本地绝对路径
145
+
146
+ Args:
147
+ value: 输入值
148
+ workspace: 限制资源路径,可传递空字符串忽略校验. Defaults to None.
149
+ allow_domains: 限制资源地址的域名,可传递空数组忽略校验. Defaults to None.
150
+
151
+ Raises:
152
+ ParamValidateException: 参数校验异常
153
+
154
+ Returns:
155
+ 系统文件绝对路径
156
+ """
157
+ # 首先是字符串
158
+ value = cls._validate_str(value, **kwargs)
159
+ ori_value = value
160
+ # 判断是否是链接
161
+ parsed_url = urllib.parse.urlparse(value)
162
+ if parsed_url.scheme not in _ALLOW_SCHEMES:
163
+ value = os.path.realpath(os.fspath(value))
164
+ if not os.path.isfile(value):
165
+ raise ParamValidateException(f"系统文件不存在: {ori_value}")
166
+
167
+ workspaces: tuple = settings.PROCESSOR_WORKSPACES or ()
168
+ _workspace = [os.path.realpath(os.fspath(ws)) for ws in workspaces]
169
+ if _workspace and not value.startswith(tuple(_workspace)):
170
+ raise ParamValidateException(f"文件必须在 PROCESSOR_WORKSPACES={workspaces} 目录下: {ori_value}")
171
+ else:
172
+ # 是链接地址
173
+ domain = parsed_url.netloc
174
+ if not domain:
175
+ raise ParamValidateException(f"链接未解析出域名: {ori_value}")
176
+ allow_domains = settings.PROCESSOR_ALLOW_DOMAINS
177
+ if allow_domains and not parsed_url.netloc.endswith(tuple(allow_domains)):
178
+ raise ParamValidateException(
179
+ f"域名不合法, {parsed_url.netloc} 不在 {allow_domains} 范围内: {ori_value}"
180
+ )
181
+ return value
182
+
128
183
  @classmethod
129
184
  def parse_str(cls, param_str: str) -> dict:
130
185
  """将字符串参数转化为json格式数据
@@ -304,6 +359,53 @@ def compute_splice_two_im(
304
359
  return w, h, x1, y1, x2, y2
305
360
 
306
361
 
362
+ def validate_ori_im(ori_im: Image) -> None:
363
+ src_w, src_h = ori_im.size
364
+ if src_w > settings.PROCESSOR_MAX_W_H or src_h > settings.PROCESSOR_MAX_W_H:
365
+ raise ProcessLimitException(
366
+ f"图像宽和高单边像素不能超过{settings.PROCESSOR_MAX_W_H}像素,输入图像({src_w}, {src_h})"
367
+ )
368
+ if src_w * src_h > settings.PROCESSOR_MAX_PIXEL:
369
+ raise ProcessLimitException(f"图像总像素不可超过{settings.PROCESSOR_MAX_PIXEL}像素,输入图像({src_w}, {src_h})")
370
+
371
+
372
+ def trans_uri_to_im(uri: str) -> Image:
373
+ """将输入资源转换成Image对象
374
+
375
+ Args:
376
+ uri: 文件路径 或者 可下载的链接地址
377
+
378
+ Raises:
379
+ ProcessLimitException: 处理图像大小/像素限制
380
+
381
+ Returns:
382
+ Image对象
383
+ """
384
+ parsed_url = urllib.parse.urlparse(uri)
385
+ if parsed_url.scheme in _ALLOW_SCHEMES:
386
+ with tempfile.NamedTemporaryFile() as fp:
387
+ # 输入值计算md5作为文件名;重复地址本地若存在不下载多次
388
+ urlretrieve(uri, filename=fp.name)
389
+ fp.seek(0)
390
+
391
+ size = os.path.getsize(fp.name)
392
+ if size > settings.PROCESSOR_MAX_FILE_SIZE * 1024 * 1024:
393
+ raise ProcessLimitException(f"图像文件大小不得超过{settings.PROCESSOR_MAX_FILE_SIZE}MB")
394
+
395
+ ori_im = Image.open(fp)
396
+ validate_ori_im(ori_im)
397
+ # 解决临时文件close后im对象不能正常使用得问题
398
+ ori_im = ori_im.copy()
399
+ else:
400
+ size = os.path.getsize(uri)
401
+ if size > settings.PROCESSOR_MAX_FILE_SIZE * 1024 * 1024:
402
+ raise ProcessLimitException(f"图像文件大小不得超过{settings.PROCESSOR_MAX_FILE_SIZE}MB")
403
+ ori_im = Image.open(uri)
404
+ validate_ori_im(ori_im)
405
+
406
+ return ori_im
407
+
408
+
307
409
  class ImgSaveParser(BaseParser):
308
410
  KEY = ""
309
411
  ARGS = {
@@ -5,7 +5,8 @@ import typing
5
5
  from PIL import Image
6
6
 
7
7
  from imgprocessor import enums, settings
8
- from .base import BaseParser, pre_processing, compute_by_geography, compute_splice_two_im
8
+ from imgprocessor.exceptions import ParamValidateException
9
+ from .base import BaseParser, pre_processing, compute_by_geography, compute_splice_two_im, trans_uri_to_im
9
10
 
10
11
 
11
12
  class MergeParser(BaseParser):
@@ -13,11 +14,13 @@ class MergeParser(BaseParser):
13
14
  KEY = enums.OpAction.MERGE
14
15
  ARGS = {
15
16
  # 要处理的图片
16
- "image": {"type": enums.ArgType.STRING, "required": True, "base64_encode": True},
17
+ "image": {"type": enums.ArgType.URI, "required": True, "base64_encode": True},
17
18
  # 对image的处理参数
18
- "action": {"type": enums.ArgType.STRING, "base64_encode": True},
19
+ "actions": {"type": enums.ArgType.ACTION, "base64_encode": True},
19
20
  # 使用输入图像的大小作为参照进行缩放
20
21
  "p": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": 1000},
22
+ # 是否将imgae当做背景放在输入图像之下; 定义输入图像和image参数的拼接顺序
23
+ "bg": {"type": enums.ArgType.INTEGER, "default": 0, "choices": [0, 1]},
21
24
  # 对齐方式
22
25
  "order": {"type": enums.ArgType.INTEGER, "choices": enums.PositionOrder},
23
26
  "align": {"type": enums.ArgType.INTEGER, "default": enums.PositionAlign.BOTTOM, "choices": enums.PositionAlign},
@@ -30,38 +33,52 @@ class MergeParser(BaseParser):
30
33
  # 拼接后大小包含2个图像,空白区域使用color颜色填充
31
34
  "color": {
32
35
  "type": enums.ArgType.STRING,
33
- "default": "FFFFFF",
36
+ "default": "0000", # 为了保证透明背景
34
37
  "regex": r"^([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3,4})$",
35
38
  },
36
39
  }
37
40
 
38
41
  def __init__(
39
42
  self,
40
- image: typing.Optional[str] = None,
41
- action: typing.Optional[str] = None,
43
+ image: str = "",
44
+ actions: typing.Union[str, list] = "",
42
45
  p: int = 0,
43
46
  order: typing.Optional[int] = None,
44
47
  align: int = 2,
45
48
  interval: int = 0,
49
+ bg: int = 0,
46
50
  g: typing.Optional[str] = None,
47
51
  x: int = 0,
48
52
  y: int = 0,
49
53
  pf: str = "",
50
- color: str = "FFFFFF",
54
+ color: str = "0000",
51
55
  **kwargs: typing.Any,
52
56
  ) -> None:
53
57
  self.image = image
54
- self.action = action
55
58
  self.p = p
56
59
  self.order = order
57
60
  self.align = align
58
61
  self.interval = interval
62
+ self.bg = bg
59
63
  self.g = g
60
64
  self.x = x
61
65
  self.y = y
62
66
  self.pf = pf
63
67
  self.color = color
64
68
 
69
+ self.actions: list[BaseParser] = []
70
+ if actions:
71
+ from imgprocessor.processor import ProcessParams
72
+
73
+ try:
74
+ if isinstance(actions, str):
75
+ params = ProcessParams.parse_str(actions)
76
+ else:
77
+ params = ProcessParams(actions=actions)
78
+ self.actions = params.actions
79
+ except ParamValidateException as e:
80
+ raise ParamValidateException(f"merage操作中actions参数校验异常,其中 {e}")
81
+
65
82
  def compute(self, src_w: int, src_h: int, w2: int, h2: int) -> tuple:
66
83
  if self.order in enums.PositionOrder: # type: ignore
67
84
  order = typing.cast(int, self.order)
@@ -92,18 +109,25 @@ class MergeParser(BaseParser):
92
109
  im = pre_processing(im, use_alpha=True)
93
110
  src_w, src_h = im.size
94
111
 
95
- im2 = Image.open(self.image)
112
+ # 处理要合并的图像
113
+ im2 = trans_uri_to_im(self.image)
96
114
  im2 = pre_processing(im2, use_alpha=True)
97
- if self.action:
98
- from imgprocessor.processor import ProcessParams, handle_img_actions
115
+ if self.actions:
116
+ from imgprocessor.processor import ProcessorCtr
99
117
 
100
- params = ProcessParams.parse_str(self.action)
101
- im2 = handle_img_actions(im2, params.actions)
118
+ im2 = ProcessorCtr.handle_img_actions(im2, self.actions)
102
119
  if self.p:
103
120
  w2, h2 = round(src_w * self.p / 100), round(src_h * self.p / 100)
104
121
  im2 = im2.resize((w2, h2), resample=Image.LANCZOS)
122
+
123
+ if self.bg:
124
+ # 调整拼接顺序
125
+ im, im2 = im2, im
126
+
127
+ src_w, src_h = im.size
105
128
  w2, h2 = im2.size
106
129
 
130
+ # 计算合并像素点
107
131
  w, h, x1, y1, x2, y2 = self.compute(src_w, src_h, w2, h2)
108
132
  out = Image.new("RGBA", (w, h), color=f"#{self.color}")
109
133
  out.paste(im, (x1, y1), im)
@@ -20,7 +20,7 @@ class ResizeParser(BaseParser):
20
20
  "limit": {"type": enums.ArgType.INTEGER, "default": 1, "choices": [0, 1]},
21
21
  "color": {
22
22
  "type": enums.ArgType.STRING,
23
- "default": "FFFFFF",
23
+ "default": "FFFFFF", # 默认白色
24
24
  "regex": r"^([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3,4})$",
25
25
  },
26
26
  "p": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": 1000},
@@ -6,7 +6,7 @@ from PIL import Image, ImageFont, ImageDraw
6
6
 
7
7
  from imgprocessor import enums, settings, utils
8
8
  from imgprocessor.exceptions import ParamValidateException
9
- from .base import BaseParser, pre_processing, compute_splice_two_im, compute_by_geography
9
+ from .base import BaseParser, pre_processing, compute_splice_two_im, compute_by_geography, trans_uri_to_im
10
10
 
11
11
 
12
12
  class WatermarkParser(BaseParser):
@@ -25,12 +25,13 @@ class WatermarkParser(BaseParser):
25
25
  "padx": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 4096},
26
26
  "pady": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 4096},
27
27
  # 图片水印路径
28
- "image": {"type": enums.ArgType.STRING, "base64_encode": True},
28
+ "image": {"type": enums.ArgType.URI, "base64_encode": True},
29
29
  # 水印的原始设计参照尺寸,会根据原图大小缩放水印
30
30
  "design": {"type": enums.ArgType.INTEGER, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
31
31
  # 文字
32
32
  "text": {"type": enums.ArgType.STRING, "base64_encode": True, "max_length": 64},
33
33
  "font": {"type": enums.ArgType.STRING, "base64_encode": True},
34
+ # 文字默认黑色
34
35
  "color": {"type": enums.ArgType.STRING, "default": "000000", "regex": "^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$"},
35
36
  "size": {"type": enums.ArgType.INTEGER, "default": 40, "min": 1, "max": 1000},
36
37
  # 文字水印的阴影透明度, 0表示没有阴影
@@ -99,7 +100,7 @@ class WatermarkParser(BaseParser):
99
100
  w1, h1, w2, h2 = 0, 0, 0, 0
100
101
  icon = None
101
102
  if self.image:
102
- icon = Image.open(self.image)
103
+ icon = trans_uri_to_im(self.image)
103
104
  icon = pre_processing(icon, use_alpha=True)
104
105
  if not self.text:
105
106
  # 没有文字,直接返回
imgprocessor/processor.py CHANGED
@@ -1,73 +1,88 @@
1
1
  #!/usr/bin/env python
2
2
  # coding=utf-8
3
3
  import typing
4
- import os
5
4
  import tempfile
6
5
  import colorsys
7
6
 
8
7
  from PIL import Image, ImageOps
9
8
 
10
- from imgprocessor import settings, enums
11
- from imgprocessor.exceptions import ProcessLimitException
9
+ from imgprocessor import enums, settings
12
10
  from imgprocessor.parsers import BaseParser, ProcessParams
13
-
14
-
15
- def handle_img_actions(ori_im: Image, actions: list[BaseParser]) -> Image:
16
- src_w, src_h = ori_im.size
17
- if src_w > settings.PROCESSOR_MAX_W_H or src_h > settings.PROCESSOR_MAX_W_H:
18
- raise ProcessLimitException(
19
- f"图像宽和高单边像素不能超过{settings.PROCESSOR_MAX_W_H}像素,输入图像({src_w}, {src_h})"
20
- )
21
- if src_w * src_h > settings.PROCESSOR_MAX_PIXEL:
22
- raise ProcessLimitException(f"图像总像素不可超过{settings.PROCESSOR_MAX_PIXEL}像素,输入图像({src_w}, {src_h})")
23
-
24
- im = ori_im
25
- im = ImageOps.exif_transpose(im)
26
-
27
- for parser in actions:
28
- im = parser.do_action(im)
29
-
30
- return im
31
-
32
-
33
- def save_img_to_file(
34
- im: Image,
35
- out_path: typing.Optional[str] = None,
36
- **kwargs: typing.Any,
37
- ) -> typing.Optional[typing.ByteString]:
38
- fmt = kwargs.get("format") or im.format
39
- kwargs["format"] = fmt
40
-
41
- if fmt.upper() == enums.ImageFormat.JPEG and im.mode == "RGBA":
42
- im = im.convert("RGB")
43
-
44
- if not kwargs.get("quality"):
45
- if fmt.upper() == enums.ImageFormat.JPEG and im.format == enums.ImageFormat.JPEG:
46
- kwargs["quality"] = "keep"
47
- else:
48
- kwargs["quality"] = settings.PROCESSOR_DEFAULT_QUALITY
49
-
50
- if out_path:
51
- # icc_profile 是为解决色域的问题
52
- im.save(out_path, **kwargs)
53
- return None
54
-
55
- # 没有传递保存的路径,返回文件内容
56
- suffix = fmt or "png"
57
- with tempfile.NamedTemporaryFile(suffix=f".{suffix}") as fp:
58
- im.save(fp.name, **kwargs)
59
- fp.seek(0)
60
- content = fp.read()
61
- return content
62
-
63
-
64
- def process_image_by_path(
65
- input_path: str, out_path: str, params: typing.Union[ProcessParams, dict, str]
11
+ from imgprocessor.parsers.base import trans_uri_to_im
12
+
13
+
14
+ class ProcessorCtr(object):
15
+
16
+ def __init__(self, input_uri: str, out_path: str, params: typing.Union[ProcessParams, dict, str]) -> None:
17
+ # 输入文件检验路径
18
+ self.input_uri = BaseParser._validate_uri(input_uri)
19
+ self.out_path = out_path
20
+ # 初始化处理参数
21
+ if isinstance(params, dict):
22
+ params = ProcessParams(**params)
23
+ elif isinstance(params, str):
24
+ params = ProcessParams.parse_str(params)
25
+ params = typing.cast(ProcessParams, params)
26
+ self.params = params
27
+
28
+ def run(self) -> typing.Optional[typing.ByteString]:
29
+ # 初始化输入
30
+ ori_im = trans_uri_to_im(self.input_uri)
31
+ # 处理图像
32
+ im = self.handle_img_actions(ori_im, self.params.actions)
33
+ # 输出、保存
34
+ kwargs = self.params.save_parser.compute(ori_im, im)
35
+ return self.save_img_to_file(im, out_path=self.out_path, **kwargs)
36
+
37
+ @classmethod
38
+ def handle_img_actions(cls, ori_im: Image, actions: list[BaseParser]) -> Image:
39
+ im = ori_im
40
+ # 解决旋转问题
41
+ im = ImageOps.exif_transpose(im)
42
+ for parser in actions:
43
+ im = parser.do_action(im)
44
+ return im
45
+
46
+ @classmethod
47
+ def save_img_to_file(
48
+ cls,
49
+ im: Image,
50
+ out_path: typing.Optional[str] = None,
51
+ **kwargs: typing.Any,
52
+ ) -> typing.Optional[typing.ByteString]:
53
+ fmt = kwargs.get("format") or im.format
54
+ kwargs["format"] = fmt
55
+
56
+ if fmt.upper() == enums.ImageFormat.JPEG and im.mode == "RGBA":
57
+ im = im.convert("RGB")
58
+
59
+ if not kwargs.get("quality"):
60
+ if fmt.upper() == enums.ImageFormat.JPEG and im.format == enums.ImageFormat.JPEG:
61
+ kwargs["quality"] = "keep"
62
+ else:
63
+ kwargs["quality"] = settings.PROCESSOR_DEFAULT_QUALITY
64
+
65
+ if out_path:
66
+ # icc_profile 是为解决色域的问题
67
+ im.save(out_path, **kwargs)
68
+ return None
69
+
70
+ # 没有传递保存的路径,返回文件内容
71
+ suffix = fmt or "png"
72
+ with tempfile.NamedTemporaryFile(suffix=f".{suffix}") as fp:
73
+ im.save(fp.name, **kwargs)
74
+ fp.seek(0)
75
+ content = fp.read()
76
+ return content
77
+
78
+
79
+ def process_image(
80
+ input_uri: str, out_path: str, params: typing.Union[ProcessParams, dict, str]
66
81
  ) -> typing.Optional[typing.ByteString]:
67
82
  """处理图像
68
83
 
69
84
  Args:
70
- input_path: 输入图像文件路径
85
+ input_uri: 输入图像路径
71
86
  out_path: 输出图像保存路径
72
87
  params: 图像处理参数
73
88
 
@@ -77,21 +92,8 @@ def process_image_by_path(
77
92
  Returns:
78
93
  默认输出直接存储无返回,仅当out_path为空时会返回处理后图像的二进制内容
79
94
  """
80
- size = os.path.getsize(input_path)
81
- if size > settings.PROCESSOR_MAX_FILE_SIZE * 1024 * 1024:
82
- raise ProcessLimitException(f"图像文件大小不得超过{settings.PROCESSOR_MAX_FILE_SIZE}MB")
83
- if isinstance(params, dict):
84
- params = ProcessParams(**params)
85
- elif isinstance(params, str):
86
- params = ProcessParams.parse_str(params)
87
- params = typing.cast(ProcessParams, params)
88
-
89
- ori_im = Image.open(input_path)
90
- # 处理图像
91
- im = handle_img_actions(ori_im, params.actions)
92
-
93
- kwargs = params.save_parser.compute(ori_im, im)
94
- return save_img_to_file(im, out_path=out_path, **kwargs)
95
+ ctr = ProcessorCtr(input_uri, out_path, params)
96
+ return ctr.run()
95
97
 
96
98
 
97
99
  def extract_main_color(img_path: str, delta_h: float = 0.3) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-img-processor
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Image editor using Python and Pillow.
5
5
  Home-page: https://github.com/SkylerHu/py-img-processor.git
6
6
  Author: SkylerHu
@@ -71,6 +71,8 @@ Image editor using Python and Pillow.
71
71
  | PROCESSOR_MAX_PIXEL | int | width x height总像素3亿,处理前后的值都被此配置限制 | 300000000 |
72
72
  | PROCESSOR_DEFAULT_QUALITY | int | 图像处理后的默认质量 | 75 |
73
73
  | PROCESSOR_TEXT_FONT | str | 默认字体文件,默认从系统中寻找;也可以直接传递字体文件路径 | Arial Unicode.ttf |
74
+ | PROCESSOR_WORKSPACES | tuple | 限制水印等资源路径 (startswith匹配), 默认无限制 | `()` |
75
+ | PROCESSOR_ALLOW_DOMAINS | tuple | 限制链接地址域名 (endswith匹配),默认无限制 | `()` |
74
76
 
75
77
  > `注意`:`PROCESSOR_TEXT_FONT` 字体的设置是文字水印必要参数,需保证系统已安装该字体。默认值 `Arial Unicode.ttf` 是MacOS系统存在的字体,建议设置字体文件路径。
76
78
 
@@ -82,11 +84,15 @@ Image editor using Python and Pillow.
82
84
 
83
85
 
84
86
  ### 处理函数
85
- `process_image_by_path(input_path, out_path, params)`
87
+ ```python
88
+ from imgprocessor.processor import process_image
89
+
90
+ process_image(input_uri, out_path, params)
91
+ ```
86
92
 
87
93
  参数说明:
88
94
 
89
- - `input_path` str,输入图像文件路径
95
+ - `input_uri` str,输入图像文件路径或者链接地址
90
96
  - `out_path` str, 输出图像保存路径
91
97
  - `params` str or json,图像处理参数,参数说明详见 [Reference.md](https://github.com/SkylerHu/py-img-processor/blob/master/docs/Reference.md)
92
98
 
@@ -100,9 +106,9 @@ Image editor using Python and Pillow.
100
106
 
101
107
  ```python
102
108
  from imgprocessor.utils import base64url_encode
103
- from imgprocessor.processor import process_image_by_path
109
+ from imgprocessor.processor import process_image
104
110
 
105
- process_image_by_path(
111
+ process_image(
106
112
  "docs/imgs/lenna-400x225.jpg",
107
113
  "/tmp/output.png",
108
114
  # 对图片缩放、裁剪、生成圆角、并转成png存储
@@ -120,7 +126,7 @@ process_image_by_path(
120
126
  - 其他参数都放在 `actions` 数组中;
121
127
 
122
128
  ```python
123
- process_image_by_path(
129
+ process_image(
124
130
  "docs/imgs/lenna-400x225.jpg",
125
131
  "/tmp/output.png",
126
132
  {
@@ -0,0 +1,23 @@
1
+ imgprocessor/__init__.py,sha256=SlzflXwdzE50xadA57E1_Yil-Zp6DL9pCvbuEYSp4UY,2485
2
+ imgprocessor/enums.py,sha256=uionzxbpytnM1iWPTj2Imh6v20kOhGy1i2HPsH-J14U,3129
3
+ imgprocessor/exceptions.py,sha256=kj9win_XjCbZnXr93sYs4Cfg1hAJX2fn5hgr1YMNoHQ,384
4
+ imgprocessor/main.py,sha256=WDRsVtHNqlsngZHVZHuY0IE93OsXC5XD1fL-Zbw0KDs,4117
5
+ imgprocessor/processor.py,sha256=8btCEkQPnxpRg9FNmMyGiwEEVnrDbLFn-Jh4goUWRYM,4466
6
+ imgprocessor/utils.py,sha256=KBRQJaN8FuuRD2QQPYuuHQ7S-2ML-ctz4rstQTrEdog,1202
7
+ imgprocessor/parsers/__init__.py,sha256=BfnkuAAuKRaVf-CVs_oY8zYmX99jOsYQD0hqhRf8Obg,3009
8
+ imgprocessor/parsers/alpha.py,sha256=grOnmZSYhO0wI2pJfGz6CfTwOTc8kfcxspmfM0nzAzA,865
9
+ imgprocessor/parsers/base.py,sha256=xctgD_l43onshLYkOl1dN255SfIdIcDCCWbBgV0v5SA,16056
10
+ imgprocessor/parsers/blur.py,sha256=Y9BeTK7e-y8HPNeLQxgtqiquOVf2cVLXhXn1bv9072g,661
11
+ imgprocessor/parsers/circle.py,sha256=z0IZZSv_zuY2jzB6kN01sB1jGhBure33DPt7JAEeNsk,1653
12
+ imgprocessor/parsers/crop.py,sha256=P2kqNVn9tiorq_kgBXLxygthL2EKulNkP6aRh1FrmVw,3464
13
+ imgprocessor/parsers/gray.py,sha256=ZDBHZlBfI91TePWWBU0feSHmtD1ekre5-uV2BDLFTKA,396
14
+ imgprocessor/parsers/merge.py,sha256=mbnv24_6bkDu56maC1OHjmNr_HsDUWWALtAgTHFcWws,4984
15
+ imgprocessor/parsers/resize.py,sha256=W0fZrnqHSotCew0OkqkGeWlr6I9lWRTwT4OwSPzIFfo,5036
16
+ imgprocessor/parsers/rotate.py,sha256=EDKvlxTgZZu7vAkSS9cuZ296dbAhe5Aq6huH9Hjtvy0,722
17
+ imgprocessor/parsers/watermark.py,sha256=VwGzUNkGZvn_HedAHdau7zrUlSDf8iLNzS1gNxqLFkQ,8536
18
+ py_img_processor-1.2.0.dist-info/LICENSE,sha256=Sr4UxtHWGmoO_enyTUeAN276LkBqJpse0guCcb6f9BQ,1066
19
+ py_img_processor-1.2.0.dist-info/METADATA,sha256=jqGq_13-XU7QuaD2cWvccXP3oxXQl9rb6nx2-toyBUg,7553
20
+ py_img_processor-1.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
21
+ py_img_processor-1.2.0.dist-info/entry_points.txt,sha256=MLeLjzpkH7DkDMgICWCQ99D9ElqAvTBVBGA8yjg4dhQ,57
22
+ py_img_processor-1.2.0.dist-info/top_level.txt,sha256=5Pm26oHcqZoihGGxc5N6qQJ2LuVa2i4au_uqHBMqehI,13
23
+ py_img_processor-1.2.0.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- imgprocessor/__init__.py,sha256=2ebw4pYHInmKQKQEbkwQ-fHx0uM7ED026wgR04y7EYo,2243
2
- imgprocessor/enums.py,sha256=-eA9pv5g-0Xm23jrHv5aJf2g56t95-hUgzSz4xlx5Go,3042
3
- imgprocessor/exceptions.py,sha256=kj9win_XjCbZnXr93sYs4Cfg1hAJX2fn5hgr1YMNoHQ,384
4
- imgprocessor/main.py,sha256=QDkz__RJ_iIjrb1p56bhPzZ7yLbtzhXvvKpLmvkJEkI,4133
5
- imgprocessor/processor.py,sha256=4f6DTNeIRMTAlvtEn7UQQFuNUxMQJSB_dfkzERjvUQ4,4388
6
- imgprocessor/utils.py,sha256=KBRQJaN8FuuRD2QQPYuuHQ7S-2ML-ctz4rstQTrEdog,1202
7
- imgprocessor/parsers/__init__.py,sha256=3aSxKTflK2hZxaiYtaX3HsFvLwg9QE7YLIp4gdTf7T8,3004
8
- imgprocessor/parsers/alpha.py,sha256=grOnmZSYhO0wI2pJfGz6CfTwOTc8kfcxspmfM0nzAzA,865
9
- imgprocessor/parsers/base.py,sha256=80VTehgA8LwwVety3_IGSJ_vsX01mWAZNb-c6v3AT2o,11832
10
- imgprocessor/parsers/blur.py,sha256=Y9BeTK7e-y8HPNeLQxgtqiquOVf2cVLXhXn1bv9072g,661
11
- imgprocessor/parsers/circle.py,sha256=z0IZZSv_zuY2jzB6kN01sB1jGhBure33DPt7JAEeNsk,1653
12
- imgprocessor/parsers/crop.py,sha256=P2kqNVn9tiorq_kgBXLxygthL2EKulNkP6aRh1FrmVw,3464
13
- imgprocessor/parsers/gray.py,sha256=ZDBHZlBfI91TePWWBU0feSHmtD1ekre5-uV2BDLFTKA,396
14
- imgprocessor/parsers/merge.py,sha256=JwJKIVlmeKxEQIeuYBKT_i5E6jBBOM2m4fqKWpPfuhs,4049
15
- imgprocessor/parsers/resize.py,sha256=Zeh9LDLQVaEqijcOsljKGDNTQ2rUmijzQ0ziAgYsSoI,5020
16
- imgprocessor/parsers/rotate.py,sha256=EDKvlxTgZZu7vAkSS9cuZ296dbAhe5Aq6huH9Hjtvy0,722
17
- imgprocessor/parsers/watermark.py,sha256=y5ddCw9lRS1fwMjHtI0lIGpWcCIFSQA6Q0l7gpcNS7g,8488
18
- py_img_processor-1.1.0.dist-info/LICENSE,sha256=Sr4UxtHWGmoO_enyTUeAN276LkBqJpse0guCcb6f9BQ,1066
19
- py_img_processor-1.1.0.dist-info/METADATA,sha256=bEzbYrVLFqjJBP6pbvt-55Tq789Yo6e_L3Pgyi--RCY,7286
20
- py_img_processor-1.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
21
- py_img_processor-1.1.0.dist-info/entry_points.txt,sha256=MLeLjzpkH7DkDMgICWCQ99D9ElqAvTBVBGA8yjg4dhQ,57
22
- py_img_processor-1.1.0.dist-info/top_level.txt,sha256=5Pm26oHcqZoihGGxc5N6qQJ2LuVa2i4au_uqHBMqehI,13
23
- py_img_processor-1.1.0.dist-info/RECORD,,