testasdasdaw / app.py
wuhp's picture
Update app.py
b9339ab verified
import os
import struct
import tempfile
from typing import BinaryIO, Iterator
import gradio as gr
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
# ────────────────────────────────────────────────────────── helpers ──
def _read_chunks(fp: BinaryIO) -> Iterator[tuple[bytes, bytes]]:
"""
Yield (raw_chunk_bytes, chunk_type_bytes) for every chunk in *fp*.
`raw_chunk_bytes` includes length, type, data and CRC exactly as in file,
so we can copy them verbatim later.
The PNG signature must already be consumed by the caller.
"""
while True:
length_bytes = fp.read(4)
if not length_bytes:
break # EOF
if len(length_bytes) != 4:
raise ValueError("Corrupted PNG: unexpected EOF while reading chunk length.")
length = struct.unpack(">I", length_bytes)[0] # ← fixed (no stray space)
chunk_type = fp.read(4)
data = fp.read(length)
crc = fp.read(4)
if len(chunk_type) != 4 or len(data) != length or len(crc) != 4:
raise ValueError("Corrupted PNG: truncated chunk.")
yield length_bytes + chunk_type + data + crc, chunk_type
def clone_metadata(src_path: str, tgt_path: str, out_path: str) -> str:
"""
Copy all ancillary chunks (anything except IHDR / IDAT / IEND) from *src_path*
into *tgt_path* immediately after its IHDR, then save to *out_path*.
Pixel data of the target stays untouched.
"""
with open(src_path, "rb") as fs, open(tgt_path, "rb") as ft:
if fs.read(8) != PNG_SIGNATURE or ft.read(8) != PNG_SIGNATURE:
raise ValueError("Both files must be valid PNG images.")
src_chunks = list(_read_chunks(fs))
tgt_chunks = list(_read_chunks(ft))
# take every non-critical chunk from source
ancillary = [
raw for raw, tp in src_chunks
if tp not in (b"IHDR", b"IDAT", b"IEND")
]
out_chunks: list[bytes] = []
for raw, tp in tgt_chunks:
out_chunks.append(raw)
if tp == b"IHDR": # inject right after IHDR
out_chunks.extend(ancillary)
with open(out_path, "wb") as fo:
fo.write(PNG_SIGNATURE)
for chunk in out_chunks:
fo.write(chunk)
return out_path
# ────────────────────────────────────────────────────────── gradio ui ──
def process(src_file, tgt_file):
"""Gradio wrapper β†’ returns path to the merged PNG."""
if not src_file or not tgt_file:
raise gr.Error("Please upload both images.")
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
tmp.close() # we only need the path
try:
clone_metadata(src_file, tgt_file, tmp.name)
except Exception as e:
os.remove(tmp.name)
raise gr.Error(str(e))
return tmp.name
with gr.Blocks(title="Kovaaks PNG Metadata Cloner") as demo:
gr.Markdown(
"""
## Kovaaks PNG Metadata Cloner 🏹
**Step 1.** Upload a *metadata source* PNG that already works in-game
**Step 2.** Upload your *target* PNG (new 50 Γ— 50 crosshair)
**Step 3.** Click **Clone metadata** and download the result.
Put the output file into
`%LOCALAPPDATA%\\FPSAimTrainer\\Saved\\MyImport\\crosshairs`
"""
)
with gr.Row():
src = gr.File(label="Image 1 – source (metadata)", type="filepath")
tgt = gr.File(label="Image 2 – target (pixels)", type="filepath")
out = gr.File(label="Output PNG", interactive=False)
btn = gr.Button("Clone metadata β†’")
btn.click(fn=process, inputs=[src, tgt], outputs=[out])
gr.Markdown(
"βœ”οΈ Ancillary chunks (`sRGB`, `gAMA`, `pHYs`, `eXIf`, `tEXt`, …) are copied verbatim. "
"Pixel data from the target image is left untouched."
)
if __name__ == "__main__":
demo.launch(show_api=False, share=False)