betterqr 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- betterqr-0.1.0/PKG-INFO +5 -0
- betterqr-0.1.0/README.md +4 -0
- betterqr-0.1.0/pyproject.toml +14 -0
- betterqr-0.1.0/setup.cfg +4 -0
- betterqr-0.1.0/src/betterqr/__init__.py +6 -0
- betterqr-0.1.0/src/betterqr/cli.py +307 -0
- betterqr-0.1.0/src/betterqr/core.py +822 -0
- betterqr-0.1.0/src/betterqr/encoder.py +185 -0
- betterqr-0.1.0/src/betterqr/extras/__init__.py +2 -0
- betterqr-0.1.0/src/betterqr/extras/animation.py +351 -0
- betterqr-0.1.0/src/betterqr/extras/image_ops.py +327 -0
- betterqr-0.1.0/src/betterqr/gf.py +74 -0
- betterqr-0.1.0/src/betterqr/matrix.py +349 -0
- betterqr-0.1.0/src/betterqr/render/__init__.py +0 -0
- betterqr-0.1.0/src/betterqr/render/base.py +17 -0
- betterqr-0.1.0/src/betterqr/render/raster.py +186 -0
- betterqr-0.1.0/src/betterqr/render/terminal.py +103 -0
- betterqr-0.1.0/src/betterqr/render/vector.py +184 -0
- betterqr-0.1.0/src/betterqr/tables.py +147 -0
- betterqr-0.1.0/src/betterqr.egg-info/PKG-INFO +5 -0
- betterqr-0.1.0/src/betterqr.egg-info/SOURCES.txt +23 -0
- betterqr-0.1.0/src/betterqr.egg-info/dependency_links.txt +1 -0
- betterqr-0.1.0/src/betterqr.egg-info/entry_points.txt +2 -0
- betterqr-0.1.0/src/betterqr.egg-info/requires.txt +1 -0
- betterqr-0.1.0/src/betterqr.egg-info/top_level.txt +1 -0
betterqr-0.1.0/PKG-INFO
ADDED
betterqr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "betterqr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [{ name = "DevX-Dragon" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"pillow>=10.0.0"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
betterqr = "betterqr.cli:main"
|
betterqr-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""
|
|
2
|
+
betterqr CLI
|
|
3
|
+
============
|
|
4
|
+
The cleanest QR code generator in your terminal.
|
|
5
|
+
|
|
6
|
+
BASIC USAGE
|
|
7
|
+
betterqr "https://example.com" → qr.png
|
|
8
|
+
betterqr "Hello World" out.png
|
|
9
|
+
betterqr "https://example.com" out.svg
|
|
10
|
+
|
|
11
|
+
SHAPES
|
|
12
|
+
betterqr "text" -s circle
|
|
13
|
+
betterqr "text" -s rounded
|
|
14
|
+
betterqr "text" -s diamond / star / gapped
|
|
15
|
+
|
|
16
|
+
COLORS
|
|
17
|
+
betterqr "text" --fill "#6C3082" --back "#F3E8FF"
|
|
18
|
+
betterqr "text" --fill "#000" --back transparent
|
|
19
|
+
|
|
20
|
+
GRADIENTS
|
|
21
|
+
betterqr "text" --gradient "#FF6B6B" "#4ECDC4"
|
|
22
|
+
betterqr "text" --gradient "#1d4ed8" "#7c3aed" --gradient-dir radial
|
|
23
|
+
|
|
24
|
+
LOGO
|
|
25
|
+
betterqr "https://mysite.com" --logo logo.png
|
|
26
|
+
betterqr "https://mysite.com" --logo logo.png --logo-ratio 0.3 -e H
|
|
27
|
+
|
|
28
|
+
FRAME & LABEL
|
|
29
|
+
betterqr "Scan Me!" --frame rounded --label "Visit our site"
|
|
30
|
+
betterqr "Scan Me!" --frame fancy --label "WiFi Password: abc123"
|
|
31
|
+
|
|
32
|
+
ANIMATION (saves .gif)
|
|
33
|
+
betterqr "Hello" out.gif --effect shimmer
|
|
34
|
+
betterqr "Hello" out.gif --effect matrix --fps 12
|
|
35
|
+
betterqr "Hello" out.gif --effect wave --frames 30
|
|
36
|
+
|
|
37
|
+
DATA TYPES
|
|
38
|
+
betterqr --wifi MyNet MyPassword
|
|
39
|
+
betterqr --wifi MyNet MyPassword --security WEP
|
|
40
|
+
betterqr --contact "Jane Doe" --phone "+1-555-0199" --email "jane@example.com"
|
|
41
|
+
betterqr --geo 51.5074 -0.1278
|
|
42
|
+
betterqr --sms "+1-555-0199" "Hello!"
|
|
43
|
+
betterqr --email "hi@example.com" "Subject" "Body text"
|
|
44
|
+
betterqr --phone "+1-800-555-0199"
|
|
45
|
+
|
|
46
|
+
TERMINAL PREVIEW
|
|
47
|
+
betterqr "Hello" --print
|
|
48
|
+
betterqr "Hello" --print --invert
|
|
49
|
+
Note: Add stuff here as well if you contribute lol
|
|
50
|
+
"""
|
|
51
|
+
import argparse
|
|
52
|
+
import sys
|
|
53
|
+
import os
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _color(s):
|
|
57
|
+
"""Validate a color argument."""
|
|
58
|
+
if s.lower() == 'transparent': return s
|
|
59
|
+
if not s.startswith('#'): s = '#' + s
|
|
60
|
+
s = s.lstrip('#')
|
|
61
|
+
if len(s) not in (3, 6):
|
|
62
|
+
raise argparse.ArgumentTypeError(f"Invalid color '{s}'. Use hex like #FF0000 or #F00")
|
|
63
|
+
return '#' + s
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
p = argparse.ArgumentParser(
|
|
68
|
+
prog="betterqr",
|
|
69
|
+
description="BetterQR — Beautiful, scannable QR codes. Zero external QR dependencies.",
|
|
70
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
71
|
+
epilog=__doc__,
|
|
72
|
+
add_help=True,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# ── Core ──────────────────────────────────────────────────────────
|
|
76
|
+
p.add_argument("data", nargs="?", help="Data to encode (text, URL, etc.)")
|
|
77
|
+
p.add_argument("output", nargs="?", default="qr.png",
|
|
78
|
+
help="Output file (.png .jpg .svg .gif) [default: qr.png]")
|
|
79
|
+
|
|
80
|
+
# ── QR settings ───────────────────────────────────────────────────
|
|
81
|
+
qr = p.add_argument_group("QR settings")
|
|
82
|
+
qr.add_argument("-e", "--ecc", default="M", choices=["L","M","Q","H"],
|
|
83
|
+
metavar="L|M|Q|H",
|
|
84
|
+
help="Error correction: L=7%% M=15%% Q=25%% H=30%% [default: M]. Use H with logos.")
|
|
85
|
+
qr.add_argument("-v", "--version", type=int, metavar="1-40",
|
|
86
|
+
help="Force QR version 1-40 (auto-selected by default)")
|
|
87
|
+
qr.add_argument("--box-size", type=int, default=10, metavar="PX",
|
|
88
|
+
help="Pixels per module [default: 10]")
|
|
89
|
+
qr.add_argument("--border", type=int, default=4, metavar="N",
|
|
90
|
+
help="Quiet zone width in modules [default: 4]")
|
|
91
|
+
|
|
92
|
+
# ── Style ─────────────────────────────────────────────────────────
|
|
93
|
+
st = p.add_argument_group("Style")
|
|
94
|
+
st.add_argument("-s", "--shape", default="square",
|
|
95
|
+
choices=["square","circle","rounded","diamond","star","gapped",
|
|
96
|
+
"vertical_bar","horizontal_bar"],
|
|
97
|
+
metavar="SHAPE",
|
|
98
|
+
help="Module shape [default: square]\n"
|
|
99
|
+
" square | circle | rounded | diamond | star | gapped | vertical_bar | horizontal_bar")
|
|
100
|
+
st.add_argument("--fill", default="#000000", type=_color, metavar="COLOR",
|
|
101
|
+
help="Dark module color [default: #000000]")
|
|
102
|
+
st.add_argument("--back", default="#FFFFFF", type=_color, metavar="COLOR",
|
|
103
|
+
help="Background color [default: #FFFFFF] (use 'transparent' for PNG alpha)")
|
|
104
|
+
st.add_argument("--finder", type=_color, metavar="COLOR",
|
|
105
|
+
help="Separate color for the 3 finder squares")
|
|
106
|
+
|
|
107
|
+
# ── Gradient ──────────────────────────────────────────────────────
|
|
108
|
+
gr = p.add_argument_group("Gradient")
|
|
109
|
+
gr.add_argument("--gradient", nargs=2, metavar=("START","END"),
|
|
110
|
+
help="Two colors for a gradient fill. Example: --gradient '#FF6B6B' '#4ECDC4'")
|
|
111
|
+
gr.add_argument("--gradient-dir", default="diagonal",
|
|
112
|
+
choices=["horizontal","vertical","diagonal","radial"],
|
|
113
|
+
metavar="DIR",
|
|
114
|
+
help="Gradient direction [default: diagonal]\n"
|
|
115
|
+
" horizontal | vertical | diagonal | radial")
|
|
116
|
+
|
|
117
|
+
# ── Logo ──────────────────────────────────────────────────────────
|
|
118
|
+
lo = p.add_argument_group("Logo / image")
|
|
119
|
+
lo.add_argument("--logo", metavar="PATH",
|
|
120
|
+
help="Embed a logo/image in the center of the QR code (PNG/JPG)")
|
|
121
|
+
lo.add_argument("--logo-ratio", type=float, default=0.25, metavar="0.1-0.35",
|
|
122
|
+
help="Logo size as fraction of QR total size [default: 0.25]")
|
|
123
|
+
lo.add_argument("--logo-shape", default="square",
|
|
124
|
+
choices=["square","circle","rounded"],
|
|
125
|
+
help="Logo background shape [default: square]")
|
|
126
|
+
|
|
127
|
+
# ── Frame / label ─────────────────────────────────────────────────
|
|
128
|
+
fr = p.add_argument_group("Frame & label")
|
|
129
|
+
fr.add_argument("--frame", metavar="STYLE",
|
|
130
|
+
choices=["simple","rounded","double","shadow","fancy"],
|
|
131
|
+
help="Add a decorative frame: simple | rounded | double | shadow | fancy")
|
|
132
|
+
fr.add_argument("--frame-color", type=_color, default="#000000", metavar="COLOR",
|
|
133
|
+
help="Frame color [default: #000000]")
|
|
134
|
+
fr.add_argument("--label", metavar="TEXT",
|
|
135
|
+
help="Text label to add below (or above) the QR code")
|
|
136
|
+
fr.add_argument("--label-above", action="store_true",
|
|
137
|
+
help="Place label above the QR instead of below")
|
|
138
|
+
fr.add_argument("--label-color", type=_color, default="#000000", metavar="COLOR")
|
|
139
|
+
fr.add_argument("--label-size", type=int, default=14, metavar="PX")
|
|
140
|
+
|
|
141
|
+
# ── Animation ─────────────────────────────────────────────────────
|
|
142
|
+
an = p.add_argument_group("Animation (saves .gif)")
|
|
143
|
+
an.add_argument("--effect", metavar="EFFECT",
|
|
144
|
+
choices=["shimmer","fade","scan","pulse","build","matrix",
|
|
145
|
+
"wave","blink","typewriter","rotate"],
|
|
146
|
+
help="Animation effect:\n"
|
|
147
|
+
" shimmer | fade | scan | pulse | build |\n"
|
|
148
|
+
" matrix | wave | blink | typewriter | rotate")
|
|
149
|
+
an.add_argument("--frames", type=int, default=20, metavar="N",
|
|
150
|
+
help="Number of animation frames [default: 20]")
|
|
151
|
+
an.add_argument("--fps", type=int, default=15, metavar="N",
|
|
152
|
+
help="Animation playback speed [default: 15]")
|
|
153
|
+
an.add_argument("--accent", type=_color, metavar="COLOR",
|
|
154
|
+
help="Accent color for some animation effects")
|
|
155
|
+
|
|
156
|
+
# ── Data type shortcuts ───────────────────────────────────────────
|
|
157
|
+
dt = p.add_argument_group("Data type shortcuts")
|
|
158
|
+
dt.add_argument("--wifi", nargs="+", metavar=("SSID", "PASS"),
|
|
159
|
+
help="Wi-Fi QR: --wifi SSID [PASSWORD]")
|
|
160
|
+
dt.add_argument("--security", default="WPA", choices=["WPA","WEP","nopass"],
|
|
161
|
+
help="Wi-Fi security type [default: WPA]")
|
|
162
|
+
dt.add_argument("--contact", metavar="NAME",
|
|
163
|
+
help="vCard contact QR (use with --phone --email --org --url)")
|
|
164
|
+
dt.add_argument("--phone", metavar="NUMBER",
|
|
165
|
+
help="Phone number (for --contact or standalone tel: QR)")
|
|
166
|
+
dt.add_argument("--email", nargs="+", metavar=("ADDRESS", "SUBJECT"),
|
|
167
|
+
help="Email QR: --email ADDRESS [SUBJECT] [BODY]")
|
|
168
|
+
dt.add_argument("--org", metavar="NAME", help="Organization (for --contact)")
|
|
169
|
+
dt.add_argument("--url", metavar="URL", help="URL (for --contact)")
|
|
170
|
+
dt.add_argument("--geo", nargs=2, metavar=("LAT","LON"),
|
|
171
|
+
help="Location QR: --geo 51.5074 -0.1278")
|
|
172
|
+
dt.add_argument("--sms", nargs=2, metavar=("PHONE","MSG"),
|
|
173
|
+
help="SMS QR: --sms PHONE 'Message body'")
|
|
174
|
+
|
|
175
|
+
# ── Output ────────────────────────────────────────────────────────
|
|
176
|
+
ou = p.add_argument_group("Output")
|
|
177
|
+
ou.add_argument("--print", action="store_true",
|
|
178
|
+
help="Print QR to terminal (works alongside file output)")
|
|
179
|
+
ou.add_argument("--invert", action="store_true",
|
|
180
|
+
help="Invert terminal colors (for dark-background terminals)")
|
|
181
|
+
ou.add_argument("--terminal-style", default="block",
|
|
182
|
+
choices=["block","ascii","compact"],
|
|
183
|
+
help="Terminal render style [default: block]")
|
|
184
|
+
ou.add_argument("--info", action="store_true",
|
|
185
|
+
help="Print QR code metadata (version, size, mode, etc.)")
|
|
186
|
+
|
|
187
|
+
args = p.parse_args()
|
|
188
|
+
|
|
189
|
+
# ── Build data payload ────────────────────────────────────────────
|
|
190
|
+
from .core import QR, WiFi, VCard, GeoLocation, SMS, Email, Phone
|
|
191
|
+
|
|
192
|
+
data = None
|
|
193
|
+
|
|
194
|
+
if args.wifi:
|
|
195
|
+
ssid = args.wifi[0]
|
|
196
|
+
pw = args.wifi[1] if len(args.wifi) > 1 else ""
|
|
197
|
+
data = WiFi(ssid, pw, args.security)
|
|
198
|
+
|
|
199
|
+
elif args.contact:
|
|
200
|
+
data = VCard(
|
|
201
|
+
name = args.contact,
|
|
202
|
+
phone = args.phone or "",
|
|
203
|
+
email = args.email[0] if args.email else "",
|
|
204
|
+
org = args.org or "",
|
|
205
|
+
url = args.url or "",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
elif args.geo:
|
|
209
|
+
data = GeoLocation(float(args.geo[0]), float(args.geo[1]))
|
|
210
|
+
|
|
211
|
+
elif args.sms:
|
|
212
|
+
data = SMS(args.sms[0], args.sms[1])
|
|
213
|
+
|
|
214
|
+
elif args.email and not args.contact:
|
|
215
|
+
addr = args.email[0]
|
|
216
|
+
subject = args.email[1] if len(args.email) > 1 else ""
|
|
217
|
+
body = args.email[2] if len(args.email) > 2 else ""
|
|
218
|
+
data = Email(addr, subject, body)
|
|
219
|
+
|
|
220
|
+
elif args.phone and not args.contact:
|
|
221
|
+
data = Phone(args.phone)
|
|
222
|
+
|
|
223
|
+
elif args.data:
|
|
224
|
+
data = args.data
|
|
225
|
+
|
|
226
|
+
else:
|
|
227
|
+
p.print_help()
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
|
|
230
|
+
# ── Build QR ─────────────────────────────────────────────────────
|
|
231
|
+
try:
|
|
232
|
+
qr = QR(data, ecc=args.ecc, version=args.version)
|
|
233
|
+
except ValueError as e:
|
|
234
|
+
print(f"✗ Error: {e}", file=sys.stderr)
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
|
|
237
|
+
# ── Apply style ───────────────────────────────────────────────────
|
|
238
|
+
fill = args.fill
|
|
239
|
+
if args.gradient:
|
|
240
|
+
qr.gradient(args.gradient[0], args.gradient[1], args.gradient_dir)
|
|
241
|
+
else:
|
|
242
|
+
qr.style(fill=fill, back=args.back, shape=args.shape,
|
|
243
|
+
finder=args.finder, box_size=args.box_size, border=args.border)
|
|
244
|
+
|
|
245
|
+
# Gradient + shape/back
|
|
246
|
+
if args.gradient:
|
|
247
|
+
qr._shape = args.shape
|
|
248
|
+
qr._back = args.back
|
|
249
|
+
qr._box_size = args.box_size
|
|
250
|
+
qr._border = args.border
|
|
251
|
+
if args.finder: qr._finder_color = args.finder
|
|
252
|
+
|
|
253
|
+
# Logo
|
|
254
|
+
if args.logo:
|
|
255
|
+
qr.logo(args.logo, ratio=args.logo_ratio, shape=args.logo_shape)
|
|
256
|
+
|
|
257
|
+
# Frame / label
|
|
258
|
+
if args.frame or args.label:
|
|
259
|
+
frame_style = args.frame or "simple"
|
|
260
|
+
qr.frame(
|
|
261
|
+
style = frame_style,
|
|
262
|
+
color = args.frame_color,
|
|
263
|
+
label = args.label,
|
|
264
|
+
label_color = args.label_color,
|
|
265
|
+
label_size = args.label_size,
|
|
266
|
+
label_position = "top" if args.label_above else "bottom",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Animation
|
|
270
|
+
if args.effect:
|
|
271
|
+
qr.animate(
|
|
272
|
+
effect = args.effect,
|
|
273
|
+
frames = args.frames,
|
|
274
|
+
fps = args.fps,
|
|
275
|
+
accent = args.accent,
|
|
276
|
+
)
|
|
277
|
+
# Force .gif output if not already
|
|
278
|
+
if not args.output.lower().endswith(".gif"):
|
|
279
|
+
args.output = args.output.rsplit(".", 1)[0] + ".gif"
|
|
280
|
+
|
|
281
|
+
# ── Info ─────────────────────────────────────────────────────────
|
|
282
|
+
if args.info:
|
|
283
|
+
info = qr.info()
|
|
284
|
+
print("QR Code Info")
|
|
285
|
+
print("─" * 30)
|
|
286
|
+
for k, v in info.items():
|
|
287
|
+
print(f" {k:<14} {v}")
|
|
288
|
+
print()
|
|
289
|
+
|
|
290
|
+
# ── Terminal print ────────────────────────────────────────────────
|
|
291
|
+
if getattr(args, 'print'):
|
|
292
|
+
print(qr.to_terminal(style=args.terminal_style, invert=args.invert))
|
|
293
|
+
|
|
294
|
+
# ── Save file ─────────────────────────────────────────────────────
|
|
295
|
+
if args.output:
|
|
296
|
+
try:
|
|
297
|
+
qr.save(args.output)
|
|
298
|
+
ext = args.output.rsplit('.',1)[-1].upper()
|
|
299
|
+
print(f"✓ Saved {args.output} "
|
|
300
|
+
f"[v{qr.version} ECC-{qr.ecc_level} {qr.module_count}×{qr.module_count} modules]")
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"✗ Failed to save: {e}", file=sys.stderr)
|
|
303
|
+
sys.exit(1)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
if __name__ == "__main__":
|
|
307
|
+
main()
|