novelai-python 0.3.1__tar.gz → 0.3.3__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 (51) hide show
  1. {novelai_python-0.3.1 → novelai_python-0.3.3}/PKG-INFO +2 -2
  2. {novelai_python-0.3.1 → novelai_python-0.3.3}/README.md +1 -1
  3. {novelai_python-0.3.1 → novelai_python-0.3.3}/pyproject.toml +1 -1
  4. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/__init__.py +4 -0
  5. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/ai/generate_image.py +9 -0
  6. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/credential/ApiToken.py +1 -0
  7. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/credential/JwtToken.py +1 -1
  8. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/credential/UserAuth.py +1 -1
  9. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/__init__.py +2 -1
  10. novelai_python-0.3.1/src/novelai_python/sdk/ai/generate_image.py → novelai_python-0.3.3/src/novelai_python/sdk/ai/generate_image/__init__.py +10 -83
  11. novelai_python-0.3.3/src/novelai_python/sdk/ai/generate_image/_enum.py +78 -0
  12. novelai_python-0.3.3/src/novelai_python/sdk/ai/generate_image/suggest_tags.py +127 -0
  13. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/ai/upscale.py +1 -1
  14. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/user/information.py +2 -1
  15. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/user/subscription.py +2 -1
  16. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/server.py +20 -0
  17. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/random_prompt/__init__.py +16 -21
  18. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/useful.py +30 -10
  19. novelai_python-0.3.1/src/novelai_python/utils/random_prompt/tag_artist.py +0 -398
  20. {novelai_python-0.3.1 → novelai_python-0.3.3}/LICENSE +0 -0
  21. {novelai_python-0.3.1 → novelai_python-0.3.3}/NOTICE.MD +0 -0
  22. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_exceptions.py +0 -0
  23. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/__init__.py +0 -0
  24. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/ai/__init__.py +0 -0
  25. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/ai/upscale.py +0 -0
  26. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/schema.py +0 -0
  27. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/user/__init__.py +0 -0
  28. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/user/information.py +0 -0
  29. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/user/login.py +0 -0
  30. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/_response/user/subscription.py +0 -0
  31. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/credential/__init__.py +0 -0
  32. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/credential/_base.py +0 -0
  33. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/ai/LICENSE +0 -0
  34. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/ai/__init__.py +0 -0
  35. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/schema.py +0 -0
  36. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/user/__init__.py +0 -0
  37. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/sdk/user/login.py +0 -0
  38. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/__init__.py +0 -0
  39. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/encode.py +0 -0
  40. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/hash.py +0 -0
  41. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/random_prompt/tag.py +0 -0
  42. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/random_prompt/tag_character.py +0 -0
  43. {novelai_python-0.3.1 → novelai_python-0.3.3}/src/novelai_python/utils/random_prompt/tag_nsfw.py +0 -0
  44. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/__init__.py +0 -0
  45. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_random_prompt.py +0 -0
  46. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_server.py +0 -0
  47. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_server_run.py +0 -0
  48. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_upscale.py +0 -0
  49. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_user_information.py +0 -0
  50. {novelai_python-0.3.1 → novelai_python-0.3.3}/tests/test_user_login.py +0 -0
  51. {novelai_python-0.3.1 → novelai_python-0.3.3}/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.3.1
3
+ Version: 0.3.3
4
4
  Summary: Novelai Python Binding With Pydantic
5
5
  Author-Email: sudoskys <coldlando@hotmail.com>
6
6
  License: Apache License 2.0
@@ -39,7 +39,7 @@ The goal of this repository is to use Pydantic to build legitimate requests to a
39
39
  - [x] /user/login
40
40
  - [x] /user/information
41
41
  - [x] /ai/upscale
42
- - [ ] /ai/generate-image/suggest-tags
42
+ - [x] /ai/generate-image/suggest-tags
43
43
  - [ ] /ai/annotate-image
44
44
  - [ ] /ai/classify
45
45
  - [ ] /ai/generate-prompt
@@ -12,7 +12,7 @@ The goal of this repository is to use Pydantic to build legitimate requests to a
12
12
  - [x] /user/login
13
13
  - [x] /user/information
14
14
  - [x] /ai/upscale
15
- - [ ] /ai/generate-image/suggest-tags
15
+ - [x] /ai/generate-image/suggest-tags
16
16
  - [ ] /ai/annotate-image
17
17
  - [ ] /ai/classify
18
18
  - [ ] /ai/generate-prompt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "novelai-python"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "Novelai Python Binding With Pydantic"
5
5
  authors = [
6
6
  { name = "sudoskys", email = "coldlando@hotmail.com" },
@@ -13,6 +13,7 @@ from ._exceptions import (
13
13
  )
14
14
  from .credential import JwtCredential, LoginCredential, ApiCredential
15
15
  from .sdk import GenerateImageInfer, ImageGenerateResp
16
+ from .sdk import SuggestTags, SuggestTagsResp
16
17
  from .sdk import Information, InformationResp
17
18
  from .sdk import Login, LoginResp
18
19
  from .sdk import Subscription, SubscriptionResp
@@ -31,6 +32,9 @@ __all__ = [
31
32
  "Login",
32
33
  "LoginResp",
33
34
 
35
+ "SuggestTags",
36
+ "SuggestTagsResp",
37
+
34
38
  "Information",
35
39
  "InformationResp",
36
40
 
@@ -22,3 +22,12 @@ class ImageGenerateResp(RespBase):
22
22
  if not isinstance(self.meta.raw_request.get("parameters"), dict):
23
23
  raise Exception("Resp parameters is not dict")
24
24
  return self.meta.raw_request.get("parameters").get(key, default)
25
+
26
+
27
+ class SuggestTagsResp(RespBase):
28
+ class Tag(BaseModel):
29
+ tag: str
30
+ count: int
31
+ confidence: float
32
+
33
+ tags: List[Tag] = None
@@ -25,6 +25,7 @@ class ApiCredential(CredentialBase):
25
25
  "Accept": "*/*",
26
26
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
27
27
  "Accept-Encoding": "gzip, deflate, br",
28
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
28
29
  "Authorization": f"Bearer {self.api_token.get_secret_value()}",
29
30
  "Content-Type": "application/json",
30
31
  "Origin": "https://novelai.net",
@@ -24,7 +24,7 @@ class JwtCredential(CredentialBase):
24
24
  self._session = AsyncSession(timeout=timeout, headers={
25
25
  "Accept": "*/*",
26
26
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
27
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
27
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
28
28
  "Accept-Encoding": "gzip, deflate, br",
29
29
  "Authorization": f"Bearer {self.jwt_token.get_secret_value()}",
30
30
  "Content-Type": "application/json",
@@ -30,7 +30,7 @@ class LoginCredential(CredentialBase):
30
30
  resp = await Login.build(user_name=self.username, password=self.password.get_secret_value()).request()
31
31
  self._session = AsyncSession(timeout=timeout, headers={
32
32
  "Accept": "*/*",
33
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
33
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
34
34
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
35
35
  "Accept-Encoding": "gzip, deflate, br",
36
36
  "Authorization": f"Bearer {resp.accessToken}",
@@ -5,7 +5,8 @@
5
5
  # @Software: PyCharm
6
6
 
7
7
  from .ai.generate_image import GenerateImageInfer, ImageGenerateResp # noqa 401
8
+ from .ai.generate_image.suggest_tags import SuggestTags, SuggestTagsResp # noqa 401
9
+ from .ai.upscale import Upscale, UpscaleResp # noqa 401
8
10
  from .user.information import Information, InformationResp # noqa 401
9
11
  from .user.login import Login, LoginResp # noqa 401
10
12
  from .user.subscription import Subscription, SubscriptionResp # noqa 401
11
- from .ai.upscale import Upscale, UpscaleResp # noqa 401
@@ -1,14 +1,13 @@
1
1
  # -*- coding: utf-8 -*-
2
- # @Time : 2024/1/26 上午11:21
2
+ # @Time : 2024/2/13 下午8:08
3
3
  # @Author : sudoskys
4
- # @File : generate_image.py
4
+ # @File : __init__.py.py
5
5
  # @Software: PyCharm
6
6
  import base64
7
7
  import json
8
8
  import math
9
9
  import random
10
10
  from copy import deepcopy
11
- from enum import Enum, IntEnum
12
11
  from io import BytesIO
13
12
  from typing import Optional, Union
14
13
  from urllib.parse import urlparse
@@ -21,84 +20,12 @@ from loguru import logger
21
20
  from pydantic import BaseModel, ConfigDict, PrivateAttr, field_validator, model_validator, Field
22
21
  from typing_extensions import override
23
22
 
24
- from ..schema import ApiBaseModel
25
- from ..._exceptions import APIError, AuthError, ConcurrentGenerationError, SessionHttpError
26
- from ..._response.ai.generate_image import ImageGenerateResp
27
- from ...credential import CredentialBase
28
- from ...utils import try_jsonfy, NovelAiMetadata
29
-
30
-
31
- class Sampler(Enum):
32
- K_EULER = "k_euler"
33
- K_EULER_ANCESTRAL = "k_euler_ancestral"
34
- K_DPMPP_2S_ANCESTRAL = "k_dpmpp_2s_ancestral"
35
- K_DPMPP_2M = "k_dpmpp_2m"
36
- K_DPMPP_SDE = "k_dpmpp_sde"
37
- DDIM_V3 = "ddim_v3"
38
-
39
-
40
- class NoiseSchedule(Enum):
41
- NATIVE = "native"
42
- KARRAS = "karras"
43
- EXPONENTIAL = "exponential"
44
- POLYEXPONENTIAL = "polyexponential"
45
-
46
-
47
- class UCPreset(IntEnum):
48
- TYPE0 = 0
49
- TYPE1 = 1
50
- TYPE2 = 2
51
- TYPE3 = 3
52
-
53
-
54
- class Action(Enum):
55
- GENERATE = "generate"
56
- """Generate Image"""
57
- IMG2IMG = "img2img"
58
- """Image to Image"""
59
- INFILL = "infill"
60
- """Inpainting"""
61
-
62
-
63
- class Model(Enum):
64
- NAI_DIFFUSION_3 = "nai-diffusion-3"
65
- NAI_DIFFUSION_3_INPAINTING = "nai-diffusion-3-inpainting"
66
-
67
- NAI_DIFFUSION = "nai-diffusion"
68
- NAI_DIFFUSION_INPAINTING = "nai-diffusion-inpainting"
69
-
70
- SAFE_DIFFUSION = "safe-diffusion"
71
- SAFE_DIFFUSION_INPAINTING = "safe-diffusion-inpainting"
72
-
73
- NAI_DIFFUSION_FURRY = "nai-diffusion-furry"
74
- FURRY_DIFFUSION_INPAINTING = "furry-diffusion-inpainting"
75
-
76
-
77
- class ControlNetModel(Enum):
78
- HED = "hed"
79
- """边缘检测"""
80
- MIDAS = "midas"
81
- """景深"""
82
- FAKE_SCRIBBLE = "fake_scribble"
83
- """伪涂鸦"""
84
- M_LSD = "mlsd"
85
- """(建筑)线条检测"""
86
- LANDSCAPER = "uniformer"
87
- """风景生成"""
88
-
89
-
90
- class Resolution(Enum):
91
- RES_512_768 = (512, 768)
92
- RES_768_512 = (768, 512)
93
- RES_640_640 = (640, 640)
94
- RES_832_1216 = (832, 1216)
95
- RES_1216_832 = (1216, 832)
96
- RES_1024_1024 = (1024, 1024)
97
- RES_1024_1536 = (1024, 1536)
98
- RES_1536_1024 = (1536, 1024)
99
- RES_1472_1472 = (1472, 1472)
100
- RES_1088_1920 = (1088, 1920)
101
- RES_1920_1088 = (1920, 1088)
23
+ from ._enum import Model, Sampler, NoiseSchedule, ControlNetModel, Action, UCPreset
24
+ from ...schema import ApiBaseModel
25
+ from ...._exceptions import APIError, AuthError, ConcurrentGenerationError, SessionHttpError
26
+ from ...._response.ai.generate_image import ImageGenerateResp
27
+ from ....credential import CredentialBase
28
+ from ....utils import try_jsonfy, NovelAiMetadata
102
29
 
103
30
 
104
31
  class GenerateImageInfer(ApiBaseModel):
@@ -309,7 +236,7 @@ class GenerateImageInfer(ApiBaseModel):
309
236
  seed: int = None,
310
237
  steps: int = 28,
311
238
  scale: float = 6.0,
312
- cfg_rescale: int = 0,
239
+ cfg_rescale: float = 0,
313
240
  sampler: Union[Sampler, str] = Sampler.K_EULER,
314
241
  width: int = 832,
315
242
  height: int = 1216,
@@ -395,7 +322,7 @@ class GenerateImageInfer(ApiBaseModel):
395
322
  "Sec-Fetch-Site": "same-site",
396
323
  "Pragma": "no-cache",
397
324
  "Cache-Control": "no-cache",
398
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0"
325
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
399
326
  }
400
327
 
401
328
  async def request(self,
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/13 下午8:10
3
+ # @Author : sudoskys
4
+ # @File : _enum.py
5
+ # @Software: PyCharm
6
+ from enum import Enum, IntEnum
7
+
8
+
9
+ class Sampler(Enum):
10
+ K_EULER = "k_euler"
11
+ K_EULER_ANCESTRAL = "k_euler_ancestral"
12
+ K_DPMPP_2S_ANCESTRAL = "k_dpmpp_2s_ancestral"
13
+ K_DPMPP_2M = "k_dpmpp_2m"
14
+ K_DPMPP_SDE = "k_dpmpp_sde"
15
+ DDIM_V3 = "ddim_v3"
16
+
17
+
18
+ class NoiseSchedule(Enum):
19
+ NATIVE = "native"
20
+ KARRAS = "karras"
21
+ EXPONENTIAL = "exponential"
22
+ POLYEXPONENTIAL = "polyexponential"
23
+
24
+
25
+ class UCPreset(IntEnum):
26
+ TYPE0 = 0
27
+ TYPE1 = 1
28
+ TYPE2 = 2
29
+ TYPE3 = 3
30
+
31
+
32
+ class Action(Enum):
33
+ GENERATE = "generate"
34
+ """Generate Image"""
35
+ IMG2IMG = "img2img"
36
+ """Image to Image"""
37
+ INFILL = "infill"
38
+ """Inpainting"""
39
+
40
+
41
+ class ControlNetModel(Enum):
42
+ HED = "hed"
43
+ """边缘检测"""
44
+ MIDAS = "midas"
45
+ """景深"""
46
+ FAKE_SCRIBBLE = "fake_scribble"
47
+ """伪涂鸦"""
48
+ M_LSD = "mlsd"
49
+ """(建筑)线条检测"""
50
+ LANDSCAPER = "uniformer"
51
+ """风景生成"""
52
+
53
+
54
+ class Resolution(Enum):
55
+ RES_512_768 = (512, 768)
56
+ RES_768_512 = (768, 512)
57
+ RES_640_640 = (640, 640)
58
+ RES_832_1216 = (832, 1216)
59
+ RES_1216_832 = (1216, 832)
60
+ RES_1024_1024 = (1024, 1024)
61
+ RES_1024_1536 = (1024, 1536)
62
+ RES_1536_1024 = (1536, 1024)
63
+ RES_1472_1472 = (1472, 1472)
64
+ RES_1088_1920 = (1088, 1920)
65
+ RES_1920_1088 = (1920, 1088)
66
+
67
+ class Model(Enum):
68
+ NAI_DIFFUSION_3 = "nai-diffusion-3"
69
+ NAI_DIFFUSION_3_INPAINTING = "nai-diffusion-3-inpainting"
70
+
71
+ NAI_DIFFUSION = "nai-diffusion"
72
+ NAI_DIFFUSION_INPAINTING = "nai-diffusion-inpainting"
73
+
74
+ SAFE_DIFFUSION = "safe-diffusion"
75
+ SAFE_DIFFUSION_INPAINTING = "safe-diffusion-inpainting"
76
+
77
+ NAI_DIFFUSION_FURRY = "nai-diffusion-furry"
78
+ FURRY_DIFFUSION_INPAINTING = "furry-diffusion-inpainting"
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/13 下午8:09
3
+ # @Author : sudoskys
4
+ # @File : suggest-tags.py
5
+ # @Software: PyCharm
6
+ from typing import Optional, Union
7
+ from urllib.parse import urlparse
8
+
9
+ import curl_cffi
10
+ import httpx
11
+ from curl_cffi.requests import AsyncSession
12
+ from loguru import logger
13
+ from pydantic import PrivateAttr
14
+
15
+ from ._enum import Model
16
+ from ...schema import ApiBaseModel
17
+ from ...._exceptions import APIError, AuthError, SessionHttpError
18
+ from ...._response.ai.generate_image import SuggestTagsResp
19
+ from ....credential import CredentialBase
20
+ from ....utils import try_jsonfy
21
+
22
+
23
+ class SuggestTags(ApiBaseModel):
24
+ _endpoint: Optional[str] = PrivateAttr("https://api.novelai.net")
25
+ model: Model = Model.NAI_DIFFUSION_3
26
+ prompt: str = "landscape"
27
+
28
+ @property
29
+ def endpoint(self):
30
+ return self._endpoint
31
+
32
+ @endpoint.setter
33
+ def endpoint(self, value):
34
+ self._endpoint = value
35
+
36
+ @property
37
+ def base_url(self):
38
+ return f"{self.endpoint.strip('/')}/ai/generate-image/suggest-tags"
39
+
40
+ async def necessary_headers(self, request_data) -> dict:
41
+ return {
42
+ "Host": urlparse(self.endpoint).netloc,
43
+ "Accept": "*/*",
44
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
45
+ "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
46
+ "Accept-Encoding": "gzip, deflate, br",
47
+ "Access-Control-Allow-Origin": "*",
48
+ "Referer": "https://novelai.net/",
49
+ "Content-Type": "application/json",
50
+ "Origin": "https://novelai.net",
51
+ "Connection": "keep-alive",
52
+ "Sec-Fetch-Dest": "empty",
53
+ "Sec-Fetch-Mode": "cors",
54
+ "Sec-Fetch-Site": "same-site",
55
+ "Pragma": "no-cache",
56
+ "Cache-Control": "no-cache",
57
+ "TE": "trailers",
58
+ }
59
+
60
+ async def request(self,
61
+ session: Union[AsyncSession, CredentialBase],
62
+ *,
63
+ override_headers: Optional[dict] = None
64
+ ) -> SuggestTagsResp:
65
+ """
66
+ Request to get user subscription information
67
+ :param override_headers:
68
+ :param session:
69
+ :return:
70
+ """
71
+ # Data Build
72
+ request_data = self.model_dump(mode="json", exclude_none=True)
73
+ if isinstance(session, AsyncSession):
74
+ session.headers.update(await self.necessary_headers(request_data))
75
+ elif isinstance(session, CredentialBase):
76
+ session = await session.get_session(update_headers=await self.necessary_headers(request_data))
77
+ # Header
78
+ if override_headers:
79
+ session.headers.clear()
80
+ session.headers.update(override_headers)
81
+ logger.debug("SuggestTags")
82
+ try:
83
+ assert hasattr(session, "get"), "session must have get method."
84
+ response = await session.get(
85
+ url=self.base_url + "?" + "&".join([f"{k}={v}" for k, v in request_data.items()])
86
+ )
87
+ if "application/json" not in response.headers.get('Content-Type') or response.status_code != 200:
88
+ logger.error(
89
+ f"Error with content type: {response.headers.get('Content-Type')} and code: {response.status_code}"
90
+ )
91
+ try:
92
+ _msg = response.json()
93
+ except Exception as e:
94
+ logger.warning(e)
95
+ if not isinstance(response.content, str) and len(response.content) < 50:
96
+ raise APIError(
97
+ message=f"Unexpected content type: {response.headers.get('Content-Type')}",
98
+ request=request_data,
99
+ code=response.status_code,
100
+ response=try_jsonfy(response.content)
101
+ )
102
+ else:
103
+ _msg = {"statusCode": response.status_code, "message": response.content}
104
+ status_code = _msg.get("statusCode", response.status_code)
105
+ message = _msg.get("message", "Unknown error")
106
+ if status_code in [400, 401, 402]:
107
+ # 400 : validation error
108
+ # 401 : unauthorized
109
+ # 402 : payment required
110
+ # 409 : conflict
111
+ raise AuthError(message, request=request_data, code=status_code, response=_msg)
112
+ if status_code in [500]:
113
+ # An unknown error occured.
114
+ raise APIError(message, request=request_data, code=status_code, response=_msg)
115
+ raise APIError(message, request=request_data, code=status_code, response=_msg)
116
+ return SuggestTagsResp.model_validate(response.json())
117
+ except curl_cffi.requests.errors.RequestsError as exc:
118
+ logger.exception(exc)
119
+ raise SessionHttpError("An AsyncSession RequestsError occurred, maybe SSL error. Try again later!")
120
+ except httpx.HTTPError as exc:
121
+ logger.exception(exc)
122
+ raise SessionHttpError("An HTTPError occurred, maybe SSL error. Try again later!")
123
+ except APIError as e:
124
+ raise e
125
+ except Exception as e:
126
+ logger.opt(exception=e).exception("An Unexpected error occurred")
127
+ raise e
@@ -73,7 +73,7 @@ class Upscale(ApiBaseModel):
73
73
  return {
74
74
  "Host": urlparse(self.endpoint).netloc,
75
75
  "Accept": "*/*",
76
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
76
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
77
77
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
78
78
  "Accept-Encoding": "gzip, deflate, br",
79
79
  "Referer": "https://novelai.net/",
@@ -4,6 +4,7 @@
4
4
  # @File : information.py
5
5
  # @Software: PyCharm
6
6
  from typing import Optional, Union
7
+ from urllib.parse import urlparse
7
8
 
8
9
  import curl_cffi
9
10
  import httpx
@@ -35,7 +36,7 @@ class Information(ApiBaseModel):
35
36
 
36
37
  async def necessary_headers(self, request_data) -> dict:
37
38
  return {
38
- "Host": "api.novelai.net",
39
+ "Host": urlparse(self.endpoint).netloc,
39
40
  "Accept": "*/*",
40
41
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
41
42
  "Accept-Encoding": "gzip, deflate, br",
@@ -4,6 +4,7 @@
4
4
  # @File : subscription.py.py
5
5
  # @Software: PyCharm
6
6
  from typing import Optional, Union
7
+ from urllib.parse import urlparse
7
8
 
8
9
  import curl_cffi
9
10
  import httpx
@@ -35,7 +36,7 @@ class Subscription(ApiBaseModel):
35
36
 
36
37
  async def necessary_headers(self, request_data) -> dict:
37
38
  return {
38
- "Host": "api.novelai.net",
39
+ "Host": urlparse(self.endpoint).netloc,
39
40
  "Accept": "*/*",
40
41
  "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
41
42
  "Accept-Encoding": "gzip, deflate, br",
@@ -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.generate_image.suggest_tags import SuggestTags
18
19
  from .sdk.ai.upscale import Upscale
19
20
  from .sdk.user.information import Information
20
21
  from .sdk.user.login import Login
@@ -118,6 +119,25 @@ async def upscale(
118
119
  return JSONResponse(status_code=500, content=e.__dict__)
119
120
 
120
121
 
122
+ @app.get("/ai/generate_image/suggest_tags")
123
+ async def suggest_tags(
124
+ req: SuggestTags,
125
+ current_token: str = Depends(get_current_token)
126
+ ):
127
+ """
128
+ 生成建议
129
+ :param current_token: Authorization
130
+ :param req: SuggestTags
131
+ :return:
132
+ """
133
+ try:
134
+ _result = await req.request(session=get_session(current_token))
135
+ return _result.model_dump()
136
+ except Exception as e:
137
+ logger.exception(e)
138
+ return JSONResponse(status_code=500, content=e.__dict__)
139
+
140
+
121
141
  @app.post("/ai/generate_image")
122
142
  async def generate_image(
123
143
  req: GenerateImageInfer,
@@ -13,7 +13,6 @@ from .tag import (
13
13
  socks,
14
14
  action, expression, footwears, bottoms, color as colors
15
15
  )
16
- from .tag_artist import rankArtist
17
16
  from .tag_character import rankMoods, rankCharacter, rankIdentity
18
17
  from .tag_nsfw import nsfw
19
18
 
@@ -30,9 +29,11 @@ class RandomPromptGenerator(object):
30
29
  :param existing_tags: a list of existing tags
31
30
  :return: a tag
32
31
  """
33
- valid_tags = [tag
34
- for tag in tags
35
- if len(tag) < 3 or not tag[2] or any(sub_tag in existing_tags for sub_tag in tag[2])]
32
+ valid_tags = [
33
+ tag
34
+ for tag in tags
35
+ if len(tag) < 3 or not tag[2] or any(sub_tag in existing_tags for sub_tag in tag[2])
36
+ ]
36
37
  total_weight = sum(tagr[1] for tagr in valid_tags if len(tagr) > 1)
37
38
  if total_weight == 0:
38
39
  if isinstance(tags, list):
@@ -70,7 +71,7 @@ class RandomPromptGenerator(object):
70
71
  has_unique_feature = any(feature in features for feature in unique_features)
71
72
  if random.random() < 0.1 and enable_skin_color:
72
73
  features.append(self.get_weighted_choice(sinkColor, features))
73
- if random.random() < 0.8:
74
+ if random.random() < 0.7:
74
75
  features.append(self.get_weighted_choice(eyeColors, features))
75
76
  if random.random() < 0.1:
76
77
  features.append(self.get_weighted_choice(eyeCharacteristics, features))
@@ -230,12 +231,6 @@ class RandomPromptGenerator(object):
230
231
  return ', '.join(tags)
231
232
  if random.random() < 0.3:
232
233
  tags.append(self.get_weighted_choice(artStyle, tags))
233
- if random.random() < 0.6:
234
- tags.append("{" + self.get_weighted_choice(rankArtist, tags) + "}")
235
- if random.random() < 0.5:
236
- tags.append("[" + self.get_weighted_choice(rankArtist, tags) + "]")
237
- if random.random() < 0.4:
238
- tags.append("{{" + self.get_weighted_choice(rankArtist, tags) + "}}")
239
234
  c_count = 0
240
235
  d_count = 0
241
236
  u_count = 0
@@ -286,23 +281,23 @@ class RandomPromptGenerator(object):
286
281
  features = []
287
282
  if random.random() < 0.2:
288
283
  features = self.character_features(nsfw['fu'], None, True, irs)
289
- if random.random() < 0.6:
284
+ if random.random() < 0.3:
290
285
  tags.append(self.get_weighted_choice(nsfw["sex"], tags))
291
- if random.random() < 0.6:
286
+ if random.random() < 0.5:
292
287
  tags.append(self.get_weighted_choice(nsfw["pussy"], tags))
293
- if random.random() < 0.6:
288
+ if random.random() < 0.3:
294
289
  tags.append(self.get_weighted_choice(nsfw["sexMod"], tags))
295
- if random.random() < 0.6:
290
+ if random.random() < 0.5:
296
291
  tags.append(self.get_weighted_choice(nsfw["sexActionMode"], tags))
297
- if random.random() < 0.2:
292
+ if random.random() < 0.1:
298
293
  tags.append(self.get_weighted_choice(nsfw["bdsm"], tags))
299
- if random.random() < 0.3:
294
+ if random.random() < 0.2:
300
295
  tags.append(self.get_weighted_choice(nsfw["sexAccessories"], tags))
301
296
  tags.extend(features)
302
- if random.random() < 0.65 and enable_moods:
297
+ if random.random() < 0.4 and enable_moods:
303
298
  # 心情
304
299
  tags.append("[" + self.get_weighted_choice(rankMoods, tags) + "]")
305
- if random.random() < 0.5 and enable_identity:
300
+ if random.random() < 0.4 and enable_identity:
306
301
  # 身份
307
302
  tags.append("[" + self.get_weighted_choice(rankIdentity, tags) + "]")
308
303
  if random.random() < 0.2 and enable_character:
@@ -315,9 +310,9 @@ class RandomPromptGenerator(object):
315
310
  num_objects = random.randint(1, 3)
316
311
  for _ in range(num_objects):
317
312
  tags.append(self.get_weighted_choice(backgroundObjects, tags))
318
- if random.random() < 0.3:
313
+ if random.random() < 0.1:
319
314
  tags.append(self.get_weighted_choice(cameraPerspective, tags))
320
- if random.random() < 0.7:
315
+ if random.random() < 0.5:
321
316
  tags.append(self.get_weighted_choice(cameraAngle, tags))
322
317
  for _ in range(c_count):
323
318
  if random.random() < 0.2:
@@ -47,20 +47,27 @@ class QueSelect(object):
47
47
  return selected
48
48
 
49
49
 
50
+ import cv2 as cv
51
+ import numpy as np
52
+
53
+
50
54
  def create_mask_from_sketch(original_img_bytes: bytes,
51
55
  sketch_img_bytes: bytes,
52
- output_format: str = '.png',
53
- jagged_edges: bool = True
56
+ min_block_size: int = 15,
57
+ jagged_edges: bool = True,
58
+ output_format: str = '.png'
54
59
  ) -> bytes:
55
60
  """
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
61
+ Function to create a mask from original and sketch images input as bytes. Returns BytesIO object.
62
+
63
+ :param original_img_bytes: Bytes corresponding to the original image.
64
+ :param sketch_img_bytes: Bytes corresponding to the sketch image.
65
+ :param min_block_size: The minimum size of the pixel blocks. Default is 1, i.e., no pixelation.
66
+ :param jagged_edges: If set to True, the edges of the resulting mask will be more jagged.
67
+ :param output_format: Format of the output image. Defaults to '.png'. It could also be '.jpg'
68
+ :return: bytes corresponding to the resultant mask
63
69
  """
70
+
64
71
  # Load images
65
72
  ori_img = cv.imdecode(np.frombuffer(original_img_bytes, np.uint8), cv.IMREAD_COLOR)
66
73
  sketch_img = cv.imdecode(np.frombuffer(sketch_img_bytes, np.uint8), cv.IMREAD_COLOR)
@@ -78,6 +85,7 @@ def create_mask_from_sketch(original_img_bytes: bytes,
78
85
  # Threshold to create the mask
79
86
  _, thresh = cv.threshold(diff_gray, 10, 255, cv.THRESH_BINARY)
80
87
 
88
+ # Create bigger kernel for morphological operations
81
89
  if jagged_edges:
82
90
  # Use a bigger kernel for dilation to create larger 'step' effect at the edges
83
91
  kernel = np.ones((7, 7), np.uint8)
@@ -87,8 +95,20 @@ def create_mask_from_sketch(original_img_bytes: bytes,
87
95
  # Perform morphological opening to remove small noise
88
96
  opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
89
97
 
98
+ # Perform morphological dilation
99
+ dilation = cv.dilate(opening, kernel, iterations=2)
100
+
101
+ # Perform morphological closing to connect separated areas
102
+ closing = cv.morphologyEx(dilation, cv.MORPH_CLOSE, kernel, iterations=3)
103
+
90
104
  # Further remove noise with a Gaussian filter
91
- smooth = cv.GaussianBlur(opening, (5, 5), 0)
105
+ smooth = cv.GaussianBlur(closing, (5, 5), 0)
106
+
107
+ if min_block_size > 1:
108
+ # Resize to smaller image, then resize back to original size to create a pixelated effect
109
+ small = cv.resize(smooth, (smooth.shape[1] // min_block_size, smooth.shape[0] // min_block_size),
110
+ interpolation=cv.INTER_LINEAR)
111
+ smooth = cv.resize(small, (smooth.shape[1], smooth.shape[0]), interpolation=cv.INTER_NEAREST)
92
112
 
93
113
  if jagged_edges:
94
114
  # Apply additional thresholding to create sharper, jagged edges
@@ -1,398 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2024/1/26 下午5:08
3
- # @Author : sudoskys
4
- # @File : tag_artist.py
5
- # @Software: PyCharm
6
-
7
- rankArtist = [
8
- [
9
- "redi_(rasec_asdjh)",
10
- 50
11
- ],
12
- [
13
- "suoiresnu",
14
- 41
15
- ],
16
- [
17
- "kulo_jawa",
18
- 30
19
- ],
20
- [
21
- "sakanaya_(sakanaya952)",
22
- 25
23
- ],
24
- [
25
- "momisan",
26
- 20
27
- ],
28
- [
29
- "tansuan_(ensj3875)",
30
- 19
31
- ],
32
- [
33
- "hitmanatee",
34
- 18
35
- ],
36
- [
37
- "yoru0409",
38
- 18
39
- ],
40
- [
41
- "zovokia",
42
- 14
43
- ],
44
- [
45
- "memeh",
46
- 13
47
- ],
48
- [
49
- "honashi",
50
- 13
51
- ],
52
- [
53
- "pinkmill",
54
- 13
55
- ],
56
- [
57
- "kanjy00u",
58
- 12
59
- ],
60
- [
61
- "eckert&eich",
62
- 12
63
- ],
64
- [
65
- "ningen mame",
66
- 100
67
- ],
68
- [
69
- "ciloranko",
70
- 100
71
- ],
72
- [
73
- "sho (sho lwlw)",
74
- 80
75
- ],
76
- [
77
- "tianliang duohe fangdongye",
78
- 100
79
- ],
80
- [
81
- "alphonse (white datura)",
82
- 80
83
- ],
84
- [
85
- "rei (sanbonzakura)",
86
- 100
87
- ],
88
- [
89
- "mamyouda",
90
- 80
91
- ],
92
- [
93
- "ogipote",
94
- 100
95
- ],
96
- [
97
- "fuzichoco",
98
- 100
99
- ],
100
- [
101
- "norza",
102
- 80
103
- ],
104
- [
105
- "AkiZero1510",
106
- 100
107
- ],
108
- [
109
- "ATDAN",
110
- 100
111
- ],
112
- [
113
- "mignon",
114
- 80
115
- ],
116
- [
117
- "shexyo",
118
- 100
119
- ],
120
- [
121
- "musashi (dishwasher1910)",
122
- 100
123
- ],
124
- [
125
- "modare",
126
- 80
127
- ],
128
- [
129
- "asteroidill",
130
- 80
131
- ],
132
- [
133
- "potg(piotegu)",
134
- 80
135
- ],
136
- [
137
- "kedama milk",
138
- 100
139
- ],
140
- [
141
- "jun (aousa0328)",
142
- 80
143
- ],
144
- [
145
- "sky freedom",
146
- 80
147
- ],
148
- [
149
- "ask (askzy)",
150
- 100
151
- ],
152
- [
153
- "toridamono",
154
- 100
155
- ],
156
- [
157
- "sencha (senchat)",
158
- 80
159
- ],
160
- [
161
- "void_0",
162
- 80
163
- ],
164
- [
165
- "mandrill",
166
- 100
167
- ],
168
- [
169
- "tokkyu",
170
- 100
171
- ],
172
- [
173
- "houkisei",
174
- 80
175
- ],
176
- [
177
- "konya karasue",
178
- 100
179
- ],
180
- [
181
- "akakura",
182
- 100
183
- ],
184
- [
185
- "kawacy",
186
- 100
187
- ],
188
- [
189
- "reoen",
190
- 100
191
- ],
192
- [
193
- "nixeu",
194
- 100
195
- ],
196
- [
197
- "cierra_(ra-bit)",
198
- 100
199
- ],
200
- [
201
- "artistask_(askzy)",
202
- 100
203
- ],
204
- [
205
- "gomzi",
206
- 100
207
- ],
208
- [
209
- "you_shimizu",
210
- 100
211
- ],
212
- [
213
- "demizu_posuka",
214
- 100
215
- ],
216
- [
217
- "asakuraf",
218
- 100
219
- ],
220
- [
221
- "ningen_mame",
222
- 100
223
- ],
224
- [
225
- "sho_(sho_lwlw)",
226
- 100
227
- ],
228
- [
229
- "alphonse_(white_datura)",
230
- 100
231
- ],
232
- [
233
- "rei_(sanbonzakura)",
234
- 100
235
- ],
236
- [
237
- "wlop",
238
- 100
239
- ],
240
- [
241
- "as109",
242
- 100
243
- ],
244
- [
245
- "Milkychu",
246
- 100
247
- ],
248
- [
249
- "Cogecha",
250
- 100
251
- ],
252
- [
253
- "liduke",
254
- 100
255
- ],
256
- [
257
- "infukun",
258
- 100
259
- ],
260
- [
261
- "amazuyu_tatsuki",
262
- 100
263
- ],
264
- [
265
- "Happ\u014dbi Jin",
266
- 100
267
- ],
268
- [
269
- "Rella",
270
- 100
271
- ],
272
- [
273
- "omone hokoma agm",
274
- 100
275
- ],
276
- [
277
- "toosaka asagi",
278
- 100
279
- ],
280
- [
281
- "Mochizuki Kei",
282
- 100
283
- ],
284
- [
285
- "Namie",
286
- 100
287
- ],
288
- [
289
- "Hoji",
290
- 100
291
- ],
292
- [
293
- "sy4",
294
- 100
295
- ],
296
- [
297
- "mika_pikazo",
298
- 100
299
- ],
300
- [
301
- "momoko(momopoco)",
302
- 100
303
- ],
304
- [
305
- "rurudo",
306
- 100
307
- ],
308
- [
309
- "missile228",
310
- 100
311
- ],
312
- [
313
- "miyase_mahiro",
314
- 100
315
- ],
316
- [
317
- "touzai(poppin phl95)",
318
- 100
319
- ],
320
- [
321
- "kukka",
322
- 100
323
- ],
324
- [
325
- "kantoku",
326
- 100
327
- ],
328
- [
329
- "poco(asahi age)",
330
- 100
331
- ],
332
- [
333
- "refeia",
334
- 100
335
- ],
336
- [
337
- "ke-ta",
338
- 100
339
- ],
340
- [
341
- "anmi",
342
- 100
343
- ],
344
- [
345
- "tiv",
346
- 100
347
- ],
348
- [
349
- "chen_bin",
350
- 100
351
- ],
352
- [
353
- "lm7 (op-center)",
354
- 100
355
- ],
356
- [
357
- "shion (mirudakemann)",
358
- 100
359
- ],
360
- [
361
- "asteroid ill",
362
- 100
363
- ],
364
- [
365
- "potg (piotegu)",
366
- 100
367
- ],
368
- [
369
- "KinHasu",
370
- 100
371
- ],
372
- [
373
- "ryou_(ryoutarou)",
374
- 100
375
- ],
376
- [
377
- "mamimi_(mamamimi)",
378
- 100
379
- ],
380
- ]
381
-
382
- if __name__ == "__main__":
383
- var = vars().copy()
384
-
385
- # 获取tags列表
386
- tags = []
387
- for key, value in var.items():
388
- if not key.startswith('_') and key != 'var':
389
- tags.extend(value)
390
- print(tags)
391
-
392
- # 展开二级列表
393
- tags = [tag[0] for tag in tags]
394
- print(tags)
395
- import json
396
-
397
- with open('../../../../playground/cos/tag_artist.json', 'w') as f:
398
- json.dump(tags, f)
File without changes
File without changes