Files
ubuntu-based-kiosk/library/quickadd/tests/check_scan.py
Claude 5fc7450af7 library: scan with a phone camera (Quick Add)
A 'Scan with camera' button opens the phone's own camera app; the photo is shrunk
on the phone and read on the server with zbarimg, then goes through the normal add
flow (also available on the sticker step). Works over plain HTTP on iPhone and
Android - no HTTPS needed.

- /api/scan: JPEG/PNG only (checked by magic bytes before ImageMagick sees it),
  12 MB cap, login required, 20 s timeout, temp files cleaned up
- UPC-A reads (zbar reports 13 digits with a leading zero) normalised to the 12
  digits printed on the item so duplicate checks match
- tests/gen_photos.py + tests/check_scan.py: phone-like photos (tilt, blur, noise,
  low light, upside down, EXIF rotation, small, UPC, Code 39 sticker); 10/10 read
  at the size the page sends

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 13:12:58 +00:00

40 lines
1.8 KiB
Python

"""Regression check for photo scanning: generate phone-like photos of real barcodes and make
sure the running Quick Add reads them (at the size the page sends, 2400 px).
python3 -m venv /tmp/qa-venv && /tmp/qa-venv/bin/pip install zxing-cpp pillow
PHOTO_DIR=/tmp/qa-photos /tmp/qa-venv/bin/python tests/gen_photos.py
PHOTO_DIR=/tmp/qa-photos /tmp/qa-venv/bin/python tests/check_scan.py http://127.0.0.1:8082 admin PASSWORD
"""
import base64, io, json, os, sys, urllib.request
from PIL import Image, ImageOps
url, user, pw = sys.argv[1].rstrip("/"), sys.argv[2], sys.argv[3]
D = os.environ.get("PHOTO_DIR", "/tmp/quickadd-photos")
auth = "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode()
def scan(data, kind):
req = urllib.request.Request(f"{url}/api/scan?kind={kind}", data=data, method="POST",
headers={"Content-Type": "image/jpeg", "Authorization": auth})
with urllib.request.urlopen(req, timeout=60) as r:
return json.load(r)["codes"]
def shrunk(path, m=2400): # what the page does before uploading
im = ImageOps.exif_transpose(Image.open(path)).convert("RGB")
k = min(1, m / max(im.size))
im = im.resize((round(im.width * k), round(im.height * k)), Image.LANCZOS)
b = io.BytesIO(); im.save(b, "JPEG", quality=90); return b.getvalue()
bad = 0
for n in sorted(f for f in os.listdir(D) if f.endswith(".jpg")):
base = n[:-4]
exp_file = os.path.join(D, base + ".expect")
exp = open(exp_file).read() if os.path.exists(exp_file) else None
codes = scan(shrunk(os.path.join(D, n)), "sticker" if base.startswith("sticker") else "isbn")
ok = (codes[:1] == [exp]) if exp else (codes == [])
bad += not ok
print(("ok " if ok else "FAIL ") + f"{base:20s} expected {exp!s:14s} got {codes[:2]}")
sys.exit(1 if bad else 0)