Compare commits

..

6 Commits

Author SHA1 Message Date
nono
3dc6a41724 Update requiremetnss 2025-06-11 00:44:12 +02:00
nono
abaf506d56 Change the return type to a JSON message for the API, add logging 2025-06-10 19:57:33 +02:00
nono
866d89eb09 Add video attributs 2025-06-10 19:38:55 +02:00
nono
7df902df52 Remove one button, streamline UI 2025-06-10 19:38:42 +02:00
nono
80b16f260e Add contrast correction 2025-06-10 19:38:29 +02:00
nono
1735e468aa Add SSL context and folder creation for the uploads 2025-06-10 19:37:59 +02:00
5 changed files with 74 additions and 57 deletions

View File

@ -1,31 +1,36 @@
Adafruit-Thermal~=1.1.0 Adafruit_Thermal==1.1.0
appdirs~=1.4.4 appdirs==1.4.4
argcomplete~=2.0.0 argcomplete==2.0.6
click~=8.1.3 cffi==1.17.1
commonmark~=0.9.1 click==8.1.8
Deprecated~=1.2.13 commonmark==0.9.1
escpos~=2.0.0 cryptography==45.0.4
Flask~=2.1.2 Deprecated==1.2.18
Flask-Limiter~=2.4.5.1 escpos==2.0.0
future~=0.18.2 Flask==2.1.3
itsdangerous~=2.1.2 Flask-Limiter==2.4.5.1
Jinja2~=3.1.2 future==0.18.3
limits~=2.6.1 itsdangerous==2.1.2
MarkupSafe~=2.1.1 Jinja2==3.1.6
packaging~=21.3 limits==2.6.3
Pillow MarkupSafe==2.1.5
Pygments~=2.12.0 numpy==2.3.0
pyparsing~=3.0.8 packaging==21.3
pyserial~=3.5 pillow==11.2.1
python-barcode~=0.13.1 pycparser==2.22
pyusb~=1.2.1 Pygments==2.12.0
PyYAML~=6.0 pyparsing==3.0.9
qrcode~=7.3.1 pyserial==3.5
rich~=12.4.1 python-barcode==0.13.1
six~=1.16.0 pyusb==1.2.1
toml~=0.10.2 PyYAML==6.0.2
typing_extensions~=4.2.0 qrcode==7.3.1
Unidecode~=1.3.4 rich==12.4.4
viivakoodi~=0.8.0 setuptools==80.9.0
Werkzeug~=2.1.2 six==1.16.0
wrapt~=1.14.1 toml==0.10.2
typing_extensions==4.2.0
Unidecode==1.3.8
viivakoodi==0.8.0
Werkzeug==2.1.2
wrapt==1.14.1

View File

@ -40,6 +40,16 @@ vendor_id = configuration_file["printer"]["vendor_id"]
device_id = configuration_file["printer"]["device_id"] device_id = configuration_file["printer"]["device_id"]
UPLOAD_FOLDER = str(configuration_file["printer"]["upload_folder"]) UPLOAD_FOLDER = str(configuration_file["printer"]["upload_folder"])
try:
os.mkdir(UPLOAD_FOLDER)
app.logger.debug(f"Directory '{UPLOAD_FOLDER}' created successfully.")
except FileExistsError:
app.logger.error(f"Directory '{UPLOAD_FOLDER}' already exists.")
except PermissionError:
app.logger.error(f"Permission denied: Unable to create '{UPLOAD_FOLDER}'.")
except Exception as e:
app.logger.error(f"An error occurred: {e}")
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
# Output the config file # Output the config file
@ -61,6 +71,9 @@ printer.init_printer()
# Web routes # Web routes
web = Web(app, printer) web = Web(app, printer)
if __name__ == "__main__":
app.run(ssl_context='adhoc')
limiter = Limiter( limiter = Limiter(
app, app,
@ -103,15 +116,16 @@ def api_print_sms():
txt = request.form["txt"] txt = request.form["txt"]
sign = request.form["signature"] sign = request.form["signature"]
except Exception as e: except Exception as e:
flash(e,'error') app.logger.error(str(e) + " - Whoops, no forms submitted or missing signature.")
redirect(url_for('index')) return jsonify({'message': 'Error getting the information from the form :' + e}), 500
try: try:
web.print_sms(txt,sign) web.print_sms(txt,sign)
except Exception as e: except Exception as e:
pass return jsonify({'message': 'Error printing the SMS:' + e}), 500
return redirect(url_for('index'))
return jsonify({'message': 'Message printed'}), 200
@app.route('/api/print/img', methods=['POST']) @app.route('/api/print/img', methods=['POST'])
@limiter.limit("6/minute", override_defaults=False) @limiter.limit("6/minute", override_defaults=False)
@ -121,43 +135,41 @@ def api_print_image():
try: try:
sign = request.form["signature"] sign = request.form["signature"]
except Exception as e: except Exception as e:
flash(str(e),'error')
app.logger.error(str(e) + " - Whoops, no forms submitted or missing signature.") app.logger.error(str(e) + " - Whoops, no forms submitted or missing signature.")
return redirect(url_for('index')) return jsonify({'message': 'Error getting the information from the form :' + e}), 500
if request.method == 'POST': if request.method == 'POST':
# check if the post request has the file part # check if the post request has the file part
try: try:
if 'img' not in request.files: if 'img' not in request.files:
flash('No file found. Did you use the good form ?', 'error')
app.logger.error("No file found. Did you use the good form ?") app.logger.error("No file found. Did you use the good form ?")
return redirect(url_for("index")) return jsonify({'message': 'No file found. Did you use the good form ?'}), 500
else: else:
file = request.files['img'] file = request.files['img']
except Exception as e: except Exception as e:
if sign is not None and photo is not None: app.logger.error('Error getting the files :' + e)
pass return jsonify({'message': 'Error getting the files :' + e}), 500
else:
flash(str(e), 'error')
app.logger.error("Couldn't get an image nor signature : " + str(e))
# If the user does not select a file, the browser submits an # If the user does not select a file, the browser submits an
# empty file without a filename. # empty file without a filename.
if file.filename == '': if file.filename == '':
app.logger.error("Submitted file has no filename !") app.logger.error("Submitted file has no filename !")
flash('No file submitted, please select a file','error') return jsonify({'message': "Submitted file has no filename !" + e}), 500
return redirect(url_for("index"))
try: try:
app.logger.debug("Sending the image to the printer.") app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign) web.print_image(file, sign)
except Exception as e: except Exception as e:
pass app.logger.error("The image could not be printed : " + e )
else: return jsonify({'message': "The image could not be printed :" + e}), 500
flash('Cannot access to page with this method.','error')
app.logger.debug('Bad access type to this API.') else:
return jsonify({'message': "Method Not Allowed, please POST"}), 403
app.logger.debug('Bad access type to this API, please POST')
return jsonify({'message': 'Message printed'}), 200
return redirect(url_for("index"))
@app.route('/login') @app.route('/login')
@limiter.limit("1/second", override_defaults=False) @limiter.limit("1/second", override_defaults=False)
@ -174,10 +186,12 @@ def logout_page():
@app.errorhandler(429) @app.errorhandler(429)
def ratelimit_handler(e): def ratelimit_handler(e):
flash("Rate limit reached, please slow down :) ( Currently at "+ e.description + ")", 'error') flash("Rate limit reached, please slow down :) ( Currently at "+ e.description + ")", 'error')
app.logger.debug('Rate limit reached ' + e)
return redirect(url_for("index")) return redirect(url_for("index"))
@app.route("/ping") @app.route("/ping")
@limiter.exempt @limiter.exempt
def ping(): def ping():
flash("🏓 Pong !",'info') flash("🏓 Pong !",'info')
app.logger.debug('🏓 Pong !')
return redirect(url_for("index")) return redirect(url_for("index"))

View File

@ -177,7 +177,7 @@ class Printer(object):
def process_image(self, path): def process_image(self, path):
brightness_factor = 1.5 # Used only if image is too dark brightness_factor = 1.5 # Used only if image is too dark
brightness_threshold = 150 # Brightness threshold (0255) brightness_threshold = 150 # Brightness threshold (0255)
contrast_factor = 0.8 # Less than 1.0 = lower contrast contrast_factor = 1.2 # Less than 1.0 = lower contrast
max_width = 575 max_width = 575
max_height = 1000 max_height = 1000

View File

@ -190,6 +190,9 @@ async function get_webcam(options){
printButton.removeAttribute("disabled",""); printButton.removeAttribute("disabled","");
current_stream = stream; current_stream = stream;
video.srcObject = stream; video.srcObject = stream;
video.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('playsinline', '')
video.play(); video.play();
return true; return true;
}) })

View File

@ -27,11 +27,6 @@
<path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/> <path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/>
<path d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/> <path d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
</svg> </svg>
<b>&</b>
<svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" fill="currentColor" class="bi bi-printer" viewBox="0 0 16 16">
<path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"/>
<path d="M5 1a2 2 0 0 0-2 2v2H2a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h1v1a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-1h1a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-1V3a2 2 0 0 0-2-2H5zM4 3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2H4V3zm1 5a2 2 0 0 0-2 2v1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v-1a2 2 0 0 0-2-2H5zm7 2v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1z"/>
</svg>
</button> </button>
<button class="col-sm col-lg-3 offset-lg-2 btn btn-secondary justify-content-center" name="flip" id="flip" data-bs-toggle="tooltip" data-bs-placement="top" title="Change camera" disabled=""> <button class="col-sm col-lg-3 offset-lg-2 btn btn-secondary justify-content-center" name="flip" id="flip" data-bs-toggle="tooltip" data-bs-placement="top" title="Change camera" disabled="">
<svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" fill="currentColor" class="bi bi-arrow-repeat" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" fill="currentColor" class="bi bi-arrow-repeat" viewBox="0 0 16 16">