novelai-python 0.4.13__tar.gz → 0.4.15__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 (80) hide show
  1. {novelai_python-0.4.13 → novelai_python-0.4.15}/NOTICE.MD +1 -1
  2. {novelai_python-0.4.13 → novelai_python-0.4.15}/PKG-INFO +2 -2
  3. {novelai_python-0.4.13 → novelai_python-0.4.15}/pyproject.toml +1 -1
  4. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/credential/ApiToken.py +6 -0
  5. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/credential/JwtToken.py +6 -0
  6. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/credential/UserAuth.py +5 -0
  7. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/_enum.py +112 -7
  8. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate_image/__init__.py +59 -51
  9. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/server.py +45 -2
  10. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_server_run.py +1 -1
  11. {novelai_python-0.4.13 → novelai_python-0.4.15}/LICENSE +0 -0
  12. {novelai_python-0.4.13 → novelai_python-0.4.15}/README.md +0 -0
  13. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/__init__.py +0 -0
  14. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_exceptions.py +0 -0
  15. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/__init__.py +0 -0
  16. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/__init__.py +0 -0
  17. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/generate.py +0 -0
  18. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/generate_image.py +0 -0
  19. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/generate_stream.py +0 -0
  20. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/generate_voice.py +0 -0
  21. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/ai/upscale.py +0 -0
  22. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/schema.py +0 -0
  23. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/user/__init__.py +0 -0
  24. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/user/information.py +0 -0
  25. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/user/login.py +0 -0
  26. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/_response/user/subscription.py +0 -0
  27. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/credential/__init__.py +0 -0
  28. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/credential/_base.py +0 -0
  29. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/__init__.py +0 -0
  30. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/LICENSE +0 -0
  31. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/__init__.py +0 -0
  32. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/_const.py +0 -0
  33. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/_cost.py +0 -0
  34. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/augment_image/__init__.py +0 -0
  35. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/augment_image/_enum.py +0 -0
  36. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate/__init__.py +0 -0
  37. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate/_const.py +0 -0
  38. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate/_enum.py +0 -0
  39. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate/_preset.py +0 -0
  40. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate_image/suggest_tags.py +0 -0
  41. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate_stream.py +0 -0
  42. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate_voice/__init__.py +0 -0
  43. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/generate_voice/_enum.py +0 -0
  44. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/ai/upscale.py +0 -0
  45. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/schema.py +0 -0
  46. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/user/__init__.py +0 -0
  47. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/user/information.py +0 -0
  48. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/user/login.py +0 -0
  49. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/sdk/user/subscription.py +0 -0
  50. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/NOTICE.MD +0 -0
  51. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/__init__.py +0 -0
  52. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/bpe_simple_vocab_16e6.txt.gz +0 -0
  53. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/gpt2-genji_tokenizer.json +0 -0
  54. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/gpt2_tokenizer.json +0 -0
  55. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/nerdstash_v1.model +0 -0
  56. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/nerdstash_v2.model +0 -0
  57. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/novelai.model +0 -0
  58. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/novelai_v2.model +0 -0
  59. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/pile_tokenizer.json +0 -0
  60. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tokenizer/simple_tokenizer.py +0 -0
  61. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/__init__.py +0 -0
  62. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/image_metadata/__init__.py +0 -0
  63. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/image_metadata/lsb_extractor.py +0 -0
  64. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/image_metadata/lsb_injector.py +0 -0
  65. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/paint_mask/__init__.py +0 -0
  66. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/random_prompt/__init__.py +0 -0
  67. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/random_prompt/tag.py +0 -0
  68. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/random_prompt/tag_character.py +0 -0
  69. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/tool/random_prompt/tag_nsfw.py +0 -0
  70. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/utils/__init__.py +0 -0
  71. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/utils/encode.py +0 -0
  72. {novelai_python-0.4.13 → novelai_python-0.4.15}/src/novelai_python/utils/useful.py +0 -0
  73. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/__init__.py +0 -0
  74. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_generate_voice.py +0 -0
  75. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_random_prompt.py +0 -0
  76. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_server.py +0 -0
  77. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_upscale.py +0 -0
  78. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_user_information.py +0 -0
  79. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_user_login.py +0 -0
  80. {novelai_python-0.4.13 → novelai_python-0.4.15}/tests/test_user_subscription.py +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  The MIT license applies to the files in:
6
6
 
7
- file: "src/novelai_python/sdk/ai/generate_image.py" from https://github.com/HanaokaYuzu/NovelAI-API
7
+ file: "src/novelai_python/sdk/ai/generate-image.py" from https://github.com/HanaokaYuzu/NovelAI-API
8
8
  file: "src/novelai_python/tokenizer/novelai.model" from https://github.com/NovelAI/novelai-tokenizer
9
9
  file: "src/novelai_python/tokenizer/novelai_v2.model from https://github.com/NovelAI/novelai-tokenizer
10
10
  file: "src/novelai_python/tool/image_metadata/lsb_injector.py" from https://github.com/NovelAI/novelai-image-metadata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: novelai-python
3
- Version: 0.4.13
3
+ Version: 0.4.15
4
4
  Summary: NovelAI Python Binding With Pydantic
5
5
  Author-Email: sudoskys <coldlando@hotmail.com>
6
6
  License: Apache License 2.0
@@ -27,9 +27,9 @@ Requires-Dist: pynacl>=1.5.0
27
27
  Requires-Dist: ftfy>=6.2.0
28
28
  Requires-Dist: regex>=2023.12.25
29
29
  Requires-Dist: tokenizers>=0.15.2
30
+ Provides-Extra: testing
30
31
  Requires-Dist: pytest>=7.4.4; extra == "testing"
31
32
  Requires-Dist: pytest-asyncio>=0.23.4; extra == "testing"
32
- Provides-Extra: testing
33
33
  Description-Content-Type: text/markdown
34
34
 
35
35
  ![banner](https://github.com/LlmKira/novelai-python/blob/dev/playground/banner-raw.png?raw=true)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "novelai-python"
3
- version = "0.4.13"
3
+ version = "0.4.15"
4
4
  description = "NovelAI Python Binding With Pydantic"
5
5
  authors = [
6
6
  { name = "sudoskys", email = "coldlando@hotmail.com" },
@@ -4,6 +4,9 @@
4
4
  # @File : ApiToken.py
5
5
  # @Software: PyCharm
6
6
 
7
+ import datetime
8
+
9
+ import shortuuid
7
10
  from curl_cffi.requests import AsyncSession
8
11
  from loguru import logger
9
12
  from pydantic import SecretStr, Field, field_validator
@@ -16,6 +19,7 @@ class ApiCredential(CredentialBase):
16
19
  ApiCredential is the base class for all credential.
17
20
  """
18
21
  api_token: SecretStr = Field(None, description="api token")
22
+ _x_correlation_id: str = shortuuid.uuid()[0:6]
19
23
 
20
24
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
21
25
  headers = {
@@ -26,6 +30,8 @@ class ApiCredential(CredentialBase):
26
30
  "Content-Type": "application/json",
27
31
  "Origin": "https://novelai.net",
28
32
  "Referer": "https://novelai.net/",
33
+ "x-correlation-id": self._x_correlation_id,
34
+ "x-initiated-at": f"{datetime.datetime.now(datetime.UTC).isoformat()}Z",
29
35
  }
30
36
 
31
37
  if update_headers:
@@ -3,6 +3,9 @@
3
3
  # @Author : sudoskys
4
4
  # @File : JwtToken.py
5
5
  # @Software: PyCharm
6
+ import datetime
7
+
8
+ import shortuuid
6
9
  from curl_cffi.requests import AsyncSession
7
10
  from loguru import logger
8
11
  from pydantic import SecretStr, Field, field_validator
@@ -15,6 +18,7 @@ class JwtCredential(CredentialBase):
15
18
  JwtCredential is the base class for all credential.
16
19
  """
17
20
  jwt_token: SecretStr = Field(None, description="jwt token")
21
+ _x_correlation_id: str = shortuuid.uuid()[0:6]
18
22
 
19
23
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
20
24
  headers = {
@@ -25,6 +29,8 @@ class JwtCredential(CredentialBase):
25
29
  "Content-Type": "application/json",
26
30
  "Origin": "https://novelai.net",
27
31
  "Referer": "https://novelai.net/",
32
+ "x-correlation-id": self._x_correlation_id,
33
+ "x-initiated-at": f"{datetime.datetime.now(datetime.UTC).isoformat()}Z",
28
34
  }
29
35
 
30
36
  if update_headers:
@@ -2,9 +2,11 @@
2
2
  # @Time : 2024/2/7 下午12:14
3
3
  # @Author : sudoskys
4
4
  # @File : UserAuth.py
5
+ import datetime
5
6
  import time
6
7
  from typing import Optional
7
8
 
9
+ import shortuuid
8
10
  from curl_cffi.requests import AsyncSession
9
11
  from pydantic import SecretStr, Field
10
12
 
@@ -19,6 +21,7 @@ class LoginCredential(CredentialBase):
19
21
  password: SecretStr = Field(None, description="password")
20
22
  _session_headers: dict = {}
21
23
  _update_at: Optional[int] = None
24
+ _x_correlation_id: str = shortuuid.uuid()[0:6]
22
25
 
23
26
  async def get_session(self, timeout: int = 180, update_headers: dict = None):
24
27
  headers = {
@@ -29,6 +32,8 @@ class LoginCredential(CredentialBase):
29
32
  "Content-Type": "application/json",
30
33
  "Origin": "https://novelai.net",
31
34
  "Referer": "https://novelai.net/",
35
+ "x-correlation-id": self._x_correlation_id,
36
+ "x-initiated-at": f"{datetime.datetime.now(datetime.UTC).isoformat()}Z",
32
37
  }
33
38
 
34
39
  # 30 天有效期
@@ -4,25 +4,27 @@
4
4
  # @File : _enum.py
5
5
  # @Software: PyCharm
6
6
  from enum import Enum, IntEnum
7
+ from typing import List
8
+
9
+ from pydantic.dataclasses import dataclass
7
10
 
8
11
 
9
12
  class Sampler(Enum):
13
+ PLMS = "plms"
14
+ DDIM = "ddim"
10
15
  K_EULER = "k_euler"
11
16
  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
- DDIM = "ddim"
18
- PLMS = "plms"
19
17
  K_DPM_2 = "k_dpm_2"
20
18
  K_DPM_2_ANCESTRAL = "k_dpm_2_ancestral"
21
19
  K_LMS = "k_lms"
20
+ K_DPMPP_2S_ANCESTRAL = "k_dpmpp_2s_ancestral"
21
+ K_DPMPP_SDE = "k_dpmpp_sde"
22
+ K_DPMPP_2M = "k_dpmpp_2m"
22
23
  K_DPM_ADAPTIVE = "k_dpm_adaptive"
23
24
  K_DPM_FAST = "k_dpm_fast"
24
25
  K_DPMPP_2M_SDE = "k_dpmpp_2m_sde"
25
26
  K_DPMPP_3M_SDE = "k_dpmpp_3m_sde"
27
+ DDIM_V3 = "ddim_v3"
26
28
  NAI_SMEA = "nai_smea"
27
29
  NAI_SMEA_DYN = "nai_smea_dyn"
28
30
 
@@ -34,6 +36,56 @@ class NoiseSchedule(Enum):
34
36
  POLYEXPONENTIAL = "polyexponential"
35
37
 
36
38
 
39
+ def get_supported_noise_schedule(sample_type: Sampler) -> List[NoiseSchedule]:
40
+ """
41
+ Get supported noise schedule for a given sample type
42
+ :param sample_type: Sampler
43
+ :return: List[NoiseSchedule]
44
+ """
45
+ if sample_type in [
46
+ Sampler.K_EULER_ANCESTRAL,
47
+ Sampler.K_DPMPP_2S_ANCESTRAL,
48
+ Sampler.K_DPMPP_2M,
49
+ Sampler.K_DPMPP_2M_SDE,
50
+ Sampler.K_DPMPP_SDE,
51
+ Sampler.K_EULER
52
+ ]:
53
+ return [
54
+ NoiseSchedule.NATIVE,
55
+ NoiseSchedule.KARRAS,
56
+ NoiseSchedule.EXPONENTIAL,
57
+ NoiseSchedule.POLYEXPONENTIAL
58
+ ]
59
+ elif sample_type in [Sampler.K_DPM_2]:
60
+ return [
61
+ NoiseSchedule.EXPONENTIAL,
62
+ NoiseSchedule.POLYEXPONENTIAL
63
+ ]
64
+ else:
65
+ return []
66
+
67
+
68
+ def get_default_noise_schedule(sample_type: Sampler) -> NoiseSchedule:
69
+ """
70
+ Get default noise schedule for a given sample type
71
+ :param sample_type: Sampler
72
+ :return: NoiseSchedule
73
+ """
74
+ if sample_type in [
75
+ Sampler.K_EULER_ANCESTRAL,
76
+ Sampler.K_DPMPP_2S_ANCESTRAL,
77
+ Sampler.K_DPMPP_2M,
78
+ Sampler.K_DPMPP_2M_SDE,
79
+ Sampler.K_DPMPP_SDE,
80
+ Sampler.K_EULER
81
+ ]:
82
+ return NoiseSchedule.KARRAS
83
+ elif sample_type in [Sampler.K_DPM_2]:
84
+ return NoiseSchedule.EXPONENTIAL
85
+ else:
86
+ return NoiseSchedule.NATIVE
87
+
88
+
37
89
  class UCPreset(IntEnum):
38
90
  TYPE0 = 0
39
91
  TYPE1 = 1
@@ -120,6 +172,8 @@ class ModelGroups(Enum):
120
172
 
121
173
 
122
174
  def get_model_group(model: str) -> ModelGroups:
175
+ if isinstance(model, Enum):
176
+ model = model.value
123
177
  mapping = {
124
178
  "stable-diffusion": ModelGroups.STABLE_DIFFUSION,
125
179
  "nai-diffusion": ModelGroups.STABLE_DIFFUSION,
@@ -162,8 +216,59 @@ PROMOTION = {
162
216
  "Stable Diffusion F1022D28": Model.NAI_DIFFUSION_2,
163
217
  "Stable Diffusion XL B0BDF6C1": Model.NAI_DIFFUSION_3,
164
218
  "Stable Diffusion XL C1E1DE52": Model.NAI_DIFFUSION_3,
219
+ "Stable Diffusion XL 7BCCAA2C": Model.NAI_DIFFUSION_3,
165
220
  "Stable Diffusion XL 8BA2AF87": Model.NAI_DIFFUSION_3,
166
221
  "Stable Diffusion XL 4BE8C60C": Model.NAI_DIFFUSION_FURRY_3,
167
222
  "Stable Diffusion XL C8704949": Model.NAI_DIFFUSION_FURRY_3,
223
+ "Stable Diffusion XL 37C2B166": Model.NAI_DIFFUSION_FURRY_3,
168
224
  "Stable Diffusion XL 9CC2F394": Model.NAI_DIFFUSION_FURRY_3,
169
225
  }
226
+
227
+
228
+ def get_default_uc_preset(model: str, uc_preset: int) -> str:
229
+ if isinstance(model, Enum):
230
+ model = model.value
231
+ if isinstance(uc_preset, Enum):
232
+ uc_preset = uc_preset.value
233
+
234
+ @dataclass
235
+ class UcPrompt:
236
+ label: str
237
+ text: str
238
+
239
+ mapper = {
240
+ ModelGroups.STABLE_DIFFUSION: [
241
+ UcPrompt("lowQualityPlusBadAnatomy",
242
+ text="lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"),
243
+ UcPrompt("lowQuality",
244
+ text="lowres, text, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"),
245
+ UcPrompt("none", text="lowres")
246
+ ],
247
+ ModelGroups.STABLE_DIFFUSION_GROUP_2: [
248
+ UcPrompt("heavy",
249
+ text="lowres, bad, text, error, missing, extra, fewer, cropped, jpeg artifacts, worst quality, bad quality, watermark, displeasing, unfinished, chromatic aberration, scan, scan artifacts"),
250
+ UcPrompt("light", text="lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"),
251
+ UcPrompt("none", text="lowres")
252
+ ],
253
+ ModelGroups.STABLE_DIFFUSION_XL: [
254
+ UcPrompt("heavy",
255
+ text="lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]"),
256
+ UcPrompt("light", text="lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing"),
257
+ UcPrompt("humanFocus",
258
+ text="lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched pupils, heart-shaped pupils, glowing eyes"),
259
+ UcPrompt("none", text="lowres")
260
+ ],
261
+ ModelGroups.STABLE_DIFFUSION_XL_FURRY: [
262
+ UcPrompt("heavy",
263
+ text="{{worst quality}}, [displeasing], {unusual pupils}, guide lines, {{unfinished}}, {bad}, url, artist name, {{tall image}}, mosaic, {sketch page}, comic panel, impact (font), [dated], {logo}, ych, {what}, {where is your god now}, {distorted text}, repeated text, {floating head}, {1994}, {widescreen}, absolutely everyone, sequence, {compression artifacts}, hard translated, {cropped}, {commissioner name}, unknown text, high contrast"),
264
+ UcPrompt("light",
265
+ text="{worst quality}, guide lines, unfinished, bad, url, tall image, widescreen, compression artifacts, unknown text"),
266
+ UcPrompt("none", text="lowres")
267
+ ],
268
+ }
269
+ model_group = get_model_group(model)
270
+ prompts: List[UcPrompt] = mapper.get(model_group, [UcPrompt("none", "lowres")])
271
+ if 0 <= uc_preset < len(prompts):
272
+ return prompts[uc_preset].text
273
+ else:
274
+ return "lowres"
@@ -23,8 +23,9 @@ from pydantic import BaseModel, ConfigDict, PrivateAttr, field_validator, model_
23
23
  from tenacity import retry, stop_after_attempt, wait_random, retry_if_exception
24
24
  from typing_extensions import override
25
25
 
26
- from novelai_python.sdk.ai._enum import Model, Sampler, NoiseSchedule, ControlNetModel, Action, UCPreset, INPAINTING_MODEL_LIST
27
26
  from novelai_python.sdk.ai._cost import CostCalculator
27
+ from novelai_python.sdk.ai._enum import Model, Sampler, NoiseSchedule, ControlNetModel, Action, UCPreset, \
28
+ INPAINTING_MODEL_LIST, get_default_noise_schedule, get_supported_noise_schedule, get_default_uc_preset
28
29
  from ...schema import ApiBaseModel
29
30
  from ...._exceptions import APIError, AuthError, ConcurrentGenerationError, SessionHttpError
30
31
  from ...._response.ai.generate_image import ImageGenerateResp, RequestParams
@@ -52,7 +53,7 @@ class Params(BaseModel):
52
53
  controlnet_strength: Optional[float] = Field(1.0, ge=0.1, le=2, multiple_of=0.1)
53
54
  """ControlNet Strength"""
54
55
  dynamic_thresholding: Optional[bool] = False
55
- """Reduce artifacts caused by high prompt guidance values"""
56
+ """Decrisp:Reduce artifacts caused by high prompt guidance values"""
56
57
  height: Optional[int] = Field(1216, ge=64, le=49152)
57
58
  """Height For Generate Image"""
58
59
  image: Optional[Union[str, bytes]] = None
@@ -69,7 +70,7 @@ class Params(BaseModel):
69
70
  """Number of samples"""
70
71
  negative_prompt: Optional[str] = ''
71
72
  """Negative Prompt"""
72
- noise_schedule: Optional[NoiseSchedule] = NoiseSchedule.NATIVE
73
+ noise_schedule: Optional[NoiseSchedule] = None
73
74
  """Noise Schedule"""
74
75
  # Misc
75
76
  params_version: Optional[int] = 1
@@ -112,7 +113,7 @@ class Params(BaseModel):
112
113
  # TODO: find out the usage
113
114
  qualityToggle: Optional[bool] = True
114
115
  """Whether to add the quality prompt"""
115
- sampler: Optional[Sampler] = Sampler.K_EULER
116
+ sampler: Optional[Sampler] = Sampler.K_EULER_ANCESTRAL
116
117
  """Sampler For Generate Image"""
117
118
  scale: Optional[float] = Field(6.0, ge=0, le=10, multiple_of=0.1)
118
119
  """Prompt Guidance"""
@@ -123,6 +124,8 @@ class Params(BaseModel):
123
124
  le=4294967295 - 7,
124
125
  )
125
126
  """Seed"""
127
+ skip_cfg_above_sigma: Optional[int] = None
128
+ """Variety Boost, a new feature to improve the diversity of samples."""
126
129
  extra_noise_seed: Optional[int] = Field(
127
130
  default_factory=lambda: random.randint(0, 4294967295 - 7),
128
131
  gt=0,
@@ -133,7 +136,7 @@ class Params(BaseModel):
133
136
  # TODO: find out the usage
134
137
  sm_dyn: Optional[bool] = False
135
138
  # TODO: find out the usage
136
- steps: Optional[int] = Field(28, ge=1, le=50)
139
+ steps: Optional[int] = Field(23, ge=1, le=50)
137
140
  """Steps"""
138
141
  ucPreset: Optional[UCPreset] = 0
139
142
  """The Negative Prompt Preset"""
@@ -212,6 +215,16 @@ class Params(BaseModel):
212
215
  # Validators
213
216
  @model_validator(mode="after")
214
217
  def image_validator(self):
218
+ # Noise schedule check
219
+ if self.noise_schedule is None:
220
+ self.noise_schedule = get_default_noise_schedule(self.sampler)
221
+ supported_noise_schedule = get_supported_noise_schedule(self.sampler)
222
+ if supported_noise_schedule:
223
+ if self.noise_schedule not in supported_noise_schedule:
224
+ raise ValueError(f"Invalid noise_schedule, must be one of {supported_noise_schedule}")
225
+ else:
226
+ logger.warning(f"Inactive sampler {self.sampler} does not support noise_schedule.")
227
+ # Check the noise value
215
228
  if self.sampler:
216
229
  if self.sampler in [Sampler.DDIM, Sampler.DDIM_V3]:
217
230
  self.sm = False
@@ -223,6 +236,7 @@ class Params(BaseModel):
223
236
  self.sm_dyn = True
224
237
  if not self.sm_dyn:
225
238
  logger.warning("sm and sm_dyn is enabled when using nai_smea_dyn sampler.")
239
+ # Make sure the image is base64 encoded
226
240
  if isinstance(self.image, str) and self.image.startswith("data:"):
227
241
  raise ValueError("Invalid `image` format, must be base64 encoded directly.")
228
242
  if isinstance(self.mask, str) and self.mask.startswith("data:"):
@@ -310,7 +324,7 @@ class GenerateImageInfer(ApiBaseModel):
310
324
 
311
325
  action: Union[str, Action] = Field(Action.GENERATE, description="Mode for img generate")
312
326
  input: str = "1girl, best quality, amazing quality, very aesthetic, absurdres"
313
- model: Optional[Model] = "nai-diffusion-3"
327
+ model: Optional[Union[Model, str]] = "nai-diffusion-3"
314
328
  parameters: Params = Params()
315
329
  model_config = ConfigDict(extra="ignore")
316
330
 
@@ -324,30 +338,10 @@ class GenerateImageInfer(ApiBaseModel):
324
338
  """
325
339
  if self.parameters.negative_prompt is None:
326
340
  self.parameters.negative_prompt = ""
327
- if self.parameters.ucPreset == 0:
328
- # 0: 重型
329
- self.parameters.negative_prompt = (
330
- "lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, "
331
- "watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, "
332
- "username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched pupils, heart-shaped pupils, "
333
- "glowing eyes, lowres, bad anatomy, bad hands, text, error, missing fingers, "
334
- "extra digit, fewer digits, cropped, worst quality, low quality, normal quality, "
335
- f"jpeg artifacts, signature, watermark, username, blurry "
336
- f",{self.parameters.negative_prompt}"
337
- )
338
- elif self.parameters.ucPreset == 1:
339
- # 1: 轻型
340
- self.parameters.negative_prompt = (f"lowres, jpeg artifacts, worst quality, watermark, blurry, "
341
- f"very displeasing"
342
- f",{self.parameters.negative_prompt}")
343
- elif self.parameters.ucPreset == 2:
344
- # 2: 人物
345
- self.parameters.negative_prompt = ("lowres, {bad}, error, fewer, extra, missing, worst quality, "
346
- "jpeg artifacts, bad quality, watermark, unfinished, displeasing, "
347
- "chromatic aberration, signature, extra digits, artistic error, "
348
- "username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched "
349
- f"pupils, heart-shaped pupils, glowing eyes "
350
- f",{self.parameters.negative_prompt}")
341
+ default_negative_prompt = get_default_uc_preset(self.model, self.parameters.ucPreset)
342
+ self.parameters.negative_prompt = ", ".join(
343
+ filter(None, [default_negative_prompt, self.parameters.negative_prompt])
344
+ )
351
345
  if self.parameters.qualityToggle:
352
346
  self.input += ", best quality, amazing quality, very aesthetic, absurdres"
353
347
 
@@ -430,7 +424,10 @@ class GenerateImageInfer(ApiBaseModel):
430
424
  height: int = 1216,
431
425
  qualityToggle: bool = None,
432
426
  image: Union[str, bytes] = None,
427
+ decrisp_mode: bool = False,
428
+ variety_boost: bool = False,
433
429
  noise: float = 0,
430
+ noise_schedule: Union[NoiseSchedule, str] = None,
434
431
  reference_image_multiple: List[Union[str, bytes]] = None,
435
432
  reference_strength_multiple: List[float] = None,
436
433
  reference_information_extracted_multiple: List[float] = None,
@@ -450,6 +447,11 @@ class GenerateImageInfer(ApiBaseModel):
450
447
  """
451
448
  The build function, more convenient to create a GenerateImageInfer instance.
452
449
  构建函数,更方便地创建一个GenerateImageInfer实例。
450
+ :param decrisp_mode: Reduce artifacts caused by high prompt guidance values.
451
+ 减少由高提示引导值引起的伪影。
452
+ :param variety_boost: A new feature to improve the diversity of samples.
453
+ Variety Boost means your negative prompt will only be used after the body shape has been decided.
454
+ 一项新功能,用于提高样本的多样性。但是,这意味着您的负面提示只会在身体形状确定后使用。
453
455
  :param reference_information_extracted_multiple: The reference information extracted for Vibe mode.
454
456
  Vibe模式的提取的参考信息。
455
457
  :param reference_image_multiple: The reference image for Vibe mode.
@@ -459,52 +461,53 @@ class GenerateImageInfer(ApiBaseModel):
459
461
  :param ucPreset: 0: Heavy, 1: Light, 2: Character
460
462
  0: 重量级, 1: 轻量级, 2: 角色
461
463
  :param qualityToggle: add the quality prompt or not.
462
- 是否添加质量提示。
464
+ 是否添加质量提示。
463
465
  :param prompt: input prompt for generation.
464
- 生成的输入提示。
466
+ 生成的输入提示。
465
467
  :param model: select a model.
466
- 选择一个模型。
468
+ 选择一个模型。
467
469
  :param action: mode for image generation [generate, img2img, infill].
468
- 图像生成模式 [生成, 图像到图像, 填充]。
470
+ 图像生成模式 [生成, 图像到图像, 填充]。
469
471
  :param negative_prompt: the content of negative prompt.
470
472
  负面提示的内容。
471
473
  :param seed: the seed for generate image.
472
- 生成图像的种子。
474
+ 生成图像的种子。
473
475
  :param steps: the steps for generate image.
474
- 生成图像的步骤。
476
+ 生成图像的步骤。
475
477
  :param scale: the scale for generate image.
476
- 生成图像的比例。
478
+ 生成图像的比例。
477
479
  :param cfg_rescale: prompt guidance rescale 0-1 lower is more creative.
478
480
  提示引导的重新缩放0-1,越低则越富有创造性。
479
481
  :param sampler: the sampler for generate image.
480
482
  生成图像的采样器。
481
483
  :param width: the width of the image.
482
- 图像的宽度。
484
+ 图像的宽度。
483
485
  :param height: the height of the image.
484
- 图像的高度。
486
+ 图像的高度。
485
487
  :param image: the input image.
486
- 输入图像。
488
+ 输入图像。
487
489
  :param noise: the noise to be added in the image.
488
- 要添加到图像中的噪声。
490
+ 要添加到图像中的噪声。
491
+ :param noise_schedule: the noise schedule for generate image.
492
+ 生成图像的噪声计划。
489
493
  :param add_original_image: Overlay Original Image. Prevents the existing image from changing, only for IMG2IMG mode.
490
- 叠加原始图像。防止现有图像发生更改,仅适用于IMG2IMG模式。
494
+ 叠加原始图像。防止现有图像发生更改,仅适用于IMG2IMG模式。
491
495
  :param strength: the strength of IMG2IMG mode.
492
- IMG2IMG模式的强度。
496
+ IMG2IMG模式的强度。
493
497
  :param mask: the inpainting mask.
494
- 纹理绘制掩码。
498
+ 纹理绘制掩码。
495
499
  :param controlnet_model: the model used for the control network.
496
- 用于控制网络的模型。
500
+ 用于控制网络的模型。
497
501
  :param controlnet_condition: the condition used for the control network.
498
- 用于控制网络的条件。
502
+ 用于控制网络的条件。
499
503
  :param sm: whether to use sm.
500
- 是否使用sm。
504
+ 是否使用sm。
501
505
  :param sm_dyn: whether to use dynamic sm.
502
- 是否使用动态sm。
506
+ 是否使用动态sm。
503
507
  :param uncond_scale: the strength of the unrelated content.
504
- 无关内容的强度。
508
+ 无关内容的强度。
505
509
  :param kwargs: any additional parameters.
506
- 其他任何参数。
507
-
510
+ 其他任何参数。
508
511
  :return: self
509
512
  返回:自身
510
513
  """
@@ -528,6 +531,7 @@ class GenerateImageInfer(ApiBaseModel):
528
531
  "sm_dyn": sm_dyn,
529
532
  "sm": sm,
530
533
  "uncond_scale": uncond_scale,
534
+ "noise_schedule": noise_schedule,
531
535
  })
532
536
 
533
537
  def _merge_param(v1, v2):
@@ -579,6 +583,10 @@ class GenerateImageInfer(ApiBaseModel):
579
583
  "noise": noise,
580
584
  }
581
585
  )
586
+ if decrisp_mode:
587
+ kwargs.update({"dynamic_thresholding": True})
588
+ if variety_boost:
589
+ kwargs.update({"skip_cfg_above_sigma": 19})
582
590
  # 清理空值
583
591
  param = {k: v for k, v in kwargs.items() if v is not None}
584
592
  _build_prop = Params(**param)
@@ -15,6 +15,7 @@ from loguru import logger
15
15
  from starlette.responses import JSONResponse, StreamingResponse
16
16
 
17
17
  from novelai_python import LLMStream, LLMStreamResp
18
+ from novelai_python.sdk.ai.augment_image import ReqType, Moods, AugmentImageInfer
18
19
  from .credential import JwtCredential, SecretStr
19
20
  from .sdk.ai.generate import LLM
20
21
  from .sdk.ai.generate_image import GenerateImageInfer, Model
@@ -123,7 +124,7 @@ async def upscale(
123
124
  return JSONResponse(status_code=500, content=e.__dict__)
124
125
 
125
126
 
126
- @app.get("/ai/generate_image/suggest_tags")
127
+ @app.get("/ai/generate-image/suggest_tags")
127
128
  async def suggest_tags(
128
129
  model: Model,
129
130
  prompt: str,
@@ -145,7 +146,7 @@ async def suggest_tags(
145
146
  return JSONResponse(status_code=500, content=e.__dict__)
146
147
 
147
148
 
148
- @app.post("/ai/generate_image")
149
+ @app.post("/ai/generate-image")
149
150
  async def generate_image(
150
151
  req: GenerateImageInfer,
151
152
  current_token: str = Depends(get_current_token)
@@ -244,6 +245,48 @@ async def generate_voice(
244
245
  return JSONResponse(status_code=500, content=e.__dict__)
245
246
 
246
247
 
248
+ @app.post("/ai/augment-image")
249
+ async def augment_image(
250
+ req_type: ReqType,
251
+ image: bytes,
252
+ mood: Optional[Moods] = None,
253
+ prompt: Optional[str] = None,
254
+ defry: Optional[int] = None,
255
+ current_token: str = Depends(get_current_token)
256
+ ):
257
+ """
258
+ 生成图片
259
+ :param current_token: Authorization
260
+ :param req_type: ReqType
261
+ :param image: bytes
262
+ :param mood: Optional[Moods]
263
+ :param prompt: Optional[str]
264
+ :param defry: Optional[int]
265
+ :return:
266
+ """
267
+ try:
268
+ req = AugmentImageInfer.build(
269
+ req_type=req_type,
270
+ image=image,
271
+ mood=mood,
272
+ prompt=prompt,
273
+ defry=defry,
274
+ )
275
+ _result = await req.request(session=get_session(current_token))
276
+ zip_file_bytes = io.BytesIO()
277
+ with zipfile.ZipFile(zip_file_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zip_file:
278
+ for file in _result.files:
279
+ zip_file.writestr(zinfo_or_arcname=file[0], data=file[1])
280
+ # return the zip file
281
+ zip_file_bytes.seek(0)
282
+ return StreamingResponse(zip_file_bytes, media_type='application/zip', headers={
283
+ 'Content-Disposition': 'attachment;filename=image.zip'
284
+ })
285
+ except Exception as e:
286
+ logger.exception(e)
287
+ return JSONResponse(status_code=500, content=e.__dict__)
288
+
289
+
247
290
  # 获取输入参数
248
291
  def usage():
249
292
  print("Usage: python -m novelai_python.server -h <host> -p <port>")
@@ -29,7 +29,7 @@ def test_subscription_without_api_key(mock_subscription):
29
29
  @patch('src.novelai_python.server.GenerateImageInfer')
30
30
  def test_generate_image_without_api_key(mock_generate_image):
31
31
  mock_generate_image.return_value.request.return_value = {"status": "image generated"}
32
- response = client.post("/ai/generate_image")
32
+ response = client.post("/ai/generate-image")
33
33
  assert response.status_code == 403
34
34
 
35
35
 
File without changes