-
Notifications
You must be signed in to change notification settings - Fork 0
PhysicalCollectionQRLabelPipeline
title: Physical Collection QR Label Pipeline radar_quadrant: Techniques radar_ring: Trial radar_position: inner
When a physical card collection grows past 50–100 items, visual identification becomes a bottleneck — pulling a card from a top loader to read it defeats the purpose of organised storage. This technique bridges a digital catalog with physical labels, making each card self-identifying without internet access.
A pipeline script reads a card catalog export, encodes identity and pricing data into a QR code per card, and sends the result directly to a thermal label printer — no manual steps, no UI. Scanning a printed label with a phone camera shows the card's identity offline; tapping the embedded link opens a live pricing page when online.
Stage 1 — Export: Coda.io, via a CLI export command, produces a CSV
filtered to rows where a printed boolean column is false.
Stage 2 — Encode: A Python script reads each CSV row and builds a QR payload combining human-readable identity fields with obfuscated pricing fields and a live pricing URL. A deterministic QR code library (Segno) generates an image sized to the target label dimensions at 300 DPI. Because QR generation is deterministic, any label can be regenerated from source data at any time without storing the image file.
Stage 3 — Print: Four paths are available to drive the printer from a
script. Path A — dymon: A community wrapper (dymon) sends each
image via TCP/IP (dymon_pbm --net <IP>) for LAN-connected models. On macOS,
Path A carries significant setup friction: no pre-built binary exists (must
compile from source using Xcode CLT, Homebrew, and cmake); the --usb backend
is Linux-only (a LAN-connected printer is required); Segno's PNG output must
be converted to PBM with pixel calibration before printing; and 550 Turbo
support is unconfirmed in the README. Once running, dymon operates as a
launchd background service with no per-job startup step. Path B — DYMO
Connect Web Service: The DYMO Connect Web Service is a
local REST API (port ~41951) exposed by DYMO Connect for Desktop
(free, macOS Mojave and later). It can be called directly with Python requests
or the lightweight dymopy wrapper (pip install dymopy) -- no .NET
required. Labels are submitted as DYMO XML rather than raw images, which
requires an additional formatting step beyond Segno's PNG output. Path B works
with both the 450 and 550 Turbo on any macOS without compilation. Path C —
HP + Avery sheet: WeasyPrint (brew install weasyprint) renders
an HTML/CSS template sized to Avery 6572 dimensions (2⅝" × 2",
landscape) into a letter-sized PDF containing 15 labels per sheet arranged in
three columns. The label area (66.7mm × 50.8mm) accommodates a vertical stack
— QR code (~40mm square) on top, identity and pricing text below. The PDF is
sent to any CUPS-registered HP inkjet or laser printer with lp -d <printer> -o media=letter output.pdf. No dedicated label printer is required; any HP
printer already registered in macOS handles the job. Path C requires a minimum
batch of one full sheet (15 cards) to avoid wasting stock. Path D — Brother QL: The
brother_ql Python package sends PNG output from Segno directly to
a Brother QL-series thermal printer via USB (pyusb backend, brew install libusb) or network, with no driver or intermediate format conversion required.
Third-party label stock is viable: Brother QL printers do not enforce NFC DRM,
making ~$10/1,000 third-party stock available. A 62mm continuous roll
accommodates the QR payload with more layout margin than DYMO 30334. Hardware
is required: QL-700 (USB only, ~$80) or QL-720NW (network, ~$130). Path D is
documented from the brother_ql README and community reports; it has not been
validated against physical hardware by the author.
Stage 4 — Mark printed: After successful printing, the pipeline calls the
Coda CLI to set printed = true per card, making subsequent runs idempotent.
No official HTML/CSS template exists for Avery 6572 -- Avery publishes Word and PDF formats only. The layout below is estimated from the Avery 6490 glabels definition (the closest size in the open-source glabels database: 2.6875" × 2", same 3×5 layout) and requires a plain-paper calibration cycle before committing to label stock.
-
brew install weasyprintandpip install weasyprint-- WeasyPrint renders HTML/CSS to PDF - Segno already in the pipeline for QR image generation
- Any CUPS-registered HP printer (
lpstat -pto list available printers) - Jinja2 for template rendering (
pip install jinja2)
| Parameter | Value | Source |
|---|---|---|
| Label size | 2.625" × 2" (66.7 × 50.8mm) | Avery confirmed |
| Layout | 3 columns × 5 rows | Calculated |
| Left/right margin | ~0.21875" (5.56mm) | Calculated from label width |
| Top/bottom margin | 0.5" (12.7mm) | Avery 6490 glabels reference |
| Column gap | ~0.09375" (2.38mm) | Avery 6490 glabels reference |
| Row gap | 0 | Avery 6490 glabels reference |
templates/label_6572.html
<!DOCTYPE html>
<html>
<head>
<style>
@page {
size: 8.5in 11in;
margin: 0.5in 0.21875in;
}
body { margin: 0; padding: 0; }
.sheet {
display: grid;
grid-template-columns: repeat(3, 2.625in);
grid-template-rows: repeat(5, 2in);
column-gap: 0.09375in;
row-gap: 0;
}
.label {
width: 2.625in;
height: 2in;
display: flex;
flex-direction: column;
align-items: center;
padding: 2mm;
box-sizing: border-box;
overflow: hidden;
}
.qr { width: 40mm; height: 40mm; }
.text { font-size: 7pt; text-align: center; margin-top: 1mm; line-height: 1.3; }
</style>
</head>
<body>
<div class="sheet">
{% for card in cards %}
<div class="label">
<img class="qr" src="{{ card.qr_path }}">
<div class="text">
<strong>{{ card.set }} | {{ card.rarity }}</strong><br>
{{ card.character }} #{{ card.number }}<br>
{{ card.prices_obfuscated }}
</div>
</div>
{% endfor %}
</div>
</body>
</html>generate_labels.py
from jinja2 import Template
import weasyprint, subprocess
def render_labels(cards, template_path="templates/label_6572.html", out="labels.pdf"):
html = Template(open(template_path).read()).render(cards=cards)
weasyprint.HTML(string=html, base_url=".").write_pdf(out)
subprocess.run(["lp", "-d", "<printer>", "-o", "media=letter", out])This is a one-time step per printer model. It does not repeat per job.
- Run
generate_labels.pywith dummy data; print output on plain paper. - Overlay the plain paper on an Avery 6572 sheet against a light source.
- Measure X and Y shift in mm.
- Adjust
@page marginto absorb the offset -- e.g. a 2mm rightward shift means increase left margin by 2mm and decrease right margin by 2mm. - Reprint on plain paper; confirm alignment; then run on label stock.
The QR payload for a 57mm × 32mm label stays under 200 characters:
Owner: dennislwm
Set: VIV | Rarity: RR | Type: Holo
Character: Pikachu VMAX | Card#: 044/185
Prices: 2250|3500|4000
https://www.pricecharting.com/game/pokemon-vivid-voltage/pikachu-vmax-44
The offline text block satisfies the first priority — card identity without internet. The PriceCharting URL satisfies the second — live market prices with one tap. Margin data (cost, menu, and maximum prices) is encoded as integers offset by a personal constant known only to the owner; the values read as arbitrary numbers to a casual observer. The offset is documented privately, not on the label or in the pipeline code.
Offline-first payload. The QR text is readable by any phone camera without internet. The URL is secondary — useful when online, irrelevant when not.
Obfuscation over omission for pricing. Pricing fields are included in encoded form rather than excluded, so the label remains complete as a standalone artifact. The encoding is not cryptographic; it deters casual reading, not a determined one. Private margin data (the Coda catalog) stays behind authentication and is never encoded into the label.
Pre-cut labels over continuous roll. DYMO 30334 (57mm × 32mm, 1,000 per roll) fit the back of an Ultra Pro standard top loader with comfortable margin on both sides of the 76.2mm outer width. The 550 Turbo includes Automatic Label Recognition (ALR) that reads roll size and remaining count from an NFC chip on the roll -- and rejects rolls that do not carry a recognised chip. This DRM blocks third-party stock at the hardware level. Genuine DYMO 30334 stock costs approximately $42 per 1,000; third-party RFID-compatible alternatives exist but are unverified against the 550's DRM check. Standard thermal stock is adequate for collection storage; durable polypropylene stock (approximately $80 per 1,000) is justified only for labels subject to regular handling or moisture exposure.
Composite key from existing columns. No new identifier column is needed.
Set code, card number, and condition form a composite key
(e.g. VIV-044-NM) that is human-readable, short, and derivable from data
already in the catalog. Duplicate copies of the same card add a suffix counter.
Wired connection over Bluetooth. A wired printer connection eliminates
the open-source Bluetooth driver dependency that community-maintained
alternatives carry. Two hardware options are available: the DYMO LabelWriter
450 (USB only, no NFC DRM, third-party label stock viable at ~$10/1,000) and
the DYMO LabelWriter 550 Turbo (USB + wired LAN, NFC DRM enforces
genuine DYMO stock at ~$42/1,000). On macOS, dymon's USB backend does not
function -- the 450 is only usable via Path B (DYMO Connect Web Service). The
550 Turbo's LAN port is accessible via dymon --net on macOS following the
build fix merged in May 2026. DYMO Connect for Desktop (free,
macOS Mojave and later) supports both models without this restriction.
Ranked by severity:
Already-printed tracking (high). Without a printed boolean in the
catalog, the pipeline reprints every card on each run. The flag must be
maintained in Coda and flipped via CLI after each successful print job.
NFC label DRM (high). The 550 series reads NFC chips on label rolls and
rejects non-genuine DYMO stock at the hardware level. Third-party labels at
$10 per 1,000 -- viable on the older 450 series -- are blocked on the 550
Turbo unless they carry a compatible chip. Genuine DYMO 30334 stock ($42 per
1,000) is the safe baseline. RFID-compatible third-party alternatives exist
but are unverified against the 550 Turbo's DRM check.
dymon macOS friction (high). Path A on macOS requires resolving five
compounding issues before a single label prints: (1) no pre-built binary --
dymon must be compiled from source; Apple Clang compatibility was only fixed
in May 2026 (issue #13); build deps are Xcode CLT, Homebrew,
and cmake; (2) --usb is Linux-only -- the LabelWriter 450 cannot be used
with dymon on macOS; a LAN-connected printer (550 Turbo) is required;
(3) PNG-to-PBM conversion -- dymon expects PBM input; Segno produces PNG;
Pillow converts in one line but pixel dimensions must be calibrated or prints
are blurred or clipped (issue #10); (4) launchd service
registration -- dymon must be running before the pipeline executes; without a
launchd entry every job requires a manual server start; (5) 550 Turbo support
unconfirmed -- the dymon README lists "550" but does not name the Turbo
variant. Path B (DYMO Connect Web Service via dymopy) avoids all five.
DYMO Connect Web Service label format (medium). Path B submits labels as DYMO XML rather than raw images. The pipeline generates PNGs via Segno; an additional step to embed the QR image into a DYMO XML label definition is required before calling the Web Service.
Path C sheet minimum run (medium). Path C prints one full sheet of 15 Avery 6572 labels per job. Runs of fewer than 15 cards waste stock. The pipeline should be batched to near-full sheets; ad-hoc single-card reprints are uneconomical.
Path C template calibration (medium). No official HTML/CSS template exists for Avery 6572 -- Avery publishes Word and PDF formats only. Initial layout dimensions are estimated from the Avery 6490 glabels reference and require a plain-paper calibration cycle before printing on label stock. One-time effort per printer model; does not recur per job. See the Path C Template Calibration section above for the starting layout, sample template, and calibration steps.
Path D hardware unverified (medium). Path D is documented from the brother_ql README and community reports. The author has not validated it against physical hardware; hardware-specific surprises analogous to the dymon 550 Turbo uncertainty in Path A are possible on first run.
Obfuscation offset documentation (medium). The personal offset that encodes pricing must be documented somewhere private and durable. If forgotten, printed labels cannot be decoded. The pipeline code must not contain the offset value.
QR image scaling calibration (medium). Segno generates QR images at a scale specified in pixels per module. A 57mm × 32mm label at 300 DPI is 674 × 378 pixels. This calibration is a one-time step but must be correct before batch printing begins; a mismatched scale produces a cropped or padded label.
Column filtering in the export script (low). The Coda CSV export includes all columns, including raw unobfuscated pricing. The encoding script must explicitly select only the public identity columns for plain text and apply the offset transformation to pricing columns before building the payload.
Path C inkjet durability (low). Inkjet-printed Avery labels are susceptible to smearing under moisture and abrasion. For labels stored in binders or boxes with minimal handling, this is background risk. Laser printing on the same Avery 6572 stock mitigates it; durable inkjet label stock is a further option.
Label durability (low). Standard DYMO thermal labels fade over five to ten years under ambient light and heat. For a long-term collection stored in binders or boxes, this is a background risk rather than an immediate blocker. Durable polypropylene stock (approximately $80 per 1,000) mitigates it at roughly double the label cost.
The pipeline does not manage label placement consistency on top loaders — that requires a physical jig or careful manual alignment. The Coda catalog URL is intentionally excluded from the QR payload: the catalog is behind a login wall and its URL adds length without offline value.
Promoted to Trial on the strength of Path C: the pipeline has been assembled
and validated end-to-end on dbmacm3 running macOS. The implementation
(pylabel) ships as a standalone Python project with a Makefile, test
suite, two calibrated templates (Avery 6572 letter and Avery L7161 A4), and a
confirmed default CUPS printer. make run exports the card catalog via Coda
CLI, generates QR codes with Segno, renders the label sheet via WeasyPrint, and
submits to the HP LaserJet M211dw in one command.
Path A (dymon) remains unsuitable as the primary macOS print path due to five compounding setup issues. Path B (DYMO Connect Web Service via dymopy) is the lower-friction DYMO alternative but requires the desktop service running and an additional DYMO XML formatting step. Path D (Brother QL) is the simplest thermal alternative -- direct PNG input, no DRM, cheaper stock -- but requires dedicated hardware and has not been validated by the author. The batch minimum of 15 cards per sheet run is the primary operational constraint on Path C; ad-hoc single-card reprints are uneconomical.