diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..863356e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/Users/katharina/.local/share/virtualenvs/brother_ql_web-SwH3dpgY/bin/python" +} \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..d6657e6 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +brother-ql = "*" +bottle = "*" +Jinja2 = "*" +pylibdmtx = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..9fad60e --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,170 @@ +{ + "_meta": { + "hash": { + "sha256": "da7a1334691b7adf61fae33efd94321812b4a0180e56621a5ba349a940004db0" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "bottle": { + "hashes": [ + "sha256:a9d73ffcbc6a1345ca2d7949638db46349f5b2b77dac65d6494d45c23628da2c", + "sha256:f6b8a34fe9aa406f9813c02990db72ca69ce6a158b5b156d2c41f345016a723d" + ], + "index": "pypi", + "version": "==0.12.19" + }, + "brother-ql": { + "hashes": [ + "sha256:1a7a3faa56924c4e7670da82016486fc310af339acb2ffb98bd863a2667ab2c2", + "sha256:1f5c57a039f012c9c204397f470001f76ebb7483421ebde985d0cb3c614e8660" + ], + "index": "pypi", + "version": "==0.9.4" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.2" + }, + "jinja2": { + "hashes": [ + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" + ], + "index": "pypi", + "version": "==3.0.1" + }, + "markupsafe": { + "hashes": [ + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.1" + }, + "packbits": { + "hashes": [ + "sha256:bc6b370bb34e04ac8cfa835e06c0484380affc6d593adb8009dd6c0f7bfff034" + ], + "version": "==0.6" + }, + "pillow": { + "hashes": [ + "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5", + "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4", + "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9", + "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a", + "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9", + "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727", + "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120", + "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c", + "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2", + "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797", + "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b", + "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f", + "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef", + "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232", + "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb", + "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9", + "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812", + "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178", + "sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291", + "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b", + "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5", + "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b", + "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1", + "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713", + "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4", + "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484", + "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c", + "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9", + "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388", + "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d", + "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602", + "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9", + "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e", + "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2" + ], + "markers": "python_version >= '3.6'", + "version": "==8.2.0" + }, + "pylibdmtx": { + "hashes": [ + "sha256:267d6e75b5a62c177697f35e154249d24baa33cb8a331079fbeeffd7858cb848", + "sha256:6c75c4c9463e3872b3ab10e0b2fd8d906a9cede6f865e924a4f2d9ec766f0a2d", + "sha256:709f80fafa1f79bf02c9393a8931fd1e7c1e04f8bf3ca9112fa2229751682bed" + ], + "index": "pypi", + "version": "==0.1.9" + }, + "pyusb": { + "hashes": [ + "sha256:7d449ad916ce58aff60b89aae0b65ac130f289c24d6a5b7b317742eccffafc38", + "sha256:f18eb813d3a1439918071234589162c2f209a19adbeffeb1377ce078a4aebc70" + ], + "version": "==1.1.1" + } + }, + "develop": {} +} diff --git a/brother_ql_web.py b/brother_ql_web.py index ec48319..dcdeaaf 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -70,6 +70,9 @@ def get_label_context(request): 'margin_bottom': float(d.get('margin_bottom', 45))/100., 'margin_left': float(d.get('margin_left', 35))/100., 'margin_right': float(d.get('margin_right', 35))/100., + 'grocycode': d.get('grocycode', None), + 'product': d.get('product', None), + 'duedate': d.get('duedate', None) } context['margin_top'] = int(context['font_size']*context['margin_top']) context['margin_bottom'] = int(context['font_size']*context['margin_bottom']) @@ -145,6 +148,63 @@ def create_label_im(text, **kwargs): draw.multiline_text(offset, text, kwargs['fill_color'], font=im_font, align=kwargs['align']) return im +def create_label_grocy(text, **kwargs): + product = kwargs['product'] + duedate = kwargs['duedate'] + grocycode = kwargs['grocycode'] + + + # prepare grocycode datamatrix + from pylibdmtx.pylibdmtx import encode + encoded = encode(grocycode.encode('utf8'), size="SquareAuto") # adjusted for 300x300 dpi - results in DM code roughly 5x5mm + datamatrix = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels) + datamatrix.save('/tmp/dmtx.png') + + product_font = ImageFont.truetype(kwargs['font_path'], 100) + duedate_font = ImageFont.truetype(kwargs['font_path'], 60) + width = kwargs['width'] + height = 200 + if kwargs['orientation'] == 'rotated': + tw = width + width = height + height = tw + + im = Image.new('RGB', (width, height), 'white') + draw = ImageDraw.Draw(im) + if kwargs['orientation'] == 'standard': + vertical_offset = kwargs['margin_top'] + horizontal_offset = kwargs['margin_left'] + elif kwargs['orientation'] == 'rotated': + vertical_offset = kwargs['margin_top'] + horizontal_offset = kwargs['margin_left'] + datamatrix.transpose(Image.ROTATE_270) + + im.paste(datamatrix, (horizontal_offset, vertical_offset, horizontal_offset + encoded.width, vertical_offset + encoded.height)) + + if kwargs['orientation'] == 'standard': + vertical_offset += -10 + horizontal_offset = encoded.width + 40 + elif kwargs['orientation'] == 'rotated': + vertical_offset += encoded.width + 40 + horizontal_offset += -10 + + textoffset = horizontal_offset, vertical_offset + + draw.text(textoffset, product, kwargs['fill_color'], font=product_font) + + if duedate is not None: + if kwargs['orientation'] == 'standard': + vertical_offset += 110 + horizontal_offset = kwargs['margin_left'] + elif kwargs['orientation'] == 'rotated': + vertical_offset = kwargs['margin_left'] + horizontal_offset += 110 + textoffset = horizontal_offset, vertical_offset + + draw.text(textoffset, duedate, kwargs['fill_color'], font=duedate_font) + + return im + @get('/api/preview/text') @post('/api/preview/text') def get_preview_image(): @@ -165,6 +225,56 @@ def image_to_png_bytes(im): image_buffer.seek(0) return image_buffer.read() +@post('/api/print/grocy') +@get('/api/print/grocy') +def print_grocy(): + """ + API endpoint to consume the grocy label webhook. + + returns; JSON + """ + + return_dict = {'success' : False } + + try: + context = get_label_context(request) + except LookupError as e: + return_dict['error'] = e.msg + return return_dict + + if context['product'] is None: + return_dict['error'] = 'Please provide the product for the label' + return return_dict + + im = create_label_grocy(**context) + if DEBUG: im.save('sample-out.png') + + if context['kind'] == ENDLESS_LABEL: + rotate = 0 if context['orientation'] == 'standard' else 90 + elif context['kind'] in (ROUND_DIE_CUT_LABEL, DIE_CUT_LABEL): + rotate = 'auto' + + qlr = BrotherQLRaster(CONFIG['PRINTER']['MODEL']) + red = False + if 'red' in context['label_size']: + red = True + create_label(qlr, im, context['label_size'], red=red, threshold=context['threshold'], cut=True, rotate=rotate) + + if not DEBUG: + try: + be = BACKEND_CLASS(CONFIG['PRINTER']['PRINTER']) + be.write(qlr.data) + be.dispose() + del be + except Exception as e: + return_dict['message'] = str(e) + logger.warning('Exception happened: %s', e) + return return_dict + + return_dict['success'] = True + if DEBUG: return_dict['data'] = str(qlr.data) + return return_dict + @post('/api/print/text') @get('/api/print/text') def print_text():