Added QR code/barcode integration [closes #119]
This commit is contained in:
parent
c26d456109
commit
c9db887505
10 changed files with 195 additions and 0 deletions
|
@ -246,6 +246,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
|
||||||
'pvcheetah',
|
'pvcheetah',
|
||||||
'pyotp',
|
'pyotp',
|
||||||
'linode_api4',
|
'linode_api4',
|
||||||
|
'pyzbar',
|
||||||
]
|
]
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
sys.path.insert(0, os.path.abspath('../..'))
|
||||||
|
|
5
docs/source/platypush/plugins/qrcode.rst
Normal file
5
docs/source/platypush/plugins/qrcode.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.qrcode``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.qrcode
|
||||||
|
:members:
|
5
docs/source/platypush/responses/qrcode.rst
Normal file
5
docs/source/platypush/responses/qrcode.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.response.qrcode``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.response.qrcode
|
||||||
|
:members:
|
|
@ -87,6 +87,7 @@ Plugins
|
||||||
platypush/plugins/ping.rst
|
platypush/plugins/ping.rst
|
||||||
platypush/plugins/printer.cups.rst
|
platypush/plugins/printer.cups.rst
|
||||||
platypush/plugins/pushbullet.rst
|
platypush/plugins/pushbullet.rst
|
||||||
|
platypush/plugins/qrcode.rst
|
||||||
platypush/plugins/redis.rst
|
platypush/plugins/redis.rst
|
||||||
platypush/plugins/sensor.rst
|
platypush/plugins/sensor.rst
|
||||||
platypush/plugins/serial.rst
|
platypush/plugins/serial.rst
|
||||||
|
|
|
@ -15,6 +15,7 @@ Responses
|
||||||
platypush/responses/pihole.rst
|
platypush/responses/pihole.rst
|
||||||
platypush/responses/ping.rst
|
platypush/responses/ping.rst
|
||||||
platypush/responses/printer.cups.rst
|
platypush/responses/printer.cups.rst
|
||||||
|
platypush/responses/qrcode.rst
|
||||||
platypush/responses/stt.rst
|
platypush/responses/stt.rst
|
||||||
platypush/responses/system.rst
|
platypush/responses/system.rst
|
||||||
platypush/responses/todoist.rst
|
platypush/responses/todoist.rst
|
||||||
|
|
33
platypush/backend/http/app/routes/plugins/qrcode/__init__.py
Normal file
33
platypush/backend/http/app/routes/plugins/qrcode/__init__.py
Normal 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:
|
62
platypush/message/response/qrcode.py
Normal file
62
platypush/message/response/qrcode.py
Normal 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:
|
80
platypush/plugins/qrcode.py
Normal file
80
platypush/plugins/qrcode.py
Normal 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:
|
|
@ -246,3 +246,8 @@ croniter
|
||||||
|
|
||||||
# Support for Linode integration
|
# Support for Linode integration
|
||||||
# linode_api4
|
# linode_api4
|
||||||
|
|
||||||
|
# Support for QR codes
|
||||||
|
# qrcode
|
||||||
|
# Pillow
|
||||||
|
# pyzbar
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -293,5 +293,7 @@ setup(
|
||||||
'otp': ['pyotp'],
|
'otp': ['pyotp'],
|
||||||
# Support for Linode integration
|
# Support for Linode integration
|
||||||
'linode': ['linode_api4'],
|
'linode': ['linode_api4'],
|
||||||
|
# Support for QR codes
|
||||||
|
'qrcode': ['qrcode[pil]', 'Pillow', 'pyzbar'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue