Optimize analyzer resize, Bump python version, Change compose name
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim AS build
|
FROM python:3.14-slim AS build
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ Here is a batch of usefull commands.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose down -v # Remove volume containings results
|
docker compose down -v # Remove volume containings results
|
||||||
docker compose -f docker-compose-dev.yml up # Start application in developpement mode
|
docker compose -f compose.dev.yml up # Start application in developpement mode
|
||||||
rm -rf aperisolve/results # Remove results from developpement mode
|
rm -rf aperisolve/results # Remove results from developpement mode
|
||||||
docker exec -it postgres psql -U aperiuser -d aperisolve # Get postgresql shell
|
docker exec -it postgres psql -U aperiuser -d aperisolve # Get postgresql shell
|
||||||
docker exec -it aperisolve-web bash # Get shell in running web app
|
docker exec -it aperisolve-web bash # Get shell in running web app
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note, if you used [docker-compose-dev.yml](docker-compose-dev.yml), you must remove "results" when switching to [production docker](docker-compose.yml).
|
> Note, if you used [compose.dev.yml](compose.dev.yml), you must remove "results" when switching to [production docker](compose.yml).
|
||||||
|
|
||||||
### Code quality
|
### Code quality
|
||||||
|
|
||||||
@@ -94,7 +94,6 @@ GitHub CI will alert in case of failure.
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [x] PngCheck
|
|
||||||
- [ ] Zsteg extract / --all --> **Download `zsteg` files** such as mp3 encoded on LSB
|
- [ ] Zsteg extract / --all --> **Download `zsteg` files** such as mp3 encoded on LSB
|
||||||
- [ ] Test on mobile
|
- [ ] Test on mobile
|
||||||
- [ ] i18n
|
- [ ] i18n
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Image size bruteforcer for common PNG sizes."""
|
"""Image size bruteforcer for common PNG sizes."""
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .utils import update_data
|
from .utils import update_data
|
||||||
|
|
||||||
@@ -116,74 +117,79 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
|
|||||||
|
|
||||||
# 3. Initialize Logs
|
# 3. Initialize Logs
|
||||||
logs = []
|
logs = []
|
||||||
recovered_images = []
|
recovered_image = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(input_img, "rb") as image:
|
with open(input_img, "rb") as image:
|
||||||
f = image.read()
|
f = image.read()
|
||||||
b = bytearray(f)
|
|
||||||
|
|
||||||
# Find IHDR start
|
b = bytearray(f)
|
||||||
IHDR = b.find(b'\x49\x48\x44\x52')
|
|
||||||
if IHDR == -1:
|
|
||||||
logs.append("Failure: PNG header (IHDR) not found.")
|
|
||||||
update_data(output_dir, {"image_resize": {"status": "error", "error": "PNG header not found."}})
|
|
||||||
return
|
|
||||||
|
|
||||||
# Calculate Chunk Length (The 4 bytes BEFORE IHDR tag)
|
# Find IHDR start
|
||||||
chunk_length = int.from_bytes((b[IHDR- 4: IHDR]), byteorder="big") + 4
|
IHDR = b.find(b'\x49\x48\x44\x52')
|
||||||
|
if IHDR == -1:
|
||||||
|
logs.append("Failure: PNG header (IHDR) not found.")
|
||||||
|
update_data(output_dir, {"image_resize": {"status": "error", "error": "PNG header not found."}})
|
||||||
|
if extracted_dir.exists():
|
||||||
|
shutil.rmtree(extracted_dir, ignore_errors=True)
|
||||||
|
return
|
||||||
|
|
||||||
# Extract Target CRC (The 4 bytes AFTER the chunk data)
|
# Calculate Chunk Length (The 4 bytes BEFORE IHDR tag)
|
||||||
target_crc = b[IHDR + chunk_length:IHDR + chunk_length + 4]
|
chunk_length = int.from_bytes((b[IHDR- 4: IHDR]), byteorder="big") + 4
|
||||||
|
|
||||||
# Isolate the header chunk (Type + Data)
|
# Extract Target CRC (The 4 bytes AFTER the chunk data)
|
||||||
header_chunk = b[IHDR:IHDR + chunk_length]
|
target_crc = b[IHDR + chunk_length:IHDR + chunk_length + 4]
|
||||||
|
|
||||||
logs.append(f"Target CRC found: 0x{target_crc.hex()}")
|
# Isolate the header chunk (Type + Data)
|
||||||
|
header_chunk = b[IHDR:IHDR + chunk_length]
|
||||||
|
|
||||||
match_found = False
|
logs.append(f"Target CRC found: 0x{target_crc.hex()}")
|
||||||
|
|
||||||
# Brute-force Loop
|
match_found = False
|
||||||
for size in EXPECTED_SIZES:
|
|
||||||
width, height = size
|
|
||||||
|
|
||||||
# Convert dimensions to 4-byte Big Endian arrays
|
# Brute-force Loop
|
||||||
width_bytes = bytearray(width.to_bytes(4, byteorder='big'))
|
for size in EXPECTED_SIZES:
|
||||||
height_bytes = bytearray(height.to_bytes(4, byteorder='big'))
|
width, height = size
|
||||||
|
|
||||||
# Check if this size matches the file's CRC
|
# Convert dimensions to 4-byte Big Endian arrays
|
||||||
if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes):
|
width_bytes = bytearray(width.to_bytes(4, byteorder='big'))
|
||||||
match_found = True
|
height_bytes = bytearray(height.to_bytes(4, byteorder='big'))
|
||||||
logs.append(f"Success: Match found! Dimensions: {width}x{height}")
|
|
||||||
|
|
||||||
# Create Output Filename
|
# Check if this size matches the file's CRC
|
||||||
output_filename = f"recovered_{width}x{height}.png"
|
if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes):
|
||||||
output_path = extracted_dir / output_filename
|
match_found = True
|
||||||
|
logs.append(f"Success: Match found! Dimensions: {width}x{height}")
|
||||||
|
|
||||||
# Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End]
|
# Create Output Filename
|
||||||
full_png_data = b[:IHDR+4] + width_bytes + height_bytes + b[IHDR+12:]
|
output_filename = f"recovered_{width}x{height}.png"
|
||||||
|
output_path = extracted_dir / output_filename
|
||||||
|
|
||||||
# Write the recovered file to disk
|
# Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End]
|
||||||
with open(output_path, "wb") as out_f:
|
full_png_data = b[:IHDR+4] + width_bytes + height_bytes + b[IHDR+12:]
|
||||||
out_f.write(full_png_data)
|
|
||||||
|
|
||||||
recovered_images.append(output_filename)
|
# Write the recovered file to disk
|
||||||
break # Stop looking after finding the match
|
with output_path.open("wb") as out_f:
|
||||||
|
out_f.write(full_png_data)
|
||||||
|
|
||||||
if not match_found:
|
recovered_image = output_filename
|
||||||
logs.append("Failure: No matching dimensions found.")
|
break # Stop looking after finding the match
|
||||||
|
|
||||||
|
if not match_found:
|
||||||
|
logs.append("Failure: No matching dimensions found.")
|
||||||
|
|
||||||
# 4. Final Data Update
|
# 4. Final Data Update
|
||||||
output_data = {
|
output_data = {
|
||||||
"image_resize": { # Key name must match your module name in AperiSolve
|
"image_resize": { # Key name must match your module name in AperiSolve
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"output": logs,
|
"output": logs,
|
||||||
"images": recovered_images, # Tells frontend to display the image
|
"images": recovered_image, # Tells frontend to display the image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_data(output_dir, output_data)
|
update_data(output_dir, output_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
update_data(output_dir, {"image_resize": {"status": "error", "error": str(e)}})
|
update_data(output_dir, {"image_resize": {"status": "error", "error": str(e)}})
|
||||||
|
if extracted_dir.exists():
|
||||||
|
shutil.rmtree(extracted_dir, ignore_errors=True)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
Reference in New Issue
Block a user