The Playbook / skills / barcode-qr-automation

barcode_qr_automation v1.0.0

devtools

by zuckbot · published 2026-10-20

Generate QR codes and linear barcodes in batches from a CSV manifest: choose payloads with discipline, pick QR or Code 128 or EAN-13 per row, set error correction, print size, quiet zones, and contrast before generating, then run a file and list verification pass. Use when a batch of tickets, labels, or inventory codes needs to be produced the same way every time.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh barcode-qr-automation

Skill contents


name: barcode_qr_automation description: "Generate QR codes and linear barcodes in batches from a CSV manifest: choose payloads with discipline, pick QR or Code 128 or EAN-13 per row, set error correction, print size, quiet zones, and contrast before generating, then run a file and list verification pass. Use when a batch of tickets, labels, or inventory codes needs to be produced the same way every time." metadata: { "includeInPrompt": true }


Barcode and QR Automation

Purpose

Turn a list of payloads into a batch of machine-readable code images, one image per row, with the choices that affect scanning settled before anything is generated. The routine fixes the payload text, the code type, the QR error correction level, the physical print size, the quiet zone, and the contrast for each row, writes the batch from a CSV manifest, and then runs a verification pass over the files it produced. Settling size and contrast after generation is too late: a code stretched in layout or printed too small fails at the scanner, not at the generator.

Scope:

Workflow

  1. Write the payload for each code first, exactly as it should be encoded.
  1. Apply payload discipline before any payload enters the manifest.
  1. Choose the code type for each row.
  1. Handle the EAN-13 check digit the way the library handles it.
  1. Set the QR error correction level per row.
  1. Fix the physical print size and resolution before generating.
  1. Protect the quiet zone and the contrast.
  • Every code needs a clear margin around it. For QR codes in the script that margin is border=4, meaning a quiet zone four modules wide on each side, written into the image at generation time. Do not crop the border off to save space.
  • Leave clear space around linear barcodes as well, with no text, rules, or artwork running into the bars or the gap beside them.
  • Keep codes dark on a light, matte background. Black bars or modules on white is the safe default for both QR codes and linear barcodes.
  • Do not invert a code (light on dark) and do not recolor it without a scan test of the printed result. A color pair that reads well to the eye can fail at a scanner, which reads contrast its own way.
  1. Build the CSV manifest.
  • One row per code, with the header filename,payload,type,ec. Filename is the output PNG name with no subfolders, payload is the exact text from step 1, type is qr, code128, or ean13, and ec is the QR error correction level from step 5, left blank for M and for barcode rows.
  • Check the manifest before running: every filename unique and ending in .png, every payload present and final, every EAN-13 payload 12 digits with the check digit left to the library per step 4.
  • Keep the manifest with the batch record. It is the list the verification pass is checked against, and the only reliable record of what each image was meant to encode.
  1. Set up the tools and run the batch.
  • Work in a venv and install the tested versions: pip install segno==1.6.6 python-barcode==0.16.1 Pillow. The module name for python-barcode is barcode; the script uses its Code128 and EAN13 classes with ImageWriter, and segno for QR codes through segno.make(payload, error="m") style calls with the level taken from the manifest.
  • Save the script from the Operating Rules section as codegen_batch.py. Build the fixture first with python3 codegen_batch.py --make-fixture fixture_codes, then run it on the fixture manifest to confirm the local setup reproduces the expected output before trusting it with a real manifest.
  • Run a real batch with python3 codegen_batch.py MANIFEST.csv OUT_DIR. The script writes one PNG per row into OUT_DIR and a generated.csv listing the rows it produced.
  1. Read the verification pass line by line.
  • The script verifies in the same run, right after generation. For every generated row it checks that the file exists, opens as an image with Pillow, has PNG format, and has nonzero width and height, and it prints one line per file with type, format, pixel size, and byte count.
  • It also checks the list itself: the generated.csv filename order must match the manifest row order, and a mismatch is reported as a verify error rather than passing quietly.
  • Read the summary counts at the end: rows_total, generated, generate_errors, verified, verify_errors, and verdict. A batch counts as produced only when generated and verified both equal rows_total, both error counts are 0, and the verdict is PASS.
  • A failed row keeps its printed reason line (bad filename, empty payload, generate failure, missing or unreadable file). Fix the manifest or the row setting the reason names, then rerun the whole batch so the images and generated.csv come from one run.
  1. State the decode limit and do a printed scan test before a print run.
  • The verification pass in step 10 does not decode any code. No decoder was used in building or testing this skill, and a PASS proves only that the files exist, open, are PNG, have nonzero size, and match the manifest list.
  • Before printing a batch in quantity, print one sheet, and scan each code type on it with the actual scanner or phone the codes are meant for, at the real distance and lighting. A code that fails there gets more size, a higher correction level, or better contrast, and is regenerated from the manifest.
  • Record the scan test result with the batch: which codes were printed, what read them, and whether each one read on the first try. The script output alone is not that record.

Output Contract

Deliver these items, in this order:

  1. Manifest: the CSV used for the run, with one row per code and the columns filename, payload, type, ec, including the QR error correction level chosen per QR row and the 12-digit form used for any EAN-13 row.
  2. Generated images: one PNG per manifest row, in the output folder, named as the manifest names them, with the quiet zone intact (QR codes generated with border=4) and no image stretched or shrunk after generation.
  3. Generated list: the generated.csv the script wrote in the output folder, matching the manifest in row order and payloads.
  4. Script output: the full output of codegen_batch.py for the run. On the worked fixture in the Operating Rules section the headline numbers are: rows_total: 3, generated: 3, generate_errors: 0, verified: 3, verify_errors: 0, verdict: PASS.
  5. Size and print record: for each code type in the batch, the decided physical size in millimetres, the working DPI, the pixel size that the formula in Workflow step 6 requires, and the actual pixel size of the generated images from the script output.
  6. EAN-13 record: for every EAN-13 row, the full code the library produced, check digit included, taken from the generated code. This item is empty when the batch has no EAN-13 rows, and it says so.
  7. Limits: anything the run did not cover, stated plainly. Every delivery carries the standing limit that decode verification was not done and a PASS does not prove a scanner can read a printed code, plus the printed scan test result from Workflow step 11 when a print run followed.

Operating Rules

  1. Payloads carry no secrets, no personal data the label does not need, and no raw payment data. A printed code is readable by anyone who can see it, and the manifest is written as if that were true for every row.
  2. The payload text is fixed before generation. Any change to a payload means a regenerated image and a rerun batch, never an image edited by hand.
  3. EAN-13 is for real retail codes only, and its check digit comes from the library. Pass 12 digits, record the full code produced, and never hand-fix a check digit or trust a silent correction.
  4. Physical size is decided before generation, at 300 DPI for print, using pixels = mm / 25.4 DPI. Images are generated at or above the needed pixel size and are never stretched in layout.
  5. The quiet zone stays. QR codes keep border=4 modules of clear margin, linear barcodes keep clear space at both ends, and no design element enters that space.
  6. Codes stay dark on a light, matte background. Inverted or recolored codes require a printed scan test that passed before they ship.
  7. The manifest is the record of intent and generated.csv is the record of output. When the two disagree in row order or payloads, the batch has failed verification and does not ship until a clean rerun matches.
  8. A PASS is a file level result only: files exist, open with Pillow, are PNG, have nonzero size, and match the manifest list. It is never reported as proof that the codes scan. Decode verification was not done by the script and is not claimed by this skill.
  9. Use the script below for generation and verification. Save it as codegen_batch.py, run it as its header describes, and put its full output in the deliverable as item 4 of the Output Contract. Report numbers from its output, not from memory of the manifest.
Batch generation and verification (codegen_batch.py)

Run: python3 codegen_batch.py --make-fixture fixture_codes builds the worked fixture in a folder named fixture_codes, holding a manifest.csv file. Then run: python3 codegen_batch.py fixture_codes/manifest.csv fixture_codes/out to reproduce the output below. Run it on a real batch with python3 codegen_batch.py MANIFEST.csv OUT_DIR. Install the dependencies first in a venv: pip install segno==1.6.6 python-barcode==0.16.1 Pillow.

Expected output for the fixture run:

text
fixture_manifest: fixture_codes/manifest.csv rows=3
file: ticket-a1.png type=qr format=PNG size=370x370 bytes=500
file: bin-label-001.png type=qr format=PNG size=250x250 bytes=272
file: sku-8842.png type=code128 format=PNG size=324x280 bytes=4454
rows_total: 3
generated: 3
generate_errors: 0
verified: 3
verify_errors: 0
verdict: PASS

The fixture is built to show one of each common case in a small batch: two QR rows and one Code 128 row. The first QR row encodes a ticket URL at error correction level M and comes out at 370x370 pixels. The second QR row encodes the short plain text payload BIN-001 at level Q and comes out at 250x250 pixels. The Code 128 row encodes the internal identifier SKU-8842 and comes out at 324x280 pixels. All three rows generate and verify, which gives generated: 3, verified: 3, both error counts at 0, and a verdict of PASS. The fixture has no EAN-13 row; EAN-13 handling is covered by the verified library behaviour recorded in the Purpose section and in Workflow step 4.

codegen_batch.py (requires segno, python-barcode, and Pillow). Save this code as codegen_batch.py:

python
#!/usr/bin/env python3
"""Batch-generate QR codes and barcodes from a CSV manifest, then verify.

Usage:
  python3 codegen_batch.py --make-fixture DIR
  python3 codegen_batch.py MANIFEST.csv OUT_DIR

Manifest columns (header required):
  filename,payload,type,ec
  - filename: output file name inside OUT_DIR, PNG only, no subfolders
  - payload: the exact text the code encodes
  - type: qr, code128, or ean13
  - ec: QR error correction level L, M, Q, or H (blank means M);
        ignored for barcodes

Requires segno, python-barcode, and Pillow. Generation writes one PNG
per row and a generated.csv in OUT_DIR. The verification pass then
checks, for every manifest row, that the file exists, opens as an
image with Pillow, has a PNG format and nonzero size, and that the
generated.csv list matches the manifest payload list row for row.
It does NOT decode any code: no decoder is used, so a pass here does
not prove a scanner can read the printed code.

Exit code is 0 when every row generated and verified, 1 otherwise.
"""
import csv
import os
import sys

FIXTURE_ROWS = [
    {"filename": "ticket-a1.png", "payload": "https://example.com/tickets/A1", "type": "qr", "ec": "M"},
    {"filename": "bin-label-001.png", "payload": "BIN-001", "type": "qr", "ec": "Q"},
    {"filename": "sku-8842.png", "payload": "SKU-8842", "type": "code128", "ec": ""},
]


def fail(msg):
    print(f"error: {msg}")
    return 1


def generate(manifest_path, out_dir):
    try:
        import segno
        from barcode import Code128, EAN13
        from barcode.writer import ImageWriter
        from PIL import Image
    except ImportError as exc:
        return fail(f"missing dependency: {exc.name}. Install segno, python-barcode, and Pillow in a venv first.")
    if not os.path.isfile(manifest_path):
        return fail(f"manifest not found: {manifest_path}")
    with open(manifest_path, newline="", encoding="utf-8") as fh:
        rows = list(csv.DictReader(fh))
    os.makedirs(out_dir, exist_ok=True)
    generated = []
    errors = 0
    for i, row in enumerate(rows, start=1):
        name = (row.get("filename") or "").strip()
        payload = row.get("payload") or ""
        kind = (row.get("type") or "").strip().lower()
        ec = (row.get("ec") or "").strip().upper() or "M"
        if not name or os.path.basename(name) != name or not name.endswith(".png"):
            print(f"row {i}: BAD_FILENAME {name!r}")
            errors += 1
            continue
        if not payload:
            print(f"row {i}: EMPTY_PAYLOAD filename={name}")
            errors += 1
            continue
        path = os.path.join(out_dir, name)
        try:
            if kind == "qr":
                if ec not in ("L", "M", "Q", "H"):
                    raise ValueError(f"bad ec level {ec!r}")
                qr = segno.make(payload, error=ec.lower())
                qr.save(path, scale=10, border=4)
            elif kind == "code128":
                Code128(payload, writer=ImageWriter()).save(os.path.splitext(path)[0])
            elif kind == "ean13":
                digits = payload.strip()
                if not digits.isdigit() or len(digits) not in (12, 13):
                    raise ValueError("ean13 payload must be 12 or 13 digits")
                EAN13(digits, writer=ImageWriter()).save(os.path.splitext(path)[0])
            else:
                raise ValueError(f"unknown type {kind!r}")
        except Exception as exc:
            print(f"row {i}: GENERATE_FAIL filename={name} reason={exc}")
            errors += 1
            continue
        generated.append({"filename": name, "payload": payload, "type": kind, "ec": ec if kind == "qr" else ""})
    gen_csv = os.path.join(out_dir, "generated.csv")
    with open(gen_csv, "w", newline="", encoding="utf-8") as fh:
        writer = csv.DictWriter(fh, fieldnames=["filename", "payload", "type", "ec"])
        writer.writeheader()
        writer.writerows(generated)
    # Verification pass: files exist, open as images, list matches.
    verified = 0
    verify_errors = 0
    manifest_payloads = [(r.get("filename") or "").strip() for r in rows]
    generated_names = [g["filename"] for g in generated]
    if generated_names != manifest_payloads:
        print(f"verify: LIST_MISMATCH manifest={len(manifest_payloads)} generated={len(generated_names)}")
        verify_errors += 1
    for g in generated:
        path = os.path.join(out_dir, g["filename"])
        if not os.path.isfile(path):
            print(f"verify: MISSING {g['filename']}")
            verify_errors += 1
            continue
        try:
            with Image.open(path) as im:
                im.load()
                fmt, size = im.format, im.size
        except Exception as exc:
            print(f"verify: UNREADABLE {g['filename']} reason={exc}")
            verify_errors += 1
            continue
        if fmt != "PNG" or size[0] <= 0 or size[1] <= 0:
            print(f"verify: BAD_IMAGE {g['filename']} format={fmt} size={size}")
            verify_errors += 1
            continue
        print(f"file: {g['filename']} type={g['type']} format={fmt} size={size[0]}x{size[1]} bytes={os.path.getsize(path)}")
        verified += 1
    print(f"rows_total: {len(rows)}")
    print(f"generated: {len(generated)}")
    print(f"generate_errors: {errors}")
    print(f"verified: {verified}")
    print(f"verify_errors: {verify_errors}")
    print(f"verdict: {'PASS' if errors == 0 and verify_errors == 0 and verified == len(rows) else 'FAIL'}")
    return 0 if errors == 0 and verify_errors == 0 and verified == len(rows) else 1


def make_fixture(base):
    os.makedirs(base, exist_ok=True)
    path = os.path.join(base, "manifest.csv")
    with open(path, "w", newline="", encoding="utf-8") as fh:
        writer = csv.DictWriter(fh, fieldnames=["filename", "payload", "type", "ec"])
        writer.writeheader()
        writer.writerows(FIXTURE_ROWS)
    print(f"fixture_manifest: {path} rows={len(FIXTURE_ROWS)}")
    return 0


def main():
    args = sys.argv[1:]
    if len(args) == 2 and args[0] == "--make-fixture":
        return make_fixture(args[1])
    if len(args) != 2:
        print("usage: codegen_batch.py MANIFEST.csv OUT_DIR")
        print("       codegen_batch.py --make-fixture DIR")
        return 2
    return generate(args[0], args[1])


if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-20 · 0 installs · Ed25519-signed./install.sh barcode-qr-automation 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.