novelai-python 0.2.9__tar.gz → 0.3.1__tar.gz

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.
Files changed (50) hide show
  1. {novelai_python-0.2.9 → novelai_python-0.3.1}/PKG-INFO +2 -1
  2. {novelai_python-0.2.9 → novelai_python-0.3.1}/pyproject.toml +2 -1
  3. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/ai/generate_image.py +17 -12
  4. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/server.py +29 -1
  5. novelai_python-0.3.1/src/novelai_python/utils/useful.py +104 -0
  6. novelai_python-0.2.9/src/novelai_python/utils/useful.py +0 -44
  7. {novelai_python-0.2.9 → novelai_python-0.3.1}/LICENSE +0 -0
  8. {novelai_python-0.2.9 → novelai_python-0.3.1}/NOTICE.MD +0 -0
  9. {novelai_python-0.2.9 → novelai_python-0.3.1}/README.md +0 -0
  10. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/__init__.py +0 -0
  11. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_exceptions.py +0 -0
  12. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/__init__.py +0 -0
  13. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/ai/__init__.py +0 -0
  14. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/ai/generate_image.py +0 -0
  15. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/ai/upscale.py +0 -0
  16. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/schema.py +0 -0
  17. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/user/__init__.py +0 -0
  18. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/user/information.py +0 -0
  19. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/user/login.py +0 -0
  20. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/_response/user/subscription.py +0 -0
  21. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/credential/ApiToken.py +0 -0
  22. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/credential/JwtToken.py +0 -0
  23. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/credential/UserAuth.py +0 -0
  24. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/credential/__init__.py +0 -0
  25. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/credential/_base.py +0 -0
  26. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/__init__.py +0 -0
  27. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/ai/LICENSE +0 -0
  28. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/ai/__init__.py +0 -0
  29. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/ai/upscale.py +0 -0
  30. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/schema.py +0 -0
  31. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/user/__init__.py +0 -0
  32. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/user/information.py +0 -0
  33. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/user/login.py +0 -0
  34. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/sdk/user/subscription.py +0 -0
  35. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/__init__.py +0 -0
  36. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/encode.py +0 -0
  37. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/hash.py +0 -0
  38. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/random_prompt/__init__.py +0 -0
  39. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/random_prompt/tag.py +0 -0
  40. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/random_prompt/tag_artist.py +0 -0
  41. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/random_prompt/tag_character.py +0 -0
  42. {novelai_python-0.2.9 → novelai_python-0.3.1}/src/novelai_python/utils/random_prompt/tag_nsfw.py +0 -0
  43. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/__init__.py +0 -0
  44. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_random_prompt.py +0 -0
  45. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_server.py +0 -0
  46. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_server_run.py +0 -0
  47. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_upscale.py +0 -0
  48. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_user_information.py +0 -0
  49. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_user_login.py +0 -0
  50. {novelai_python-0.2.9 → novelai_python-0.3.1}/tests/test_user_subscription.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: novelai-python
3
- Version: 0.2.9
3
+ Version: 0.3.1
4
4
  Summary: Novelai Python Binding With Pydantic
5
5
  Author-Email: sudoskys <coldlando@hotmail.com>
6
6
  License: Apache License 2.0
@@ -19,6 +19,7 @@ Requires-Dist: fastapi>=0.109.0
19
19
  Requires-Dist: uvicorn[standard]>=0.27.0.post1
20
20
  Requires-Dist: numpy>=1.24.4
21
21
  Requires-Dist: argon2-cffi>=23.1.0
22
+ Requires-Dist: opencv-python>=4.8.1.78
22
23
  Requires-Dist: pytest>=7.4.4; extra == "testing"
23
24
  Requires-Dist: pytest-asyncio>=0.23.4; extra == "testing"
24
25
  Provides-Extra: testing
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "novelai-python"
3
- version = "0.2.9"
3
+ version = "0.3.1"
4
4
  description = "Novelai Python Binding With Pydantic"
5
5
  authors = [
6
6
  { name = "sudoskys", email = "coldlando@hotmail.com" },
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "uvicorn[standard]>=0.27.0.post1",
19
19
  "numpy>=1.24.4",
20
20
  "argon2-cffi>=23.1.0",
21
+ "opencv-python>=4.8.1.78",
21
22
  ]
22
23
  requires-python = ">=3.8"
23
24
  readme = "README.md"
@@ -7,6 +7,7 @@ import base64
7
7
  import json
8
8
  import math
9
9
  import random
10
+ from copy import deepcopy
10
11
  from enum import Enum, IntEnum
11
12
  from io import BytesIO
12
13
  from typing import Optional, Union
@@ -207,6 +208,14 @@ class GenerateImageInfer(ApiBaseModel):
207
208
  parameters: Params = Params()
208
209
  model_config = ConfigDict(extra="ignore")
209
210
 
211
+ @property
212
+ def endpoint(self):
213
+ return self._endpoint
214
+
215
+ @endpoint.setter
216
+ def endpoint(self, value):
217
+ self._endpoint = value
218
+
210
219
  @override
211
220
  def model_post_init(self, *args) -> None:
212
221
  """
@@ -241,20 +250,14 @@ class GenerateImageInfer(ApiBaseModel):
241
250
  def validate_model(self):
242
251
  if self.action == Action.INFILL and not self.parameters.mask:
243
252
  logger.warning("Mask maybe required for infill mode.")
253
+ if self.action != Action.GENERATE:
254
+ self.parameters.extra_noise_seed = self.parameters.seed
244
255
  return self
245
256
 
246
257
  @property
247
258
  def base_url(self):
248
259
  return f"{self.endpoint.strip('/')}/ai/generate-image"
249
260
 
250
- @property
251
- def endpoint(self):
252
- return self._endpoint
253
-
254
- @endpoint.setter
255
- def endpoint(self, value):
256
- self._endpoint = value
257
-
258
261
  def calculate_cost(self, is_opus: bool = False):
259
262
  """
260
263
  Calculate the Anlas cost of current parameters.
@@ -392,6 +395,7 @@ class GenerateImageInfer(ApiBaseModel):
392
395
  "Sec-Fetch-Site": "same-site",
393
396
  "Pragma": "no-cache",
394
397
  "Cache-Control": "no-cache",
398
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0"
395
399
  }
396
400
 
397
401
  async def request(self,
@@ -419,20 +423,21 @@ class GenerateImageInfer(ApiBaseModel):
419
423
  session.headers.clear()
420
424
  session.headers.update(override_headers)
421
425
  try:
422
- _log_data = request_data.copy()
426
+ _log_data = deepcopy(request_data)
423
427
  if self.action == Action.GENERATE:
424
428
  logger.debug(f"Request Data: {_log_data}")
425
429
  else:
426
430
  _log_data.get("parameters", {}).update({
427
- "image": "base64 data" if self.parameters.image else "None",
431
+ "image": "base64 data" if self.parameters.image else None,
428
432
  }
429
433
  )
430
434
  _log_data.get("parameters", {}).update(
431
435
  {
432
- "mask": "base64 data" if self.parameters.mask else "None",
436
+ "mask": "base64 data" if self.parameters.mask else None,
433
437
  }
434
438
  )
435
- logger.debug(f"Request Data: {request_data}")
439
+ logger.debug(f"Request Data: {_log_data}")
440
+ del _log_data
436
441
  except Exception as e:
437
442
  logger.warning(f"Error when print log data: {e}")
438
443
  try:
@@ -15,6 +15,7 @@ from starlette.responses import JSONResponse, StreamingResponse
15
15
 
16
16
  from .credential import JwtCredential, SecretStr
17
17
  from .sdk.ai.generate_image import GenerateImageInfer
18
+ from .sdk.ai.upscale import Upscale
18
19
  from .sdk.user.information import Information
19
20
  from .sdk.user.login import Login
20
21
  from .sdk.user.subscription import Subscription
@@ -90,6 +91,33 @@ async def subscription(
90
91
  return JSONResponse(status_code=500, content=e.__dict__)
91
92
 
92
93
 
94
+ @app.post("/ai/upscale")
95
+ async def upscale(
96
+ req: Upscale,
97
+ current_token: str = Depends(get_current_token)
98
+ ):
99
+ """
100
+ 生成图片
101
+ :param current_token: Authorization
102
+ :param req: Upscale
103
+ :return:
104
+ """
105
+ try:
106
+ _result = await req.request(session=get_session(current_token), remove_sign=True)
107
+ zip_file_bytes = io.BytesIO()
108
+ with zipfile.ZipFile(zip_file_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zip_file:
109
+ file = _result.files # ONLY TUPLE
110
+ zip_file.writestr(zinfo_or_arcname=file[0], data=file[1])
111
+ # return the zip file
112
+ zip_file_bytes.seek(0)
113
+ return StreamingResponse(zip_file_bytes, media_type='application/zip', headers={
114
+ 'Content-Disposition': 'attachment;filename=image.zip'
115
+ })
116
+ except Exception as e:
117
+ logger.exception(e)
118
+ return JSONResponse(status_code=500, content=e.__dict__)
119
+
120
+
93
121
  @app.post("/ai/generate_image")
94
122
  async def generate_image(
95
123
  req: GenerateImageInfer,
@@ -110,7 +138,7 @@ async def generate_image(
110
138
  # return the zip file
111
139
  zip_file_bytes.seek(0)
112
140
  return StreamingResponse(zip_file_bytes, media_type='application/zip', headers={
113
- 'Content-Disposition': 'attachment;filename=multiple_files.zip'
141
+ 'Content-Disposition': 'attachment;filename=image.zip'
114
142
  })
115
143
  except Exception as e:
116
144
  logger.exception(e)
@@ -0,0 +1,104 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/9 下午10:41
3
+ # @Author : sudoskys
4
+ # @File : useful.py
5
+ # @Software: PyCharm
6
+ import collections
7
+ import random
8
+ from typing import List, Union
9
+
10
+ import cv2 as cv
11
+ import numpy as np
12
+
13
+
14
+ def enum_to_list(enum_):
15
+ return list(map(lambda x: x.value, enum_._member_map_.values()))
16
+
17
+
18
+ class QueSelect(object):
19
+ def __init__(self, data: List[str]):
20
+ """
21
+ A queue selector
22
+ :param data:
23
+ """
24
+ self.data = collections.deque(data)
25
+ self.used = collections.deque()
26
+ self.users = {}
27
+
28
+ def get(self, user_id: Union[int, str]) -> str:
29
+ user_id = str(user_id)
30
+ if user_id not in self.users:
31
+ self.users[user_id] = {'data': self.data.copy(), 'used': collections.deque()}
32
+
33
+ user_data = self.users[user_id]['data']
34
+ user_used = self.users[user_id]['used']
35
+
36
+ if len(user_data) == 0:
37
+ user_data, user_used = user_used, user_data
38
+ # 随机掉 User data
39
+ if len(user_data) > 2:
40
+ random.shuffle(user_data)
41
+ self.users[user_id]['data'] = user_data
42
+ self.users[user_id]['used'] = user_used
43
+
44
+ selected = user_data.popleft()
45
+ user_used.append(selected)
46
+
47
+ return selected
48
+
49
+
50
+ def create_mask_from_sketch(original_img_bytes: bytes,
51
+ sketch_img_bytes: bytes,
52
+ output_format: str = '.png',
53
+ jagged_edges: bool = True
54
+ ) -> bytes:
55
+ """
56
+ Function to create a mask from original and sketch images input as bytes. Returns bytes.
57
+
58
+ :param jagged_edges: Whether to create jagged edges in the mask. Defaults to False.
59
+ :param original_img_bytes: Bytes corresponding to the original image.
60
+ :param sketch_img_bytes: Bytes corresponding to the sketch image.
61
+ :param output_format: Format of the output image. Defaults to '.png'. It could also be '.jpg'
62
+ :returns bytes: Bytes corresponding to the resultant mask
63
+ """
64
+ # Load images
65
+ ori_img = cv.imdecode(np.frombuffer(original_img_bytes, np.uint8), cv.IMREAD_COLOR)
66
+ sketch_img = cv.imdecode(np.frombuffer(sketch_img_bytes, np.uint8), cv.IMREAD_COLOR)
67
+
68
+ # Check if images have the same size
69
+ if ori_img.shape != sketch_img.shape:
70
+ raise ValueError("Images must have the same size.")
71
+
72
+ # Calculate difference between the original and sketch images
73
+ diff_img = cv.absdiff(ori_img, sketch_img)
74
+
75
+ # Convert the difference image to grayscale
76
+ diff_gray = cv.cvtColor(diff_img, cv.COLOR_BGR2GRAY)
77
+
78
+ # Threshold to create the mask
79
+ _, thresh = cv.threshold(diff_gray, 10, 255, cv.THRESH_BINARY)
80
+
81
+ if jagged_edges:
82
+ # Use a bigger kernel for dilation to create larger 'step' effect at the edges
83
+ kernel = np.ones((7, 7), np.uint8)
84
+ else:
85
+ kernel = np.ones((3, 3), np.uint8)
86
+
87
+ # Perform morphological opening to remove small noise
88
+ opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
89
+
90
+ # Further remove noise with a Gaussian filter
91
+ smooth = cv.GaussianBlur(opening, (5, 5), 0)
92
+
93
+ if jagged_edges:
94
+ # Apply additional thresholding to create sharper, jagged edges
95
+ _, smooth = cv.threshold(smooth, 128, 255, cv.THRESH_BINARY)
96
+
97
+ # Convert image to BytesIO object
98
+ is_success, buffer = cv.imencode(output_format, smooth)
99
+ if is_success:
100
+ output_io = buffer.tobytes()
101
+ else:
102
+ raise ValueError("Error during conversion of image to BytesIO object")
103
+
104
+ return output_io
@@ -1,44 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2024/2/9 下午10:41
3
- # @Author : sudoskys
4
- # @File : useful.py
5
- # @Software: PyCharm
6
- import collections
7
- import random
8
- from typing import List, Union
9
-
10
-
11
- def enum_to_list(enum_):
12
- return list(map(lambda x: x.value, enum_._member_map_.values()))
13
-
14
-
15
- class QueSelect(object):
16
- def __init__(self, data: List[str]):
17
- """
18
- A queue selector
19
- :param data:
20
- """
21
- self.data = collections.deque(data)
22
- self.used = collections.deque()
23
- self.users = {}
24
-
25
- def get(self, user_id: Union[int, str]) -> str:
26
- user_id = str(user_id)
27
- if user_id not in self.users:
28
- self.users[user_id] = {'data': self.data.copy(), 'used': collections.deque()}
29
-
30
- user_data = self.users[user_id]['data']
31
- user_used = self.users[user_id]['used']
32
-
33
- if len(user_data) == 0:
34
- user_data, user_used = user_used, user_data
35
- # 随机掉 User data
36
- if len(user_data) > 2:
37
- random.shuffle(user_data)
38
- self.users[user_id]['data'] = user_data
39
- self.users[user_id]['used'] = user_used
40
-
41
- selected = user_data.popleft()
42
- user_used.append(selected)
43
-
44
- return selected
File without changes
File without changes
File without changes