grocy-ql-labeler/app.py
2025-06-18 15:15:01 +02:00

140 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app.py
import asyncio
from functools import lru_cache
from io import BytesIO
from typing import List
from barcode import Code128
from barcode.writer import ImageWriter
from brother_ql.backends.helpers import send
from brother_ql.conversion import convert
from brother_ql.raster import BrotherQLRaster
from fastapi import FastAPI, HTTPException
from PIL import Image, ImageOps
from pydantic import BaseModel, Field
from pylibdmtx.pylibdmtx import encode
if not hasattr(Image, "ANTIALIAS"):
Image.ANTIALIAS = Image.Resampling.LANCZOS
app = FastAPI(title="QL-1060N Stacked Barcode Printer")
@lru_cache(maxsize=256)
def make_code128(data: str) -> Image.Image:
# 1) Instantiate the barcode object (checksum auto-added for Code128)
code = Code128(data, writer=ImageWriter())
# 2) Render to a PIL.Image, passing any writer options and optional text override
pil_img = code.render(
writer_options={
"module_height": 15.0, # bar height
"module_width": 0.5, # bar thickness
"quiet_zone": 1.0, # margin on each side
"font_size": 10, # text size
"text_distance": 5.0, # gap between bars and text
# you can also pass 'format': 'PNG' here if needed
},
text=data, # explicitly draw your data string under the bars
)
# 3) Convert to 1-bit for your Brother QL workflow
return pil_img.convert("1")
@lru_cache(maxsize=256)
def make_datamatrix(data: str) -> Image.Image:
dm = encode(data.encode("utf-8"))
pil_img = Image.frombytes("RGB", (dm.width, dm.height), dm.pixels)
return pil_img.convert("1")
class PrintRequest(BaseModel):
data: str = Field(..., description="String to encode")
printer_ip: str = Field(
"192.168.178.49", description="Brother QL-1060N IP on your LAN"
)
model: str = Field("QL-1060N", description="Brother model")
label: str = Field("12", description="12 mm continuous tape")
def compose_label(images: List[Image.Image]) -> Image.Image:
"""
Horizontally stack each barcode image with padding,
first scaling them all to the same height.
"""
border, spacing = 10, 20
# 1) Determine target inner height (max of original heights)
inner_h = max(img.height for img in images)
# 2) Scale each image to inner_h, then frame it
framed = []
for img in images:
w, h = img.size
new_w = int(w * inner_h / h)
# high-quality up-scaling
img_scaled = img.resize((new_w, inner_h), Image.LANCZOS)
# add white border
framed_img = ImageOps.expand(img_scaled, border=border, fill="white")
framed.append(framed_img)
# 3) Compute final canvas size
total_w = sum(img.width for img in framed) + spacing * (len(framed) - 1)
final_h = inner_h + 2 * border
# 4) Create canvas and paste each framed image
canvas = Image.new("1", (total_w, final_h), 1)
x = 0
for img in framed:
# vertical center (though theyre all same height now)
y = (final_h - img.height) // 2
canvas.paste(img, (x, y))
x += img.width + spacing
return canvas
def send_to_printer(image: Image.Image, printer_ip: str, model: str, label: str):
qlr = BrotherQLRaster(model)
qlr.exception_on_warning = True
instructions = convert(
qlr=qlr,
images=[image],
label=label,
rotate="90", # landscape
threshold=70.0,
dither=False,
compress=True,
cut=True,
)
send(
instructions=instructions,
printer_identifier=printer_ip,
backend_identifier="network",
blocking=True,
)
@app.post("/print", summary="Print Code128 + DataMatrix side-by-side")
async def print_stacked(req: PrintRequest):
try:
loop = asyncio.get_running_loop()
code_task = loop.run_in_executor(None, make_code128, req.data)
dm_task = loop.run_in_executor(None, make_datamatrix, req.data)
code_img, dm_img = await asyncio.gather(code_task, dm_task)
label_img = compose_label([code_img, dm_img])
send_to_printer(label_img, req.printer_ip, req.model, req.label)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return {
"status": "printed",
"model": req.model,
"label_width_mm": req.label,
"layout": "code128 + datamatrix side by side",
}