Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(optional):LSB #3

Merged
merged 3 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "novelai-python"
version = "0.1.6"
version = "0.1.7"
description = "Novelai Python Binding With Pydantic"
authors = [
{ name = "sudoskys", email = "[email protected]" },
Expand All @@ -16,6 +16,7 @@ dependencies = [
"curl-cffi>=0.5.10",
"fastapi>=0.109.0",
"uvicorn[standard]>=0.27.0.post1",
"numpy>=1.24.4",
]
requires-python = ">=3.8"
readme = "README.md"
Expand Down
18 changes: 16 additions & 2 deletions src/novelai_python/sdk/ai/generate_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import httpx
from curl_cffi.requests import AsyncSession
from loguru import logger
from novelai_python.utils import NovelAiMetadata
from pydantic import BaseModel, ConfigDict, PrivateAttr, field_validator, model_validator

from ..._exceptions import APIError, AuthError
Expand Down Expand Up @@ -229,7 +230,15 @@ def build(cls,
parameters=cls.Params(**param)
)

async def generate(self, session: Union[AsyncSession, JwtCredential]) -> ImageGenerateResp:
async def generate(self, session: Union[AsyncSession, JwtCredential],
*,
remove_sign: bool = False) -> ImageGenerateResp:
"""
生成图片
:param session: session
:param remove_sign: 移除追踪信息
:return:
"""
if isinstance(session, JwtCredential):
session = session.session
request_data = self.model_dump(exclude_none=True)
Expand Down Expand Up @@ -274,7 +283,12 @@ async def generate(self, session: Union[AsyncSession, JwtCredential]) -> ImageGe
response=try_jsonfy(response.content)
)
for filename in file_list:
unzip_content.append((filename, zip_file.read(filename)))
data = zip_file.read(filename)
if remove_sign:
data = NovelAiMetadata.rehash(BytesIO(data), remove_stealth=True)
if not isinstance(data, bytes):
data = data.getvalue()
unzip_content.append((filename, data))
return ImageGenerateResp(
meta=ImageGenerateResp.RequestParams(
endpoint=self.base_url,
Expand Down
7 changes: 5 additions & 2 deletions src/novelai_python/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
from .hash import NovelAiMetadata


def png_info_reset(img: Union[str, io.BytesIO]):
def png_info_reset(img: Union[str, io.BytesIO],
*,
remove_stealth: bool = False):
"""
reset png info hash value
:param remove_stealth: LCB
:param img: BytesIO 对象
:return:
"""
_fixed = NovelAiMetadata.rehash(img_io=img)
_fixed = NovelAiMetadata.rehash(img_io=img, remove_stealth=remove_stealth)
return _fixed


Expand Down
48 changes: 43 additions & 5 deletions src/novelai_python/utils/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from io import BytesIO
from typing import Union

import numpy as np
from PIL import Image
from PIL.PngImagePlugin import PngInfo
from loguru import logger
Expand All @@ -35,16 +36,33 @@ def metadata(self):
return {"Title": self.title, "Description": self.description, "Comment": self.comment}

@staticmethod
def rehash(img_io):
cls = NaiPic.read_from_img(img_io)
def rehash(img_io, remove_stealth: bool = False):
cls = NovelAiMetadata.build_from_img(img_io)
cls.comment["signed_hash"] = sign_message(json.dumps(cls.description), "novalai-client")
cls.write_out(img_io=img_io)
return img_io
_new_img_io = cls.write_out(img_io=img_io, remove_stealth=remove_stealth)
return _new_img_io

def write_out(self, img_io: BytesIO):
@staticmethod
def remove_stealth_info(img_io):
image = Image.open(img_io).convert('RGBA')
data = np.array(image)
data[..., 3] = 254
new_image = Image.fromarray(data, 'RGBA')
_new_img_io = BytesIO()
new_image.save(_new_img_io, format="PNG")
return _new_img_io

def write_out(self, img_io: BytesIO, *, remove_stealth: bool = False):
with Image.open(img_io) as img:
if remove_stealth:
img = img.convert('RGBA')
data = np.array(img)
data[..., 3] = 254
img = Image.fromarray(data, 'RGBA')
metadata = PngInfo()
for k, v in self.metadata.items():
if isinstance(v, dict):
v = json.dumps(v)
metadata.add_text(k, v)
_new_img = BytesIO()
img.save(_new_img, format="PNG", pnginfo=metadata, quality=95, optimize=False)
Expand Down Expand Up @@ -88,3 +106,23 @@ def decode_base64(encoded_data):
# 使用base64解码
decoded_data = base64.b64decode(byte_data)
return decoded_data.decode("UTF-8") # 再次使用UTF-8将字节对象转为字符串


def decode_image(img: Union[str, BytesIO], output_path: str):
encoded_image = Image.open(img)
red_channel = encoded_image.split()[3]
x_size = encoded_image.size[0]
y_size = encoded_image.size[1]
decoded_image = Image.new("RGB", encoded_image.size)
pixels = decoded_image.load()
for i in range(x_size):
for j in range(y_size):
if i < 7: # the left 7 columns
r = red_channel.getpixel((i, j))
if r > 254: # above the threshold
pixels[i, j] = (0, 0, 0) # black
else:
pixels[i, j] = (255, 255, 255) # white
else:
pixels[i, j] = (255, 255, 255) # default to white for right part of image
decoded_image.save(output_path)
Loading