q6 commited on
Commit
dbebc2c
·
1 Parent(s): c8b3c75

remove scan.py

Browse files
Files changed (1) hide show
  1. scan.py +0 -227
scan.py DELETED
@@ -1,227 +0,0 @@
1
- import io
2
- import json
3
- import zlib
4
- from pathlib import Path
5
-
6
- import numpy as np
7
- from PIL import Image
8
-
9
- ROOT = Path(".")
10
- OUTPUT = Path("novelai_prompts.json")
11
- PNG_SIG = b"\x89PNG\r\n\x1a\n"
12
- ALPHA_SIGS = (b"stealth_pnginfo", b"stealth_pngcomp")
13
- RGB_SIGS = (b"stealth_rgbinfo", b"stealth_rgbcomp")
14
- PROMPT_KEYS = ("prompt", "negative_prompt", "v4_prompt", "v4_negative_prompt")
15
- NAI_KEYS = {
16
- "cfg_rescale",
17
- "legacy_v3_extend",
18
- "noise_schedule",
19
- "request_type",
20
- "signed_hash",
21
- "uc",
22
- "v4_negative_prompt",
23
- "v4_prompt",
24
- }
25
-
26
-
27
- def decode_text(data):
28
- for enc in ("utf-8", "latin-1"):
29
- try:
30
- return data.decode(enc).strip()
31
- except UnicodeDecodeError:
32
- pass
33
- return data.decode("utf-8", errors="replace").strip()
34
-
35
-
36
- def parse_json(value):
37
- if isinstance(value, dict):
38
- return value
39
- try:
40
- return json.loads(value)
41
- except (TypeError, json.JSONDecodeError):
42
- return None
43
-
44
-
45
- def parse_png(data):
46
- if not data.startswith(PNG_SIG):
47
- return None
48
- fields = {}
49
- index = len(PNG_SIG)
50
- while index + 8 <= len(data):
51
- size = int.from_bytes(data[index : index + 4], "big")
52
- kind = data[index + 4 : index + 8]
53
- index += 8
54
- content = data[index : min(index + size, len(data))]
55
- if kind == b"tEXt" and b"\0" in content:
56
- key, text = content.split(b"\0", 1)
57
- fields[decode_text(key)] = decode_text(text)
58
- elif kind == b"iTXt" and b"\0" in content:
59
- parts = content.split(b"\0", 5)
60
- if len(parts) == 6:
61
- key, flag, method, _, _, text = parts
62
- if flag == b"\x01" and method == b"\x00":
63
- try:
64
- text = zlib.decompress(text)
65
- except zlib.error:
66
- text = b""
67
- fields[decode_text(key)] = decode_text(text)
68
- elif kind == b"zTXt" and b"\0" in content:
69
- key, rest = content.split(b"\0", 1)
70
- if rest[:1] == b"\x00":
71
- try:
72
- fields[decode_text(key)] = decode_text(zlib.decompress(rest[1:]))
73
- except zlib.error:
74
- pass
75
- index += size + 4
76
- return fields or None
77
-
78
-
79
- def byteize(bits):
80
- bits = bits[: (bits.shape[0] // 8) * 8]
81
- bits = np.bitwise_and(bits, 1).reshape((-1, 8))
82
- return np.packbits(bits, axis=1).flatten()
83
-
84
-
85
- def parse_stealth_channel(bits, sigs):
86
- raw = bytes(byteize(bits))
87
- magic = next((sig for sig in sigs if raw.startswith(sig)), None)
88
- if not magic:
89
- return None, False
90
- try:
91
- start = len(magic)
92
- bit_len = int.from_bytes(raw[start : start + 4], "big")
93
- start += 4
94
- if bit_len <= 0 or bit_len % 8 or start + bit_len // 8 > len(raw):
95
- return None, True
96
- payload = raw[start : start + bit_len // 8]
97
- if magic.endswith(b"comp"):
98
- payload = zlib.decompress(payload, wbits=31)
99
- text = decode_text(payload)
100
- fields = parse_json(text)
101
- return (fields if isinstance(fields, dict) else {"Stealth": text}), True
102
- except (UnicodeDecodeError, ValueError, zlib.error):
103
- return None, True
104
-
105
-
106
- def parse_stealth(data):
107
- try:
108
- image = Image.open(io.BytesIO(data))
109
- detected = False
110
- if "A" in image.getbands():
111
- alpha = np.array(image.getchannel("A")).T.reshape((-1,))
112
- fields, found = parse_stealth_channel(alpha, ALPHA_SIGS)
113
- if fields:
114
- return fields, True, None
115
- detected = found
116
- rgb = np.array(image.convert("RGB")).transpose((1, 0, 2)).reshape((-1,))
117
- fields, found = parse_stealth_channel(rgb, RGB_SIGS)
118
- return fields, detected or found, None
119
- except Exception as e:
120
- return None, False, str(e)
121
-
122
-
123
- def get_field(fields, name, default=None):
124
- wanted = name.casefold()
125
- return next((value for key, value in fields.items() if key.casefold() == wanted), default)
126
-
127
-
128
- def has_novelai_comment(value):
129
- data = parse_json(value)
130
- if not isinstance(data, dict):
131
- return False
132
- keys = set(data)
133
- if keys & NAI_KEYS:
134
- return True
135
- return (
136
- isinstance(data.get("prompt"), str)
137
- and isinstance(data.get("steps"), int)
138
- and ("scale" in data or "sampler" in data)
139
- )
140
-
141
-
142
- def is_novelai(fields):
143
- if not fields:
144
- return False
145
- software = str(get_field(fields, "Software", "")).casefold()
146
- source = str(get_field(fields, "Source", "")).casefold()
147
- title = str(get_field(fields, "Title", ""))
148
- comment = get_field(fields, "Comment", "")
149
- return (
150
- "novelai" in software
151
- or "novelai" in source
152
- or title == "AI generated image"
153
- or has_novelai_comment(comment)
154
- or bool({str(key).casefold() for key in fields} & NAI_KEYS)
155
- )
156
-
157
-
158
- def prompt_record(source, fields):
159
- comment = parse_json(get_field(fields, "Comment"))
160
- if not isinstance(comment, dict):
161
- comment = fields if has_novelai_comment(fields) else {}
162
- return {
163
- "source": source,
164
- "prompt": get_field(fields, "Description") or comment.get("prompt"),
165
- "negative_prompt": comment.get("uc"),
166
- "v4_prompt": comment.get("v4_prompt"),
167
- "v4_negative_prompt": comment.get("v4_negative_prompt"),
168
- }
169
-
170
-
171
- def scan(path):
172
- record = {
173
- "file": path.relative_to(ROOT).as_posix(),
174
- "novelai": False,
175
- "prompt_found": False,
176
- "stealth_detected": False,
177
- "prompts": [],
178
- "missing": None,
179
- "error": None,
180
- }
181
- try:
182
- data = path.read_bytes()
183
- normal = parse_png(data)
184
- stealth, detected, error = parse_stealth(data)
185
- record["stealth_detected"] = detected
186
- record["error"] = error
187
- if is_novelai(normal):
188
- record["prompts"].append(prompt_record("normal", normal))
189
- if is_novelai(stealth):
190
- record["prompts"].append(prompt_record("stealth", stealth))
191
- record["novelai"] = bool(record["prompts"])
192
- record["prompt_found"] = any(
193
- any(item.get(key) not in (None, "", {}, []) for key in PROMPT_KEYS)
194
- for item in record["prompts"]
195
- )
196
- if record["novelai"] and not record["prompt_found"]:
197
- record["missing"] = "NovelAI metadata found without a prompt"
198
- elif detected and not stealth:
199
- record["missing"] = "Stealth metadata could not be decoded"
200
- elif detected and not record["novelai"]:
201
- record["missing"] = "Stealth metadata is not NovelAI"
202
- elif not record["novelai"]:
203
- record["missing"] = "No NovelAI metadata"
204
- except Exception as e:
205
- record["missing"] = "Could not scan PNG"
206
- record["error"] = str(e)
207
- return record
208
-
209
-
210
- def main():
211
- paths = sorted(
212
- (path for path in ROOT.rglob("*") if path.is_file() and path.suffix.casefold() == ".png"),
213
- key=lambda path: path.as_posix().casefold(),
214
- )
215
- files = [scan(path) for path in paths]
216
- files = [item for item in files if item["prompt_found"]]
217
- result = {
218
- "root": ROOT.resolve().as_posix(),
219
- "scanned": len(paths),
220
- "found": len(files),
221
- "files": files,
222
- }
223
- OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
224
- print(f"Scanned {len(paths)} PNGs; found {len(files)}; wrote {OUTPUT}")
225
-
226
-
227
- main()