Skip to content

Instantly share code, notes, and snippets.

Created September 7, 2023 20:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ethanhs/e7aba09a0857c37840572d9cc961257c to your computer and use it in GitHub Desktop.
Save ethanhs/e7aba09a0857c37840572d9cc961257c to your computer and use it in GitHub Desktop.
OCF Printing -- Client side
#!/usr/bin/env python3.7
from ocflib.printing.quota import get_quota
from ocflib.printing.quota import get_connection
from import user_is_sorried
from import user_exists
from 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
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(
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()
def run(self):
error = ''
frame =
if frame is None:
# 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 ="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:
# 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
# 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):
print('ERROR: encountered exception in main UI loop:')
# run the main loop every 30ms
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)
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 =, capture_output=True, text=True, cwd=remote)
if proc.returncode != 0:
return proc.stdout + proc.stderr
return ''
def close(self):
scanner = PrintScanner(VideoStream(src=-1, resolution=(1280,720)).start())
# give some time for the camera to get set up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment