| import gzip |
| import json |
| import os |
| import sys |
|
|
| import numpy as np |
| from PIL import Image, PngImagePlugin |
|
|
| def byteize(alpha): |
| alpha = alpha.T.reshape((-1,)) |
| alpha = alpha[: (alpha.shape[0] // 8) * 8] |
| alpha = np.bitwise_and(alpha, 1) |
| alpha = alpha.reshape((-1, 8)) |
| return np.packbits(alpha, axis=1) |
|
|
|
|
| class LSBExtractor: |
| def __init__(self, alpha): |
| self.data = byteize(alpha) |
| self.pos = 0 |
|
|
| def get_next_n_bytes(self, n): |
| n_bytes = self.data[self.pos : self.pos + n] |
| self.pos += n |
| return bytearray(n_bytes) |
|
|
| def read_32bit_integer(self): |
| bytes_list = self.get_next_n_bytes(4) |
| if len(bytes_list) != 4: |
| return None |
| return int.from_bytes(bytes_list, byteorder="big") |
|
|
|
|
| def extract_stealth_png(path): |
| with Image.open(path) as image: |
| if "A" not in image.getbands(): |
| raise ValueError("image has no alpha channel") |
| alpha = np.array(image.getchannel("A")) |
|
|
| reader = LSBExtractor(alpha) |
| magic = "stealth_pngcomp" |
| read_magic = reader.get_next_n_bytes(len(magic)).decode("utf-8") |
| if read_magic != magic: |
| raise ValueError("magic number mismatch") |
|
|
| read_len_bits = reader.read_32bit_integer() |
| if read_len_bits is None: |
| raise ValueError("missing payload length") |
| read_len = read_len_bits // 8 |
|
|
| payload = reader.get_next_n_bytes(read_len) |
| json_bytes = gzip.decompress(payload) |
| try: |
| json_data = json.loads(json_bytes.decode("utf-8")) |
| if "Comment" in json_data and isinstance(json_data["Comment"], str): |
| json_data["_CommentRaw"] = json_data["Comment"] |
| json_data["Comment"] = json.loads(json_data["Comment"]) |
| except json.JSONDecodeError: |
| return {"WebUI": json_bytes.decode("utf-8", errors="replace")} |
| return json_data |
|
|
|
|
| def main() -> int: |
| path = sys.argv[1] if len(sys.argv) > 1 else "21_138614363.png" |
| if not os.path.exists(path): |
| print(f"File not found: {path}") |
| return 1 |
|
|
| data = extract_stealth_png(path) |
| print(json.dumps(data, indent=2, ensure_ascii=True, sort_keys=True)) |
| if isinstance(data, dict) and "Comment" in data: |
| comment_json = data.get("_CommentRaw") |
| if not isinstance(comment_json, str): |
| if isinstance(data["Comment"], str): |
| comment_json = data["Comment"] |
| else: |
| comment_json = json.dumps( |
| data["Comment"], |
| ensure_ascii=True, |
| separators=(",", ":"), |
| ) |
| reply = input("Create 1x1 metadata PNG? [y/N] ").strip().lower() |
| if reply in ("y", "yes"): |
| base, _ = os.path.splitext(path) |
| out_path = f"{base}_meta.png" |
| img = Image.new("RGBA", (1, 1), (0, 0, 0, 0)) |
| meta = PngImagePlugin.PngInfo() |
| meta.add_text("Comment", comment_json) |
| img.save(out_path, pnginfo=meta) |
| print(f"Wrote {out_path}") |
| input() |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|