diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a374170 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.words": [ + "barcodes", + "Datamatrix", + "Grocy", + "libdmtx", + "venv" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index bdfd491..a901456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.3.0 + +- Added support for using QR codes instead of Datamatrix + # 0.2.0 - Scaling barcode by 2x or 4x space permitting diff --git a/README.md b/README.md index b4be288..3ef1347 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ Example Label -This project is intended to be a webhook target for [Grocy](https://github.com/grocy/grocy) to print labels to a brother QL label printer. Datamatrix barcodes are used instead of QR or linear barcodes, this matches what Grocy uses by default. +This project is intended to be a webhook target for [Grocy](https://github.com/grocy/grocy) to print labels to a brother QL label printer. + +Datamatrix or QR codes can be used with Datamatrix being the default. Datamatrix will fit better in smaller labels but I've found aren't as easily read by the Grocy +barcode reader or by the [Android App](https://github.com/patzly/grocy-android). Only die-cut labels are supported as I don't have any endless rolls to test with. @@ -18,23 +21,23 @@ Once you have this running somewhere update your config at `app/data/config.php` Setting('LABEL_PRINTER_HOOK_JSON', false); Setting('FEATURE_FLAG_LABEL_PRINTER', true); - ``` ## Environment Variables The label size and printer are configured via environmental variables. You can also create a `.env` file instead. -| Variable | Default | Description | -| -------- | ------- | ----------- | -| LABEL_SIZE | 62x29 | See the [brother_ql](https://github.com/pklaus/brother_ql) readme for the names of the labels | -| PRINTER_MODEL | QL-500 | The printer model, ie `QL-500`. One of the values accepted by brother_ql | -| PRINTER_PATH | file:///dev/usb/lp1 | Where the printer is found on the system. For network printers use `tcp://printer.address` | -| NAME_FONT | NotoSerif-Regular.ttf | The file name of the font in the fonts directory | -| NAME_FONT_SIZE | 48 | The size of that font | -| NAME_MAX_LINES | 4 | The maximum number of lines to use for the name | -| DUE_DATE_FONT | NotoSerif-Regular.ttf | NotoSerif-Regular.ttf | The file name of the font in the fonts directory | -| DUE_DATE_FONT_SIZE | 30 | The size of that font | +| Variable | Default | Description | +| ------------------ | --------------------- | --------------------------------------------------------------------------------------------- | +| LABEL_SIZE | 62x29 | See the [brother_ql](https://github.com/pklaus/brother_ql) readme for the names of the labels | +| PRINTER_MODEL | QL-500 | The printer model. One of the values accepted by brother_ql | +| PRINTER_PATH | file:///dev/usb/lp1 | Where the printer is found on the system. For network printers use `tcp://printer.address` | +| BARCODE_FORMAT | Datamatrix | `Datamatrix` or `QRCode` | +| NAME_FONT | NotoSerif-Regular.ttf | The file name of the font in the fonts directory | +| NAME_FONT_SIZE | 48 | The size of that font | +| NAME_MAX_LINES | 4 | The maximum number of lines to use for the name | +| DUE_DATE_FONT | NotoSerif-Regular.ttf | The file name of the font in the fonts directory | +| DUE_DATE_FONT_SIZE | 30 | The size of that font | Included fonts are `NotoSans-Regular.ttf` and `NotoSerif-Regular.ttf` diff --git a/app.py b/app/__init__.py similarity index 84% rename from app.py rename to app/__init__.py index 3d1379f..424b33e 100644 --- a/app.py +++ b/app/__init__.py @@ -6,13 +6,14 @@ from brother_ql.labels import ALL_LABELS from brother_ql import BrotherQLRaster, create_label from brother_ql.backends import guess_backend, backend_factory -from imaging import createBarcode, createLabelImage +from app.imaging import createBarcode, createLabelImage load_dotenv() LABEL_SIZE = getenv("LABEL_SIZE", "62x29") PRINTER_MODEL = getenv("PRINTER_MODEL", "QL-500") PRINTER_PATH = getenv("PRINTER_PATH", "file:///dev/usb/lp1") +BARCODE_FORMAT = getenv("BARCODE_FORMAT", "Datamatrix") NAME_FONT = getenv("NAME_FONT", "NotoSerif-Regular.ttf") NAME_FONT_SIZE = int(getenv("NAME_FONT_SIZE", "48")) NAME_MAX_LINES = int(getenv("NAME_MAX_LINES", "4")) @@ -25,8 +26,8 @@ label_spec = next(x for x in ALL_LABELS if x.identifier == LABEL_SIZE) thisDir = path.dirname(path.abspath(__file__)) -nameFont = ImageFont.truetype(path.join(thisDir, "fonts", NAME_FONT), NAME_FONT_SIZE) -ddFont = ImageFont.truetype(path.join(thisDir, "fonts", DUE_DATE_FONT), DUE_DATE_FONT_SIZE) +nameFont = ImageFont.truetype(path.join(thisDir, "..", "fonts", NAME_FONT), NAME_FONT_SIZE) +ddFont = ImageFont.truetype(path.join(thisDir, "..", "fonts", DUE_DATE_FONT), DUE_DATE_FONT_SIZE) app = Flask(__name__) @@ -56,7 +57,7 @@ def get_params(): def print_route(): (name, barcode, dueDate) = get_params(); - label = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode), dueDate, ddFont) + label = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) buf = BytesIO() label.save(buf, format="PNG") @@ -69,7 +70,7 @@ def print_route(): def test(): (name, barcode, dueDate) = get_params(); - img = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode), dueDate, ddFont) + img = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) buf = BytesIO() img.save(buf, format="PNG") buf.seek(0) diff --git a/imaging.py b/app/imaging.py similarity index 88% rename from imaging.py rename to app/imaging.py index 5223ef9..67a371b 100644 --- a/imaging.py +++ b/app/imaging.py @@ -1,11 +1,24 @@ from pylibdmtx.pylibdmtx import encode +import qrcode from PIL import Image, ImageColor, ImageFont, ImageDraw -def createBarcode(text: str): +def createDatamatrix(text: str): encoded = encode(text.encode('utf8'), "Ascii", "ShapeAuto") barcode = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels) return barcode +def createQRCode(text: str): + return qrcode.make(text, box_size = 1) + +def createBarcode(text: str, type: str): + match type: + case "QRCode": + return createQRCode(text) + case "DataMatrix": + return createDatamatrix(text) + case _: + return createDatamatrix(text) + def createLabelImage(labelSize : tuple, text : str, textFont : ImageFont, textMaxLines : int, barcode : Image, dueDate : str, dueDateFont : ImageFont): # increase the size of the barcode if space permits if (barcode.size[1] * 4) < labelSize[1]: @@ -14,6 +27,7 @@ def createLabelImage(labelSize : tuple, text : str, textFont : ImageFont, textMa barcode = barcode.resize((barcode.size[0] * 2, barcode.size[1] * 2), Image.Resampling.NEAREST) label = Image.new("RGB", labelSize, ImageColor.getrgb("#FFF")) + # vertically align barcode barcode_padding = [0, (int)((label.size[1] / 2) - (barcode.size[1] / 2))] label.paste(barcode, barcode_padding) @@ -23,8 +37,6 @@ def createLabelImage(labelSize : tuple, text : str, textFont : ImageFont, textMa nameMaxWidth = label.size[0] - barcode.size[0] nameLeftMargin = (nameMaxWidth - nameTextWidth) / 2 - print((nameTextWidth, nameMaxWidth, nameLeftMargin)) - draw.multiline_text( [barcode.size[0] + nameLeftMargin, 0], nameText, diff --git a/requirements.txt b/requirements.txt index f939759..f0ea0ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ brother_ql >= 0.9, <= 1.0 Pillow == 10.* pylibdmtx == 0.1.* python-dotenv == 1.* -gunicorn \ No newline at end of file +gunicorn +qrcode[pil] == 7.4.* \ No newline at end of file