q6 commited on
Commit
8a05955
·
1 Parent(s): 0cde756
Files changed (1) hide show
  1. Client/Scripts/test-short.py +210 -23
Client/Scripts/test-short.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import zlib
3
 
@@ -8,12 +10,9 @@ MAGIC = b"stealth_pngcomp"
8
  PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
9
  USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"
10
  REQUEST_TIMEOUT = 45
 
11
  POST_IDS = [
12
- "116370378",
13
- "137989718",
14
- "127319128",
15
- "134714737",
16
- "121968369",
17
  ]
18
 
19
 
@@ -252,6 +251,167 @@ def stream_magic_and_length(session, url, referer):
252
  raise ValueError("Not enough PNG data to read header bits.")
253
 
254
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  def read_post_ids(path):
256
  with open(path, "r") as handle:
257
  return [line.strip() for line in handle if line.strip()]
@@ -306,32 +466,59 @@ def main() -> int:
306
  continue
307
 
308
  for page_index, url in png_pages:
309
- try:
310
- info = stream_magic_and_length(
311
- session,
312
- url,
313
- f"https://www.pixiv.net/artworks/{post_id}",
314
- )
315
- except Exception as exc:
316
- print(f" Page {page_index}: error {exc}")
317
- continue
 
 
 
 
 
 
 
318
 
319
- magic = info["magic"]
320
- if magic == MAGIC:
321
  length_bits = info["length_bits"]
322
  length_bytes = length_bits // 8 if length_bits is not None else None
323
  print(
324
  f" Page {page_index}: magic ok, "
325
  f"len_bits={length_bits}, len_bytes={length_bytes}, "
326
- f"width={info['width']}, rows={info['rows_needed']}"
327
  )
 
 
 
 
 
 
328
  else:
329
- magic_ascii = magic.decode("ascii", errors="replace")
330
- print(
331
- f" Page {page_index}: magic mismatch "
332
- f"ascii={magic_ascii!r} hex={magic.hex()}, "
333
- f"width={info['width']}, rows={info['rows_needed']}"
334
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
  return 0
337
 
 
1
+ import gzip
2
+ import json
3
  import os
4
  import zlib
5
 
 
10
  PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
11
  USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"
12
  REQUEST_TIMEOUT = 45
13
+ LONG = False
14
  POST_IDS = [
15
+ "139395291",
 
 
 
 
16
  ]
17
 
18
 
 
251
  raise ValueError("Not enough PNG data to read header bits.")
252
 
253
 
254
+ def stream_magic_length_and_payload(session, url, referer):
255
+ headers = {
256
+ "Referer": referer,
257
+ "User-Agent": USER_AGENT,
258
+ "Accept-Encoding": "identity",
259
+ }
260
+ required_bytes = len(MAGIC) + 4
261
+ required_bits = required_bytes * 8
262
+ with session.get(url, headers=headers, stream=True, timeout=REQUEST_TIMEOUT) as response:
263
+ response.raise_for_status()
264
+ response.raw.decode_content = False
265
+ stream = response.raw
266
+
267
+ signature = read_exact(stream, len(PNG_SIGNATURE))
268
+ if signature != PNG_SIGNATURE:
269
+ raise ValueError("Not a PNG file.")
270
+
271
+ width = height = None
272
+ row_bytes = None
273
+ bpp = None
274
+ rows_needed_header = None
275
+ rows_needed_total = None
276
+ rows_processed = 0
277
+ prev_row = None
278
+ scanline_buf = bytearray()
279
+ out_bytes = bytearray()
280
+ current_byte = 0
281
+ bits_in_current = 0
282
+ decompressor = zlib.decompressobj()
283
+ length_bits = None
284
+ total_bytes = None
285
+
286
+ while True:
287
+ length_bytes = read_exact(stream, 4)
288
+ chunk_length = int.from_bytes(length_bytes, "big")
289
+ chunk_type = read_exact(stream, 4)
290
+
291
+ if chunk_type == b"IHDR":
292
+ ihdr = read_exact(stream, chunk_length)
293
+ width, height, bit_depth, color_type, compression, filter_method, interlace = parse_ihdr(ihdr)
294
+ if compression != 0 or filter_method != 0 or interlace != 0:
295
+ raise ValueError("Unsupported PNG compression/filter/interlace.")
296
+ if bit_depth != 8:
297
+ raise ValueError("Unsupported PNG bit depth.")
298
+ if color_type not in (4, 6):
299
+ raise ValueError("PNG does not have an alpha channel.")
300
+ if width <= 0 or height <= 0:
301
+ raise ValueError("Invalid PNG dimensions.")
302
+ channels = 2 if color_type == 4 else 4
303
+ bpp = channels
304
+ row_bytes = width * bpp
305
+ rows_needed_header = (required_bits + width - 1) // width
306
+ if rows_needed_header > height:
307
+ raise ValueError("PNG too short to read header bits.")
308
+ prev_row = bytearray(row_bytes)
309
+ read_exact(stream, 4)
310
+ continue
311
+
312
+ if chunk_type == b"IDAT":
313
+ if row_bytes is None:
314
+ raise ValueError("IDAT before IHDR.")
315
+ remaining = chunk_length
316
+ while remaining > 0:
317
+ chunk = read_exact(stream, min(16384, remaining))
318
+ remaining -= len(chunk)
319
+ output = decompressor.decompress(chunk)
320
+ if output:
321
+ scanline_buf.extend(output)
322
+
323
+ while len(scanline_buf) >= row_bytes + 1:
324
+ filter_type = scanline_buf[0]
325
+ raw_row = scanline_buf[1:1 + row_bytes]
326
+ del scanline_buf[:1 + row_bytes]
327
+ row = unfilter_row(filter_type, raw_row, prev_row, bpp)
328
+ prev_row = row
329
+ rows_processed += 1
330
+
331
+ alpha_offset = bpp - 1
332
+ for i in range(alpha_offset, row_bytes, bpp):
333
+ bit = row[i] & 1
334
+ current_byte = (current_byte << 1) | bit
335
+ bits_in_current += 1
336
+ if bits_in_current == 8:
337
+ out_bytes.append(current_byte)
338
+ bits_in_current = 0
339
+ current_byte = 0
340
+
341
+ if total_bytes is None and len(out_bytes) >= required_bytes:
342
+ magic = bytes(out_bytes[: len(MAGIC)])
343
+ length_bits = int.from_bytes(
344
+ out_bytes[len(MAGIC):required_bytes],
345
+ "big",
346
+ )
347
+ if magic != MAGIC:
348
+ return {
349
+ "magic": magic,
350
+ "length_bits": length_bits,
351
+ "width": width,
352
+ "height": height,
353
+ "rows_needed_header": rows_needed_header,
354
+ "rows_needed_total": None,
355
+ "rows_processed": rows_processed,
356
+ "payload": None,
357
+ }
358
+ payload_len = length_bits // 8
359
+ total_bytes = required_bytes + payload_len
360
+ rows_needed_total = (total_bytes * 8 + width - 1) // width
361
+ if rows_needed_total > height:
362
+ raise ValueError("PNG too short to read payload bits.")
363
+ if len(out_bytes) >= total_bytes:
364
+ return {
365
+ "magic": magic,
366
+ "length_bits": length_bits,
367
+ "width": width,
368
+ "height": height,
369
+ "rows_needed_header": rows_needed_header,
370
+ "rows_needed_total": rows_needed_total,
371
+ "rows_processed": rows_processed,
372
+ "payload": bytes(out_bytes[required_bytes:total_bytes]),
373
+ }
374
+
375
+ if total_bytes is not None and len(out_bytes) >= total_bytes:
376
+ return {
377
+ "magic": bytes(out_bytes[: len(MAGIC)]),
378
+ "length_bits": length_bits,
379
+ "width": width,
380
+ "height": height,
381
+ "rows_needed_header": rows_needed_header,
382
+ "rows_needed_total": rows_needed_total,
383
+ "rows_processed": rows_processed,
384
+ "payload": bytes(out_bytes[required_bytes:total_bytes]),
385
+ }
386
+
387
+ read_exact(stream, 4)
388
+ continue
389
+
390
+ read_exact(stream, chunk_length)
391
+ read_exact(stream, 4)
392
+ if chunk_type == b"IEND":
393
+ break
394
+
395
+ raise ValueError("Not enough PNG data to read payload bits.")
396
+
397
+
398
+ def decode_payload(payload):
399
+ try:
400
+ json_bytes = gzip.decompress(payload)
401
+ except Exception:
402
+ return payload.decode("utf-8", errors="replace"), "raw"
403
+ try:
404
+ data = json.loads(json_bytes.decode("utf-8"))
405
+ except json.JSONDecodeError:
406
+ return json_bytes.decode("utf-8", errors="replace"), "text"
407
+ if "Comment" in data and isinstance(data["Comment"], str):
408
+ try:
409
+ data["Comment"] = json.loads(data["Comment"])
410
+ except json.JSONDecodeError:
411
+ pass
412
+ return data, "json"
413
+
414
+
415
  def read_post_ids(path):
416
  with open(path, "r") as handle:
417
  return [line.strip() for line in handle if line.strip()]
 
466
  continue
467
 
468
  for page_index, url in png_pages:
469
+ referer = f"https://www.pixiv.net/artworks/{post_id}"
470
+ if LONG:
471
+ try:
472
+ info = stream_magic_length_and_payload(session, url, referer)
473
+ except Exception as exc:
474
+ print(f" Page {page_index}: error {exc}")
475
+ continue
476
+
477
+ magic = info["magic"]
478
+ if magic != MAGIC:
479
+ magic_ascii = magic.decode("ascii", errors="replace")
480
+ print(
481
+ f" Page {page_index}: no stealth, "
482
+ f"magic={magic_ascii!r} hex={magic.hex()}"
483
+ )
484
+ continue
485
 
 
 
486
  length_bits = info["length_bits"]
487
  length_bytes = length_bits // 8 if length_bits is not None else None
488
  print(
489
  f" Page {page_index}: magic ok, "
490
  f"len_bits={length_bits}, len_bytes={length_bytes}, "
491
+ f"width={info['width']}, rows={info['rows_needed_total']}"
492
  )
493
+ payload = info["payload"] or b""
494
+ data, data_type = decode_payload(payload)
495
+ if data_type == "json":
496
+ print(json.dumps(data, ensure_ascii=True, sort_keys=True, indent=2))
497
+ else:
498
+ print(data)
499
  else:
500
+ try:
501
+ info = stream_magic_and_length(session, url, referer)
502
+ except Exception as exc:
503
+ print(f" Page {page_index}: error {exc}")
504
+ continue
505
+
506
+ magic = info["magic"]
507
+ if magic == MAGIC:
508
+ length_bits = info["length_bits"]
509
+ length_bytes = length_bits // 8 if length_bits is not None else None
510
+ print(
511
+ f" Page {page_index}: magic ok, "
512
+ f"len_bits={length_bits}, len_bytes={length_bytes}, "
513
+ f"width={info['width']}, rows={info['rows_needed']}"
514
+ )
515
+ else:
516
+ magic_ascii = magic.decode("ascii", errors="replace")
517
+ print(
518
+ f" Page {page_index}: magic mismatch "
519
+ f"ascii={magic_ascii!r} hex={magic.hex()}, "
520
+ f"width={info['width']}, rows={info['rows_needed']}"
521
+ )
522
 
523
  return 0
524