novelai-python 0.4.5__tar.gz → 0.4.6__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.
- {novelai_python-0.4.5 → novelai_python-0.4.6}/PKG-INFO +2 -2
- {novelai_python-0.4.5 → novelai_python-0.4.6}/README.md +1 -1
- {novelai_python-0.4.5 → novelai_python-0.4.6}/pyproject.toml +1 -1
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/__init__.py +311 -220
- novelai_python-0.4.5/src/novelai_python/sdk/ai/generate_voice.py → novelai_python-0.4.6/src/novelai_python/sdk/ai/generate_voice/__init__.py +4 -9
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/server.py +35 -4
- {novelai_python-0.4.5 → novelai_python-0.4.6}/LICENSE +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/NOTICE.MD +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_exceptions.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/generate_image.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/generate_voice.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/upscale.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/schema.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/information.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/login.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/subscription.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/credential/ApiToken.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/credential/JwtToken.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/credential/UserAuth.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/credential/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/credential/_base.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/LICENSE +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/_const.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/_enum.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/suggest_tags.py +0 -0
- {novelai_python-0.4.5/src/novelai_python/sdk/ai → novelai_python-0.4.6/src/novelai_python/sdk/ai/generate_voice}/_enum.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/upscale.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/schema.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/user/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/user/information.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/user/login.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/user/subscription.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tokenizer/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tokenizer/novelai.model +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tokenizer/novelai_v2.model +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/image_metadata/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/image_metadata/lsb_extractor.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/image_metadata/lsb_injector.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/paint_mask/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/tag.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/tag_character.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/tag_nsfw.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/utils/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/utils/encode.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/utils/useful.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/__init__.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_generate_voice.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_random_prompt.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_server.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_server_run.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_upscale.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_user_information.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/tests/test_user_login.py +0 -0
- {novelai_python-0.4.5 → novelai_python-0.4.6}/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.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: NovelAI Python Binding With Pydantic
|
|
5
5
|
Author-Email: sudoskys <coldlando@hotmail.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -129,7 +129,7 @@ print(s)
|
|
|
129
129
|
|
|
130
130
|
```shell
|
|
131
131
|
pip install novelai_python
|
|
132
|
-
python3 -m novelai_python.server -h '
|
|
132
|
+
python3 -m novelai_python.server -h '127.0.0.1' -p 7888
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
## Acknowledgements 🙏
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/__init__.py
RENAMED
|
@@ -9,7 +9,7 @@ import math
|
|
|
9
9
|
import random
|
|
10
10
|
from copy import deepcopy
|
|
11
11
|
from io import BytesIO
|
|
12
|
-
from typing import Optional, Union, Tuple
|
|
12
|
+
from typing import Optional, Union, Tuple, List
|
|
13
13
|
from urllib.parse import urlparse
|
|
14
14
|
from zipfile import ZipFile
|
|
15
15
|
|
|
@@ -33,6 +33,265 @@ from ....credential import CredentialBase
|
|
|
33
33
|
from ....utils import try_jsonfy
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def is_multiple_of_01(num, precision=1e-10):
|
|
37
|
+
remainder = num % 0.01
|
|
38
|
+
return abs(remainder) < precision or abs(0.01 - remainder) < precision
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Params(BaseModel):
|
|
42
|
+
add_original_image: Optional[bool] = Field(True, description="Overlay Original Image")
|
|
43
|
+
"""
|
|
44
|
+
Overlay Original Image.Prevents the existing image from changing,
|
|
45
|
+
but can introduce seams along the edge of the mask.
|
|
46
|
+
叠加原始图像
|
|
47
|
+
防止现有图像发生更改,但可能会沿蒙版边缘引入接缝。
|
|
48
|
+
"""
|
|
49
|
+
mask: Optional[Union[str, bytes]] = None
|
|
50
|
+
"""Mask for Inpainting"""
|
|
51
|
+
cfg_rescale: Optional[float] = Field(0, ge=0, le=1, multiple_of=0.02)
|
|
52
|
+
"""Prompt Guidance Rescale"""
|
|
53
|
+
controlnet_strength: Optional[float] = Field(1.0, ge=0.1, le=2, multiple_of=0.1)
|
|
54
|
+
"""ControlNet Strength"""
|
|
55
|
+
dynamic_thresholding: Optional[bool] = False
|
|
56
|
+
"""Reduce artifacts caused by high prompt guidance values"""
|
|
57
|
+
height: Optional[int] = Field(1216, ge=64, le=49152)
|
|
58
|
+
"""Height For Generate Image"""
|
|
59
|
+
image: Optional[Union[str, bytes]] = None
|
|
60
|
+
"""Image for img2img"""
|
|
61
|
+
strength: Optional[float] = Field(default=0.5, ge=0.01, le=0.99, multiple_of=0.01)
|
|
62
|
+
"""Strength for img2img"""
|
|
63
|
+
noise: Optional[float] = Field(default=0, ge=0, le=0.99, multiple_of=0.01)
|
|
64
|
+
"""Noise for img2img"""
|
|
65
|
+
controlnet_condition: Optional[str] = None
|
|
66
|
+
"""ControlNet Condition"""
|
|
67
|
+
controlnet_model: Optional[ControlNetModel] = None
|
|
68
|
+
"""ControlNet Model"""
|
|
69
|
+
n_samples: Optional[int] = Field(1, ge=1, le=8)
|
|
70
|
+
"""Number of samples"""
|
|
71
|
+
negative_prompt: Optional[str] = ''
|
|
72
|
+
"""Negative Prompt"""
|
|
73
|
+
noise_schedule: Optional[NoiseSchedule] = NoiseSchedule.NATIVE
|
|
74
|
+
"""Noise Schedule"""
|
|
75
|
+
# Misc
|
|
76
|
+
params_version: Optional[int] = 1
|
|
77
|
+
"""Params Version For Request"""
|
|
78
|
+
reference_image_multiple: Optional[List[Union[str, bytes]]] = None
|
|
79
|
+
"""Reference Image For Vibe Mode"""
|
|
80
|
+
reference_information_extracted_multiple: Optional[List[float]] = None
|
|
81
|
+
"""Reference Information Extracted For Vibe Mode"""
|
|
82
|
+
|
|
83
|
+
@field_validator('reference_information_extracted_multiple')
|
|
84
|
+
def reference_information_extracted_multiple_validator(cls, v):
|
|
85
|
+
# List[Field(..., ge=0, le=1, multiple_of=0.01)]
|
|
86
|
+
if v is None:
|
|
87
|
+
return v
|
|
88
|
+
for i in v:
|
|
89
|
+
if not 0 <= i <= 1:
|
|
90
|
+
raise ValueError("Invalid reference_information_extracted_multiple, must be in [0, 1].")
|
|
91
|
+
if not is_multiple_of_01(i):
|
|
92
|
+
raise ValueError("Invalid reference_information_extracted_multiple, must be multiple of 0.01.")
|
|
93
|
+
return v
|
|
94
|
+
|
|
95
|
+
reference_strength_multiple: Optional[List[float]] = None
|
|
96
|
+
|
|
97
|
+
@field_validator('reference_strength_multiple')
|
|
98
|
+
def reference_strength_multiple_validator(cls, v):
|
|
99
|
+
# Field(0.6,ge=0,le=1,multiple_of=0.01,description="the stronger the AI will try to emulate visual cues.")
|
|
100
|
+
if v is None:
|
|
101
|
+
return v
|
|
102
|
+
for i in v:
|
|
103
|
+
if not 0 <= i <= 1:
|
|
104
|
+
raise ValueError("Invalid reference_strength_multiple, must be in [0, 1].")
|
|
105
|
+
if not is_multiple_of_01(i):
|
|
106
|
+
raise ValueError("Invalid reference_strength_multiple, must be multiple of 0.01.")
|
|
107
|
+
return v
|
|
108
|
+
|
|
109
|
+
"""Reference Strength For Vibe Mode"""
|
|
110
|
+
legacy: Optional[bool] = False
|
|
111
|
+
# TODO: find out the usage
|
|
112
|
+
legacy_v3_extend: Optional[bool] = False
|
|
113
|
+
# TODO: find out the usage
|
|
114
|
+
qualityToggle: Optional[bool] = True
|
|
115
|
+
"""Whether to add the quality prompt"""
|
|
116
|
+
sampler: Optional[Sampler] = Sampler.K_EULER
|
|
117
|
+
"""Sampler For Generate Image"""
|
|
118
|
+
scale: Optional[float] = Field(6.0, ge=0, le=10, multiple_of=0.1)
|
|
119
|
+
"""Prompt Guidance"""
|
|
120
|
+
# Seed
|
|
121
|
+
seed: Optional[int] = Field(
|
|
122
|
+
default_factory=lambda: random.randint(0, 4294967295 - 7),
|
|
123
|
+
gt=0,
|
|
124
|
+
le=4294967295 - 7,
|
|
125
|
+
)
|
|
126
|
+
"""Seed"""
|
|
127
|
+
extra_noise_seed: Optional[int] = Field(
|
|
128
|
+
default_factory=lambda: random.randint(0, 4294967295 - 7),
|
|
129
|
+
gt=0,
|
|
130
|
+
le=4294967295 - 7,
|
|
131
|
+
)
|
|
132
|
+
"""Extra Noise Seed"""
|
|
133
|
+
sm: Optional[bool] = False
|
|
134
|
+
# TODO: find out the usage
|
|
135
|
+
sm_dyn: Optional[bool] = False
|
|
136
|
+
# TODO: find out the usage
|
|
137
|
+
steps: Optional[int] = Field(28, ge=1, le=50)
|
|
138
|
+
"""Steps"""
|
|
139
|
+
ucPreset: Optional[UCPreset] = 0
|
|
140
|
+
"""The Negative Prompt Preset"""
|
|
141
|
+
uncond_scale: Optional[float] = Field(1.0, ge=0, le=1.5, multiple_of=0.05)
|
|
142
|
+
"""Undesired Content Strength"""
|
|
143
|
+
width: Optional[int] = Field(832, ge=64, le=49152)
|
|
144
|
+
"""Width For Image"""
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def resize_image(image: Union[str, bytes], width: int, height: int):
|
|
148
|
+
"""
|
|
149
|
+
Resize the image to the specified size.
|
|
150
|
+
:param image: The image to be resized
|
|
151
|
+
:param width: The width of the image
|
|
152
|
+
:param height: The height of the image
|
|
153
|
+
:return: The resized image
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(image, str):
|
|
156
|
+
image = base64.b64decode(image)
|
|
157
|
+
open_image = Image.open(BytesIO(image)).convert("RGBA")
|
|
158
|
+
# 如果尺寸相同,直接返回
|
|
159
|
+
if open_image.width == width and open_image.height == height:
|
|
160
|
+
logger.debug("Image size is same, return directly.")
|
|
161
|
+
if isinstance(image, bytes):
|
|
162
|
+
return base64.b64encode(image).decode("utf-8")
|
|
163
|
+
return image
|
|
164
|
+
open_image = open_image.resize((width, height), Image.Resampling.BICUBIC)
|
|
165
|
+
buffered = BytesIO()
|
|
166
|
+
open_image.save(buffered, format="PNG")
|
|
167
|
+
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def add_image_to_black_background(
|
|
171
|
+
image: Union[str, bytes],
|
|
172
|
+
target_size: Tuple[int, int] = (448, 448),
|
|
173
|
+
transparency: bool = False
|
|
174
|
+
):
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
缩放图像到指定的黑色透明背景上,使其尽可能大且保持比例。
|
|
178
|
+
:param transparency: 是否透明
|
|
179
|
+
:param image: 图像
|
|
180
|
+
:param target_size: 目标尺寸
|
|
181
|
+
:return: 新图像
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
if isinstance(image, str):
|
|
185
|
+
image = base64.b64decode(image)
|
|
186
|
+
# Decode the image from the base64 string
|
|
187
|
+
npimg = np.frombuffer(image, np.uint8)
|
|
188
|
+
# Load the image with OpenCV
|
|
189
|
+
img = cv2.imdecode(npimg, cv2.IMREAD_UNCHANGED)
|
|
190
|
+
# Calculate the aspect ratio (keeping aspect ratio)
|
|
191
|
+
ratio = min(target_size[0] / img.shape[1], target_size[1] / img.shape[0])
|
|
192
|
+
new_image_size = (int(img.shape[1] * ratio), int(img.shape[0] * ratio))
|
|
193
|
+
# Resize the image
|
|
194
|
+
img = cv2.resize(img, new_image_size, interpolation=cv2.INTER_LINEAR)
|
|
195
|
+
# Check if the image has alpha channel (transparency)
|
|
196
|
+
if img.shape[2] == 3: # no alpha channel, add one
|
|
197
|
+
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
|
198
|
+
# Create new image with black background
|
|
199
|
+
delta_w = target_size[0] - img.shape[1]
|
|
200
|
+
delta_h = target_size[1] - img.shape[0]
|
|
201
|
+
top, bottom = delta_h // 2, delta_h - (delta_h // 2)
|
|
202
|
+
left, right = delta_w // 2, delta_w - (delta_w // 2)
|
|
203
|
+
# If the transparency flag is set the background is transparent, otherwise it is black
|
|
204
|
+
color = [0, 0, 0, 0] if transparency else [0, 0, 0, 255]
|
|
205
|
+
# Add padding to the image
|
|
206
|
+
new_img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
|
|
207
|
+
# Convert the image to base64
|
|
208
|
+
_, buffer = cv2.imencode(".png", new_img)
|
|
209
|
+
# buffer here is a numpy array, convert it to bytes
|
|
210
|
+
image_bytes = buffer.tobytes()
|
|
211
|
+
return base64.b64encode(image_bytes).decode("utf-8")
|
|
212
|
+
|
|
213
|
+
# Validators
|
|
214
|
+
@model_validator(mode="after")
|
|
215
|
+
def image_validator(self):
|
|
216
|
+
if self.sampler:
|
|
217
|
+
if self.sampler in [Sampler.DDIM, Sampler.DDIM_V3]:
|
|
218
|
+
self.sm = False
|
|
219
|
+
self.sm_dyn = False
|
|
220
|
+
if self.sm_dyn or self.sm:
|
|
221
|
+
logger.warning("sm and sm_dyn is disabled when using ddim sampler.")
|
|
222
|
+
if self.sampler in [Sampler.NAI_SMEA_DYN]:
|
|
223
|
+
self.sm = True
|
|
224
|
+
self.sm_dyn = True
|
|
225
|
+
if not self.sm_dyn:
|
|
226
|
+
logger.warning("sm and sm_dyn is enabled when using nai_smea_dyn sampler.")
|
|
227
|
+
if isinstance(self.image, str) and self.image.startswith("data:"):
|
|
228
|
+
raise ValueError("Invalid `image` format, must be base64 encoded directly.")
|
|
229
|
+
if isinstance(self.mask, str) and self.mask.startswith("data:"):
|
|
230
|
+
raise ValueError("Invalid `mask` format, must be base64 encoded directly.")
|
|
231
|
+
if isinstance(self.image, bytes):
|
|
232
|
+
self.image = base64.b64encode(self.image).decode("utf-8")
|
|
233
|
+
# Resize the image to the specified size
|
|
234
|
+
if self.reference_image_multiple is not None:
|
|
235
|
+
new_images = []
|
|
236
|
+
for reference_image in self.reference_image_multiple:
|
|
237
|
+
if isinstance(reference_image, str):
|
|
238
|
+
if reference_image.startswith("data:"):
|
|
239
|
+
raise ValueError("Invalid `reference_image` format, must be base64 encoded directly.")
|
|
240
|
+
new_images.append(reference_image)
|
|
241
|
+
elif isinstance(reference_image, bytes):
|
|
242
|
+
new_image = self.add_image_to_black_background(reference_image,
|
|
243
|
+
target_size=(448, 448),
|
|
244
|
+
transparency=True)
|
|
245
|
+
if isinstance(new_image, bytes):
|
|
246
|
+
new_image = base64.b64encode(new_image).decode("utf-8")
|
|
247
|
+
new_images.append(new_image)
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError("Invalid `reference_image` format, must be base64 encoded directly.")
|
|
250
|
+
self.reference_image_multiple = new_images
|
|
251
|
+
# 如果都不是 None 时,比较他们的长度
|
|
252
|
+
if all([self.reference_strength_multiple,
|
|
253
|
+
self.reference_image_multiple,
|
|
254
|
+
self.reference_information_extracted_multiple]):
|
|
255
|
+
if len(self.reference_strength_multiple) != len(self.reference_image_multiple) != len(
|
|
256
|
+
self.reference_information_extracted_multiple):
|
|
257
|
+
raise ValueError("All three reference_* must be of equal length")
|
|
258
|
+
|
|
259
|
+
# 如果有一个存在,其他都必须存在
|
|
260
|
+
ref_items = [self.reference_strength_multiple,
|
|
261
|
+
self.reference_image_multiple,
|
|
262
|
+
self.reference_information_extracted_multiple]
|
|
263
|
+
if any(ref_items) and not all(ref_items):
|
|
264
|
+
raise ValueError("All fields must be present together or none should be present")
|
|
265
|
+
|
|
266
|
+
if isinstance(self.mask, bytes):
|
|
267
|
+
self.mask = base64.b64encode(self.mask).decode("utf-8")
|
|
268
|
+
if self.image is not None:
|
|
269
|
+
self.image = self.resize_image(self.image, self.width, self.height)
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
@field_validator('width')
|
|
273
|
+
def width_validator(cls, v: int):
|
|
274
|
+
"""
|
|
275
|
+
Must be multiple of 64
|
|
276
|
+
:param v:
|
|
277
|
+
:return: fixed value
|
|
278
|
+
"""
|
|
279
|
+
if v % 64 != 0:
|
|
280
|
+
raise ValueError("Invalid width, must be multiple of 64.")
|
|
281
|
+
return v
|
|
282
|
+
|
|
283
|
+
@field_validator('height')
|
|
284
|
+
def height_validator(cls, v: int):
|
|
285
|
+
"""
|
|
286
|
+
Must be multiple of 64
|
|
287
|
+
:param v:
|
|
288
|
+
:return: fixed value
|
|
289
|
+
"""
|
|
290
|
+
if v % 64 != 0:
|
|
291
|
+
raise ValueError("Invalid height, must be multiple of 64.")
|
|
292
|
+
return v
|
|
293
|
+
|
|
294
|
+
|
|
36
295
|
class GenerateImageInfer(ApiBaseModel):
|
|
37
296
|
_endpoint: str = PrivateAttr("https://image.novelai.net")
|
|
38
297
|
|
|
@@ -44,214 +303,6 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
44
303
|
def endpoint(self, value):
|
|
45
304
|
self._endpoint = value
|
|
46
305
|
|
|
47
|
-
class Params(BaseModel):
|
|
48
|
-
add_original_image: Optional[bool] = Field(True, description="Overlay Original Image")
|
|
49
|
-
"""
|
|
50
|
-
Overlay Original Image.Prevents the existing image from changing,
|
|
51
|
-
but can introduce seams along the edge of the mask.
|
|
52
|
-
叠加原始图像
|
|
53
|
-
防止现有图像发生更改,但可能会沿蒙版边缘引入接缝。
|
|
54
|
-
"""
|
|
55
|
-
mask: Optional[Union[str, bytes]] = None
|
|
56
|
-
"""Mask for Inpainting"""
|
|
57
|
-
cfg_rescale: Optional[float] = Field(0, ge=0, le=1, multiple_of=0.02)
|
|
58
|
-
"""Prompt Guidance Rescale"""
|
|
59
|
-
controlnet_strength: Optional[float] = Field(1.0, ge=0.1, le=2, multiple_of=0.1)
|
|
60
|
-
"""ControlNet Strength"""
|
|
61
|
-
dynamic_thresholding: Optional[bool] = False
|
|
62
|
-
"""Reduce artifacts caused by high prompt guidance values"""
|
|
63
|
-
height: Optional[int] = Field(1216, ge=64, le=49152)
|
|
64
|
-
"""Height For Generate Image"""
|
|
65
|
-
image: Optional[Union[str, bytes]] = None
|
|
66
|
-
"""Image for img2img"""
|
|
67
|
-
strength: Optional[float] = Field(default=0.5, ge=0.01, le=0.99, multiple_of=0.01)
|
|
68
|
-
"""Strength for img2img"""
|
|
69
|
-
noise: Optional[float] = Field(default=0, ge=0, le=0.99, multiple_of=0.01)
|
|
70
|
-
"""Noise for img2img"""
|
|
71
|
-
controlnet_condition: Optional[str] = None
|
|
72
|
-
"""ControlNet Condition"""
|
|
73
|
-
controlnet_model: Optional[ControlNetModel] = None
|
|
74
|
-
"""ControlNet Model"""
|
|
75
|
-
n_samples: Optional[int] = Field(1, ge=1, le=8)
|
|
76
|
-
"""Number of samples"""
|
|
77
|
-
negative_prompt: Optional[str] = ''
|
|
78
|
-
"""Negative Prompt"""
|
|
79
|
-
noise_schedule: Optional[NoiseSchedule] = NoiseSchedule.NATIVE
|
|
80
|
-
"""Noise Schedule"""
|
|
81
|
-
# Misc
|
|
82
|
-
params_version: Optional[int] = 1
|
|
83
|
-
"""Params Version For Request"""
|
|
84
|
-
reference_image: Optional[Union[str, bytes]] = None
|
|
85
|
-
"""Reference Image For Vibe Mode"""
|
|
86
|
-
reference_information_extracted: Optional[float] = Field(1,
|
|
87
|
-
ge=0,
|
|
88
|
-
le=1,
|
|
89
|
-
multiple_of=0.01,
|
|
90
|
-
description="extracting concepts or features"
|
|
91
|
-
)
|
|
92
|
-
"""Reference Information Extracted For Vibe Mode"""
|
|
93
|
-
reference_strength: Optional[float] = Field(0.6,
|
|
94
|
-
ge=0,
|
|
95
|
-
le=1,
|
|
96
|
-
multiple_of=0.01,
|
|
97
|
-
description="the stronger the AI will try to emulate visual cues."
|
|
98
|
-
)
|
|
99
|
-
"""Reference Strength For Vibe Mode"""
|
|
100
|
-
legacy: Optional[bool] = False
|
|
101
|
-
# TODO: find out the usage
|
|
102
|
-
legacy_v3_extend: Optional[bool] = False
|
|
103
|
-
# TODO: find out the usage
|
|
104
|
-
qualityToggle: Optional[bool] = True
|
|
105
|
-
"""Whether to add the quality prompt"""
|
|
106
|
-
sampler: Optional[Sampler] = Sampler.K_EULER
|
|
107
|
-
"""Sampler For Generate Image"""
|
|
108
|
-
scale: Optional[float] = Field(6.0, ge=0, le=10, multiple_of=0.1)
|
|
109
|
-
"""Prompt Guidance"""
|
|
110
|
-
# Seed
|
|
111
|
-
seed: Optional[int] = Field(
|
|
112
|
-
default_factory=lambda: random.randint(0, 4294967295 - 7),
|
|
113
|
-
gt=0,
|
|
114
|
-
le=4294967295 - 7,
|
|
115
|
-
)
|
|
116
|
-
"""Seed"""
|
|
117
|
-
extra_noise_seed: Optional[int] = Field(
|
|
118
|
-
default_factory=lambda: random.randint(0, 4294967295 - 7),
|
|
119
|
-
gt=0,
|
|
120
|
-
le=4294967295 - 7,
|
|
121
|
-
)
|
|
122
|
-
"""Extra Noise Seed"""
|
|
123
|
-
sm: Optional[bool] = False
|
|
124
|
-
# TODO: find out the usage
|
|
125
|
-
sm_dyn: Optional[bool] = False
|
|
126
|
-
# TODO: find out the usage
|
|
127
|
-
steps: Optional[int] = Field(28, ge=1, le=50)
|
|
128
|
-
"""Steps"""
|
|
129
|
-
ucPreset: Optional[UCPreset] = 0
|
|
130
|
-
"""The Negative Prompt Preset"""
|
|
131
|
-
uncond_scale: Optional[float] = Field(1.0, ge=0, le=1.5, multiple_of=0.05)
|
|
132
|
-
"""Undesired Content Strength"""
|
|
133
|
-
width: Optional[int] = Field(832, ge=64, le=49152)
|
|
134
|
-
"""Width For Image"""
|
|
135
|
-
|
|
136
|
-
@staticmethod
|
|
137
|
-
def resize_image(image: Union[str, bytes], width: int, height: int):
|
|
138
|
-
"""
|
|
139
|
-
Resize the image to the specified size.
|
|
140
|
-
:param image: The image to be resized
|
|
141
|
-
:param width: The width of the image
|
|
142
|
-
:param height: The height of the image
|
|
143
|
-
:return: The resized image
|
|
144
|
-
"""
|
|
145
|
-
if isinstance(image, str):
|
|
146
|
-
image = base64.b64decode(image)
|
|
147
|
-
open_image = Image.open(BytesIO(image)).convert("RGBA")
|
|
148
|
-
# 如果尺寸相同,直接返回
|
|
149
|
-
if open_image.width == width and open_image.height == height:
|
|
150
|
-
logger.debug("Image size is same, return directly.")
|
|
151
|
-
if isinstance(image, bytes):
|
|
152
|
-
return base64.b64encode(image).decode("utf-8")
|
|
153
|
-
return image
|
|
154
|
-
open_image = open_image.resize((width, height), Image.Resampling.BICUBIC)
|
|
155
|
-
buffered = BytesIO()
|
|
156
|
-
open_image.save(buffered, format="PNG")
|
|
157
|
-
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
158
|
-
|
|
159
|
-
@staticmethod
|
|
160
|
-
def add_image_to_black_background(
|
|
161
|
-
image: Union[str, bytes],
|
|
162
|
-
target_size: Tuple[int, int] = (448, 448),
|
|
163
|
-
transparency: bool = False
|
|
164
|
-
):
|
|
165
|
-
|
|
166
|
-
"""
|
|
167
|
-
缩放图像到指定的黑色透明背景上,使其尽可能大且保持比例。
|
|
168
|
-
:param transparency: 是否透明
|
|
169
|
-
:param image: 图像
|
|
170
|
-
:param target_size: 目标尺寸
|
|
171
|
-
:return: 新图像
|
|
172
|
-
"""
|
|
173
|
-
|
|
174
|
-
if isinstance(image, str):
|
|
175
|
-
image = base64.b64decode(image)
|
|
176
|
-
# Decode the image from the base64 string
|
|
177
|
-
npimg = np.frombuffer(image, np.uint8)
|
|
178
|
-
# Load the image with OpenCV
|
|
179
|
-
img = cv2.imdecode(npimg, cv2.IMREAD_UNCHANGED)
|
|
180
|
-
# Calculate the aspect ratio (keeping aspect ratio)
|
|
181
|
-
ratio = min(target_size[0] / img.shape[1], target_size[1] / img.shape[0])
|
|
182
|
-
new_image_size = (int(img.shape[1] * ratio), int(img.shape[0] * ratio))
|
|
183
|
-
# Resize the image
|
|
184
|
-
img = cv2.resize(img, new_image_size, interpolation=cv2.INTER_LINEAR)
|
|
185
|
-
# Check if the image has alpha channel (transparency)
|
|
186
|
-
if img.shape[2] == 3: # no alpha channel, add one
|
|
187
|
-
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
|
|
188
|
-
# Create new image with black background
|
|
189
|
-
delta_w = target_size[0] - img.shape[1]
|
|
190
|
-
delta_h = target_size[1] - img.shape[0]
|
|
191
|
-
top, bottom = delta_h // 2, delta_h - (delta_h // 2)
|
|
192
|
-
left, right = delta_w // 2, delta_w - (delta_w // 2)
|
|
193
|
-
# If the transparency flag is set the background is transparent, otherwise it is black
|
|
194
|
-
color = [0, 0, 0, 0] if transparency else [0, 0, 0, 255]
|
|
195
|
-
# Add padding to the image
|
|
196
|
-
new_img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
|
|
197
|
-
# Convert the image to base64
|
|
198
|
-
_, buffer = cv2.imencode(".png", new_img)
|
|
199
|
-
return base64.b64encode(buffer).decode("utf-8")
|
|
200
|
-
|
|
201
|
-
# Validators
|
|
202
|
-
@model_validator(mode="after")
|
|
203
|
-
def image_validator(self):
|
|
204
|
-
if self.sampler:
|
|
205
|
-
if self.sampler in [Sampler.DDIM, Sampler.DDIM_V3]:
|
|
206
|
-
self.sm = False
|
|
207
|
-
self.sm_dyn = False
|
|
208
|
-
if self.sm_dyn or self.sm:
|
|
209
|
-
logger.warning("sm and sm_dyn is disabled when using ddim sampler.")
|
|
210
|
-
if self.sampler in [Sampler.NAI_SMEA_DYN]:
|
|
211
|
-
self.sm = True
|
|
212
|
-
self.sm_dyn = True
|
|
213
|
-
if not self.sm_dyn:
|
|
214
|
-
logger.warning("sm and sm_dyn is enabled when using nai_smea_dyn sampler.")
|
|
215
|
-
if isinstance(self.image, str) and self.image.startswith("data:"):
|
|
216
|
-
raise ValueError("Invalid `image` format, must be base64 encoded directly.")
|
|
217
|
-
if isinstance(self.reference_image, str) and self.reference_image.startswith("data:"):
|
|
218
|
-
raise ValueError("Invalid `reference_image` format, must be base64 encoded directly.")
|
|
219
|
-
if isinstance(self.mask, str) and self.mask.startswith("data:"):
|
|
220
|
-
raise ValueError("Invalid `mask` format, must be base64 encoded directly.")
|
|
221
|
-
if isinstance(self.image, bytes):
|
|
222
|
-
self.image = base64.b64encode(self.image).decode("utf-8")
|
|
223
|
-
if isinstance(self.reference_image, bytes):
|
|
224
|
-
self.reference_image = base64.b64encode(self.reference_image).decode("utf-8")
|
|
225
|
-
if isinstance(self.mask, bytes):
|
|
226
|
-
self.mask = base64.b64encode(self.mask).decode("utf-8")
|
|
227
|
-
if self.image is not None:
|
|
228
|
-
self.image = self.resize_image(self.image, self.width, self.height)
|
|
229
|
-
if self.reference_image is not None:
|
|
230
|
-
self.reference_image = self.add_image_to_black_background(self.reference_image, width=448, height=448)
|
|
231
|
-
return self
|
|
232
|
-
|
|
233
|
-
@field_validator('width')
|
|
234
|
-
def width_validator(cls, v: int):
|
|
235
|
-
"""
|
|
236
|
-
Must be multiple of 64
|
|
237
|
-
:param v:
|
|
238
|
-
:return: fixed value
|
|
239
|
-
"""
|
|
240
|
-
if v % 64 != 0:
|
|
241
|
-
raise ValueError("Invalid width, must be multiple of 64.")
|
|
242
|
-
return v
|
|
243
|
-
|
|
244
|
-
@field_validator('height')
|
|
245
|
-
def height_validator(cls, v: int):
|
|
246
|
-
"""
|
|
247
|
-
Must be multiple of 64
|
|
248
|
-
:param v:
|
|
249
|
-
:return: fixed value
|
|
250
|
-
"""
|
|
251
|
-
if v % 64 != 0:
|
|
252
|
-
raise ValueError("Invalid height, must be multiple of 64.")
|
|
253
|
-
return v
|
|
254
|
-
|
|
255
306
|
action: Union[str, Action] = Field(Action.GENERATE, description="Mode for img generate")
|
|
256
307
|
input: str = "1girl, best quality, amazing quality, very aesthetic, absurdres"
|
|
257
308
|
model: Optional[Model] = "nai-diffusion-3"
|
|
@@ -419,6 +470,9 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
419
470
|
qualityToggle: bool = None,
|
|
420
471
|
image: Union[str, bytes] = None,
|
|
421
472
|
noise: float = 0,
|
|
473
|
+
reference_image_multiple: List[Union[str, bytes]] = None,
|
|
474
|
+
reference_strength_multiple: List[float] = None,
|
|
475
|
+
reference_information_extracted_multiple: List[float] = None,
|
|
422
476
|
reference_image: Union[str, bytes] = None,
|
|
423
477
|
reference_strength: float = None,
|
|
424
478
|
reference_information_extracted: float = None,
|
|
@@ -435,6 +489,12 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
435
489
|
"""
|
|
436
490
|
The build function, more convenient to create a GenerateImageInfer instance.
|
|
437
491
|
构建函数,更方便地创建一个GenerateImageInfer实例。
|
|
492
|
+
:param reference_information_extracted_multiple: The reference information extracted for Vibe mode.
|
|
493
|
+
Vibe模式的提取的参考信息。
|
|
494
|
+
:param reference_image_multiple: The reference image for Vibe mode.
|
|
495
|
+
Vibe模式的参考图像。
|
|
496
|
+
:param reference_strength_multiple: The strength of the reference image used in Vibe mode.
|
|
497
|
+
在Vibe模式中使用的参考图像的强度。
|
|
438
498
|
:param ucPreset: 0: Heavy, 1: Light, 2: Character
|
|
439
499
|
0: 重量级, 1: 轻量级, 2: 角色
|
|
440
500
|
:param qualityToggle: add the quality prompt or not.
|
|
@@ -465,12 +525,6 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
465
525
|
输入图像。
|
|
466
526
|
:param noise: the noise to be added in the image.
|
|
467
527
|
要添加到图像中的噪声。
|
|
468
|
-
:param reference_image: the reference image for Vibe mode.
|
|
469
|
-
Vibe模式的参考图像。
|
|
470
|
-
:param reference_strength: the strength of the reference image used in Vibe mode.
|
|
471
|
-
在Vibe模式中使用的参考图像的强度。
|
|
472
|
-
:param reference_information_extracted: whether to extract concept or features, for Vibe mode.
|
|
473
|
-
是否提取概念或特征,用于Vibe模式。
|
|
474
528
|
:param add_original_image: Overlay Original Image. Prevents the existing image from changing, only for IMG2IMG mode.
|
|
475
529
|
叠加原始图像。防止现有图像发生更改,仅适用于IMG2IMG模式。
|
|
476
530
|
:param strength: the strength of IMG2IMG mode.
|
|
@@ -514,11 +568,47 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
514
568
|
"sm": sm,
|
|
515
569
|
"uncond_scale": uncond_scale,
|
|
516
570
|
})
|
|
571
|
+
|
|
572
|
+
def _merge_param(v1, v2):
|
|
573
|
+
_list = []
|
|
574
|
+
if isinstance(v1, list):
|
|
575
|
+
_list.extend(v1)
|
|
576
|
+
else:
|
|
577
|
+
if v1 is not None:
|
|
578
|
+
_list.append(v1)
|
|
579
|
+
if isinstance(v2, list):
|
|
580
|
+
_list.extend(v2)
|
|
581
|
+
else:
|
|
582
|
+
if v2 is not None:
|
|
583
|
+
_list.append(v2)
|
|
584
|
+
return _list
|
|
585
|
+
|
|
586
|
+
if reference_image:
|
|
587
|
+
logger.warning(
|
|
588
|
+
"reference_image is deprecated, use reference_image_multiple instead."
|
|
589
|
+
)
|
|
590
|
+
reference_image_multiple = _merge_param(
|
|
591
|
+
reference_image, reference_image_multiple
|
|
592
|
+
)
|
|
593
|
+
if reference_strength:
|
|
594
|
+
logger.warning(
|
|
595
|
+
"reference_strength is deprecated, use reference_strength_multiple instead."
|
|
596
|
+
)
|
|
597
|
+
reference_strength_multiple = _merge_param(
|
|
598
|
+
reference_strength, reference_strength_multiple
|
|
599
|
+
)
|
|
600
|
+
if reference_information_extracted:
|
|
601
|
+
logger.warning(
|
|
602
|
+
"reference_information_extracted is deprecated, use reference_information_extracted_multiple instead."
|
|
603
|
+
)
|
|
604
|
+
reference_information_extracted_multiple = _merge_param(
|
|
605
|
+
reference_information_extracted, reference_information_extracted_multiple
|
|
606
|
+
)
|
|
517
607
|
kwargs.update(
|
|
518
608
|
{
|
|
519
|
-
"
|
|
520
|
-
"
|
|
521
|
-
"
|
|
609
|
+
"reference_image_multiple": reference_image_multiple,
|
|
610
|
+
"reference_strength_multiple": reference_strength_multiple,
|
|
611
|
+
"reference_information_extracted_multiple": reference_information_extracted_multiple,
|
|
522
612
|
}
|
|
523
613
|
)
|
|
524
614
|
kwargs.update(
|
|
@@ -534,7 +624,7 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
534
624
|
input=prompt,
|
|
535
625
|
model=model,
|
|
536
626
|
action=action,
|
|
537
|
-
parameters=
|
|
627
|
+
parameters=Params(**param)
|
|
538
628
|
)
|
|
539
629
|
|
|
540
630
|
async def necessary_headers(self, request_data) -> dict:
|
|
@@ -592,7 +682,8 @@ class GenerateImageInfer(ApiBaseModel):
|
|
|
592
682
|
_log_data.get("parameters", {}).update({
|
|
593
683
|
"image": "base64 data" if self.parameters.image else None,
|
|
594
684
|
"mask": "base64 data" if self.parameters.mask else None,
|
|
595
|
-
"
|
|
685
|
+
"reference_image_multiple": ["base64 data"] * len(
|
|
686
|
+
self.parameters.reference_image_multiple) if self.parameters.reference_image_multiple else None,
|
|
596
687
|
})
|
|
597
688
|
logger.debug(f"Request Data: {_log_data}")
|
|
598
689
|
del _log_data
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# @Author : sudoskys
|
|
3
|
-
import base64
|
|
4
|
-
import json
|
|
5
|
-
from io import BytesIO
|
|
6
3
|
from typing import Optional, Union, Literal
|
|
7
4
|
from urllib.parse import urlparse
|
|
8
|
-
from zipfile import ZipFile
|
|
9
5
|
|
|
10
6
|
import curl_cffi
|
|
11
7
|
import httpx
|
|
@@ -15,12 +11,11 @@ from loguru import logger
|
|
|
15
11
|
from pydantic import ConfigDict, PrivateAttr, Field, model_validator
|
|
16
12
|
from tenacity import wait_random, retry, stop_after_attempt, retry_if_exception
|
|
17
13
|
|
|
18
|
-
from ..schema import ApiBaseModel
|
|
19
|
-
from ..._exceptions import APIError, AuthError, SessionHttpError
|
|
20
|
-
from ..._response.ai.generate_voice import VoiceResponse
|
|
21
|
-
from ...credential import CredentialBase
|
|
22
|
-
from ...utils import try_jsonfy
|
|
23
14
|
from ._enum import VoiceSpeakerV1, VoiceSpeakerV2
|
|
15
|
+
from ...schema import ApiBaseModel
|
|
16
|
+
from ...._exceptions import APIError, SessionHttpError
|
|
17
|
+
from ...._response.ai.generate_voice import VoiceResponse
|
|
18
|
+
from ....credential import CredentialBase
|
|
24
19
|
|
|
25
20
|
|
|
26
21
|
class VoiceGenerate(ApiBaseModel):
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import io
|
|
7
7
|
import sys
|
|
8
8
|
import zipfile
|
|
9
|
+
from typing import Optional, Literal, Union
|
|
9
10
|
|
|
10
11
|
import uvicorn
|
|
11
12
|
from fastapi import FastAPI, Depends, Security
|
|
@@ -14,9 +15,10 @@ from loguru import logger
|
|
|
14
15
|
from starlette.responses import JSONResponse, StreamingResponse
|
|
15
16
|
|
|
16
17
|
from .credential import JwtCredential, SecretStr
|
|
17
|
-
from .sdk.ai.generate_image import GenerateImageInfer
|
|
18
|
+
from .sdk.ai.generate_image import GenerateImageInfer, Model
|
|
18
19
|
from .sdk.ai.generate_image.suggest_tags import SuggestTags
|
|
19
20
|
from .sdk.ai.upscale import Upscale
|
|
21
|
+
from .sdk.ai.generate_voice import VoiceGenerate, VoiceResponse
|
|
20
22
|
from .sdk.user.information import Information
|
|
21
23
|
from .sdk.user.login import Login
|
|
22
24
|
from .sdk.user.subscription import Subscription
|
|
@@ -121,16 +123,19 @@ async def upscale(
|
|
|
121
123
|
|
|
122
124
|
@app.get("/ai/generate_image/suggest_tags")
|
|
123
125
|
async def suggest_tags(
|
|
124
|
-
|
|
126
|
+
model: Model,
|
|
127
|
+
prompt: str,
|
|
125
128
|
current_token: str = Depends(get_current_token)
|
|
126
129
|
):
|
|
127
130
|
"""
|
|
128
131
|
生成建议
|
|
129
132
|
:param current_token: Authorization
|
|
130
|
-
:param
|
|
133
|
+
:param model: Model
|
|
134
|
+
:param prompt: str
|
|
131
135
|
:return:
|
|
132
136
|
"""
|
|
133
137
|
try:
|
|
138
|
+
req = SuggestTags(model=model, prompt=prompt)
|
|
134
139
|
_result = await req.request(session=get_session(current_token))
|
|
135
140
|
return _result.model_dump()
|
|
136
141
|
except Exception as e:
|
|
@@ -165,6 +170,32 @@ async def generate_image(
|
|
|
165
170
|
return JSONResponse(status_code=500, content=e.__dict__)
|
|
166
171
|
|
|
167
172
|
|
|
173
|
+
@app.get("/ai/generate-voice")
|
|
174
|
+
async def generate_voice(
|
|
175
|
+
text: str,
|
|
176
|
+
voice: int = -1,
|
|
177
|
+
seed: Optional[str] = None,
|
|
178
|
+
opus: bool = False,
|
|
179
|
+
version: Union[Literal["v2", "v1"], str] = "v2",
|
|
180
|
+
current_token: str = Depends(get_current_token)
|
|
181
|
+
):
|
|
182
|
+
"""
|
|
183
|
+
生成图片
|
|
184
|
+
:param current_token: Authorization
|
|
185
|
+
:param req: GenerateImageInfer
|
|
186
|
+
:return:
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
req = VoiceGenerate(text=text, voice=voice, seed=seed, opus=opus, version=version)
|
|
190
|
+
_result = await req.request(session=get_session(current_token))
|
|
191
|
+
return StreamingResponse(io.BytesIO(_result.audio), media_type='audio/mpeg', headers={
|
|
192
|
+
'Content-Disposition': 'attachment;filename=audio.mp3'
|
|
193
|
+
})
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.exception(e)
|
|
196
|
+
return JSONResponse(status_code=500, content=e.__dict__)
|
|
197
|
+
|
|
198
|
+
|
|
168
199
|
# 获取输入参数
|
|
169
200
|
def usage():
|
|
170
201
|
print("Usage: python -m novelai_python.server -h <host> -p <port>")
|
|
@@ -180,7 +211,7 @@ if __name__ == '__main__':
|
|
|
180
211
|
except getopt.GetoptError:
|
|
181
212
|
usage()
|
|
182
213
|
opts = dict(opts)
|
|
183
|
-
server_host = opts.get("-h", "
|
|
214
|
+
server_host = opts.get("-h", "127.0.0.1")
|
|
184
215
|
server_port = int(opts.get("-p", 10087))
|
|
185
216
|
print(f"Docs: http://{server_host}:{server_port}/docs")
|
|
186
217
|
uvicorn.run(app, host=server_host, port=server_port)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/generate_image.py
RENAMED
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/ai/generate_voice.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/information.py
RENAMED
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/_response/user/subscription.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/_const.py
RENAMED
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/sdk/ai/generate_image/_enum.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/image_metadata/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/image_metadata/lsb_injector.py
RENAMED
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/paint_mask/__init__.py
RENAMED
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/tag_character.py
RENAMED
|
File without changes
|
{novelai_python-0.4.5 → novelai_python-0.4.6}/src/novelai_python/tool/random_prompt/tag_nsfw.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|