Skip to content
This repository was archived by the owner on May 7, 2026. It is now read-only.

Commit e7974a9

Browse files
committed
re-add add multi-decoding
FIXME: better error handling during multi-decoding. What if CommandLineRunner prints an exception, but it's not for the first image processed? This mostly reverts commit 8fdfecb.
1 parent 1c07032 commit e7974a9

3 files changed

Lines changed: 52 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ BarCode(raw='This should be QR_CODE', parsed='This should be QR_CODE', format='Q
2929
The attributes of the decoded `BarCode` object are `raw`, `parsed`, `format`, `type`, and `points`. The list of formats which ZXing can decode is
3030
[here](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html).
3131

32-
The `decode()` method accepts an image path and takes optional parameters `try_harder` (boolean) and `possible_formats` (list of formats to consider).
32+
The `decode()` method accepts an image path (or list of paths) and takes optional parameters `try_harder` (boolean) and `possible_formats` (list of formats to consider).
3333
If no barcode is found, it returns `None`, and if it encounters any other recognizable error from the Java ZXing library, it raises `BarCodeReaderException`.
3434

3535
## Command-line interface

test/test_all.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,20 @@ def test_decoding():
3939
raise AssertionError('Expected {!r} but got {!r}'.format(expected_format, dec.format))
4040

4141

42+
@with_setup(setup_reader)
43+
def test_decoding_multiple():
44+
reader = zxing.BarCodeReader()
45+
filenames = [os.path.join(test_barcode_dir, filename) for filename, expected_format, expected_raw in test_barcodes]
46+
for dec, (filename, expected_format, expected_raw) in zip(reader.decode(filenames), test_barcodes):
47+
if dec.raw != expected_raw:
48+
raise AssertionError('{}: Expected {!r} but got {!r}'.format(filename, expected_raw, dec.parsed))
49+
if dec.format != expected_format:
50+
raise AssertionError('{}: Expected {!r} but got {!r}'.format(filename, expected_format, dec.format))
51+
52+
4253
def test_parsing():
4354
dec = zxing.BarCode.parse("""
44-
file:default.png (format: FAKE_DATA, type: TEXT):
55+
file:///tmp/default.png (format: FAKE_DATA, type: TEXT):
4556
Raw result:
4657
Élan|\tthe barcode is taking off
4758
Parsed result:
@@ -53,6 +64,7 @@ def test_parsing():
5364
Point 2: (201.0,198.0)
5465
Point 3: (205.23952,21.0)
5566
""".encode())
67+
assert dec.uri == 'file:///tmp/default.png'
5668
assert dec.format == 'FAKE_DATA'
5769
assert dec.type == 'TEXT'
5870
assert dec.raw == 'Élan|\tthe barcode is taking off'

zxing/__init__.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ def __init__(self, classpath=None, java=None):
3131
else:
3232
self.classpath = os.path.join(os.path.dirname(__file__), 'java', '*')
3333

34-
def decode(self, filename, try_harder=False, possible_formats=None):
34+
def decode(self, filenames, try_harder=False, possible_formats=None):
3535
possible_formats = (possible_formats,) if isinstance(possible_formats, str) else possible_formats
3636

37-
file_uri = pathlib.Path(filename).absolute().as_uri()
38-
cmd = [self.java, '-cp', self.classpath, self.cls, file_uri]
37+
if isinstance(filenames, str):
38+
one_file = True
39+
filenames = filenames,
40+
else:
41+
one_file = False
42+
file_uris = [ pathlib.Path(f).absolute().as_uri() for f in filenames ]
43+
44+
cmd = [self.java, '-cp', self.classpath, self.cls] + file_uris
3945
if try_harder:
4046
cmd.append('--try_harder')
4147
if possible_formats:
@@ -54,15 +60,33 @@ def decode(self, filename, try_harder=False, possible_formats=None):
5460
b'Exception in thread "main" java.lang.NoClassDefFoundError:')):
5561
raise BarCodeReaderException("Java JARs not found in classpath (%s)" % self.classpath, self.classpath)
5662
elif stdout.startswith(b'''Exception in thread "main" javax.imageio.IIOException: Can't get input stream from URL!'''):
57-
raise BarCodeReaderException("Could not find image path: %s" % filename, filename)
63+
raise BarCodeReaderException("Could not find image path: %s" % filenames, filenames)
5864
elif stdout.startswith(b'''Exception in thread "main" java.io.IOException: Could not load '''):
59-
raise BarCodeReaderException("Java library could not read image; is it in a supported format?", filename)
65+
raise BarCodeReaderException("Java library could not read image; is it in a supported format?", filenames)
6066
elif stdout.startswith(b'''Exception '''):
6167
raise BarCodeReaderException("Unknown Java exception: %s" % stdout)
6268
elif p.returncode:
6369
raise BarCodeReaderException("Unexpected Java subprocess return code %d" % p.returncode, self.java)
6470

65-
return BarCode.parse(stdout)
71+
if p.returncode:
72+
codes = [ None for fn in filenames ]
73+
else:
74+
file_results = []
75+
for line in stdout.splitlines(True):
76+
if line.startswith((b'file:///',b'Exception')):
77+
file_results.append(line)
78+
else:
79+
file_results[-1] += line
80+
codes = [ BarCode.parse(result) for result in file_results ]
81+
82+
if one_file:
83+
return codes[0]
84+
else:
85+
# zxing (insanely) randomly reorders the output blocks, so we have to put them back in the
86+
# expected order, based on their URIs
87+
d = {c.uri: c for c in codes}
88+
return [d[f] for f in file_uris]
89+
6690

6791
class CLROutputBlock(Enum):
6892
UNKNOWN = 0
@@ -74,17 +98,17 @@ class BarCode(object):
7498
@classmethod
7599
def parse(cls, zxing_output):
76100
block = CLROutputBlock.UNKNOWN
77-
format = type = None
101+
uri = format = type = None
78102
raw = parsed = b''
79103
points = []
80104

81105
for l in zxing_output.splitlines(True):
82106
if block==CLROutputBlock.UNKNOWN:
83107
if l.endswith(b': No barcode found\n'):
84108
return None
85-
m = re.search(rb"format:\s*([^,]+),\s*type:\s*([^)]+)", l)
109+
m = re.match(rb"(\S+) \(format:\s*([^,]+),\s*type:\s*([^)]+)\)", l)
86110
if m:
87-
format, type = m.group(1).decode(), m.group(2).decode()
111+
uri, format, type = m.group(1).decode(), m.group(2).decode(), m.group(3).decode()
88112
elif l.startswith(b"Raw result:"):
89113
block = CLROutputBlock.RAW
90114
elif block==CLROutputBlock.RAW:
@@ -104,15 +128,16 @@ def parse(cls, zxing_output):
104128

105129
raw = raw[:-1].decode()
106130
parsed = parsed[:-1].decode()
107-
return cls(format, type, raw, parsed, points)
131+
return cls(uri, format, type, raw, parsed, points)
108132

109-
def __init__(self, format, type, raw, parsed, points):
133+
def __init__(self, uri, format, type, raw, parsed, points):
110134
self.raw = raw
111135
self.parsed = parsed
136+
self.uri = uri
112137
self.format = format
113138
self.type = type
114139
self.points = points
115140

116141
def __repr__(self):
117-
return '{}(raw={!r}, parsed={!r}, format={!r}, type={!r}, points={!r})'.format(
118-
self.__class__.__name__, self.raw, self.parsed, self.format, self.type, self.points)
142+
return '{}(raw={!r}, parsed={!r}, uri={!r}, format={!r}, type={!r}, points={!r})'.format(
143+
self.__class__.__name__, self.raw, self.parsed, self.uri, self.format, self.type, self.points)

0 commit comments

Comments
 (0)