Compare commits

...

5 Commits

7 changed files with 272 additions and 165 deletions

View File

@ -1,13 +1,31 @@
Adafruit-Thermal>=1.1.0 Adafruit-Thermal==1.1.0
Click>=7.0 appdirs==1.4.4
Flask>=1.0.2 argcomplete==2.0.0
Flask-Limiter>=1.0.1 click==8.1.3
itsdangerous>=0.24 commonmark==0.9.1
Jinja2>=2.10 Deprecated==1.2.13
limits>=1.3 escpos==1.9
MarkupSafe>=1.0 Flask==2.1.2
Pillow>=5.3.0 Flask-Limiter==2.4.5.1
pyserial>=3.4 future==0.18.2
six>=1.11.0 itsdangerous==2.1.2
Unidecode>=1.0.22 Jinja2==3.1.2
Werkzeug>=0.14.1 limits==2.6.1
MarkupSafe==2.1.1
packaging==21.3
Pillow==9.1.0
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

4
run.sh
View File

@ -1,3 +1,7 @@
virtualenv .
pip install -r requirements.txt
export FLASK_APP=src/main.py export FLASK_APP=src/main.py
export FLASK_ENV=development export FLASK_ENV=development
flask run --host 192.168.0.42 --debugger --eager-loading flask run --host 192.168.0.42 --debugger --eager-loading

View File

@ -122,17 +122,24 @@ def api_print_image():
sign = request.form["signature"] sign = request.form["signature"]
except Exception as e: except Exception as e:
flash(str(e),'error') flash(str(e),'error')
redirect(url_for('index')) app.logger.error(str(e) + " - Whoops, no forms submitted or missing signature.")
return redirect(url_for('index'))
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:
if 'img' not in request.files: if 'img' not in request.files:
flash('No file found. Did you use the good form ?', 'error') 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 redirect(url_for("index"))
else: else:
file = request.files['img'] 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))
# 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.

160
src/static/js/webcam.js Normal file
View File

@ -0,0 +1,160 @@
let streaming;
var width = document.getElementById("video").parentNode.parentElement.clientWidth;
var height = width / (4 / 3);
function startup(){
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
switch_cameras = document.getElementById('flip')
printButton = document.getElementById('print_button');
if ( check_webcam() ){
setup_events();
clear_canvas();
} else {
console.log("Seems like it's impossible to get a webcam.");
}
}
function check_webcam(){
try {
// access video stream from webcam
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
})
// on success, stream it in video tag
// the video tag is hidden, as is the canvas.
.then(function(stream) {
switch_cameras.removeAttribute("disabled","");
video.srcObject = stream;
video.play();
})
.catch(function(err) {
console.log("An error occurred: " + err);
});
} catch (err) {
console.log("An error occurred: " + err);
console.log("Seems like they is no webcam available.")
// We disable the print button is it cannot be clicked.
printButton.setAttribute("disabled","");
// We create an alert message.
const frame_div = document.getElementById("image_dither");
frame_div.removeAttribute('class');
const alert_div = document.createElement("div");
alert_div.setAttribute("class", "alert alert-warning");
alert_div.setAttribute("role", "alert");
var alert_message = document.createTextNode("We where unable to get a Webcam device, this page will not work.");
alert_div.appendChild(alert_message);
frame_div.appendChild(alert_div);
console.log("Should be a new div somewhere");
throw new Error("Unable to get a video device, stopping the photobooth.");
} finally {
}
return true;
}
function setup_events(){
// When the video is ready, we start streaming it to the canvas.
// The canvas is hidden, but it still exists in the browser.
video.addEventListener('canplay', function(ev) {
if (!streaming) {
height = video.videoHeight / (video.videoWidth / width);
if (isNaN(height)) {
height = width / (4 / 3);
}
video.setAttribute('width', width);
video.setAttribute('height', height);
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
photo.setAttribute('width', width);
photo.setAttribute('height', height);
streaming = true;
printButton.removeAttribute("disabled","");
}
}, false);
switch_cameras.addEventListener('click', function(ev) {
alert("Just turn your phone.");
}, false );
printButton.addEventListener('click', function(ev){
data = take_picture();
print_picture(data)
ev.preventDefault();
}, false);
}
function clear_canvas() {
var context = canvas.getContext('2d');
context.fillStyle = "#AAA";
context.fillRect(0, 0, canvas.width, canvas.height);
var data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
}
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
function take_picture(){
var context = canvas.getContext('2d');
if (width && height) {
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
} else {
clear_canvas();
}
return data;
}
function print_picture(data){
var url = "/api/print/img"
var picture = dataURLtoFile(data);
const formData = new FormData();
let currentDate = new Date();
let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
formData.set("img", picture, "picture.png");
formData.set("signature", "Printed via the webcam @ " + time)
fetch(url, {
method: 'POST', // or 'PUT'
body: formData,
// headers:{
// 'Content-Type': 'multipart/form-data'
// }
}).then(response => console.log('Success:', response), true)
.catch(error => console.error('Error:', error), false);
}
window.addEventListener('load', startup, false);

View File

@ -51,7 +51,9 @@
{% endblock %} {% endblock %}
<hr> <hr>
<footer>Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter/src/branch/epson-tm-t20iii">Source code - AGPLv3</a></footer>
</div> <footer class="row">
<p class=" text-center">Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter">Source code - AGPLv3</a></p>
</footer>
</body> </body>
</html> </html>

View File

@ -5,11 +5,9 @@
<div class="card"> <div class="card">
<h3 class="card-header">Print a short message</h3> <h3 class="card-header">Print a short message</h3>
<div class="card-body"> <div class="card-body">
<form class="form-group" action="/api/print/sms" method="post"> <form class="form-group" action="/api/print/sms" method="post">
<input class="form-control" type="text" name="txt" placeholder="200 chars or less " maxlength="200" required><br> <input class="form-control" type="text" name="txt" placeholder="200 chars or less " maxlength="200" required><br>
<input class="form-control" type="text" name="signature" placeholder="Signature or pseudo" maxlength="200" required><br> <input class="form-control" type="text" name="signature" placeholder="Signature or pseudo" maxlength="200" required><br>
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer"> <input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form> </form>
</div> </div>
@ -26,5 +24,5 @@
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer"> <input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form> </form>
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@ -2,13 +2,40 @@
{% block content %} {% block content %}
<noscript class="d-block">
<div class="alert alert-warning " role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bug-fill" viewBox="0 0 16 16">
<path d="M4.978.855a.5.5 0 1 0-.956.29l.41 1.352A4.985 4.985 0 0 0 3 6h10a4.985 4.985 0 0 0-1.432-3.503l.41-1.352a.5.5 0 1 0-.956-.29l-.291.956A4.978 4.978 0 0 0 8 1a4.979 4.979 0 0 0-2.731.811l-.29-.956z"/>
<path d="M13 6v1H8.5v8.975A5 5 0 0 0 13 11h.5a.5.5 0 0 1 .5.5v.5a.5.5 0 1 0 1 0v-.5a1.5 1.5 0 0 0-1.5-1.5H13V9h1.5a.5.5 0 0 0 0-1H13V7h.5A1.5 1.5 0 0 0 15 5.5V5a.5.5 0 0 0-1 0v.5a.5.5 0 0 1-.5.5H13zm-5.5 9.975V7H3V6h-.5a.5.5 0 0 1-.5-.5V5a.5.5 0 0 0-1 0v.5A1.5 1.5 0 0 0 2.5 7H3v1H1.5a.5.5 0 0 0 0 1H3v1h-.5A1.5 1.5 0 0 0 1 11.5v.5a.5.5 0 1 0 1 0v-.5a.5.5 0 0 1 .5-.5H3a5 5 0 0 0 4.5 4.975z"/>
</svg>
This page will need Javascript and your authorization to use the webcam to work. It is not possible because Javascript seems to be deactived on this page.
</div>
</noscript>
<div class="card"> <div class="card">
<h3 class="card-header">Photomaton</h3> <h3 class="card-header">Photomaton</h3>
<div class="card-body row"> <div class="card-body row">
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<video id="video" class="align-self-center">Video stream not available.</video> <video id="video" class="align-self-center">Video stream not available.</video>
<div class="image_dither"> <div class="row align-self-center">
<button class=" btn btn-primary" name="print_picture" id="print_button" disabled="">
<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=" btn btn-secondary" name="flip" id="flip" 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">
<path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/>
<path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/>
</svg>
</button>
<div class="image_dither" id="image_dither">
<picture> <picture>
<img id="photo" class="align-self-center"> <img id="photo" class="align-self-center">
</picture> </picture>
@ -17,32 +44,22 @@
<hr> <hr>
<!-- <div class="row row-cols-2"> -->
<!-- <button class="align-self-center col-3 btn btn-danger" name="take_picture" id="startbutton">
<svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" fill="currentColor" class="bi bi-camera" viewBox="0 0 16 16">
<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>
</button>
<br><br> -->
<button class="align-self-center col-3 btn btn-danger" name="print_picture" id="print_button">
<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>
<!-- </div> --> <!-- </div> -->
</div> </div>
</div>
<style media="screen"> <style media="screen">
canvas { canvas {
display: none; display: none;
} }
video { /* video {
display: none; display: none;
} } */
.image_dither { .image_dither {
/* width: 300px; /* width: 300px;
@ -80,106 +97,7 @@
</style> </style>
<script type="text/javascript"> <script type="text/javascript" src="{{ url_for('static',filename='js/webcam.js') }}"></script>
let streaming;
var width = document.getElementById("video").parentNode.parentElement.clientWidth;
var height = width / (4 / 3);
function startup() {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
printButton = document.getElementById('print_button');
// access video stream from webcam
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
})
// on success, stream it in video tag
// the video tag is hidden, as is the canvas.
.then(function(stream) {
video.srcObject = stream;
video.play();
})
.catch(function(err) {
console.log("An error occurred: " + err);
});
video.addEventListener('canplay', function(ev) {
if (!streaming) {
height = video.videoHeight / (video.videoWidth / width);
if (isNaN(height)) {
height = width / (4 / 3);
}
video.setAttribute('width', width);
video.setAttribute('height', height);
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
photo.setAttribute('width', width);
photo.setAttribute('height', height);
streaming = true;
}
}, false);
printButton.addEventListener('click', function(ev){
data = takepicture();
printPicture(data)
ev.preventDefault();
}, false)
}
function clearphoto() {
var context = canvas.getContext('2d');
context.fillStyle = "#AAA";
context.fillRect(0, 0, canvas.width, canvas.height);
var data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
}
function takepicture() {
var context = canvas.getContext('2d');
if (width && height) {
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
} else {
clearphoto();
}
return data;
}
function printPicture(data){
var url = "/api/print/img"
var picture = data;
const formData = new FormData();
formData.append("file", picture);
fetch(url, {
method: 'POST', // or 'PUT'
body: formData,
headers:{
'Content-Type': 'multipart/form-data'
}
}).then(response => console.log('Success:', response), true)
.catch(error => console.error('Error:', error), false);
}
window.addEventListener('load', startup, false);
</script>
<br> <br>