Skip to content

Instantly share code, notes, and snippets.

@huynhbaoan
Created February 24, 2026 14:50
Show Gist options
  • Select an option

  • Save huynhbaoan/519b0fc8ee12d8d680318183ae4a8c5f to your computer and use it in GitHub Desktop.

Select an option

Save huynhbaoan/519b0fc8ee12d8d680318183ae4a8c5f to your computer and use it in GitHub Desktop.
Split
#!/usr/bin/env python3
"""
Generate numbered QR PNGs from an existing base64 file (single line or multi-line).
Workflow (you already have the .b64):
1) This script reads the .b64 text, strips ALL whitespace (handles Qrox+ newline/space behavior too),
2) Splits into chunks (default 2000 chars total payload INCLUDING header),
3) Prefixes each chunk with a fixed header: ###0000### (10 chars),
4) Generates QR images (Version 40, Error L) with screen→phone friendly sizing.
Example:
python3 qr_from_b64.py --in github-diff.txt.b64 --outdir qrs --chunk 2000
Outputs:
qrs/qr_0000.png
qrs/qr_0001.png
...
"""
import argparse
import math
import os
import re
from pathlib import Path
import qrcode
from qrcode.constants import ERROR_CORRECT_L
HEADER_FMT = "###%04d###" # length = 10
HEADER_LEN = len(HEADER_FMT % 0)
def read_b64_stripped(path: Path) -> str:
# Strip ALL whitespace so the payload is deterministic.
s = path.read_text(encoding="utf-8", errors="strict")
# remove spaces, tabs, CRLF, etc.
return re.sub(r"\s+", "", s)
def chunk_with_headers(b64: str, chunk_total: int) -> list[str]:
if chunk_total <= HEADER_LEN:
raise ValueError(f"--chunk must be > {HEADER_LEN} because header is {HEADER_LEN} chars")
payload_len = chunk_total - HEADER_LEN
total = math.ceil(len(b64) / payload_len)
frames = []
for i in range(total):
start = i * payload_len
end = start + payload_len
header = HEADER_FMT % i
frames.append(header + b64[start:end])
return frames
def make_qr_png(data: str, out_path: Path, box_size: int, border: int) -> None:
qr = qrcode.QRCode(
version=40, # lock density (avoid auto changes)
error_correction=ERROR_CORRECT_L, # L = max capacity
box_size=box_size, # screen→phone friendly
border=border, # quiet zone
)
qr.add_data(data)
qr.make(fit=False) # MUST be False since version is locked
img = qr.make_image(fill_color="black", back_color="white")
img.save(out_path)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--in", dest="infile", required=True, help="Input .b64 text file")
ap.add_argument("--outdir", required=True, help="Output directory for PNGs")
ap.add_argument("--chunk", type=int, default=2000,
help="Total characters per QR INCLUDING header (default: 2000)")
ap.add_argument("--box", type=int, default=6, help="QR box_size (default: 6)")
ap.add_argument("--border", type=int, default=2, help="QR border (default: 2)")
ap.add_argument("--dry-run", action="store_true", help="Only print counts, do not generate PNGs")
args = ap.parse_args()
infile = Path(args.infile)
outdir = Path(args.outdir)
outdir.mkdir(parents=True, exist_ok=True)
b64 = read_b64_stripped(infile)
frames = chunk_with_headers(b64, args.chunk)
total = len(frames)
payload_len = args.chunk - HEADER_LEN
print(f"Input chars (whitespace stripped): {len(b64)}")
print(f"Header len: {HEADER_LEN} | Payload per QR: {payload_len} | Total QRs: {total}")
print(f"QR params: version=40, EC=L, box_size={args.box}, border={args.border}")
print(f"Output dir: {outdir.resolve()}")
if args.dry_run:
return
# Generate PNGs
for i, frame in enumerate(frames):
out_path = outdir / f"qr_{i:04d}.png"
make_qr_png(frame, out_path, box_size=args.box, border=args.border)
print("Done.")
print("Tip: open all PNGs in Preview (spacebar) and use → arrow for fast scanning.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment