@topy-ai/maggie 0.8.11 → 0.8.13
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.
- package/README-zh-TW.md +1 -1
- package/README.md +2 -2
- package/bundled-references/ai-media-generation.md +88 -0
- package/bundled-references/captions-transcription.md +41 -0
- package/bundled-references/generative-visuals.md +94 -0
- package/bundled-references/image-recipes.md +89 -0
- package/bundled-references/web-animation-patterns.md +364 -0
- package/bundled-skills/catalog.json +4 -0
- package/bundled-skills/maggie-design/SKILL.md +7 -2
- package/bundled-skills/maggie-design-motion/SKILL.md +4 -1
- package/bundled-skills/maggie-image/SKILL.md +101 -0
- package/bundled-skills/maggie-landing-page/SKILL.md +5 -1
- package/bundled-skills/maggie-spirit/SKILL.md +4 -2
- package/bundled-skills/maggie-video/SKILL.md +3 -2
- package/bundled-tools/clis/maggie_image.py +221 -0
- package/package.json +1 -1
- package/references/ai-media-generation.md +88 -0
- package/references/captions-transcription.md +41 -0
- package/references/generative-visuals.md +94 -0
- package/references/image-recipes.md +89 -0
- package/references/web-animation-patterns.md +364 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""maggie_image - generate correct image-processing commands for marketing assets.
|
|
3
|
+
|
|
4
|
+
A recipe generator distilled from ImageMagick, sharp, svgo, resvg, squoosh and
|
|
5
|
+
imagemin (see references/image-recipes.md). Each subcommand prints the exact command
|
|
6
|
+
(reviewable/loggable) and runs it with --run only when the tool is on PATH. Raster
|
|
7
|
+
ops prefer ImageMagick (`magick` on IM7, else `convert`) since it is almost always
|
|
8
|
+
present and covers nearly everything; vector ops use the dedicated tool (svgo, resvg)
|
|
9
|
+
and are generate-only until it is installed. Web-delivery quality defaults follow
|
|
10
|
+
sharp/squoosh (JPEG/WebP ~80, AVIF ~50); OG/social are cover-cropped, logos pad.
|
|
11
|
+
|
|
12
|
+
Subcommands: resize, social, webp, avif, favicon, strip, compress, svgo, svg2png,
|
|
13
|
+
montage, presets.
|
|
14
|
+
"""
|
|
15
|
+
import argparse
|
|
16
|
+
import shlex
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
# OG/social use cover-crop; name -> (W, H)
|
|
22
|
+
SOCIAL = {"og": (1200, 630), "twitter": (1600, 900), "square": (1080, 1080),
|
|
23
|
+
"story": (1080, 1920), "reel": (1080, 1920), "landscape": (1920, 1080),
|
|
24
|
+
"linkedin": (1200, 627), "pinterest": (1000, 1500)}
|
|
25
|
+
FAVICON_PNG = [16, 32, 48, 180, 192, 512] # 180 = apple-touch
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _im():
|
|
29
|
+
return ["magick"] if shutil.which("magick") else ["convert"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _im_tool():
|
|
33
|
+
return _im()[0]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _montage():
|
|
37
|
+
return ["magick", "montage"] if shutil.which("magick") else ["montage"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def emit(args, run, tool):
|
|
41
|
+
print(" ".join(shlex.quote(a) for a in args))
|
|
42
|
+
if not run:
|
|
43
|
+
return 0
|
|
44
|
+
if not shutil.which(tool):
|
|
45
|
+
sys.stderr.write(f"{tool} not found on PATH; printed the command only.\n")
|
|
46
|
+
return 3
|
|
47
|
+
return subprocess.run(args).returncode
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resize_ops(w, h, mode, pad):
|
|
51
|
+
if mode == "cover":
|
|
52
|
+
return ["-resize", f"{w}x{h}^", "-gravity", "center", "-extent", f"{w}x{h}"]
|
|
53
|
+
if mode == "contain":
|
|
54
|
+
return ["-resize", f"{w}x{h}", "-background", pad, "-gravity", "center", "-extent", f"{w}x{h}"]
|
|
55
|
+
if mode == "shrink":
|
|
56
|
+
return ["-resize", f"{w}x{h}>"]
|
|
57
|
+
return ["-resize", f"{w}x{h}"] # fit
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def c_resize(a):
|
|
61
|
+
w, h = a.size.lower().split("x")
|
|
62
|
+
cmd = _im() + [a.input, "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
63
|
+
["-quality", str(a.quality), a.output]
|
|
64
|
+
return emit(cmd, a.run, _im_tool())
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def c_social(a):
|
|
68
|
+
w, h = SOCIAL[a.preset]
|
|
69
|
+
cmd = _im() + [a.input, "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
70
|
+
["-quality", str(a.quality), a.output]
|
|
71
|
+
return emit(cmd, a.run, _im_tool())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def c_webp(a):
|
|
75
|
+
if shutil.which("cwebp") and not a.force_im:
|
|
76
|
+
return emit(["cwebp", "-q", str(a.quality), "-m", "6", a.input, "-o", a.output], a.run, "cwebp")
|
|
77
|
+
cmd = _im() + [a.input, "-strip", "-quality", str(a.quality), "-define", "webp:method=6", a.output]
|
|
78
|
+
return emit(cmd, a.run, _im_tool())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def c_avif(a):
|
|
82
|
+
if shutil.which("avifenc") and not a.force_im:
|
|
83
|
+
return emit(["avifenc", "-s", "6", "--min", "20", "--max", "40", a.input, a.output], a.run, "avifenc")
|
|
84
|
+
cmd = _im() + [a.input, "-strip", "-quality", str(a.quality), a.output]
|
|
85
|
+
return emit(cmd, a.run, _im_tool())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def c_favicon(a):
|
|
89
|
+
import os
|
|
90
|
+
os.makedirs(a.out_dir, exist_ok=True)
|
|
91
|
+
rc = 0
|
|
92
|
+
ico = os.path.join(a.out_dir, "favicon.ico")
|
|
93
|
+
rc |= emit(_im() + [a.input, "-define", "icon:auto-resize=64,48,32,16", ico], a.run, _im_tool())
|
|
94
|
+
for s in FAVICON_PNG:
|
|
95
|
+
name = "apple-touch-icon.png" if s == 180 else f"favicon-{s}.png"
|
|
96
|
+
out = os.path.join(a.out_dir, name)
|
|
97
|
+
rc |= emit(_im() + [a.input, "-strip", "-resize", f"{s}x{s}", out], a.run, _im_tool())
|
|
98
|
+
return rc
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def c_strip(a):
|
|
102
|
+
return emit(_im() + [a.input, "-strip", a.output], a.run, _im_tool())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def c_compress(a):
|
|
106
|
+
out = a.output.lower()
|
|
107
|
+
if out.endswith((".jpg", ".jpeg")):
|
|
108
|
+
cmd = _im() + [a.input, "-strip", "-interlace", "Plane", "-quality", str(a.quality), a.output]
|
|
109
|
+
else: # png: palette-reduce + max zlib (pngquant is the better alt)
|
|
110
|
+
if shutil.which("pngquant") and not a.force_im:
|
|
111
|
+
return emit(["pngquant", "--quality", "60-82", "--strip", "--force",
|
|
112
|
+
"--output", a.output, a.input], a.run, "pngquant")
|
|
113
|
+
cmd = _im() + [a.input, "-strip", "-colors", "256",
|
|
114
|
+
"-define", "png:compression-level=9", a.output]
|
|
115
|
+
return emit(cmd, a.run, _im_tool())
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def c_svgo(a):
|
|
119
|
+
args = ["svgo", "--multipass"] if a.multipass else ["svgo"]
|
|
120
|
+
args += ["-i", a.input, "-o", a.output]
|
|
121
|
+
print("# svgo keeps viewBox and title by default (responsive + accessible).")
|
|
122
|
+
if not shutil.which("svgo"):
|
|
123
|
+
print("# svgo not installed (npm i -g svgo). Do NOT rasterize an SVG as a substitute.")
|
|
124
|
+
return emit(args, a.run, "svgo")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def c_svg2png(a):
|
|
128
|
+
if shutil.which("resvg") and not a.force_im:
|
|
129
|
+
args = ["resvg"]
|
|
130
|
+
if a.width:
|
|
131
|
+
args += ["-w", str(a.width)]
|
|
132
|
+
if a.background:
|
|
133
|
+
args += ["--background", a.background]
|
|
134
|
+
args += [a.input, a.output]
|
|
135
|
+
return emit(args, a.run, "resvg")
|
|
136
|
+
if shutil.which("rsvg-convert") and not a.force_im:
|
|
137
|
+
args = ["rsvg-convert"]
|
|
138
|
+
if a.width:
|
|
139
|
+
args += ["-w", str(a.width)]
|
|
140
|
+
return emit(args + [a.input, "-o", a.output], a.run, "rsvg-convert")
|
|
141
|
+
# ImageMagick fallback: drive size via -density (it rasterizes at DPI); lower fidelity
|
|
142
|
+
dens = 384
|
|
143
|
+
bg = ["-background", a.background] if a.background else ["-background", "none"]
|
|
144
|
+
size = ["-resize", f"{a.width}x"] if a.width else []
|
|
145
|
+
print("# ImageMagick SVG fallback is lower fidelity; install resvg for correct rendering.")
|
|
146
|
+
return emit(_im() + bg + ["-density", str(dens), a.input] + size + [a.output], a.run, _im_tool())
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def c_montage(a):
|
|
150
|
+
cmd = _montage() + list(a.inputs) + ["-tile", a.tile, "-geometry",
|
|
151
|
+
f"{a.cell}+{a.pad}+{a.pad}", "-background", a.background, a.output]
|
|
152
|
+
return emit(cmd, a.run, _montage()[0])
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def c_presets(a):
|
|
156
|
+
print("Social/OG size presets (use with `social --preset <name>`; cover-cropped):")
|
|
157
|
+
for name, (w, h) in SOCIAL.items():
|
|
158
|
+
print(f" {name:<10} {w}x{h}")
|
|
159
|
+
print("\nDefaults: strip metadata; JPEG/WebP quality 80, AVIF 50; favicon .ico "
|
|
160
|
+
"(64/48/32/16) + PNG set (16/32/48/180-apple/192/512).")
|
|
161
|
+
print(f"ImageMagick binary detected: {_im_tool()}")
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def main(argv=None):
|
|
166
|
+
ap = argparse.ArgumentParser(prog="maggie_image", description=__doc__,
|
|
167
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
168
|
+
ap.add_argument("--run", action="store_true", help="execute (needs the tool on PATH); default prints only")
|
|
169
|
+
ap.add_argument("--force-im", action="store_true", dest="force_im",
|
|
170
|
+
help="force the ImageMagick path even if a dedicated tool exists")
|
|
171
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
172
|
+
|
|
173
|
+
def io(p, out=True):
|
|
174
|
+
p.add_argument("--input", required=True)
|
|
175
|
+
if out:
|
|
176
|
+
p.add_argument("--output", required=True)
|
|
177
|
+
|
|
178
|
+
r = sub.add_parser("resize", help="resize+fit to WxH")
|
|
179
|
+
io(r); r.add_argument("--size", required=True, help="WxH, e.g. 1200x630")
|
|
180
|
+
r.add_argument("--mode", choices=["cover", "contain", "fit", "shrink"], default="cover")
|
|
181
|
+
r.add_argument("--pad", default="#ffffff"); r.add_argument("--quality", type=int, default=82)
|
|
182
|
+
r.set_defaults(fn=c_resize)
|
|
183
|
+
|
|
184
|
+
s = sub.add_parser("social", help="resize to a channel/OG preset")
|
|
185
|
+
io(s); s.add_argument("--preset", choices=list(SOCIAL), required=True)
|
|
186
|
+
s.add_argument("--mode", choices=["cover", "contain", "fit", "shrink"], default="cover")
|
|
187
|
+
s.add_argument("--pad", default="#ffffff"); s.add_argument("--quality", type=int, default=82)
|
|
188
|
+
s.set_defaults(fn=c_social)
|
|
189
|
+
|
|
190
|
+
w = sub.add_parser("webp", help="encode WebP"); io(w)
|
|
191
|
+
w.add_argument("--quality", type=int, default=80); w.set_defaults(fn=c_webp)
|
|
192
|
+
av = sub.add_parser("avif", help="encode AVIF"); io(av)
|
|
193
|
+
av.add_argument("--quality", type=int, default=50); av.set_defaults(fn=c_avif)
|
|
194
|
+
|
|
195
|
+
fv = sub.add_parser("favicon", help="favicon .ico + PNG set")
|
|
196
|
+
fv.add_argument("--input", required=True); fv.add_argument("--out-dir", dest="out_dir", required=True)
|
|
197
|
+
fv.set_defaults(fn=c_favicon)
|
|
198
|
+
|
|
199
|
+
st = sub.add_parser("strip", help="strip metadata (EXIF/GPS/ICC)"); io(st); st.set_defaults(fn=c_strip)
|
|
200
|
+
co = sub.add_parser("compress", help="compress JPEG/PNG"); io(co)
|
|
201
|
+
co.add_argument("--quality", type=int, default=82); co.set_defaults(fn=c_compress)
|
|
202
|
+
|
|
203
|
+
so = sub.add_parser("svgo", help="optimize an SVG (svgo)"); io(so)
|
|
204
|
+
so.add_argument("--multipass", action="store_true"); so.set_defaults(fn=c_svgo)
|
|
205
|
+
sp = sub.add_parser("svg2png", help="render SVG -> PNG (resvg)"); io(sp)
|
|
206
|
+
sp.add_argument("--width", type=int); sp.add_argument("--background"); sp.set_defaults(fn=c_svg2png)
|
|
207
|
+
|
|
208
|
+
mo = sub.add_parser("montage", help="contact sheet / grid")
|
|
209
|
+
mo.add_argument("--inputs", nargs="+", required=True); mo.add_argument("--output", required=True)
|
|
210
|
+
mo.add_argument("--tile", default="3x"); mo.add_argument("--cell", default="400x400")
|
|
211
|
+
mo.add_argument("--pad", type=int, default=10); mo.add_argument("--background", default="#ffffff")
|
|
212
|
+
mo.set_defaults(fn=c_montage)
|
|
213
|
+
|
|
214
|
+
sub.add_parser("presets", help="list size presets").set_defaults(fn=c_presets)
|
|
215
|
+
|
|
216
|
+
a = ap.parse_args(argv)
|
|
217
|
+
raise SystemExit(a.fn(a))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# AI media generation (planning + guardrails)
|
|
2
|
+
|
|
3
|
+
The analysis-and-plan layer for category Q (self-hosted AI image/video/audio
|
|
4
|
+
generation). This is **not** a build: generating media with these models means large
|
|
5
|
+
model-weight downloads (gigabytes) and GPU time, so the actual run is gated on an
|
|
6
|
+
explicit go-ahead and a cost statement (the global spend-first rule). This reference
|
|
7
|
+
maps the landscape, the licensing traps, the commercial-safe subset to start from,
|
|
8
|
+
and the guardrails - so the build, when approved, is scoped and safe. Distilled from
|
|
9
|
+
the reference library's Q-category projects.
|
|
10
|
+
|
|
11
|
+
## The one rule that matters: code license != weights license
|
|
12
|
+
|
|
13
|
+
A repo's code license (often Apache-2.0/MIT) says nothing about whether you may use
|
|
14
|
+
its **model weights commercially**. Many permissively-licensed code repos ship
|
|
15
|
+
weights under research-only or non-commercial terms. **Always check the specific
|
|
16
|
+
model's weight license before any commercial use**, per model, per version. Example:
|
|
17
|
+
`black-forest-labs/flux` code is Apache-2.0, but **FLUX.1-[dev] weights are
|
|
18
|
+
non-commercial** while FLUX.1-[schnell] is Apache-2.0 - same repo, different answer.
|
|
19
|
+
|
|
20
|
+
## Code-license map (from the screened repos)
|
|
21
|
+
|
|
22
|
+
| Repo | code license | adoption posture |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| AUTOMATIC1111/stable-diffusion-webui | **AGPL-3.0** | arm's-length only; never vendor code |
|
|
25
|
+
| Comfy-Org/ComfyUI | **GPL-3.0** | arm's-length (its HTTP API / workflow JSON); never vendor |
|
|
26
|
+
| lllyasviel/Fooocus, Acly/krita-ai-diffusion | **GPL-3.0** | arm's-length only |
|
|
27
|
+
| danielgatis/rembg, nadermx/backgroundremover | MIT | safe to depend on (bg removal) |
|
|
28
|
+
| xinntao/Real-ESRGAN | BSD-3 | safe to depend on (upscaling) - check weight terms |
|
|
29
|
+
| huggingface/diffusers | Apache-2.0 | safe library; **models vary** - check each |
|
|
30
|
+
| Stability-AI/generative-models, black-forest-labs/flux, guoyww/AnimateDiff, Wan-Video/Wan2.1, zai-org/CogVideo, Lightricks/LTX-Video, hpcaitech/Open-Sora, genmoai/mochi, kohya-ss/sd-scripts | Apache-2.0/MIT code | code safe; **weights almost always have their own (often non-commercial) license - verify per model** |
|
|
31
|
+
| TencentARC/GFPGAN, facefusion/facefusion | NOASSERTION | unclear; treat as restricted until verified; facefusion also has misuse/ethics constraints |
|
|
32
|
+
| suno-ai/bark, facebookresearch/audiocraft | MIT | audio/music code safe; check weight + training-data terms |
|
|
33
|
+
|
|
34
|
+
## Commercial-safe subset to start with (when approved)
|
|
35
|
+
|
|
36
|
+
Lowest risk, highest marketing value, smallest footprint - do these first:
|
|
37
|
+
|
|
38
|
+
1. **Background removal** - `rembg` (MIT; u2net weights, widely used) or
|
|
39
|
+
`backgroundremover` (MIT). Runs CPU-fine for product cut-outs. Pairs with
|
|
40
|
+
`maggie-image` compositing.
|
|
41
|
+
2. **Upscaling / restore** - `Real-ESRGAN` (BSD-3) via the standalone
|
|
42
|
+
`realesrgan-ncnn-vulkan` binary (arm's-length; do NOT vendor Upscayl which is
|
|
43
|
+
AGPL). Enlarge logos/product shots without re-shooting.
|
|
44
|
+
|
|
45
|
+
These two cover the everyday CMO needs (clean product images, upscale assets) with
|
|
46
|
+
no GPL/AGPL code vendored and no license-murky weights.
|
|
47
|
+
|
|
48
|
+
## Orchestration: ComfyUI at arm's length
|
|
49
|
+
|
|
50
|
+
For heavier generation (image/video models), the practical backend is **ComfyUI
|
|
51
|
+
driven over its HTTP API** with a saved **workflow JSON** - invoked as an external
|
|
52
|
+
service, never vendored (it is GPL). The adapter pattern mirrors
|
|
53
|
+
[provider adapter contract](provider-adapter-contract.md): a host supplies the
|
|
54
|
+
running ComfyUI endpoint, the GPU, and the chosen (license-cleared) model weights;
|
|
55
|
+
Maggie submits a workflow + prompt and retrieves the output. Image/video results then
|
|
56
|
+
flow into `maggie-image` / `maggie-video` for sizing, captioning, and assembly.
|
|
57
|
+
|
|
58
|
+
## Guardrails (before any run)
|
|
59
|
+
|
|
60
|
+
- **State cost first.** Model downloads are gigabytes and GPU time is real; announce
|
|
61
|
+
the footprint and get a yes before downloading weights or running a generation
|
|
62
|
+
(global spend-first rule). Small CPU ops (rembg) are the exception.
|
|
63
|
+
- **If a hosted/API model is used instead of self-host**, the global BYOK rule
|
|
64
|
+
applies: route through the aggregator with `provider.order` + `allow_fallbacks:
|
|
65
|
+
false` + explicit `max_tokens`/limits, use our own key, and verify `is_byok`/
|
|
66
|
+
`cost:0`.
|
|
67
|
+
- **Weights license per model** (see the rule above) - record which weights are
|
|
68
|
+
cleared for commercial use.
|
|
69
|
+
- **No vendoring** of AGPL/GPL code (sd-webui, ComfyUI, Fooocus, Upscayl); invoke at
|
|
70
|
+
arm's length.
|
|
71
|
+
- **Provenance + rights + moderation**: generated media needs a recorded,
|
|
72
|
+
redacted provenance note and a rights/moderation check before publishing (as the
|
|
73
|
+
hero-video policy in `maggie-design` already requires for generated assets).
|
|
74
|
+
- **Never pass off generated media as a real photo of a real person/place** without
|
|
75
|
+
disclosure; honour facefusion-style misuse constraints.
|
|
76
|
+
|
|
77
|
+
## Staged plan (each gated on go-ahead + cost)
|
|
78
|
+
|
|
79
|
+
1. `rembg` / `backgroundremover` bg-removal + `Real-ESRGAN` upscaling helpers
|
|
80
|
+
(license-clean, mostly CPU) - likely one release.
|
|
81
|
+
2. ComfyUI HTTP-API adapter + a couple of saved workflows (image gen, inpaint),
|
|
82
|
+
host-supplied GPU + cleared weights - one release.
|
|
83
|
+
3. Video/audio generation models as separate, individually license-vetted adapters -
|
|
84
|
+
later, per model.
|
|
85
|
+
|
|
86
|
+
Pairs with: [generative visuals](generative-visuals.md) (code-drawn visuals, no
|
|
87
|
+
models), `maggie-image` / `maggie-video` (post-processing the output),
|
|
88
|
+
`maggie media generate` (the existing generation entry point).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Captions and transcription
|
|
2
|
+
|
|
3
|
+
Local, offline speech-to-text for video captions and transcripts, used by
|
|
4
|
+
`maggie-video`'s `captions` subcommand. All engines run on the user's machine - **no
|
|
5
|
+
paid API** - so transcription has no per-minute cost, only local CPU/GPU time.
|
|
6
|
+
Distilled from the reference library's R-category projects.
|
|
7
|
+
|
|
8
|
+
## Engines
|
|
9
|
+
|
|
10
|
+
| Engine | What / how | License | Reach for it when |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| openai-whisper | The reference Python CLI: `whisper IN --model base --output_format srt --output_dir DIR` | MIT | the default; simplest install (`pip install openai-whisper`) |
|
|
13
|
+
| whisper.cpp | C/C++ port, fast on CPU, GGML models: `whisper-cli -m model.bin -f IN -osrt -of OUTBASE` | MIT | no GPU / fastest CPU / embedding in a pipeline |
|
|
14
|
+
| whisperX | whisper + forced alignment for **word-level timestamps** and diarization: `whisperx IN --model large-v3 --output_format srt --output_dir DIR` | BSD-4 | word-accurate caption timing or speaker labels |
|
|
15
|
+
|
|
16
|
+
Model size trades speed for accuracy: `tiny` / `base` (fast, rough) -> `small` /
|
|
17
|
+
`medium` -> `large-v3` (slow, best). Use `--lang` to skip auto-detect when known.
|
|
18
|
+
|
|
19
|
+
## Workflow (voiceover -> captions -> on-screen)
|
|
20
|
+
|
|
21
|
+
1. **Transcribe** the voiceover or the video's audio to SRT with a local engine
|
|
22
|
+
(`maggie_video.py captions --input vo.mp3 --output vo.srt --engine whisper
|
|
23
|
+
--model base`). A larger model is slower but more accurate.
|
|
24
|
+
2. **Review** the SRT - never ship an unreviewed transcript; whisper mishears names,
|
|
25
|
+
numbers, and brand terms. A human edits it.
|
|
26
|
+
3. **Put it on screen**, one of:
|
|
27
|
+
- burn in (`maggie_video.py subtitle --input video.mp4 --srt vo.srt --output out.mp4`),
|
|
28
|
+
- soft-mux a toggleable track (`subtitle --soft`), or
|
|
29
|
+
- a `captions` layer in a `maggie_video.py compile` timeline (see
|
|
30
|
+
[video timeline model](video-timeline-model.md)).
|
|
31
|
+
|
|
32
|
+
## Rules
|
|
33
|
+
|
|
34
|
+
- Captions lift watch time and serve sound-off viewing - caption every social video.
|
|
35
|
+
- **Never fabricate a transcript**; generate it from the real audio and have a human
|
|
36
|
+
review before publishing (accuracy, names, and brand terms).
|
|
37
|
+
- Local/offline: no audio leaves the machine, no paid API. If a hosted transcription
|
|
38
|
+
API is ever wired in instead, the global spend-first rule applies - state the cost.
|
|
39
|
+
- Keep caption text inside the safe area (see `maggie-video` channel presets).
|
|
40
|
+
|
|
41
|
+
See `maggie-video` for the subcommands and the burn-in / soft-mux / compile options.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Generative visuals (creative coding)
|
|
2
|
+
|
|
3
|
+
Patterns for generating marketing visuals in code - hero backgrounds, textures, 3D
|
|
4
|
+
product shots, data-viz, and stylized images - distilled from the leading creative-
|
|
5
|
+
coding libraries. We adopt **patterns, not code**. Output becomes a deliverable as a
|
|
6
|
+
**still** (canvas/SVG export) or a **loop** (captured via
|
|
7
|
+
[design-motion production](design-motion-production.md) Part C or
|
|
8
|
+
[ffmpeg recipes](ffmpeg-recipes.md), then assembled in `maggie-video`). Determinism
|
|
9
|
+
by seeding matches `maggie-spirit`'s philosophy: same seed -> same pixels.
|
|
10
|
+
|
|
11
|
+
## Libraries
|
|
12
|
+
|
|
13
|
+
| Library | What / model | Lang | License | Marketing fit |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| three.js | WebGL 3D: Scene + Camera + WebGLRenderer; `renderer.render(scene,camera)` in a loop | JS | MIT | 3D product spins, logo stings, depth-y hero backgrounds |
|
|
16
|
+
| Babylon.js | Higher-level 3D engine (PBR, asset pipeline); heavier | JS/TS | Apache-2.0 | 3D product config; alt to three when its pipeline earns the weight |
|
|
17
|
+
| react-three-fiber | three.js as React JSX (`<mesh><boxGeometry/>…`), `useFrame` | JS/TS | MIT | 3D on a React site |
|
|
18
|
+
| p5.js | Canvas sketch: `setup()` once + `draw()` per frame; `random()/noise()` with `randomSeed()/noiseSeed()` | JS | **LGPL-2.1** | generative hero backgrounds, flow fields, noise art |
|
|
19
|
+
| paper.js | Vector scene graph (Path/Group), boolean ops, hit-testing | JS | MIT | editable vector hero art, print-quality SVG |
|
|
20
|
+
| two.js | One 2D API across SVG/Canvas/WebGL renderers | JS | MIT | vector art you want to export as SVG or capture from canvas |
|
|
21
|
+
| rough.js | Hand-drawn/sketchy shapes; `{seed}` = reproducible | JS/TS | MIT | approachable hand-made brand style, doodle accents |
|
|
22
|
+
| zdog | Pseudo-3D round/flat vector, designer-friendly | JS | MIT | cute 3D-ish icons, mascot/logo spins |
|
|
23
|
+
| pts | Computational geometry (Space + Form + Pt) | TS/JS | Apache-2.0 | geometric/particle hero art |
|
|
24
|
+
| hydra | Live-coded video synth (chained GLSL ops) | JS | **AGPL-3.0** | trippy animated backgrounds - **pattern only, do not ship its code** |
|
|
25
|
+
| regl | Functional WebGL (stateless draw commands) | JS | MIT | custom shader backgrounds/gradients when three is overkill |
|
|
26
|
+
| d3 | Data-driven SVG (scales + selections) | JS | ISC | bespoke infographics - but defer standard charts to the `dataviz` skill |
|
|
27
|
+
| fogleman/primitive | Reproduce an image with N geometric primitives (CLI) | Go | MIT | stylize a product photo into abstract art (still or GIF) |
|
|
28
|
+
| svg.js | Lightweight SVG DOM manipulation | JS | MIT | programmatic SVG icons/badges/dividers |
|
|
29
|
+
| nannou | Rust creative framework (desktop) | Rust | MIT/Apache | note only - high-perf generative art in Rust |
|
|
30
|
+
|
|
31
|
+
**License flags:** hydra is **AGPL-3.0**, p5 is **LGPL-2.1**, processing (non-core)
|
|
32
|
+
is GPL - fine as *idea* sources, but do not vendor their code into shipped product;
|
|
33
|
+
reimplement the pattern or use an MIT equivalent (two.js / pts / regl / svg.js).
|
|
34
|
+
|
|
35
|
+
## Patterns and the deliverable path
|
|
36
|
+
|
|
37
|
+
- **2D generative** (p5 / paper / two / rough / zdog / pts / svg.js): `setup()` once +
|
|
38
|
+
`draw()`/`update()` per frame. Use for brand-tinted hero/section backgrounds,
|
|
39
|
+
textures, doodle accents, geometric motifs. **Still:** SVG-capable libs serialize
|
|
40
|
+
their `<svg>` (crisp, editable, print-ready); canvas libs use `canvas.toDataURL()`
|
|
41
|
+
-> PNG. **Loop:** run the sketch headless and capture via design-motion Part C, or
|
|
42
|
+
in-browser with CCapture.js (MIT), then hand to `maggie-video`.
|
|
43
|
+
- **3D** (three / babylon / r3f): Scene + Camera + Renderer; animate by frame index.
|
|
44
|
+
Product spins, 3D logo stings, depth-y backgrounds. **Still:**
|
|
45
|
+
`renderer.domElement.toDataURL()` (set `preserveDrawingBuffer:true`); **loop:** drive
|
|
46
|
+
by frame, capture as above. Prefer three/r3f (MIT); babylon only when needed.
|
|
47
|
+
- **Data-viz** (d3): `d3-scale` + `d3-selection`; SVG is the native output. For
|
|
48
|
+
standard charts, **defer to the `dataviz` skill** (its palette/spec); pull in d3
|
|
49
|
+
only for bespoke marks (arc diagrams, force layouts, custom infographics).
|
|
50
|
+
- **Live visuals** (hydra): chained feedback-graph ops -> animated GLSL. Renders to a
|
|
51
|
+
canvas -> capture -> `maggie-video`. **AGPL: idea only**; for an MIT shader
|
|
52
|
+
background use regl.
|
|
53
|
+
- **Image -> primitives** (fogleman/primitive, MIT, Go CLI): stylize a photo into
|
|
54
|
+
abstract geometry.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
primitive -i input.png -o output.svg -n 100 -m 1
|
|
58
|
+
# -n shapes (50-200); -m mode (1 triangle,2 rect,3 ellipse,4 circle,6 beziers,8 polygon);
|
|
59
|
+
# output type by extension: PNG/JPG (raster), SVG (vector), GIF (animated, needs ImageMagick)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Still:** PNG/SVG directly; **loop:** `.gif` or `%d`-numbered PNG frames ->
|
|
63
|
+
`maggie-video`. It is randomized (no seed): generate once and **store the artifact**;
|
|
64
|
+
do not regenerate on every build.
|
|
65
|
+
|
|
66
|
+
## Determinism
|
|
67
|
+
|
|
68
|
+
p5 `randomSeed()/noiseSeed()`, rough.js `{seed}`, and a seeded PRNG in place of
|
|
69
|
+
`Math.random` give byte-reproducible stills; the frame-index/virtual-clock rule from
|
|
70
|
+
[programmatic video](programmatic-video.md) gives reproducible motion. `primitive`
|
|
71
|
+
and `hydra` are non-deterministic - capture and store their output as a fixed asset.
|
|
72
|
+
|
|
73
|
+
## Placement
|
|
74
|
+
|
|
75
|
+
- **maggie-spirit** owns seeded brand marks/characters; borrow the 2D-canvas sketch
|
|
76
|
+
model and seed discipline for flat-vector mark construction (zdog/two.js/rough).
|
|
77
|
+
Generative *backgrounds/scenes* do not live here.
|
|
78
|
+
- **maggie-design** owns generative hero backgrounds, section textures, 3D product
|
|
79
|
+
shots and image stylization as page/brand visuals; it points at this reference and
|
|
80
|
+
cross-links design-motion / `maggie-video` for capture-to-deliverable.
|
|
81
|
+
- **Reference-only:** d3 (defer to `dataviz`), hydra (AGPL, idea only), regl, babylon,
|
|
82
|
+
nannou/processing/openFrameworks (desktop notes), svg.js.
|
|
83
|
+
|
|
84
|
+
For *AI* media generation (diffusion/video models, bg-removal, upscaling) rather than
|
|
85
|
+
code-drawn visuals, see [AI media generation](ai-media-generation.md) (self-hosted,
|
|
86
|
+
licensing + cost gated).
|
|
87
|
+
|
|
88
|
+
## Credits
|
|
89
|
+
|
|
90
|
+
Patterns adapted (not code) from three.js, p5.js, d3, paper.js, two.js, rough.js,
|
|
91
|
+
zdog, pts, hydra, regl, Babylon.js, react-three-fiber, fogleman/primitive, svg.js,
|
|
92
|
+
nannou - licenses in the table above. Capture pipeline: see
|
|
93
|
+
[programmatic video](programmatic-video.md), [design-motion production](design-motion-production.md),
|
|
94
|
+
[ffmpeg recipes](ffmpeg-recipes.md).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Image recipes
|
|
2
|
+
|
|
3
|
+
The distilled, correct image-processing command layer for marketing assets, used by
|
|
4
|
+
`maggie-image`. These are the commands `maggie_image.py` generates. Distilled from
|
|
5
|
+
ImageMagick, sharp, svgo, resvg, squoosh and imagemin (reference library category P).
|
|
6
|
+
We **invoke binaries at arm's length** (adopt defaults, not code).
|
|
7
|
+
|
|
8
|
+
**Tool posture.** ImageMagick (`magick` on IM7, else `convert`) is almost always
|
|
9
|
+
present and covers nearly every raster op, so it is the **primary** path; dedicated
|
|
10
|
+
tools (svgo, resvg, cwebp, avifenc, pngquant) are the **better-quality alternative
|
|
11
|
+
used automatically when installed**. Web-delivery quality defaults follow sharp/
|
|
12
|
+
squoosh: JPEG/WebP ~80, AVIF ~50. OG/social are **cover-cropped**; logos **pad**.
|
|
13
|
+
Strip metadata on anything public (privacy + bytes).
|
|
14
|
+
|
|
15
|
+
## Resize + fit (the most-used op)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# cover / crop to exactly WxH (fills the frame, center-crop)
|
|
19
|
+
convert in.jpg -strip -resize 1200x630^ -gravity center -extent 1200x630 -quality 82 out.jpg
|
|
20
|
+
# contain / letterbox with a pad color
|
|
21
|
+
convert in.jpg -strip -resize 1200x630 -background '#ffffff' -gravity center -extent 1200x630 out.jpg
|
|
22
|
+
# downscale-only (never enlarge): -resize 1200x630>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
sharp's smart crop (`fit:'cover', position:attention`) avoids cutting off a face;
|
|
26
|
+
ImageMagick has no equal, so for portraits prefer sharp when available.
|
|
27
|
+
|
|
28
|
+
## WebP / AVIF (file-size wins)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
convert in.png -strip -quality 80 -define webp:method=6 out.webp # or: cwebp -q 80 -m 6 in.png -o out.webp
|
|
32
|
+
convert in.png -strip -quality 50 out.avif # or: avifenc -s 6 --min 20 --max 40 in.png out.avif
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Channel / OG presets
|
|
36
|
+
|
|
37
|
+
Cover-crop to the target: OG 1200x630, Twitter 1600x900, square 1080x1080, story/reel
|
|
38
|
+
1080x1920, landscape 1920x1080, LinkedIn 1200x627, Pinterest 1000x1500. Same command
|
|
39
|
+
as cover resize with the preset size, `-strip -quality 82`.
|
|
40
|
+
|
|
41
|
+
## Favicon + OG set
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
convert in.png -define icon:auto-resize=64,48,32,16 favicon.ico # multi-res .ico
|
|
45
|
+
convert in.png -strip -resize 180x180 apple-touch-icon.png # + 16/32/48/192/512 PNGs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Strip metadata / compress
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
convert in.jpg -strip out.jpg # drop EXIF/GPS/ICC
|
|
52
|
+
convert in.jpg -strip -interlace Plane -quality 82 out.jpg # progressive JPEG
|
|
53
|
+
pngquant --quality 60-82 --strip --output out.png in.png # lossy PNG (best); IM fallback: -colors 256 -define png:compression-level=9
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## SVG optimize / render
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
svgo --multipass -i in.svg -o out.svg # keeps viewBox + title (responsive + a11y). If svgo absent, leave the SVG alone - never rasterize as a substitute.
|
|
60
|
+
resvg -w 512 in.svg out.png # best-fidelity SVG -> PNG (or rsvg-convert -w 512); IM fallback (-background none -density 384) is lower fidelity
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Montage / contact sheet (carousels, screenshot grids)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
montage a.png b.png c.png -tile 3x -geometry 400x400+10+10 -background '#ffffff' sheet.jpg
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## The CLI
|
|
70
|
+
|
|
71
|
+
`maggie_image.py` generates each command above (and runs it with `--run` when the
|
|
72
|
+
tool is on PATH; `--force-im` forces the ImageMagick path). It detects IM6 `convert`
|
|
73
|
+
vs IM7 `magick`:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
maggie tool maggie_image.py social --input hero.png --output og.jpg --preset og
|
|
77
|
+
maggie tool maggie_image.py --run webp --input hero.png --output hero.webp --quality 80
|
|
78
|
+
maggie tool maggie_image.py --run favicon --input logo.png --out-dir public/
|
|
79
|
+
maggie tool maggie_image.py svg2png --input logo.svg --output logo.png --width 512
|
|
80
|
+
maggie tool maggie_image.py presets
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Licenses
|
|
84
|
+
|
|
85
|
+
Invoked at arm's length: ImageMagick (permissive), SVGO (MIT), resvg (MIT/Apache),
|
|
86
|
+
sharp (Apache-2.0), squoosh codecs (Apache-2.0), imagemin (MIT), cwebp/avifenc
|
|
87
|
+
(BSD). **Avoid vendoring code from** Upscayl (AGPL-3.0) - for AI upscaling invoke the
|
|
88
|
+
separate `realesrgan-ncnn-vulkan` binary instead. sharp/squoosh quality defaults are
|
|
89
|
+
adopted as the preset numbers.
|