Bump version to 0.8.2; enhance image parameter extraction
Some checks failed
Gitea/kapitanbooru-uploader/pipeline/head There was a failure building this commit
Some checks failed
Gitea/kapitanbooru-uploader/pipeline/head There was a failure building this commit
This commit is contained in:
parent
df83c9dcdc
commit
f3e1463b2b
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@ -49,6 +49,19 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/main.py",
|
"program": "${workspaceFolder}/main.py",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: Debug Unit Tests",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "unittest",
|
||||||
|
"args": [
|
||||||
|
"discover",
|
||||||
|
"-s",
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
9
Jenkinsfile
vendored
9
Jenkinsfile
vendored
@ -36,6 +36,15 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stage('Run Unit Tests') {
|
||||||
|
steps {
|
||||||
|
// For unittest:
|
||||||
|
sh '. venv/bin/activate && python -m unittest discover -s tests'
|
||||||
|
// For pytest (if preferred):
|
||||||
|
// sh '. venv/bin/activate && pytest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Publish to Local PyPI') {
|
stage('Publish to Local PyPI') {
|
||||||
steps {
|
steps {
|
||||||
sh '. venv/bin/activate && twine upload --repository-url http://localhost:8090/ -u admin -p admin dist/*'
|
sh '. venv/bin/activate && twine upload --repository-url http://localhost:8090/ -u admin -p admin dist/*'
|
||||||
|
@ -32,7 +32,7 @@ class ImageBrowser(tk.Tk):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.title("Kapitanbooru Uploader")
|
self.title("Kapitanbooru Uploader")
|
||||||
self.geometry("900x600")
|
self.geometry("900x600")
|
||||||
self.version = "0.8.1"
|
self.version = "0.8.2"
|
||||||
self.acknowledged_version = parse_version(self.version)
|
self.acknowledged_version = parse_version(self.version)
|
||||||
|
|
||||||
self.settings = Settings()
|
self.settings = Settings()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Kapitanbooru Uploader 0.8.1\n"
|
"Project-Id-Version: Kapitanbooru Uploader 0.8.2\n"
|
||||||
"Report-Msgid-Bugs-To: kapitan@mlesniak.pl\n"
|
"Report-Msgid-Bugs-To: kapitan@mlesniak.pl\n"
|
||||||
"POT-Creation-Date: 2025-03-27 20:47+0100\n"
|
"POT-Creation-Date: 2025-03-27 20:47+0100\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Kapitanbooru Uploader 0.8.1\n"
|
"Project-Id-Version: Kapitanbooru Uploader 0.8.2\n"
|
||||||
"Report-Msgid-Bugs-To: kapitan@mlesniak.pl\n"
|
"Report-Msgid-Bugs-To: kapitan@mlesniak.pl\n"
|
||||||
"POT-Creation-Date: 2025-03-27 20:47+0100\n"
|
"POT-Creation-Date: 2025-03-27 20:47+0100\n"
|
||||||
"Language: pl\n"
|
"Language: pl\n"
|
||||||
|
@ -52,7 +52,7 @@ def get_artist_tags(tags_repo: TagsRepo):
|
|||||||
|
|
||||||
# Wzorce i ustawienia związane z tagami
|
# Wzorce i ustawienia związane z tagami
|
||||||
PARAMETERS_PATTERN = re.compile(
|
PARAMETERS_PATTERN = re.compile(
|
||||||
r"^(?P<prompt>.*?)\s+(?:Negative prompt:\s+(?P<negative_prompt>.*?)\s+)?Steps:\s+(?P<steps>\d+),\s+Sampler:\s+(?P<sampler>.*?),\s+Schedule type:\s+(?P<schedule_type>.*?),\s+CFG scale:\s+(?P<cfg_scale>.*?),\s+Seed:\s+(?P<seed>.*?),\s+Size:\s+(?P<size>.*?),\s+Model hash:\s+(?P<model_hash>.*?),\s+Model:\s+(?P<model>.*?),\s+(?:Denoising strength:\s+(?P<denoising_strength>.*?),\s+)?(?:Clip skip: (?P<clip_skip>.*?),\s+)?(?:Hires CFG Scale:\s+(?P<hires_cfg_scale>.*?),\s+Hires upscale:\s+(?P<hires_upscale>.*?),\s+Hires steps:\s+(?P<hires_steps>.*?),\s+Hires upscaler:\s+(?P<hires_upscaler>.*?),\s+)?Version:\s+(?P<version>.*?)\s*$",
|
r"^(?P<prompt>.*?),?\s+(?:Negative prompt:\s+(?P<negative_prompt>.*?)\s+)?Steps:\s+(?P<steps>\d+),\s+Sampler:\s+(?P<sampler>.*?),\s+Schedule type:\s+(?P<schedule_type>.*?),\s+CFG scale:\s+(?P<cfg_scale>.*?),\s+Seed:\s+(?P<seed>.*?),\s+Size:\s+(?P<size>.*?),\s+Model hash:\s+(?P<model_hash>.*?),\s+Model:\s+(?P<model>.*?),\s+(?:Denoising strength:\s+(?P<denoising_strength>.*?),\s+)?(?:Clip skip: (?P<clip_skip>.*?),\s+)?(?:Hires CFG Scale:\s+(?P<hires_cfg_scale>.*?),\s+Hires upscale:\s+(?P<hires_upscale>.*?),\s+Hires steps:\s+(?P<hires_steps>.*?),\s+Hires upscaler:\s+(?P<hires_upscaler>.*?),\s+)?(?:Lora hashes:\s+\"(?P<lora_hashes>.*?)\",\s+)?Version:\s+(?P<version>.*?)\s*$",
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
COEFFICIENT_PATTERN = re.compile(r"^.*?(:\d+|\d+\.\d+)$")
|
COEFFICIENT_PATTERN = re.compile(r"^.*?(:\d+|\d+\.\d+)$")
|
||||||
@ -122,25 +122,53 @@ def extract_parameters(img: Image, file_path: str) -> str:
|
|||||||
parameters = ""
|
parameters = ""
|
||||||
lower_path = file_path.lower()
|
lower_path = file_path.lower()
|
||||||
|
|
||||||
# For PNG: use the custom "parameters" field.
|
# Dla PNG: korzystamy z niestandardowego pola "parameters".
|
||||||
if isinstance(img, PngImagePlugin.PngImageFile):
|
if isinstance(img, PngImagePlugin.PngImageFile):
|
||||||
parameters = img.info.get("parameters", "")
|
parameters = img.info.get("parameters", "")
|
||||||
|
|
||||||
# For JPEG, WebP, and AVIF: extract EXIF UserComment (EXIF tag 37510).
|
# Dla JPEG, WebP i AVIF: wyciągamy EXIF UserComment (tag 37510) ze zagnieżdżonego słownika (tag 34665).
|
||||||
elif lower_path.endswith((".jpg", ".jpeg", ".webp", ".avif")):
|
elif lower_path.endswith((".jpg", ".jpeg", ".webp", ".avif")):
|
||||||
exif_data = img.getexif()
|
exif_data = img.getexif()
|
||||||
user_comment = exif_data.get(37510)
|
exif_ifd = exif_data.get_ifd(34665)
|
||||||
|
user_comment = None
|
||||||
|
if exif_ifd:
|
||||||
|
user_comment = exif_ifd.get(37510)
|
||||||
if user_comment:
|
if user_comment:
|
||||||
# UserComment might be stored as bytes; decode to string.
|
if isinstance(user_comment, bytes) and len(user_comment) >= 8:
|
||||||
if isinstance(user_comment, bytes):
|
header = user_comment[:8]
|
||||||
|
comment_bytes = user_comment[8:]
|
||||||
|
if header.startswith(b'ASCII'):
|
||||||
try:
|
try:
|
||||||
parameters = user_comment.decode("utf-8", errors="replace")
|
parameters = comment_bytes.decode("ascii", errors="ignore")
|
||||||
except Exception:
|
except Exception:
|
||||||
parameters = str(user_comment)
|
parameters = str(comment_bytes)
|
||||||
|
elif header.startswith(b'UNICODE'):
|
||||||
|
# Sprawdzenie obecności BOM w danych
|
||||||
|
if comment_bytes[:2] in (b'\xff\xfe', b'\xfe\xff'):
|
||||||
|
try:
|
||||||
|
parameters = comment_bytes.decode("utf-16", errors="ignore")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
parameters = comment_bytes.decode("utf-16-be", errors="ignore")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
parameters = comment_bytes.decode("utf-16-be", errors="ignore")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
parameters = comment_bytes.decode("utf-16", errors="ignore")
|
||||||
|
elif header.startswith(b'JIS'):
|
||||||
|
try:
|
||||||
|
parameters = comment_bytes.decode("shift_jis", errors="ignore")
|
||||||
|
except Exception:
|
||||||
|
parameters = str(comment_bytes)
|
||||||
|
else:
|
||||||
|
# Fallback: próba dekodowania jako ASCII
|
||||||
|
try:
|
||||||
|
parameters = comment_bytes.decode("ascii", errors="ignore")
|
||||||
|
except Exception:
|
||||||
|
parameters = str(comment_bytes)
|
||||||
else:
|
else:
|
||||||
parameters = str(user_comment)
|
parameters = str(user_comment)
|
||||||
|
|
||||||
# For GIF: extract the GIF comment.
|
# Dla GIF: wyciągamy komentarz GIF.
|
||||||
elif lower_path.endswith(".gif"):
|
elif lower_path.endswith(".gif"):
|
||||||
comment = img.info.get("comment")
|
comment = img.info.get("comment")
|
||||||
if comment:
|
if comment:
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "kapitanbooru-uploader"
|
name = "kapitanbooru-uploader"
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
description = "A GUI application for uploading images to KapitanBooru"
|
description = "A GUI application for uploading images to KapitanBooru"
|
||||||
authors = [{ name = "Michał Leśniak", email = "kapitan@mlesniak.pl" }]
|
authors = [{ name = "Michał Leśniak", email = "kapitan@mlesniak.pl" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
96
tests/test_tags.py
Normal file
96
tests/test_tags.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import unittest
|
||||||
|
from kapitanbooru_uploader import tag_processing
|
||||||
|
|
||||||
|
|
||||||
|
class TestTagProcessing(unittest.TestCase):
|
||||||
|
def test_PARAMETERS_PATTERN(self):
|
||||||
|
# Test for a valid tag with parameters
|
||||||
|
test_data = [
|
||||||
|
r"""masterpiece,best quality,amazing quality,explicit,1girl,cat ears,animal ear fluff,very short hair,messy hair,two-tone hair,streaked hair,red streaks,black hair,orange eyes,slit pupils,tan,tomboy,blush,heart,muscular female,pussy,nipples,thick thighs,wide hips,completely nude,black choker,fingernails,bracelet,emo fashion,black nails,outdoors, depth of field,cowboy shot,<lora:HYPv1-2:1>,colossal breasts,looking at viewer,seductive smile,tall female,(from below), colossal thighs,aroused,lactation,excessive lactation, pussy juice,
|
||||||
|
Negative prompt: bad quality,worst quality,worst detail,sketch,censor
|
||||||
|
Steps: 28, Sampler: Euler a, Schedule type: Automatic, CFG scale: 5, Seed: 1508055966, Size: 768x1344, Model hash: 89cb4ec0a9, Model: waiNSFWIllustrious_v120, Denoising strength: 0.4, Clip skip: 2, Hires CFG Scale: 5, Hires upscale: 2, Hires steps: 30, Hires upscaler: R-ESRGAN 4x+ Anime6B, Lora hashes: "LowRA: 0dfc93870ba3, add-detail-xl: 9c783c8ce46c, badhands: 96b1245a1ab8", Version: f1.7.0-v1.10.1RC-latest-2164-g9535244a""",
|
||||||
|
r"""1girl, nekomata okayu, hololive, nsfw,
|
||||||
|
simple background,
|
||||||
|
solo, looking at viewer, seductive smile, huge breasts, sexually suggestive,
|
||||||
|
full body, dutch angle,
|
||||||
|
masterpiece, high score, great score, absurdres
|
||||||
|
Negative prompt: lowres, bad anatomy, bad hands, text, error, missing finger, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry
|
||||||
|
Steps: 28, Sampler: Euler a, Schedule type: Automatic, CFG scale: 5, Seed: 520762214, Size: 768x1344, Model hash: 6327eca98b, Model: animagine-xl-4.0-opt, Denoising strength: 0.4, Hires CFG Scale: 5, Hires upscale: 2, Hires steps: 30, Hires upscaler: R-ESRGAN 4x+ Anime6B, Version: f1.7.0-v1.10.1RC-latest-2164-g9535244a""",
|
||||||
|
r"""score_9, score_8_up, score_7_up, score_6_up, score_5_up, score_4_up, a girl with medium breasts and gigantic thighs standing at a gym wearing black sports bra and black yoga pants, 1girl, medium breasts, standing, gym, black sports bra, black pants, yoga pants
|
||||||
|
Steps: 28, Sampler: Euler a, Schedule type: Automatic, CFG scale: 5, Seed: 3876891898, Size: 768x1344, Model hash: 67ab2fd8ec, Model: ponyDiffusionV6XL_v6StartWithThisOne, Denoising strength: 0.4, Clip skip: 2, Hires CFG Scale: 5, Hires upscale: 2, Hires steps: 30, Hires upscaler: R-ESRGAN 4x+ Anime6B, Version: f1.7.0-v1.10.1RC-latest-2164-g9535244a""",
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"prompt": r"""masterpiece,best quality,amazing quality,explicit,1girl,cat ears,animal ear fluff,very short hair,messy hair,two-tone hair,streaked hair,red streaks,black hair,orange eyes,slit pupils,tan,tomboy,blush,heart,muscular female,pussy,nipples,thick thighs,wide hips,completely nude,black choker,fingernails,bracelet,emo fashion,black nails,outdoors, depth of field,cowboy shot,<lora:HYPv1-2:1>,colossal breasts,looking at viewer,seductive smile,tall female,(from below), colossal thighs,aroused,lactation,excessive lactation, pussy juice""",
|
||||||
|
"negative_prompt": "bad quality,worst quality,worst detail,sketch,censor",
|
||||||
|
"steps": "28",
|
||||||
|
"sampler": "Euler a",
|
||||||
|
"schedule_type": "Automatic",
|
||||||
|
"cfg_scale": "5",
|
||||||
|
"seed": "1508055966",
|
||||||
|
"size": "768x1344",
|
||||||
|
"model_hash": "89cb4ec0a9",
|
||||||
|
"model": "waiNSFWIllustrious_v120",
|
||||||
|
"denoising_strength": "0.4",
|
||||||
|
"clip_skip": "2",
|
||||||
|
"hires_cfg_scale": "5",
|
||||||
|
"hires_upscale": "2",
|
||||||
|
"hires_steps": "30",
|
||||||
|
"hires_upscaler": "R-ESRGAN 4x+ Anime6B",
|
||||||
|
"lora_hashes": "LowRA: 0dfc93870ba3, add-detail-xl: 9c783c8ce46c, badhands: 96b1245a1ab8",
|
||||||
|
"version": "f1.7.0-v1.10.1RC-latest-2164-g9535244a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt": r"""1girl, nekomata okayu, hololive, nsfw,
|
||||||
|
simple background,
|
||||||
|
solo, looking at viewer, seductive smile, huge breasts, sexually suggestive,
|
||||||
|
full body, dutch angle,
|
||||||
|
masterpiece, high score, great score, absurdres""",
|
||||||
|
"negative_prompt": "lowres, bad anatomy, bad hands, text, error, missing finger, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry",
|
||||||
|
"steps": "28",
|
||||||
|
"sampler": "Euler a",
|
||||||
|
"schedule_type": "Automatic",
|
||||||
|
"cfg_scale": "5",
|
||||||
|
"seed": "520762214",
|
||||||
|
"size": "768x1344",
|
||||||
|
"model_hash": "6327eca98b",
|
||||||
|
"model": "animagine-xl-4.0-opt",
|
||||||
|
"denoising_strength": "0.4",
|
||||||
|
"clip_skip": None,
|
||||||
|
"hires_cfg_scale": "5",
|
||||||
|
"hires_upscale": "2",
|
||||||
|
"hires_steps": "30",
|
||||||
|
"hires_upscaler": "R-ESRGAN 4x+ Anime6B",
|
||||||
|
"lora_hashes": None,
|
||||||
|
"version": "f1.7.0-v1.10.1RC-latest-2164-g9535244a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt": r"""score_9, score_8_up, score_7_up, score_6_up, score_5_up, score_4_up, a girl with medium breasts and gigantic thighs standing at a gym wearing black sports bra and black yoga pants, 1girl, medium breasts, standing, gym, black sports bra, black pants, yoga pants""",
|
||||||
|
"negative_prompt": None,
|
||||||
|
"steps": "28",
|
||||||
|
"sampler": "Euler a",
|
||||||
|
"schedule_type": "Automatic",
|
||||||
|
"cfg_scale": "5",
|
||||||
|
"seed": "3876891898",
|
||||||
|
"size": "768x1344",
|
||||||
|
"model_hash": "67ab2fd8ec",
|
||||||
|
"model": "ponyDiffusionV6XL_v6StartWithThisOne",
|
||||||
|
"denoising_strength": "0.4",
|
||||||
|
"clip_skip": "2",
|
||||||
|
"hires_cfg_scale": "5",
|
||||||
|
"hires_upscale": "2",
|
||||||
|
"hires_steps": "30",
|
||||||
|
"hires_upscaler": "R-ESRGAN 4x+ Anime6B",
|
||||||
|
"lora_hashes": None,
|
||||||
|
"version": "f1.7.0-v1.10.1RC-latest-2164-g9535244a",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
for i, tag in enumerate(test_data):
|
||||||
|
with self.subTest(i=i):
|
||||||
|
result = tag_processing.PARAMETERS_PATTERN.search(tag)
|
||||||
|
self.assertIsNotNone(result)
|
||||||
|
self.assertEqual(result.groupdict(), expected[i])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user