|
10 | 10 | from math import log2, floor |
11 | 11 | import json |
12 | 12 | import logging |
| 13 | +import time |
13 | 14 |
|
14 | 15 | ## tweakable params |
15 | 16 | CHUNK_SIZE = 256 |
@@ -48,20 +49,51 @@ def open_video(idx: int): |
48 | 49 | vcap.release() |
49 | 50 |
|
50 | 51 |
|
51 | | -def capture(idx: int = 0, show_frame: bool = True) -> bytes: |
| 52 | +def capture(idx: int = 0, show_frame: bool = True) -> dict[int, str]: |
52 | 53 | """Capture a QR code from the default video feed.""" |
53 | 54 | with open_video(idx) as vcap: |
| 55 | + if not vcap.isOpened(): |
| 56 | + raise click.ClickException( |
| 57 | + f"Could not open video capture device index {idx}." |
| 58 | + ) |
| 59 | + |
| 60 | + if not show_frame: |
| 61 | + click.echo( |
| 62 | + "Capturing QR frames from camera; press Ctrl-C to abort.", err=True |
| 63 | + ) |
| 64 | + |
54 | 65 | ## set this implausibly high, until we read the keyframe (which may not be the first frame we see). |
55 | 66 | n_frames = 99999999999999 |
56 | 67 | frames = {} |
| 68 | + display_enabled = show_frame |
| 69 | + max_consecutive_read_failures = 100 |
| 70 | + consecutive_read_failures = 0 |
57 | 71 | while True: |
58 | 72 | ret, frame = vcap.read() |
| 73 | + if not ret: |
| 74 | + consecutive_read_failures += 1 |
| 75 | + if consecutive_read_failures >= max_consecutive_read_failures: |
| 76 | + raise click.ClickException( |
| 77 | + "Camera opened but no frames were received. " |
| 78 | + "Try a different --idx value and check camera permissions." |
| 79 | + ) |
| 80 | + time.sleep(0.05) |
| 81 | + continue |
| 82 | + |
| 83 | + consecutive_read_failures = 0 |
59 | 84 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) |
60 | 85 | image = Image.fromarray(gray) |
61 | 86 |
|
62 | | - cv2.imshow("Live Capture Feed", gray) |
63 | | - if cv2.waitKey(1) == 27: # esc to quit |
64 | | - break |
| 87 | + if display_enabled: |
| 88 | + try: |
| 89 | + cv2.imshow("Live Capture Feed", gray) |
| 90 | + if cv2.waitKey(1) == 27: # esc to quit |
| 91 | + break |
| 92 | + except cv2.error: |
| 93 | + logging.warning( |
| 94 | + "OpenCV display backend unavailable; disabling live preview." |
| 95 | + ) |
| 96 | + display_enabled = False |
65 | 97 |
|
66 | 98 | for decoded in pyzbar.decode(image): |
67 | 99 | try: |
@@ -149,11 +181,12 @@ def write(outfile: str, duration: int): |
149 | 181 | @main.command() |
150 | 182 | @click.option("--outfile", "-o", is_flag=False, required=False) |
151 | 183 | @click.option("--idx", "-i", is_flag=False, type=int, default=0, required=False) |
152 | | -def read(outfile: str, idx: int): |
| 184 | +@click.option("--show-frame/--no-show-frame", default=True) |
| 185 | +def read(outfile: str, idx: int, show_frame: bool): |
153 | 186 | """ |
154 | 187 | Capture QR-code encoded data from webcam. Output defaults to stdout. |
155 | 188 | """ |
156 | | - data = capture(idx) |
| 189 | + data = capture(idx, show_frame=show_frame) |
157 | 190 | output = b"" |
158 | 191 | for idx, val in sorted(data.items()): |
159 | 192 | output += bytes.fromhex(val) |
|
0 commit comments