1 Commits

Author SHA1 Message Date
n07070
0601fe8190 Add information on contributions 2026-01-04 12:07:10 +01:00
12 changed files with 238 additions and 426 deletions

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
__pycache__/
*.py[cod]
*$py.class
CACHEDIR.TAG
# C extensions
*.so

View File

@@ -57,10 +57,16 @@ $ export FLASK_APP=src/main.py
$ flask run
```
This command should start a web server with which you can test your configuration. If you plan on exposing your printer to the Internet, and give it an IP / URL, _please, please, please_, don't run it this way. Look at Flask's documentation and read about running a production server. It's a little more work, but it will prevent your computer/server being hacked in too easily.
This command should start a web server with which you can test your configuration. If you plan on exposing your printer to the Internet, and give it an IP / URL, _please, please, please_, don't run it this way. Look at Flask's documentation and read about running a production server. It's a little more work, but it will prevent your computer/server being hacked in too easily.
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

@@ -6,12 +6,6 @@ vendor_id = 0x04b8
device_id = 0x0e28
upload_folder = "src/static/uploads"
# Raspberry Pi Configuration
[rpi]
button_gpio_port_number = 17
indicator_gpio_port_number = 18
flash = true
# Users = Password
[users]
admin = "admin"

View File

@@ -5,12 +5,6 @@
vendor_id = "0x04b8"
device_id = "0x0e28"
# Raspberry Pi Configuration
[rpi]
button_gpio_port_number = 17
indicator_gpio_port_number = 18
flash = true
# Users = Password
[users]
admin = "admin"

View File

@@ -1,36 +1,36 @@
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
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

@@ -6,17 +6,11 @@
# Then we build the API around Flask,
# Then we build the web interface, using the simple Jinja2 templating.
# We support two modes :
# The first is a simple mode, where a computer, connected to a thermal printer, runs this program and exposes a web interface that makes use of the client's camera
# The seconde is booth mode, where a Raspberry Pi is connected to a thermal printer, a button and a flash. The web interface exists but may not be used, as the press of the button with take a picture and activate the flash while simply informing the web page.
# Following are the librairies we import,
from flask import Flask, request, render_template, flash, abort, redirect, url_for, make_response, jsonify # Used for the web framework
from flask_socketio import SocketIO
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from printer import Printer # The wrapper for the printer class
from raspberry import Raspberry # The Raspberry pi control Class
from web import Web # Wrapper for the web routes and API
import toml # Used for the config file parsing
import pprint # To pretty print JSON
@@ -25,8 +19,6 @@ import os # For VARS from the shell.
# Variables
app = Flask(__name__)
socketio = SocketIO(app)
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
# Load the configuration file
try:
@@ -43,7 +35,6 @@ except Exception as e:
exit(-1)
app.logger.debug("Config file loaded !")
# Define the USB connections here.
vendor_id = configuration_file["printer"]["vendor_id"]
device_id = configuration_file["printer"]["device_id"]
@@ -53,12 +44,13 @@ try:
os.mkdir(UPLOAD_FOLDER)
app.logger.debug(f"Directory '{UPLOAD_FOLDER}' created successfully.")
except FileExistsError:
app.logger.debug(f"Directory '{UPLOAD_FOLDER}' already exists.")
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
if os.getenv('LIPY_DEBUG') == True:
@@ -76,28 +68,18 @@ app.config['TEMPLATES_AUTO_RELOAD'] = True
printer = Printer(app,0x04b8, 0x0e28)
printer.init_printer()
# Find out if we are running on a Raspberry Pi
rpi = Raspberry(printer, app, socketio, configuration_file['rpi']['button_gpio_port_number'], configuration_file['rpi']['indicator_gpio_port_number'], configuration_file['rpi']['flash'] )
RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi()
if RASPBERRY_PI_CONNECTED:
rpi.initialise_gpio()
#############################################################
# Web & API routes
#############################################################
# Web routes
web = Web(app, printer)
if __name__ == "__main__":
app.run(ssl_context='adhoc')
limiter = Limiter(
get_remote_address,
app=app,
app,
key_func=get_remote_address,
default_limits=["1500 per day", "500 per hour"]
)
)
@app.route('/')
@limiter.limit("1/second", override_defaults=False)
@@ -134,12 +116,16 @@ def api_print_sms():
txt = request.form["txt"]
sign = request.form["signature"]
except Exception as e:
app.logger.error("Whoops, no forms submitted or missing signature :" + str(e))
flash("Whoops, no forms submitted or missing signature : " + str(e))
return 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
web.print_sms(txt,sign)
return redirect(url_for("index"))
try:
web.print_sms(txt,sign)
except Exception as e:
return jsonify({'message': 'Error printing the SMS:' + e}), 500
return jsonify({'message': 'Message printed'}), 200
@app.route('/api/print/img', methods=['POST'])
@limiter.limit("6/minute", override_defaults=False)
@@ -149,61 +135,40 @@ def api_print_image():
try:
sign = request.form["signature"]
except Exception as e:
app.logger.error("Whoops, no forms submitted or missing signature :" + str(e))
flash("Whoops, no forms submitted or missing signature : " + str(e))
return 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
if request.method == 'POST':
# check if the post request has the file part
try:
if 'img' not in request.files:
app.logger.error("Whoops, no images submitted :" + str(e))
flash("Whoops, no images submitted : " + str(e))
app.logger.error("No file found. Did you use the good form ?")
return jsonify({'message': 'No file found. Did you use the good form ?'}), 500
else:
file = request.files['img']
except Exception as e:
app.logger.error('Error getting the files :' + str(e))
flash('Error getting the files :' + str(e))
return redirect(url_for("index"))
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("Submitted file has no filename !")
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:
app.logger.error("The image could not be printed because : " + str(e) )
flash("The image could not be printed because : " + str(e))
return redirect(url_for("index"))
app.logger.error("The image could not be printed : " + e )
return jsonify({'message': "The image could not be printed :" + e}), 500
else:
app.logger.error("Method not allowed")
flash("Method not allowed")
return redirect(url_for("index"))
flash('Picture printed ! '),
return redirect(url_for("index"))
@app.route('/api/camera/picture')
def camera_picture():
# Returns a picture taken by the camera
if RASPBERRY_PI_CONNECTED:
try:
return rpi.camera_picture()
except Exception as e:
return jsonify({'message': 'Error getting the stream : ' + e}), 500
else:
return jsonify({'message': 'No camera present'}), 500
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
@app.route('/login')
@@ -221,7 +186,7 @@ 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 ' + str(e.description))
app.logger.debug('Rate limit reached ' + e)
return redirect(url_for("index"))
@app.route("/ping")
@@ -230,16 +195,3 @@ def ping():
flash("🏓 Pong !",'info')
app.logger.debug('🏓 Pong !')
return redirect(url_for("index"))
@socketio.on('ping')
def handle_message(data):
app.logger.debug('Received : ' + str(data))
socketio.emit('pong',"Pong !")
@socketio.on('get_camera_status')
def camera_status():
app.logger.debug('Client asked if we had a camera')
if RASPBERRY_PI_CONNECTED:
socketio.emit("camera_status", True)
else:
socketio.emit("camera_status", False)

View File

@@ -48,14 +48,16 @@ class Printer(object):
self.app.logger.error('Printer has no more paper, aborting...')
flash("No more paper on the printer. Sorry.",category='error')
self.printer.close()
raise Exception("No more paper in the printer")
return False
case 1:
self.app.logger.warning('Printer needs paper to be changed very soon ! ')
flash('Printer needs paper to be changed very soon ! ', category='info')
self.printer.close()
return True
case 2:
self.app.logger.debug('Printer has paper, good to go')
self.printer.close()
return True
def init_printer(self):
@@ -97,55 +99,56 @@ class Printer(object):
self.ready = True;
self.printer.close();
self.check_paper()
if not self.check_paper():
return False
return True
def print_sms(self, msg, signature) -> None:
def print_sms(self, msg, signature) -> bool:
clean_msg = str(msg)
clean_signature = str(signature)
if len(clean_msg) > 4096 or len(clean_msg) < 3 :
if not self.check_paper():
return False
if len(clean_msg) > 256 or len(clean_msg) < 3 :
self.app.logger.warning("Could not print message of this length: " + str(len(clean_msg)))
raise Exception("Could not print message of this length :" + str(len(clean_msg)) + ", needs to between 3 and 4096 caracters long.")
flash("Could not print message of this length :" + str(len(clean_msg)) + ", needs to between 3 and 256 caracters long.",category='error')
return False
if len(signature) > 256 or len(signature) < 3:
self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature)))
raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.")
self.app.logger.warning("Could not print message without a signature.")
flash("Could not print message without a signature.",category='error')
return False
self.check_paper()
try:
self.printer.open(self.usb_args);
self.printer.set(align='left', font='a', bold=False, underline=0, width=1, height=1, density=8, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False)
self.printer.set(align='left', font='a', bold=True, underline=1, width=1, height=1, density=6, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False)
self.printer.textln(clean_msg)
self.printer.textln("")
self.printer.set(align='left', font='b', bold=False, underline=1, width=1, height=1, density=9, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False)
self.printer.textln("> " + clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime()))
self.printer.textln("")
self.printer.textln("Printed by LittlePrinter ")
self.printer.textln("n07070.xyz/articles/littleprynter")
self.printer.textln("")
self.printer.cut()
self.printer.close()
except Exception as e:
self.app.logger.error("Unable to print because : " + str(e))
raise e
if not os.getenv('LIPY_DEBUG') == True:
try:
self.printer.open(self.usb_args);
self.printer.set(align='left', font='a', bold=False, underline=0, width=1, height=1, density=8, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False)
self.printer.textln(clean_msg)
self.printer.set(align='left', font='b', bold=False, underline=1, width=1, height=1, density=9, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False)
self.printer.textln("> " + clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime()))
self.printer.cut()
self.printer.close
except Exception as e:
flash("Unable to print because : " + e)
flash("Message printed : " + clean_msg ,category='info')
return True
def print_img(self, path, sign) -> None:
def print_img(self, path, sign):
clean_signature = str(sign)
if len(sign) > 256 or len(sign) < 3:
self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature)))
raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.")
if len(clean_signature) > 256 or len(clean_signature) < 3:
self.app.logger.warning("Could not print message without a signature.")
flash("Could not print message without a signature.",category='error')
return False
if not os.path.isfile(str(path)):
self.app.logger.warning("File does not exist : " + str(path))
raise Exception('The file path for this image :' + str(path) + " wasn't found. Please try again.")
flash('The file path for this image :' + str(path) + " wasn't found. Please try again.", 'error')
return False
else:
self.app.logger.debug("Printing file from " + str(path))
@@ -153,15 +156,11 @@ class Printer(object):
self.app.logger.debug("Proccessing the image")
path = process_image(self, path)
except Exception as e:
flash(str(e))
self.app.logger.error(str(e))
raise e
try:
self.check_paper()
self.printer.open(self.usb_args)
self.printer.textln("Printed by LittlePrinter ")
self.printer.textln("n07070.xyz/articles/littleprynter")
self.printer.textln("> " + clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime()))
self.printer.image(path)
self.printer.cut()
@@ -169,15 +168,16 @@ class Printer(object):
self.app.logger.debug("Printed an image : " + str(path))
os.remove(path)
self.app.logger.debug("Removed image.")
return True
except Exception as e:
self.printer.close()
self.app.logger.error(str(e))
raise e
flash(str(e),'error')
return False
def process_image(self, path):
brightness_factor = 1.5 # Used only if image is too dark
brightness_threshold = 100 # Brightness threshold (0255)
contrast_factor = 0.6 # Less than 1.0 = lower contrast
brightness_threshold = 150 # Brightness threshold (0255)
contrast_factor = 1.2 # Less than 1.0 = lower contrast
max_width = 575
max_height = 1000

View File

@@ -1,106 +0,0 @@
from flask_socketio import SocketIO
from gpiozero import Button, LED
from time import sleep
import io # To check if we are on a Raspberry Pi
import subprocess
class Raspberry(object):
"""
This class will manage three things :
- Connecting to a USB webcam
- Managing a push button
- Activating a flash ( or light )
- Flash an indicator light
"""
def __init__(self, printer, app, socketio, button_gpio_port_number, indicator_gpio_port_number, is_flash_present,):
self.printer = printer
self.socketio = socketio
self.app = app
self.is_flash_present = is_flash_present
self.button_gpio = button_gpio_port_number
self.led_gpio = indicator_gpio_port_number
self.image_path = self.app.config['UPLOAD_FOLDER'] + '/image.jpg'
def initialise_gpio(self):
self.button = Button(self.button_gpio)
self.led = LED(self.led_gpio)
button.when_pressed = self.on_button_pressed()
def is_raspberry_pi(self, raise_on_errors=False):
# Check if we are running on a raspberry pi
try:
with io.open('/proc/cpuinfo', 'r') as cpuinfo:
found = False
for line in cpuinfo:
if line.startswith('Hardware'):
found = True
label, value = line.strip().split(':', 1)
value = value.strip()
if value not in (
'BCM2708',
'BCM2709',
'BCM2711',
'BCM2835',
'BCM2836'
):
self.app.logger.debug('This system does not appear to be a Raspberry Pi.')
return False
if not found:
self.app.logger.error('Couldn\'t get sufficient hardware information from /proc/cpuinfo, Unable to determine if we are on a Raspberry Pi.')
return False
except IOError:
self.app.logger.error('Unable to open `/proc/cpuinfo`.')
return False
self.app.logger.debug('It seems we are on a Raspberry Pi')
return True
def take_picture(self):
# return a path to a picture
try:
subprocess.run(['fswebcam', '--no-banner', self.image_path])
except Exception as e:
self.app.logger.error('Unable to take a picture :' + e)
raise e
return True
def flash(state):
if state:
# turn on the flash
pass
else:
# turn off the flash
pass
def flash_indicator(self):
# The indicator will flash three times before return true
self.led.on()
sleep(0.1)
self.led.off()
sleep(1)
self.led.on()
sleep(0.1)
self.led.off()
sleep(1)
self.led.on()
sleep(0.1)
self.led.off()
def on_button_pressed():
self.socketio.emit('button_pressed') # Notify clients that a button has been pressed on the raspberry pi
self.flash_indicator() # The indicator will flash a countdown LED
self.flash(True)
try:
self.take_picture()
self.printer.print_image(self.image_path)
except Exception as e:
app.logger.error("Could not take a picture after the button press : " + e)
self.led.on()
self.flash(False)
self.socketio.emit('picture_taken') # Notify clients that a picture has been taken

View File

@@ -1,159 +1,39 @@
let streaming;
var current_stream;
var current_camera_is;
var supports_facing_mode;
var camera_options;
var width = document.getElementById("video").parentNode.parentElement.clientWidth;
var height = width / (4 / 3);
var socket = io();
socket.on('connect', function() {
socket.emit('ping', {data: 'I\'m connected!'});
console.log("Sent a ping to the server :)");
});
socket.on('pong', () => {
console.log('Received pong back ! ');
});
socket.on('new_image', () => {
console.log("Received new image event");
const img = document.getElementById('snapshot');
img.src = '/image?' + new Date().getTime(); // Bust cache
});
async function startup(){
function startup(){
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
switch_cameras = document.getElementById('flip')
printButton = document.getElementById('print_button');
console.log("Checking for client webcam capabilities");
// We have a camera_options dictionnary in return, or false if the device does not support webcams.
let client_webcam_capabilities = await check_webcam_capabilies();
if ( client_webcam_capabilities != false ){
get_webcam(client_webcam_capabilities);
if (check_webcam() === true ){
setup_events();
clear_canvas();
} else {
console.log("Checking for server webcam capabilities");
let server_webcam_capabilities = await check_server_camera();
if (server_webcam_capabilities === true ){
console.log("The server has a camera, using it.");
console.log("TODO");
}
else {
no_webcam_error();
console.log("Seems like it's impossible to get a webcam from the client nor the server.");
}
no_webcam_error();
console.log("Seems like it's impossible to get a webcam.");
}
}
async function check_webcam_capabilies(){
console.log("Checking for a the capabilities of the client's media devices");
// We try to start with the front facing camera,
// if we have no support, we switch back to a normal camera.
try {
// We first check if the navigator has a media device
if (!navigator.mediaDevices?.enumerateDevices) {
console.log("enumerateDevices() not supported.");
return false;
} else {
// List cameras and microphones.
console.log("The device has the following media devices : ")
await navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
});
})
.catch((err) => {
console.error(`${err.name}: ${err.message}`);
return false;
});
}
} catch (e) {
console.log("The device does not seem to support webcams " + e);
return false;
function check_webcam(){
console.log("Cheking for a camera...");
if (get_front_webcam()) {
console.log("Got front camera !");
return true;
}
console.log("Checking for the supported constraints of the media devices ");
try {
const supports = navigator.mediaDevices.getSupportedConstraints();
if (!supports['facingMode']) {
throw new Error("This browser does not support facingMode!");
} else {
console.log("The device supports facing mode, selecting the front camera by default");
supports_facing_mode = true;
camera_options = {
video: {
facingMode: 'user', // Or 'environment' if we want a camera facing away
},
audio: false
};
return camera_options;
}
} catch (e) {
console.log("Resetting to default camera : " + e);
supports_facing_mode = false;
camera_options = {
video: true,
audio: false
};
return camera_options
if (get_any_webcam()) {
console.log("Got a webcam !");
return true;
}
}
async function get_webcam(options){
stop_video_streams();
try {
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 the webcam stream");
printButton.removeAttribute("disabled","");
current_stream = stream;
video.srcObject = stream;
video.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('playsinline', '')
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;
}
}
async function check_server_camera(){
console.log("Checking for a camera on the server");
await socket.emit('get_camera_status', (response) => {
if (response){
console.log("Server has a camera !");
return true;
} else {
console.log("The server doesn't seem to have a camera");
return false;
}
});
console.log("Nope");
return false;
}
function setup_events(){
@@ -186,7 +66,7 @@ function setup_events(){
}, false);
switch_cameras.addEventListener('click', function(ev) {
flip_cameras(camera_options);
flip_cameras();
}, false );
printButton.addEventListener('click', function(ev){
@@ -244,7 +124,7 @@ function print_picture(data){
let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
formData.set("img", picture, "picture.png");
formData.set("signature", " Accès Libre ~ 1 An ~ Fête de la Musique Libre @ " + time)
formData.set("signature", "Printed via the webcam @ " + time)
fetch(url, {
method: 'POST', // or 'PUT'
@@ -257,9 +137,26 @@ function print_picture(data){
}
function flip_cameras(camera_options){
if ( supports_facing_mode ) {
get_webcam((camera_options.video.facingMode === 'user' ? 'environment' : 'user'));
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.");
}
}
@@ -280,6 +177,84 @@ function stop_video_streams(){
}
}
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.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('playsinline', '')
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.")

View File

@@ -56,6 +56,4 @@
<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>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
</html>

View File

@@ -99,7 +99,6 @@
</style>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script type="text/javascript" src="{{ url_for('static',filename='js/webcam.js') }}"></script>
<br>

View File

@@ -1,4 +1,4 @@
from flask import Flask, request, flash
from flask import Flask, request
from werkzeug.utils import secure_filename
from printer import Printer
import time
@@ -15,27 +15,28 @@ class Web(object):
def print_sms(self, texte, sign: str):
# TODO: verify the texte before printing it here ?
self.app.logger.debug("Printing : " + str(texte) + " from " + str(sign))
try:
self.printer.print_sms(texte, sign)
except Exception as e:
self.app.logger.error(e)
flash("Error while printing the SMS : "+ str(e))
if not os.getenv('LIPY_DEBUG'):
time.sleep(1)
flash("You message has been printed :)")
return self.printer.print_sms(texte, sign)
def print_image(self, image, sign: str):
def print_image(self, image, sign: str) -> bool:
self.app.logger.debug("Uploading file")
try:
self.app.logger.debug("Uploading file from " + str(sign))
if self.upload_file(image):
self.app.logger.debug("File has been uploaded, printing...")
self.printer.print_img(os.path.join(self.app.config['UPLOAD_FOLDER'], secure_filename(image.filename)), sign)
return True
else:
return False
except Exception as e:
self.app.logger.error(e)
flash("Could not upload file." + str(e))
raise Exception
flash("Your image has been printed :)")
else:
flash("Could not upload file.",'error')
return False
def login(username: str,password: str) -> bool:
pass