diff --git a/requirements.txt b/requirements.txt index 631e4d2..becc2ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,31 @@ -Adafruit-Thermal>=1.1.0 -Click>=7.0 -Flask>=1.0.2 -Flask-Limiter>=1.0.1 -itsdangerous>=0.24 -Jinja2>=2.10 -limits>=1.3 -MarkupSafe>=1.0 -Pillow>=5.3.0 -pyserial>=3.4 -six>=1.11.0 -Unidecode>=1.0.22 -Werkzeug>=0.14.1 +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~=1.9 +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~=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 diff --git a/run.sh b/run.sh index bc38cda..8b5a274 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,7 @@ +virtualenv . + +pip install -r requirements.txt + export FLASK_APP=src/main.py export FLASK_ENV=development flask run --host 192.168.0.42 --debugger --eager-loading diff --git a/src/main.py b/src/main.py index 9599dd9..57ee70c 100644 --- a/src/main.py +++ b/src/main.py @@ -51,6 +51,7 @@ app.secret_key = configuration_file["secrets"]["flask_secret_key"] app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['ALLOWED_EXTENSIONS'] = ALLOWED_EXTENSIONS app.config['MAX_CONTENT_LENGTH'] = 3 * 1000 * 1000 # Maximum 3Mb for a file upload +app.config['TEMPLATES_AUTO_RELOAD'] = True # Printer connection # Uses the class defined in the printer.py file @@ -64,7 +65,7 @@ web = Web(app, printer) limiter = Limiter( app, key_func=get_remote_address, - default_limits=["200 per day", "50 per hour"] + default_limits=["1500 per day", "500 per hour"] ) @app.route('/') @@ -73,6 +74,12 @@ def index(): app.logger.debug("Loading index") return render_template('index.html') +@app.route('/webcam') +@limiter.limit("1/second", override_defaults=False) +def webcam(): + app.logger.debug("Loading webcam interface") + return render_template('webcam.html') + # API routes # The api has the following methods # api/print/{sms,img,letter,qr,barcode} @@ -115,17 +122,24 @@ def api_print_image(): sign = request.form["signature"] except Exception as e: 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': # check if the post request has the file part - - if 'img' not in request.files: - flash('No file found. Did you use the good form ?', 'error') - return redirect(url_for("index")) - else: - file = request.files['img'] - + 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")) + 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)) # If the user does not select a file, the browser submits an # empty file without a filename. diff --git a/src/static/css/style.css b/src/static/css/style.css index 3ba4234..05ea9b8 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -1,11 +1,7 @@ -body { +/* body { margin-left: 20%; margin-right: 20%; -} - -button { - margin-top: 3em; -} +} */ /*// Small devices (landscape phones, 576px and up)*/ @media (min-width: 576px) { @@ -14,8 +10,8 @@ button { /*// Medium devices (tablets, 768px and up)*/ @media (min-width: 768px) { - margin-left: 0%; - margin-right: 0%; + margin-left: 0; + margin-right: 0; } /*// Large devices (desktops, 992px and up)*/ diff --git a/src/static/js/webcam.js b/src/static/js/webcam.js new file mode 100644 index 0000000..2bf864c --- /dev/null +++ b/src/static/js/webcam.js @@ -0,0 +1,275 @@ +let streaming; +var current_stream; +var current_camera_is; + +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() === true ){ + setup_events(); + clear_canvas(); + } else { + no_webcam_error(); + console.log("Seems like it's impossible to get a webcam."); + } +} + +function check_webcam(){ + console.log("Cheking for a camera..."); + if (get_front_webcam()) { + console.log("Got front camera !"); + return true; + } + + if (get_any_webcam()) { + console.log("Got a webcam !"); + return true; + } + console.log("Nope"); + return false; +} + +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",""); + switch_cameras.removeAttribute("disabled",""); + + + } + }, false); + + switch_cameras.addEventListener('click', function(ev) { + flip_cameras(); + }, false ); + + printButton.addEventListener('click', function(ev){ + data = take_picture(); + try { + print_picture(data); + } catch (e) { + alert("Failed to print a picture because : " + e); + } + + 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(function(response) { console.log('Success:', response); alert("Picture printed."); } , true) + .catch(error => console.error('Error:', error), false); + +} + +function flip_cameras(){ + switch (current_camera_is) { + case "front": + try { + get_any_webcam(); + } catch (e) { + console.log("Could not get another camera"); + get_front_webcam(); + } + break; + case "any": + try { + get_front_webcam(); + } catch (e) { + console.log("Could not get another camera"); + get_any_webcam(); + } + break; + default: + console.log("Impossible to switch cameras : none is selected."); + } +} + +function stop_video_streams(){ + console.log("Stopping existing video streams."); + // Stop the tracks + try { + if (current_stream) { + const tracks = current_stream.getTracks(); + tracks.forEach(track => track.stop()); + console.log("Stopped playing current videostreams."); + return true; + } else { + console.log("No streams currently playing."); + } + } catch (e) { + console.log("Could not stop playing current streams." + e); + } +} + +async function get_webcam(options){ + stop_video_streams(); + + try { + await navigator.mediaDevices.getUserMedia(options) + .then(function(stream) { + // on success, stream it in video tag + // the video tag is hidden, as is the canvas. + console.log("Got a camera ( generic )"); + + printButton.removeAttribute("disabled",""); + current_stream = stream; + video.srcObject = stream; + video.play(); + return true; + }) + .catch(function(err) { + console.log("Didn't manage to get a camera :" + err); + return false; + }); + } catch (err) { + console.log("Didn't manage to get a camera :" + err); + return false; + } +} + +function get_any_webcam(){ + var camera_options = { + video: { + facingMode: 'environment', // Or 'environment' if we want a camera facing away + }, + audio: false + }; + + if(get_webcam(camera_options)){ + console.log("Got any camera, or environment camera."); + current_camera_is = "any"; + return true; + } else { + return false; + } +} + +function get_front_webcam(){ + // We try to start with the front facing camera, + // if we have no support, we switch back to a normal camera. + try { + const supports = navigator.mediaDevices.getSupportedConstraints(); + if (!supports['facingMode']) { + throw new Error("This browser does not support facingMode!"); + } else { + var camera_options = { + video: { + facingMode: 'user', // Or 'environment' if we want a camera facing away + }, + audio: false + }; + } + } catch (e) { + console.log("Resetting to default camera : " + e); + var camera_options = { + video: true, + audio: false + }; + } + + if(get_webcam(camera_options)){ + console.log("Got the front camera"); + current_camera_is = "front"; + return true; + } else { + return false; + } +} + +function no_webcam_error(){ + 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); + throw new Error("Unable to get a video device, stopping the photobooth."); +} + +window.addEventListener('load', startup, false); diff --git a/src/templates/banner.html b/src/templates/banner.html new file mode 100644 index 0000000..17b82a5 --- /dev/null +++ b/src/templates/banner.html @@ -0,0 +1,21 @@ +
+