feat: build licensed desktop image processor

This commit is contained in:
2026-08-26 07:26:35 +08:00
parent 6ff3d71921
commit 1f31424015
32 changed files with 6515 additions and 2 deletions
View File
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""从“郑州情趣王一手科技.exe”提取的 JPEG 图片处理逻辑。
处理步骤与原程序一致:
1. 裁掉 JPEG 底部两行并以 quality=95 重新编码;
2. 把 SOF 段中的图片高度改回原始高度;
3. 删除最后一个 JPEG EOI(FF D9)结束标志;
4. 按指定大小追加随机字节,输出为同名 .file 文件。
注意:输出是刻意构造的非标准 JPEG,部分看图软件可能拒绝打开。
"""
from __future__ import annotations
import argparse
import random
import sys
from pathlib import Path
from typing import Iterable
try:
from PIL import Image
except ImportError as exc:
raise SystemExit("缺少 Pillow,请先执行:python3 -m pip install pillow") from exc
JPEG_EXTENSIONS = {".jpg", ".jpeg", ".jpe", ".jfif"}
SOF_MARKERS = {
0xC0,
0xC1,
0xC2,
0xC3,
0xC5,
0xC6,
0xC7,
0xC9,
0xCA,
0xCB,
0xCD,
0xCE,
0xCF,
}
def find_and_modify_height(data: bytearray, original_height: int) -> bytearray | None:
"""找到第一个 JPEG SOF 段,把高度字段恢复为裁剪前的高度。"""
index = 0
length = len(data)
while index < length - 1:
if data[index] != 0xFF:
index += 1
continue
marker = data[index + 1]
if marker in SOF_MARKERS:
height_offset = index + 5
if height_offset + 1 >= length:
return None
data[height_offset] = (original_height >> 8) & 0xFF
data[height_offset + 1] = original_height & 0xFF
return data
# SOI 和 EOI 标记没有长度字段。
if marker in (0xD8, 0xD9):
index += 2
continue
if index + 3 < length:
segment_length = (data[index + 2] << 8) | data[index + 3]
index += 2 + segment_length
else:
index += 2
return None
def remove_eoi(data: bytearray) -> tuple[bytearray, bool]:
"""从末尾向前查找并删除最后一个 FF D9 结束标志。"""
for index in range(len(data) - 1, 0, -1):
if data[index - 1] == 0xFF and data[index] == 0xD9:
return data[: index - 1] + data[index + 1 :], True
return data, False
def process_single_image(
input_file: Path | str,
output_file: Path | str,
junk_size_mb: float = 1.0,
) -> Path:
"""按原程序逻辑处理一张图片,并返回输出路径。"""
input_path = Path(input_file)
output_path = Path(output_file)
temp_path = Path(f"{output_path}.tmp")
if not 0 <= junk_size_mb <= 100:
raise ValueError("junk_size_mb 必须在 0 到 100 之间")
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
with Image.open(input_path) as image:
width, height = image.size
if height <= 2:
raise ValueError(f"图片高度不足 3 行:{input_path}")
cropped_image = image.crop((0, 0, width, height - 2))
try:
cropped_image.save(temp_path, "JPEG", quality=95)
finally:
cropped_image.close()
image_data = bytearray(temp_path.read_bytes())
modified_data = find_and_modify_height(image_data, height) or image_data
modified_data, _ = remove_eoi(modified_data)
junk_size_bytes = int(junk_size_mb * 1024 * 1024)
if junk_size_bytes > 0:
# 保留原程序的逐字节伪随机生成方式。
modified_data += bytearray(
random.getrandbits(8) for _ in range(junk_size_bytes)
)
output_path.write_bytes(modified_data)
return output_path
finally:
if temp_path.exists():
temp_path.unlink()
def collect_images(paths: Iterable[Path]) -> list[Path]:
"""收集输入文件,并按原程序支持的扩展名递归扫描目录。"""
images: list[Path] = []
for path in paths:
if path.is_file() and path.suffix.lower() in JPEG_EXTENSIONS:
images.append(path)
elif path.is_dir():
images.extend(
candidate
for candidate in path.rglob("*")
if candidate.is_file()
and candidate.suffix.lower() in JPEG_EXTENSIONS
)
else:
print(f"跳过无效路径或非 JPEG 文件:{path}", file=sys.stderr)
return images
def build_output_path(input_path: Path, output_dir: Path | None) -> Path:
"""原程序使用图片主文件名加 .file 作为输出名。"""
directory = output_dir if output_dir is not None else input_path.parent
return directory / f"{input_path.stem}.file"
def main() -> int:
parser = argparse.ArgumentParser(
description="裁掉 JPEG 底部两行、伪造原高度、删除 EOI 并追加随机数据。"
)
parser.add_argument("inputs", nargs="+", type=Path, help="JPEG 文件或目录")
parser.add_argument("-o", "--output-dir", type=Path, help="统一输出目录")
parser.add_argument(
"--junk-mb",
type=float,
default=1.0,
help="每张图片追加的随机数据大小,范围 0–100 MB,默认 1 MB",
)
args = parser.parse_args()
if not 0 <= args.junk_mb <= 100:
parser.error("--junk-mb 必须在 0 到 100 之间")
images = collect_images(args.inputs)
if not images:
print("没有找到可处理的 JPEG 图片。", file=sys.stderr)
return 1
succeeded = 0
for index, input_path in enumerate(images, start=1):
output_path = build_output_path(input_path, args.output_dir)
try:
process_single_image(input_path, output_path, args.junk_mb)
succeeded += 1
print(f"[{index}/{len(images)}] 完成:{input_path} -> {output_path}")
except Exception as exc:
print(f"[{index}/{len(images)}] 失败:{input_path} - {exc}", file=sys.stderr)
print(f"处理完成:总计 {len(images)},成功 {succeeded},失败 {len(images) - succeeded}")
return 0 if succeeded == len(images) else 2
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.