update pyzbar shim
This commit is contained in:
20
pyzbar/pyproject.toml
Normal file
20
pyzbar/pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64", "wheel", 'tomli; python_version < "3.11"']
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pyzbar"
|
||||||
|
version = "0.1.9"
|
||||||
|
description = "pyzbar barcode reader with bundled zbar library (pre-built)"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["pyzbar*"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
pyzbar = ["install/**/*", "*.so"]
|
||||||
|
|
||||||
|
[tool.shim]
|
||||||
|
repo_url = "https://github.com/greatgitsby/op-dependencies"
|
||||||
|
tag = "pyzbar/v0.1.9"
|
||||||
|
datadir = "install"
|
||||||
22
pyzbar/pyzbar/__init__.py
Normal file
22
pyzbar/pyzbar/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""Read one-dimensional barcodes and QR codes from Python 2 and 3."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
__version__ = '0.1.9'
|
||||||
|
|
||||||
|
DIR = os.path.join(os.path.dirname(__file__), "install")
|
||||||
|
LIB_DIR = os.path.join(DIR, "lib")
|
||||||
|
|
||||||
|
|
||||||
|
def smoketest():
|
||||||
|
import platform
|
||||||
|
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
lib_name = "libzbar.dylib"
|
||||||
|
else:
|
||||||
|
lib_name = "libzbar.so"
|
||||||
|
lib_path = os.path.join(LIB_DIR, lib_name)
|
||||||
|
assert os.path.isfile(lib_path), f"{lib_name} not found at {lib_path}"
|
||||||
|
|
||||||
|
from .pyzbar import decode
|
||||||
|
result = decode((b'\x00', 1, 1))
|
||||||
|
assert isinstance(result, list), "decode() did not return a list"
|
||||||
70
pyzbar/pyzbar/locations.py
Normal file
70
pyzbar/pyzbar/locations.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
from itertools import chain
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['bounding_box', 'convex_hull', 'Point', 'Rect']
|
||||||
|
|
||||||
|
|
||||||
|
Point = namedtuple('Point', ['x', 'y'])
|
||||||
|
Rect = namedtuple('Rect', ['left', 'top', 'width', 'height'])
|
||||||
|
|
||||||
|
|
||||||
|
def bounding_box(locations):
|
||||||
|
"""Computes the bounding box of an iterable of (x, y) coordinates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locations: iterable of (x, y) tuples.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`Rect`: Coordinates of the bounding box.
|
||||||
|
"""
|
||||||
|
x_values = list(map(itemgetter(0), locations))
|
||||||
|
x_min, x_max = min(x_values), max(x_values)
|
||||||
|
y_values = list(map(itemgetter(1), locations))
|
||||||
|
y_min, y_max = min(y_values), max(y_values)
|
||||||
|
return Rect(x_min, y_min, x_max - x_min, y_max - y_min)
|
||||||
|
|
||||||
|
|
||||||
|
def convex_hull(points):
|
||||||
|
"""Computes the convex hull of an iterable of (x, y) coordinates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
points: iterable of (x, y) tuples.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`list`: instances of `Point` - vertices of the convex hull in
|
||||||
|
counter-clockwise order, starting from the vertex with the
|
||||||
|
lexicographically smallest coordinates.
|
||||||
|
|
||||||
|
Andrew's monotone chain algorithm. O(n log n) complexity.
|
||||||
|
https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_not_clockwise(p0, p1, p2):
|
||||||
|
return 0 <= (
|
||||||
|
(p1[0] - p0[0]) * (p2[1] - p0[1]) -
|
||||||
|
(p1[1] - p0[1]) * (p2[0] - p0[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
def go(points_):
|
||||||
|
res = []
|
||||||
|
for p in points_:
|
||||||
|
while 1 < len(res) and is_not_clockwise(res[-2], res[-1], p):
|
||||||
|
res.pop()
|
||||||
|
res.append(p)
|
||||||
|
|
||||||
|
# The last point in each list is the first point in the other list
|
||||||
|
res.pop()
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
# Discard duplicates and sort by x then y
|
||||||
|
points = sorted(set(points))
|
||||||
|
|
||||||
|
# Algorithm needs at least two points
|
||||||
|
hull = (
|
||||||
|
points if len(points) < 2 else chain(go(points), go(reversed(points)))
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(map(Point._make, hull))
|
||||||
236
pyzbar/pyzbar/pyzbar.py
Normal file
236
pyzbar/pyzbar/pyzbar.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from ctypes import cast, c_void_p, string_at
|
||||||
|
|
||||||
|
from .locations import bounding_box, convex_hull, Point, Rect
|
||||||
|
from .pyzbar_error import PyZbarError
|
||||||
|
from .wrapper import (
|
||||||
|
zbar_image_scanner_set_config,
|
||||||
|
zbar_image_scanner_create, zbar_image_scanner_destroy,
|
||||||
|
zbar_image_create, zbar_image_destroy, zbar_image_set_format,
|
||||||
|
zbar_image_set_size, zbar_image_set_data, zbar_scan_image,
|
||||||
|
zbar_image_first_symbol, zbar_symbol_get_data_length,
|
||||||
|
zbar_symbol_get_data, zbar_symbol_get_orientation,
|
||||||
|
zbar_symbol_get_loc_size, zbar_symbol_get_loc_x, zbar_symbol_get_loc_y,
|
||||||
|
zbar_symbol_get_quality, zbar_symbol_next, ZBarConfig, ZBarOrientation,
|
||||||
|
ZBarSymbol, EXTERNAL_DEPENDENCIES,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'decode', 'Point', 'Rect', 'Decoded', 'ZBarSymbol', 'EXTERNAL_DEPENDENCIES', 'ORIENTATION_AVAILABLE'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ORIENTATION_AVAILABLE = zbar_symbol_get_orientation is not None
|
||||||
|
|
||||||
|
Decoded = namedtuple('Decoded', 'data type rect polygon quality orientation')
|
||||||
|
|
||||||
|
# ZBar's magic 'fourcc' numbers that represent image formats
|
||||||
|
_FOURCC = {
|
||||||
|
'L800': 808466521,
|
||||||
|
'GRAY': 1497715271
|
||||||
|
}
|
||||||
|
|
||||||
|
_RANGEFN = getattr(globals(), 'xrange', range)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _image():
|
||||||
|
"""A context manager for `zbar_image`, created and destoyed by
|
||||||
|
`zbar_image_create` and `zbar_image_destroy`.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
POINTER(zbar_image): The created image
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PyZbarError: If the image could not be created.
|
||||||
|
"""
|
||||||
|
image = zbar_image_create()
|
||||||
|
if not image:
|
||||||
|
raise PyZbarError('Could not create zbar image')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
yield image
|
||||||
|
finally:
|
||||||
|
zbar_image_destroy(image)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _image_scanner():
|
||||||
|
"""A context manager for `zbar_image_scanner`, created and destroyed by
|
||||||
|
`zbar_image_scanner_create` and `zbar_image_scanner_destroy`.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
POINTER(zbar_image_scanner): The created scanner
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PyZbarError: If the decoder could not be created.
|
||||||
|
"""
|
||||||
|
scanner = zbar_image_scanner_create()
|
||||||
|
if not scanner:
|
||||||
|
raise PyZbarError('Could not create image scanner')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
yield scanner
|
||||||
|
finally:
|
||||||
|
zbar_image_scanner_destroy(scanner)
|
||||||
|
|
||||||
|
|
||||||
|
def _symbols_for_image(image):
|
||||||
|
"""Generator of symbols.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: `zbar_image`
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
POINTER(zbar_symbol): Symbol
|
||||||
|
"""
|
||||||
|
symbol = zbar_image_first_symbol(image)
|
||||||
|
while symbol:
|
||||||
|
yield symbol
|
||||||
|
symbol = zbar_symbol_next(symbol)
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_symbols(symbols):
|
||||||
|
"""Generator of decoded symbol information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbols: iterable of instances of `POINTER(zbar_symbol)`
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Decoded: decoded symbol
|
||||||
|
"""
|
||||||
|
for symbol in symbols:
|
||||||
|
data = string_at(
|
||||||
|
zbar_symbol_get_data(symbol),
|
||||||
|
zbar_symbol_get_data_length(symbol)
|
||||||
|
)
|
||||||
|
# The 'type' int should be a value in the ZBarSymbol enumeration
|
||||||
|
try:
|
||||||
|
symbol_type = ZBarSymbol(symbol.contents.type)
|
||||||
|
except ValueError:
|
||||||
|
# This release of zbar supports a type that pyzbar does not know about
|
||||||
|
symbol_type = "Unrecognised type [{0}]".format(symbol.contents.type)
|
||||||
|
else:
|
||||||
|
symbol_type = symbol_type.name
|
||||||
|
|
||||||
|
quality = zbar_symbol_get_quality(symbol)
|
||||||
|
polygon = convex_hull(
|
||||||
|
(
|
||||||
|
zbar_symbol_get_loc_x(symbol, index),
|
||||||
|
zbar_symbol_get_loc_y(symbol, index)
|
||||||
|
)
|
||||||
|
for index in _RANGEFN(zbar_symbol_get_loc_size(symbol))
|
||||||
|
)
|
||||||
|
|
||||||
|
if zbar_symbol_get_orientation:
|
||||||
|
orientation = ZBarOrientation(zbar_symbol_get_orientation(symbol)).name
|
||||||
|
else:
|
||||||
|
orientation = None
|
||||||
|
|
||||||
|
yield Decoded(
|
||||||
|
data=data,
|
||||||
|
type=symbol_type,
|
||||||
|
rect=bounding_box(polygon),
|
||||||
|
polygon=polygon,
|
||||||
|
orientation=orientation,
|
||||||
|
quality=quality,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pixel_data(image):
|
||||||
|
"""Returns (pixels, width, height)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
:obj: `tuple` (pixels, width, height)
|
||||||
|
"""
|
||||||
|
# Test for PIL.Image, numpy.ndarray, and imageio.core.util without
|
||||||
|
# requiring that cv2, PIL, or imageio are installed.
|
||||||
|
|
||||||
|
image_type = str(type(image))
|
||||||
|
if 'PIL.' in image_type:
|
||||||
|
if 'L' != image.mode:
|
||||||
|
image = image.convert('L')
|
||||||
|
pixels = image.tobytes()
|
||||||
|
width, height = image.size
|
||||||
|
elif 'numpy.ndarray' in image_type or 'imageio.core.util' in image_type:
|
||||||
|
# Different versions of imageio use a subclass of numpy.ndarray
|
||||||
|
# called either imageio.core.util.Image or imageio.core.util.Array.
|
||||||
|
if 3 == len(image.shape):
|
||||||
|
# Take just the first channel
|
||||||
|
image = image[:, :, 0]
|
||||||
|
if 'uint8' != str(image.dtype):
|
||||||
|
image = image.astype('uint8')
|
||||||
|
try:
|
||||||
|
pixels = image.tobytes()
|
||||||
|
except AttributeError:
|
||||||
|
# `numpy.ndarray.tobytes()` introduced in `numpy` 1.9.0 - use the
|
||||||
|
# older `tostring` method.
|
||||||
|
pixels = image.tostring()
|
||||||
|
height, width = image.shape[:2]
|
||||||
|
else:
|
||||||
|
# image should be a tuple (pixels, width, height)
|
||||||
|
pixels, width, height = image
|
||||||
|
|
||||||
|
# Check dimensions
|
||||||
|
if 0 != len(pixels) % (width * height):
|
||||||
|
raise PyZbarError(
|
||||||
|
(
|
||||||
|
'Inconsistent dimensions: image data of {0} bytes is not '
|
||||||
|
'divisible by (width x height = {1})'
|
||||||
|
).format(len(pixels), (width * height))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute bits-per-pixel
|
||||||
|
bpp = 8 * len(pixels) // (width * height)
|
||||||
|
if 8 != bpp:
|
||||||
|
raise PyZbarError(
|
||||||
|
'Unsupported bits-per-pixel [{0}]. Only [8] is supported.'.format(
|
||||||
|
bpp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return pixels, width, height
|
||||||
|
|
||||||
|
|
||||||
|
def decode(image, symbols=None):
|
||||||
|
"""Decodes datamatrix barcodes in `image`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: `numpy.ndarray`, `PIL.Image` or tuple (pixels, width, height)
|
||||||
|
symbols: iter(ZBarSymbol) the symbol types to decode; if `None`, uses
|
||||||
|
`zbar`'s default behaviour, which is to decode all symbol types.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
:obj:`list` of :obj:`Decoded`: The values decoded from barcodes.
|
||||||
|
"""
|
||||||
|
pixels, width, height = _pixel_data(image)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
with _image_scanner() as scanner:
|
||||||
|
if symbols:
|
||||||
|
# Disable all but the symbols of interest
|
||||||
|
disable = set(ZBarSymbol).difference(symbols)
|
||||||
|
for symbol in disable:
|
||||||
|
zbar_image_scanner_set_config(
|
||||||
|
scanner, symbol, ZBarConfig.CFG_ENABLE, 0
|
||||||
|
)
|
||||||
|
# I think it likely that zbar will detect all symbol types by
|
||||||
|
# default, in which case enabling the types of interest is
|
||||||
|
# redundant but it seems sensible to be over-cautious and enable
|
||||||
|
# them.
|
||||||
|
for symbol in symbols:
|
||||||
|
zbar_image_scanner_set_config(
|
||||||
|
scanner, symbol, ZBarConfig.CFG_ENABLE, 1
|
||||||
|
)
|
||||||
|
with _image() as img:
|
||||||
|
zbar_image_set_format(img, _FOURCC['L800'])
|
||||||
|
zbar_image_set_size(img, width, height)
|
||||||
|
zbar_image_set_data(img, cast(pixels, c_void_p), len(pixels), None)
|
||||||
|
decoded = zbar_scan_image(scanner, img)
|
||||||
|
if decoded < 0:
|
||||||
|
raise PyZbarError('Unsupported image format')
|
||||||
|
else:
|
||||||
|
results.extend(_decode_symbols(_symbols_for_image(img)))
|
||||||
|
|
||||||
|
return results
|
||||||
5
pyzbar/pyzbar/pyzbar_error.py
Normal file
5
pyzbar/pyzbar/pyzbar_error.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
__all__ = ["PyZbarError"]
|
||||||
|
|
||||||
|
|
||||||
|
class PyZbarError(Exception):
|
||||||
|
pass
|
||||||
295
pyzbar/pyzbar/wrapper.py
Normal file
295
pyzbar/pyzbar/wrapper.py
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
"""Low-level wrapper around zbar's interface
|
||||||
|
"""
|
||||||
|
from ctypes import (
|
||||||
|
c_ubyte, c_char_p, c_int, c_uint, c_ulong, c_void_p, Structure,
|
||||||
|
CFUNCTYPE, POINTER
|
||||||
|
)
|
||||||
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
|
from . import zbar_library
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'EXTERNAL_DEPENDENCIES', 'LIBZBAR', 'ZBarConfig', 'ZBarSymbol', 'ZBarOrientation',
|
||||||
|
'zbar_image_create', 'zbar_image_destroy', 'zbar_image_first_symbol',
|
||||||
|
'zbar_image_scanner_create', 'zbar_image_scanner_destroy',
|
||||||
|
'zbar_image_scanner_set_config', 'zbar_image_set_data',
|
||||||
|
'zbar_image_set_format', 'zbar_image_set_size', 'zbar_scan_image',
|
||||||
|
'zbar_symbol_get_data_length', 'zbar_symbol_get_data',
|
||||||
|
'zbar_symbol_get_loc_size', 'zbar_symbol_get_loc_x',
|
||||||
|
'zbar_symbol_get_loc_y', 'zbar_symbol_next',
|
||||||
|
'zbar_symbol_get_orientation', 'zbar_symbol_get_quality',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Globals populated in load_libzbar
|
||||||
|
LIBZBAR = None
|
||||||
|
"""ctypes.CDLL
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXTERNAL_DEPENDENCIES = []
|
||||||
|
"""List of instances of ctypes.CDLL. Helpful when freezing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Types
|
||||||
|
c_ubyte_p = POINTER(c_ubyte)
|
||||||
|
c_uint_p = POINTER(c_uint)
|
||||||
|
c_ulong_p = POINTER(c_ulong)
|
||||||
|
"""unsigned char* type
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Defines and enums
|
||||||
|
@unique
|
||||||
|
class ZBarSymbol(IntEnum):
|
||||||
|
NONE = 0 # /**< no symbol decoded */
|
||||||
|
PARTIAL = 1 # /**< intermediate status */
|
||||||
|
EAN2 = 2 # /**< GS1 2-digit add-on */
|
||||||
|
EAN5 = 5 # /**< GS1 5-digit add-on */
|
||||||
|
EAN8 = 8 # /**< EAN-8 */
|
||||||
|
UPCE = 9 # /**< UPC-E */
|
||||||
|
ISBN10 = 10 # /**< ISBN-10 (from EAN-13). @since 0.4 */
|
||||||
|
UPCA = 12 # /**< UPC-A */
|
||||||
|
EAN13 = 13 # /**< EAN-13 */
|
||||||
|
ISBN13 = 14 # /**< ISBN-13 (from EAN-13). @since 0.4 */
|
||||||
|
COMPOSITE = 15 # /**< EAN/UPC composite */
|
||||||
|
I25 = 25 # /**< Interleaved 2 of 5. @since 0.4 */
|
||||||
|
DATABAR = 34 # /**< GS1 DataBar (RSS). @since 0.11 */
|
||||||
|
DATABAR_EXP = 35 # /**< GS1 DataBar Expanded. @since 0.11 */
|
||||||
|
CODABAR = 38 # /**< Codabar. @since 0.11 */
|
||||||
|
CODE39 = 39 # /**< Code 39. @since 0.4 */
|
||||||
|
PDF417 = 57 # /**< PDF417. @since 0.6 */
|
||||||
|
QRCODE = 64 # /**< QR Code. @since 0.10 */
|
||||||
|
SQCODE = 80 # /**< SQ Code. @since 0.20.1 */
|
||||||
|
CODE93 = 93 # /**< Code 93. @since 0.11 */
|
||||||
|
CODE128 = 128 # /**< Code 128 */
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ZBarConfig(IntEnum):
|
||||||
|
CFG_ENABLE = 0 # /**< enable symbology/feature */
|
||||||
|
CFG_ADD_CHECK = 1 # /**< enable check digit when optional */
|
||||||
|
CFG_EMIT_CHECK = 2 # /**< return check digit when present */
|
||||||
|
CFG_ASCII = 3 # /**< enable full ASCII character set */
|
||||||
|
CFG_NUM = 4 # /**< number of boolean decoder configs */
|
||||||
|
|
||||||
|
CFG_MIN_LEN = 0x20 # /**< minimum data length for valid decode */
|
||||||
|
CFG_MAX_LEN = 0x21 # /**< maximum data length for valid decode */
|
||||||
|
|
||||||
|
CFG_UNCERTAINTY = 0x40 # /**< required video consistency frames */
|
||||||
|
|
||||||
|
CFG_POSITION = 0x80 # /**< enable scanner to collect position data */
|
||||||
|
|
||||||
|
CFG_X_DENSITY = 0x100 # /**< image scanner vertical scan density */
|
||||||
|
CFG_Y_DENSITY = 0x101 # /**< image scanner horizontal scan density */
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ZBarOrientation(IntEnum):
|
||||||
|
UNKNOWN = -1 # /**< unable to determine orientation */
|
||||||
|
UP = 0 # /**< upright, read left to right */
|
||||||
|
RIGHT = 1 # /**< sideways, read top to bottom */
|
||||||
|
DOWN = 2 # /**< upside-down, read right to left */
|
||||||
|
LEFT = 3 # /**< sideways, read bottom to top */
|
||||||
|
|
||||||
|
|
||||||
|
# Structs
|
||||||
|
class zbar_image_scanner(Structure):
|
||||||
|
"""Opaque C++ class with private implementation
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class zbar_image(Structure):
|
||||||
|
"""Opaque C++ class with private implementation
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class zbar_symbol(Structure):
|
||||||
|
"""Opaque C++ class with private implementation
|
||||||
|
|
||||||
|
The first item in the structure is an integeger value in the ZBarSymbol
|
||||||
|
enumeration.
|
||||||
|
"""
|
||||||
|
_fields_ = [
|
||||||
|
('type', c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_libzbar():
|
||||||
|
"""Loads the zbar shared library and its dependencies.
|
||||||
|
|
||||||
|
Populates the globals LIBZBAR and EXTERNAL_DEPENDENCIES.
|
||||||
|
"""
|
||||||
|
global LIBZBAR
|
||||||
|
global EXTERNAL_DEPENDENCIES
|
||||||
|
if not LIBZBAR:
|
||||||
|
libzbar, dependencies = zbar_library.load()
|
||||||
|
LIBZBAR = libzbar
|
||||||
|
EXTERNAL_DEPENDENCIES = [LIBZBAR] + dependencies
|
||||||
|
|
||||||
|
return LIBZBAR
|
||||||
|
|
||||||
|
|
||||||
|
# Function signatures
|
||||||
|
def zbar_function(fname, restype, *args):
|
||||||
|
"""Returns a foreign function exported by `zbar`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fname (:obj:`str`): Name of the exported function as string.
|
||||||
|
restype (:obj:): Return type - one of the `ctypes` primitive C data
|
||||||
|
types.
|
||||||
|
*args: Arguments - a sequence of `ctypes` primitive C data types.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
cddl.CFunctionType: A wrapper around the function.
|
||||||
|
"""
|
||||||
|
prototype = CFUNCTYPE(restype, *args)
|
||||||
|
return prototype((fname, load_libzbar()))
|
||||||
|
|
||||||
|
|
||||||
|
zbar_version = zbar_function(
|
||||||
|
'zbar_version',
|
||||||
|
c_int,
|
||||||
|
c_uint_p, # major,
|
||||||
|
c_uint_p, # minor
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_set_verbosity = zbar_function(
|
||||||
|
'zbar_set_verbosity',
|
||||||
|
None,
|
||||||
|
c_int
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_scanner_create = zbar_function(
|
||||||
|
'zbar_image_scanner_create',
|
||||||
|
POINTER(zbar_image_scanner)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_scanner_destroy = zbar_function(
|
||||||
|
'zbar_image_scanner_destroy',
|
||||||
|
None,
|
||||||
|
POINTER(zbar_image_scanner)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_parse_config = zbar_function(
|
||||||
|
'zbar_parse_config',
|
||||||
|
c_int,
|
||||||
|
c_char_p, # config_string,
|
||||||
|
POINTER(c_int), # symbology - values in ZBarSymbol
|
||||||
|
POINTER(c_int), # config - values in ZBarConfig
|
||||||
|
POINTER(c_int), # value
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_scanner_set_config = zbar_function(
|
||||||
|
'zbar_image_scanner_set_config',
|
||||||
|
c_int,
|
||||||
|
POINTER(zbar_image_scanner), # scanner
|
||||||
|
c_int, # symbology - values in ZBarSymbol
|
||||||
|
c_int, # config - values in ZBarConfig
|
||||||
|
c_int # value
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_create = zbar_function(
|
||||||
|
'zbar_image_create',
|
||||||
|
POINTER(zbar_image)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_destroy = zbar_function(
|
||||||
|
'zbar_image_destroy',
|
||||||
|
None,
|
||||||
|
POINTER(zbar_image)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_set_format = zbar_function(
|
||||||
|
'zbar_image_set_format',
|
||||||
|
None,
|
||||||
|
POINTER(zbar_image),
|
||||||
|
c_uint
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_set_size = zbar_function(
|
||||||
|
'zbar_image_set_size',
|
||||||
|
None,
|
||||||
|
POINTER(zbar_image),
|
||||||
|
c_uint, # width
|
||||||
|
c_uint # height
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_set_data = zbar_function(
|
||||||
|
'zbar_image_set_data',
|
||||||
|
None,
|
||||||
|
POINTER(zbar_image),
|
||||||
|
c_void_p, # data
|
||||||
|
c_ulong, # raw_image_data_length
|
||||||
|
c_void_p # A function pointer(!)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_scan_image = zbar_function(
|
||||||
|
'zbar_scan_image',
|
||||||
|
c_int,
|
||||||
|
POINTER(zbar_image_scanner),
|
||||||
|
POINTER(zbar_image)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_image_first_symbol = zbar_function(
|
||||||
|
'zbar_image_first_symbol',
|
||||||
|
POINTER(zbar_symbol),
|
||||||
|
POINTER(zbar_image)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_data_length = zbar_function(
|
||||||
|
'zbar_symbol_get_data_length',
|
||||||
|
c_uint,
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_data = zbar_function(
|
||||||
|
'zbar_symbol_get_data',
|
||||||
|
c_ubyte_p,
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_loc_size = zbar_function(
|
||||||
|
'zbar_symbol_get_loc_size',
|
||||||
|
c_uint,
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_loc_x = zbar_function(
|
||||||
|
'zbar_symbol_get_loc_x',
|
||||||
|
c_int,
|
||||||
|
POINTER(zbar_symbol),
|
||||||
|
c_uint
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_loc_y = zbar_function(
|
||||||
|
'zbar_symbol_get_loc_y',
|
||||||
|
c_int,
|
||||||
|
POINTER(zbar_symbol),
|
||||||
|
c_uint
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
zbar_symbol_get_orientation = zbar_function(
|
||||||
|
'zbar_symbol_get_orientation',
|
||||||
|
c_uint,
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
# This function not present in the original pre-20
|
||||||
|
zbar_symbol_get_orientation = None
|
||||||
|
|
||||||
|
|
||||||
|
zbar_symbol_next = zbar_function(
|
||||||
|
'zbar_symbol_next',
|
||||||
|
POINTER(zbar_symbol),
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
|
|
||||||
|
zbar_symbol_get_quality = zbar_function(
|
||||||
|
'zbar_symbol_get_quality',
|
||||||
|
c_int,
|
||||||
|
POINTER(zbar_symbol)
|
||||||
|
)
|
||||||
22
pyzbar/pyzbar/zbar_library.py
Normal file
22
pyzbar/pyzbar/zbar_library.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""Loads the zbar shared library bundled with this package."""
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from ctypes import cdll
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def load():
|
||||||
|
lib_dir = Path(__file__).parent / 'install' / 'lib'
|
||||||
|
|
||||||
|
if platform.system() == 'Darwin':
|
||||||
|
lib_name = 'libzbar.dylib'
|
||||||
|
else:
|
||||||
|
lib_name = 'libzbar.so'
|
||||||
|
|
||||||
|
lib_path = lib_dir / lib_name
|
||||||
|
if not lib_path.exists():
|
||||||
|
raise ImportError(
|
||||||
|
f'Bundled zbar shared library not found at {lib_path}'
|
||||||
|
)
|
||||||
|
|
||||||
|
return cdll.LoadLibrary(str(lib_path)), []
|
||||||
103
pyzbar/setup.py
Normal file
103
pyzbar/setup.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""Shim setup.py: downloads pre-built wheels from GitHub Releases at install time."""
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import time
|
||||||
|
import zipfile
|
||||||
|
from io import BytesIO
|
||||||
|
from urllib.error import URLError
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ImportError:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.command.build_py import build_py
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
with open(os.path.join(_HERE, "pyproject.toml"), "rb") as _f:
|
||||||
|
_cfg = tomllib.load(_f)
|
||||||
|
|
||||||
|
REPO_URL = _cfg["tool"]["shim"]["repo_url"]
|
||||||
|
TAG = _cfg["tool"]["shim"]["tag"]
|
||||||
|
DATADIR = _cfg["tool"]["shim"]["datadir"]
|
||||||
|
VERSION = _cfg["project"]["version"]
|
||||||
|
MODULE = _cfg["project"]["name"].replace("-", "_")
|
||||||
|
|
||||||
|
PLATFORM_MAP = {
|
||||||
|
("Linux", "x86_64"): "linux_x86_64",
|
||||||
|
("Linux", "aarch64"): "linux_aarch64",
|
||||||
|
("Darwin", "arm64"): "macosx_11_0_arm64",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InstallPrebuilt(build_py):
|
||||||
|
def run(self):
|
||||||
|
module_dir = os.path.join(_HERE, MODULE)
|
||||||
|
data_dir = os.path.join(module_dir, DATADIR)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(data_dir, "bin")):
|
||||||
|
key = (platform.system(), platform.machine())
|
||||||
|
plat = PLATFORM_MAP.get(key)
|
||||||
|
if plat is None:
|
||||||
|
raise RuntimeError(f"unsupported platform: {key}")
|
||||||
|
|
||||||
|
whl_name = f"{MODULE}-{VERSION}-py3-none-{plat}.whl"
|
||||||
|
url = f"{REPO_URL}/releases/download/{TAG}/{whl_name}"
|
||||||
|
|
||||||
|
print(f"Downloading {url} ...")
|
||||||
|
for attempt in range(3):
|
||||||
|
try:
|
||||||
|
raw = urlopen(url, timeout=60).read()
|
||||||
|
break
|
||||||
|
except (URLError, OSError) as e:
|
||||||
|
if attempt == 2:
|
||||||
|
raise
|
||||||
|
wait = 2 ** attempt
|
||||||
|
print(f"Download failed ({e}), retrying in {wait}s ...")
|
||||||
|
time.sleep(wait)
|
||||||
|
|
||||||
|
print(f"Extracting {DATADIR} ...")
|
||||||
|
with zipfile.ZipFile(BytesIO(raw)) as zf:
|
||||||
|
prefix = f"{MODULE}/{DATADIR}/"
|
||||||
|
alt_prefix = f"{MODULE}-{VERSION}.data/purelib/{MODULE}/{DATADIR}/"
|
||||||
|
for info in zf.infolist():
|
||||||
|
for p in (prefix, alt_prefix):
|
||||||
|
if info.filename.startswith(p):
|
||||||
|
rel = info.filename[len(p):]
|
||||||
|
if not rel:
|
||||||
|
continue
|
||||||
|
dest = os.path.join(data_dir, rel)
|
||||||
|
if info.is_dir():
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
else:
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
f.write(zf.read(info))
|
||||||
|
if info.external_attr >> 16 & 0o111:
|
||||||
|
os.chmod(dest, 0o755)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Also extract compiled extension modules (.so)
|
||||||
|
ext_prefix = f"{MODULE}/"
|
||||||
|
ext_alt_prefix = f"{MODULE}-{VERSION}.data/purelib/{MODULE}/"
|
||||||
|
for info in zf.infolist():
|
||||||
|
if not info.filename.endswith('.so'):
|
||||||
|
continue
|
||||||
|
for p in (ext_prefix, ext_alt_prefix):
|
||||||
|
if info.filename.startswith(p):
|
||||||
|
rel = info.filename[len(p):]
|
||||||
|
if rel and '/' not in rel:
|
||||||
|
dest = os.path.join(module_dir, rel)
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
f.write(zf.read(info))
|
||||||
|
if info.external_attr >> 16 & 0o111:
|
||||||
|
os.chmod(dest, 0o755)
|
||||||
|
break
|
||||||
|
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
|
||||||
|
setup(cmdclass={"build_py": InstallPrebuilt})
|
||||||
Reference in New Issue
Block a user