Added QR code/barcode integration [closes #119]

This commit is contained in:
Fabio Manganiello 2020-03-10 00:07:21 +01:00
parent c26d456109
commit c9db887505
10 changed files with 195 additions and 0 deletions

View file

@ -246,6 +246,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
'pvcheetah',
'pyotp',
'linode_api4',
'pyzbar',
]
sys.path.insert(0, os.path.abspath('../..'))

View file

@ -0,0 +1,5 @@
``platypush.plugins.qrcode``
============================
.. automodule:: platypush.plugins.qrcode
:members:

View file

@ -0,0 +1,5 @@
``platypush.message.response.qrcode``
=====================================
.. automodule:: platypush.message.response.qrcode
:members:

View file

@ -87,6 +87,7 @@ Plugins
platypush/plugins/ping.rst
platypush/plugins/printer.cups.rst
platypush/plugins/pushbullet.rst
platypush/plugins/qrcode.rst
platypush/plugins/redis.rst
platypush/plugins/sensor.rst
platypush/plugins/serial.rst

View file

@ -15,6 +15,7 @@ Responses
platypush/responses/pihole.rst
platypush/responses/ping.rst
platypush/responses/printer.cups.rst
platypush/responses/qrcode.rst
platypush/responses/stt.rst
platypush/responses/system.rst
platypush/responses/todoist.rst

View file

@ -0,0 +1,33 @@
import base64
from flask import abort, request, Blueprint, Response
from platypush.backend.http.app import template_folder
from platypush.context import get_plugin
from platypush.plugins.qrcode import QrcodePlugin
qrcode = Blueprint('qrcode', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
qrcode,
]
@qrcode.route('/qrcode', methods=['GET'])
def generate_code():
"""
This route can be used to generate a QR code given a ``content`` parameter.
"""
content = request.args.get('content')
if not content:
abort(400, 'Expected content parmeter')
plugin: QrcodePlugin = get_plugin('qrcode')
response = plugin.generate(content, format='png').output
data = base64.decodebytes(response['data'].encode())
return Response(data, mimetype='image/png')
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,62 @@
import base64
from typing import Optional, List
from pyzbar.pyzbar import Decoded
from pyzbar.locations import Rect
from platypush.message import Mapping
from platypush.message.response import Response
class QrcodeResponse(Response):
pass
class QrcodeGeneratedResponse(QrcodeResponse):
# noinspection PyShadowingBuiltins
def __init__(self,
content: str,
format: str,
data: Optional[str] = None,
image_file: Optional[str] = None,
*args, **kwargs):
super().__init__(*args, output={
'text': content,
'data': data,
'format': format,
'image_file': image_file,
}, **kwargs)
class RectModel(Mapping):
def __init__(self, rect: Rect):
super().__init__()
self.left = rect.left
self.top = rect.top
self.width = rect.width
self.height = rect.height
class ResultModel(Mapping):
def __init__(self, result: Decoded, *args, **kwargs):
super().__init__(*args, **kwargs)
# noinspection PyBroadException
try:
data = result.data.decode()
except:
data = base64.encodebytes(result.data).decode()
self.data = data
self.type = result.type
self.rect = dict(RectModel(result.rect)) if result.rect else {}
class QrcodeDecodedResponse(QrcodeResponse):
def __init__(self, results: List[Decoded], image_file: Optional[str] = None, *args, **kwargs):
super().__init__(*args, output={
'image_file': image_file,
'results': [dict(ResultModel(result)) for result in results],
}, **kwargs)
# vim:sw=4:ts=4:et:

View file

@ -0,0 +1,80 @@
import base64
import io
import os
from typing import Optional
from platypush.message.response.qrcode import QrcodeGeneratedResponse, QrcodeDecodedResponse
from platypush.plugins import Plugin, action
class QrcodePlugin(Plugin):
"""
Plugin to generate and scan QR and bar codes.
Requires:
* **qrcode** (``pip install 'qrcode[pil]'``) for QR generation.
* **pyzbar** (``pip install pyzbar``) for decoding code from images.
* **Pillow** (``pip install Pillow``) for image management.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# noinspection PyShadowingBuiltins
@action
def generate(self, content: str, output_file: Optional[str] = None, show: bool = False,
format: str = 'png') -> QrcodeGeneratedResponse:
"""
Generate a QR code.
If you configured the :class`:platypush.backend.http.HttpBackend` then you can also generate
codes directly from the browser through ``http://<host>:<port>/qrcode?content=...``.
:param content: Text, URL or content of the QR code.
:param output_file: If set then the QR code will be exported in the specified image file.
Otherwise, a base64-encoded representation of its binary content will be returned in
the response as ``data``.
:param show: If True, and if the device where the application runs has an active display,
then the generated QR code will be shown on display.
:param format: Output image format (default: ``png``).
:return: :class:`platypush.message.response.qrcode.QrcodeGeneratedResponse`.
"""
import qrcode
qr = qrcode.make(content)
img = qr.get_image()
ret = {
'content': content,
'format': format,
}
if show:
img.show()
if output_file:
output_file = os.path.abspath(os.path.expanduser(output_file))
img.save(output_file, format=format)
ret['image_file'] = output_file
else:
f = io.BytesIO()
img.save(f, format=format)
ret['data'] = base64.encodebytes(f.getvalue()).decode()
return QrcodeGeneratedResponse(**ret)
@action
def decode(self, image_file: str) -> QrcodeDecodedResponse:
"""
Decode a QR code from an image file.
:param image_file: Path of the image file.
"""
from pyzbar import pyzbar
from PIL import Image
image_file = os.path.abspath(os.path.expanduser(image_file))
img = Image.open(image_file)
results = pyzbar.decode(img)
return QrcodeDecodedResponse(results)
# vim:sw=4:ts=4:et:

View file

@ -246,3 +246,8 @@ croniter
# Support for Linode integration
# linode_api4
# Support for QR codes
# qrcode
# Pillow
# pyzbar

View file

@ -293,5 +293,7 @@ setup(
'otp': ['pyotp'],
# Support for Linode integration
'linode': ['linode_api4'],
# Support for QR codes
'qrcode': ['qrcode[pil]', 'Pillow', 'pyzbar'],
},
)