Created
September 7, 2023 20:35
-
-
Save emmatyping/e7aba09a0857c37840572d9cc961257c to your computer and use it in GitHub Desktop.
OCF Printing -- Client side
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3.7 | |
from ocflib.printing.quota import get_quota | |
from ocflib.printing.quota import get_connection | |
from ocflib.account.search import user_is_sorried | |
from ocflib.account.search import user_exists | |
from imutils.video import VideoStream | |
from pyzbar import pyzbar | |
import imutils | |
import cv2 | |
from PIL import Image | |
from PIL import ImageTk | |
from PyPDF2 import PdfFileReader | |
import re | |
import subprocess | |
import threading | |
import time | |
import tkinter | |
import traceback | |
import os | |
FILE_RE = re.compile(r'[0-9a-f]{32}.*\.pdf') | |
def get_user_quota(user): | |
with get_connection() as conn: | |
return get_quota(conn, user) | |
def valid_settings(user, file, single_or_double): | |
if not user_exists(user): | |
return False | |
elif not FILE_RE.match(file): | |
return False | |
elif single_or_double not in ('single', 'double'): | |
return False | |
else: | |
return True | |
class PrintScanner: | |
def __init__(self, videostream): | |
self.videostream = videostream | |
# The root Window | |
self.root = tkinter.Tk() | |
self.root.wm_title("Start Remote Print Job") | |
self.root.wm_protocol("WM_DELETE_WINDOW", self.close) | |
self.top_text = tkinter.Label( | |
self.root, | |
text="Remote Printing Kiosk", | |
font=("Arial", 32), | |
) | |
self.top_text.pack(side=tkinter.TOP, pady=10) | |
# The panel an image is shown on | |
self.panel = tkinter.Label(self.root) | |
self.panel.pack(padx=10, pady=10) | |
# Text for any errors that occur | |
self.text = tkinter.Label(self.root) | |
# a set of seen QR codes | |
self.seen_qrs = set() | |
self.run() | |
def run(self): | |
error = '' | |
try: | |
frame = self.videostream.read() | |
if frame is None: | |
self.root.after(30, self.run) | |
return | |
# filter for QR codes only, pyzbar also supports barcodes... | |
qr_codes = pyzbar.decode(frame, symbols=[pyzbar.ZBarSymbol.QRCODE]) | |
for qr_code in qr_codes: | |
# draw a rectangle around detected QR code | |
(x, y, w, h) = qr_code.rect | |
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2) | |
# Get the data from the QR code | |
data = qr_code.data.decode("utf-8") | |
# we want to not queue a million print jobs every 30ms when we scan a new QR code | |
if data not in self.seen_qrs: | |
self.seen_qrs.add(data) | |
# try to parse the data and get an error back if it malformed or malicious | |
error = self.parse_data(data) | |
if error: | |
print(f'ERROR: {error}\n{data}') | |
self.text = tkinter.Label( | |
self.root, text=error, fg='red', font=("Arial", 24)) | |
self.text.pack(side=tkinter.BOTTOM, pady=10) | |
# convert from OpenCV to something tkinter can understand | |
# holy shit why did you choose BGR opencv | |
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) | |
self.current_image = Image.fromarray(rgb) | |
imgtk = ImageTk.PhotoImage(image=self.current_image.resize((1280,720))) | |
# place the image on the display | |
self.panel.imgtk = imgtk | |
self.panel.config(image=imgtk) | |
# clear errors after 5 seconds (TODO: this may be too short?) | |
self.root.after(5000, self.text.pack_forget) | |
except Exception as e: | |
# allow keyboard interrupt through so we can exit | |
if isinstance(e, KeyboardInterrupt): | |
raise | |
print('ERROR: encountered exception in main UI loop:') | |
traceback.print_exc(e) | |
# run the main loop every 30ms | |
self.root.after(30, self.run) | |
def parse_data(self, data): | |
data_parts = [] | |
if ':' in data: | |
data_parts = data.split(':') | |
if len(data_parts) == 3: | |
user, file, single_or_double = data_parts | |
# first check that the user exists and the file matches the expected regex, | |
# and the setting for single or double sided printing is correct | |
if not valid_settings(user, file, single_or_double): | |
return 'User, filename, or print settings invalid' | |
full_path = os.path.join(os.path.expanduser( | |
f'~{user}'), 'remote', '.user_print', file) | |
print(full_path) | |
if not os.path.exists(full_path): | |
return 'The requested file does not exist' | |
with open(full_path, 'rb') as pdf: | |
reader = PdfFileReader(pdf) | |
pages = reader.getNumPages() | |
quota = get_user_quota(user) | |
if pages > quota.daily: | |
return f'You can only print {quota.daily} more pages today' | |
return self.print(user, full_path, single_or_double) | |
return 'That QR code seems to be invalid' | |
def print(self, user, file, single_or_double): | |
user_homedir = os.path.expanduser(f'~{user}') | |
remote = os.path.join(user_homedir, 'remote') | |
cmd = ['sudo', '-u', user, 'lpr', '-U', | |
user, '-P', single_or_double, file] | |
print(' '.join(cmd)) | |
proc = subprocess.run(cmd, capture_output=True, text=True, cwd=remote) | |
if proc.returncode != 0: | |
return proc.stdout + proc.stderr | |
return '' | |
def close(self): | |
self.videostream.stop() | |
self.root.quit() | |
scanner = PrintScanner(VideoStream(src=-1, resolution=(1280,720)).start()) | |
# give some time for the camera to get set up | |
time.sleep(2.0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
opencv-python | |
pyzbar | |
imutils | |
Pillow | |
PyPDF2 | |
ocflib |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment