7 Commits

Author SHA1 Message Date
n07070
0601fe8190 Add information on contributions 2026-01-04 12:07:10 +01:00
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
6 changed files with 81 additions and 58 deletions

View File

@@ -61,6 +61,12 @@ This command should start a web server with which you can test your configuratio
Voilà !
## Contributions
Your contributions are very much welcome ! You can either request an account on git.n07070.xyz, or send me a patch by email ( see git-send-mail.io ). Please [squash](https://www.geeksforgeeks.org/git/use-of-git-squash-commits/) yours commits into one commit, and add as much information in the commit's description. The more you add comments and descriptions, the better it is.
Please also say if you had a printer to test your code, and which printer you've been using.
## Screenshots
![](src/static/images/homepage.png)

View File

@@ -1,31 +1,36 @@
Adafruit-Thermal~=1.1.0
appdirs~=1.4.4
argcomplete~=2.0.0
click~=8.1.3
commonmark~=0.9.1
Deprecated~=1.2.13
escpos~=2.0.0
Flask~=2.1.2
Flask-Limiter~=2.4.5.1
future~=0.18.2
itsdangerous~=2.1.2
Jinja2~=3.1.2
limits~=2.6.1
MarkupSafe~=2.1.1
packaging~=21.3
Pillow
Pygments~=2.12.0
pyparsing~=3.0.8
pyserial~=3.5
python-barcode~=0.13.1
pyusb~=1.2.1
PyYAML~=6.0
qrcode~=7.3.1
rich~=12.4.1
six~=1.16.0
toml~=0.10.2
typing_extensions~=4.2.0
Unidecode~=1.3.4
viivakoodi~=0.8.0
Werkzeug~=2.1.2
wrapt~=1.14.1
Adafruit_Thermal==1.1.0
appdirs==1.4.4
argcomplete==2.0.6
cffi==1.17.1
click==8.1.8
commonmark==0.9.1
cryptography==45.0.4
Deprecated==1.2.18
escpos==2.0.0
Flask==2.1.3
Flask-Limiter==2.4.5.1
future==0.18.3
itsdangerous==2.1.2
Jinja2==3.1.6
limits==2.6.3
MarkupSafe==2.1.5
numpy==2.3.0
packaging==21.3
pillow==11.2.1
pycparser==2.22
Pygments==2.12.0
pyparsing==3.0.9
pyserial==3.5
python-barcode==0.13.1
pyusb==1.2.1
PyYAML==6.0.2
qrcode==7.3.1
rich==12.4.4
setuptools==80.9.0
six==1.16.0
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"]
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'}
# Output the config file
@@ -61,6 +71,9 @@ printer.init_printer()
# Web routes
web = Web(app, printer)
if __name__ == "__main__":
app.run(ssl_context='adhoc')
limiter = Limiter(
app,
@@ -103,15 +116,16 @@ def api_print_sms():
txt = request.form["txt"]
sign = request.form["signature"]
except Exception as e:
flash(e,'error')
redirect(url_for('index'))
app.logger.error(str(e) + " - Whoops, no forms submitted or missing signature.")
return jsonify({'message': 'Error getting the information from the form :' + e}), 500
try:
web.print_sms(txt,sign)
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'])
@limiter.limit("6/minute", override_defaults=False)
@@ -121,43 +135,41 @@ def api_print_image():
try:
sign = request.form["signature"]
except Exception as e:
flash(str(e),'error')
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':
# check if the post request has the file part
try:
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 ?")
return redirect(url_for("index"))
return jsonify({'message': 'No file found. Did you use the good form ?'}), 500
else:
file = request.files['img']
except Exception as e:
if sign is not None and photo is not None:
pass
else:
flash(str(e), 'error')
app.logger.error("Couldn't get an image nor signature : " + str(e))
app.logger.error('Error getting the files :' + e)
return jsonify({'message': 'Error getting the files :' + e}), 500
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
app.logger.error("Submitted file has no filename !")
flash('No file submitted, please select a file','error')
return redirect(url_for("index"))
return jsonify({'message': "Submitted file has no filename !" + e}), 500
try:
app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign)
except Exception as e:
pass
else:
flash('Cannot access to page with this method.','error')
app.logger.debug('Bad access type to this API.')
app.logger.error("The image could not be printed : " + e )
return jsonify({'message': "The image could not be printed :" + e}), 500
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')
@limiter.limit("1/second", override_defaults=False)
@@ -174,10 +186,12 @@ def logout_page():
@app.errorhandler(429)
def ratelimit_handler(e):
flash("Rate limit reached, please slow down :) ( Currently at "+ e.description + ")", 'error')
app.logger.debug('Rate limit reached ' + e)
return redirect(url_for("index"))
@app.route("/ping")
@limiter.exempt
def ping():
flash("🏓 Pong !",'info')
app.logger.debug('🏓 Pong !')
return redirect(url_for("index"))

View File

@@ -177,7 +177,7 @@ class Printer(object):
def process_image(self, path):
brightness_factor = 1.5 # Used only if image is too dark
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_height = 1000

View File

@@ -190,6 +190,9 @@ async function get_webcam(options){
printButton.removeAttribute("disabled","");
current_stream = stream;
video.srcObject = stream;
video.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('playsinline', '')
video.play();
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="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>
<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 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">