@topy-ai/maggie 0.8.15 → 0.8.17
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 +1 -1
- package/bundled-references/captions-transcription.md +21 -1
- package/bundled-references/design-editor-patterns.md +29 -2
- package/bundled-references/ffmpeg-recipes.md +21 -0
- package/bundled-references/generative-visuals.md +23 -0
- package/bundled-references/image-recipes.md +43 -0
- package/bundled-references/programmatic-video.md +22 -0
- package/bundled-references/video-timeline-model.md +22 -4
- package/bundled-references/web-animation-patterns.md +124 -29
- package/bundled-skills/maggie-image/SKILL.md +6 -2
- package/bundled-skills/maggie-video/SKILL.md +1 -1
- package/bundled-tools/clis/maggie_image.py +40 -3
- package/bundled-tools/clis/maggie_video.py +22 -14
- package/package.json +1 -1
- package/references/captions-transcription.md +21 -1
- package/references/design-editor-patterns.md +29 -2
- package/references/ffmpeg-recipes.md +21 -0
- package/references/generative-visuals.md +23 -0
- package/references/image-recipes.md +43 -0
- package/references/programmatic-video.md +22 -0
- package/references/video-timeline-model.md +22 -4
- package/references/web-animation-patterns.md +124 -29
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maggie-video
|
|
3
3
|
description: Produce and process marketing video - render a composition to MP4/GIF/WebM, assemble clips/images/text/captions/audio on a timeline, cut to channel aspects (reel/square/youtube), burn in captions, normalize loudness, watermark, transcode, and make social cuts. Engine-neutral over the renderer (ffmpeg for assembly/transcode, a programmatic engine for code-drawn animation). Use when the user wants to make, edit, cut, caption, resize, or export a video for marketing.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.1.
|
|
5
|
+
version: 0.1.5
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Video
|
|
@@ -10,7 +10,7 @@ and are generate-only until it is installed. Web-delivery quality defaults follo
|
|
|
10
10
|
sharp/squoosh (JPEG/WebP ~80, AVIF ~50); OG/social are cover-cropped, logos pad.
|
|
11
11
|
|
|
12
12
|
Subcommands: resize, social, webp, avif, favicon, strip, compress, svgo, svg2png,
|
|
13
|
-
svg-export, composite, look, montage, presets.
|
|
13
|
+
svg-export, svg-query, composite, look, montage, presets.
|
|
14
14
|
"""
|
|
15
15
|
import argparse
|
|
16
16
|
import shlex
|
|
@@ -59,18 +59,25 @@ def _resize_ops(w, h, mode, pad):
|
|
|
59
59
|
|
|
60
60
|
def c_resize(a):
|
|
61
61
|
w, h = a.size.lower().split("x")
|
|
62
|
-
cmd = _im() + [a.input, "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
62
|
+
cmd = _im() + [a.input, "-auto-orient", "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
63
63
|
["-quality", str(a.quality), a.output]
|
|
64
64
|
return emit(cmd, a.run, _im_tool())
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def c_social(a):
|
|
68
68
|
w, h = SOCIAL[a.preset]
|
|
69
|
-
cmd = _im() + [a.input, "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
69
|
+
cmd = _im() + [a.input, "-auto-orient", "-strip"] + _resize_ops(w, h, a.mode, a.pad) + \
|
|
70
70
|
["-quality", str(a.quality), a.output]
|
|
71
71
|
return emit(cmd, a.run, _im_tool())
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def c_trim(a):
|
|
75
|
+
"""Trim uniform border / cut a product or logo out of its background
|
|
76
|
+
(ImageMagick -fuzz -trim; +repage resets the virtual canvas)."""
|
|
77
|
+
cmd = _im() + [a.input, "-auto-orient", "-fuzz", f"{a.fuzz}%", "-trim", "+repage", a.output]
|
|
78
|
+
return emit(cmd, a.run, _im_tool())
|
|
79
|
+
|
|
80
|
+
|
|
74
81
|
def c_webp(a):
|
|
75
82
|
if shutil.which("cwebp") and not a.force_im:
|
|
76
83
|
return emit(["cwebp", "-q", str(a.quality), "-m", "6", a.input, "-o", a.output], a.run, "cwebp")
|
|
@@ -156,6 +163,14 @@ def c_svg_export(a):
|
|
|
156
163
|
"""Inkscape headless export - fills the gaps resvg/ImageMagick can't: text-to-path
|
|
157
164
|
font outlining, PDF/EPS for print, and per-id slicing. GPL app, invoked at arm's
|
|
158
165
|
length (like ImageMagick)."""
|
|
166
|
+
# Actions pipeline: run an ordered verb list in one invocation (clean+flatten+export
|
|
167
|
+
# as a single call). `inkscape --action-list` prints every verb. Arm's-length (GPL).
|
|
168
|
+
if a.actions:
|
|
169
|
+
verbs = a.actions
|
|
170
|
+
if "export-filename" not in verbs and "export-do" not in verbs:
|
|
171
|
+
verbs = f"{verbs};export-filename:{a.output};export-type:{a.type};export-do"
|
|
172
|
+
print("# Inkscape actions pipeline: run an ordered verb list in one process.")
|
|
173
|
+
return emit(["inkscape", a.input, f"--actions={verbs}"], a.run, "inkscape")
|
|
159
174
|
args = ["inkscape", a.input, f"--export-type={a.type}", f"--export-filename={a.output}"]
|
|
160
175
|
if a.width:
|
|
161
176
|
args.append(f"--export-width={a.width}")
|
|
@@ -175,6 +190,18 @@ def c_svg_export(a):
|
|
|
175
190
|
return emit(args, a.run, "inkscape")
|
|
176
191
|
|
|
177
192
|
|
|
193
|
+
def c_svg_query(a):
|
|
194
|
+
"""Query an SVG's element ids and bounding boxes (Inkscape, arm's-length). Use the
|
|
195
|
+
output to build a correct --export-id slice list before slicing."""
|
|
196
|
+
if a.id:
|
|
197
|
+
args = ["inkscape", a.input, f"--query-id={a.id}",
|
|
198
|
+
"--query-x", "--query-y", "--query-width", "--query-height"]
|
|
199
|
+
else:
|
|
200
|
+
args = ["inkscape", a.input, "--query-all"] # id,x,y,w,h per object (CSV)
|
|
201
|
+
print("# Inkscape geometry query: discover ids/bounds before --export-id slicing.")
|
|
202
|
+
return emit(args, a.run, "inkscape")
|
|
203
|
+
|
|
204
|
+
|
|
178
205
|
def c_composite(a):
|
|
179
206
|
"""Layer an overlay onto a base with a blend mode (Pinta/Natron vocabulary ->
|
|
180
207
|
ImageMagick -compose). Modes: Over, Multiply, Screen, Overlay, Darken, Lighten,
|
|
@@ -253,6 +280,9 @@ def main(argv=None):
|
|
|
253
280
|
fv.add_argument("--input", required=True); fv.add_argument("--out-dir", dest="out_dir", required=True)
|
|
254
281
|
fv.set_defaults(fn=c_favicon)
|
|
255
282
|
|
|
283
|
+
tr = sub.add_parser("trim", help="trim border / cut product out of its background")
|
|
284
|
+
io(tr); tr.add_argument("--fuzz", type=int, default=5, help="color tolerance %%"); tr.set_defaults(fn=c_trim)
|
|
285
|
+
|
|
256
286
|
st = sub.add_parser("strip", help="strip metadata (EXIF/GPS/ICC)"); io(st); st.set_defaults(fn=c_strip)
|
|
257
287
|
co = sub.add_parser("compress", help="compress JPEG/PNG"); io(co)
|
|
258
288
|
co.add_argument("--quality", type=int, default=82); co.set_defaults(fn=c_compress)
|
|
@@ -275,8 +305,15 @@ def main(argv=None):
|
|
|
275
305
|
se.add_argument("--area", choices=["drawing", "page"], default="drawing")
|
|
276
306
|
se.add_argument("--id", help="export only this element/layer id")
|
|
277
307
|
se.add_argument("--text-to-path", dest="text_to_path", action="store_true", help="outline fonts")
|
|
308
|
+
se.add_argument("--actions", help="run an ordered verb pipeline instead (e.g. "
|
|
309
|
+
"'select-all;object-to-path;vacuum-defs'); auto-appends export verbs")
|
|
278
310
|
se.add_argument("--background"); se.set_defaults(fn=c_svg_export)
|
|
279
311
|
|
|
312
|
+
sq = sub.add_parser("svg-query", help="query SVG element ids + bounding boxes (Inkscape)")
|
|
313
|
+
sq.add_argument("--input", required=True)
|
|
314
|
+
sq.add_argument("--id", help="query one id's x/y/w/h (default: --query-all for every object)")
|
|
315
|
+
sq.set_defaults(fn=c_svg_query)
|
|
316
|
+
|
|
280
317
|
cp = sub.add_parser("composite", help="layer an overlay on a base with a blend mode")
|
|
281
318
|
cp.add_argument("--base", required=True); cp.add_argument("--overlay", required=True)
|
|
282
319
|
cp.add_argument("--output", required=True); cp.add_argument("--mode", default="Over")
|
|
@@ -511,17 +511,20 @@ def c_captions(a):
|
|
|
511
511
|
cmd = [tool, "-m", a.model, "-f", a.input, "-osrt", "-of",
|
|
512
512
|
os.path.join(out_dir, base)]
|
|
513
513
|
print(f"# whisper.cpp writes {os.path.join(out_dir, base)}.srt; --model is the .bin path")
|
|
514
|
-
|
|
515
|
-
tool = "whisperx"
|
|
516
|
-
cmd = [tool, a.input, "--model", a.model, "--output_format",
|
|
514
|
+
else: # openai-whisper (default) / whisperx - same flag surface
|
|
515
|
+
tool = "whisperx" if a.engine == "whisperx" else "whisper"
|
|
516
|
+
cmd = [tool, a.input, "--model", a.model, "--output_format", a.format,
|
|
517
517
|
"--output_dir", out_dir] + lang
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
518
|
+
if a.word_timestamps:
|
|
519
|
+
cmd += ["--word_timestamps", "True"]
|
|
520
|
+
if a.highlight_words:
|
|
521
|
+
cmd += ["--highlight_words", "True"] # karaoke-style per-word highlight
|
|
522
|
+
if a.initial_prompt:
|
|
523
|
+
cmd += ["--initial_prompt", a.initial_prompt] # bias brand/product spellings
|
|
524
|
+
print(f"# {tool} writes {os.path.join(out_dir, base)}.{a.format} in --output_dir; "
|
|
525
|
+
f"openai-whisper's default model is now 'turbo' (large-v3-turbo).")
|
|
526
|
+
print("# transcription is local/offline (no paid API); a larger model is slower but better.")
|
|
527
|
+
print("# --highlight-words gives karaoke captions; --initial-prompt fixes brand-term spelling.")
|
|
525
528
|
print("# then: maggie_video.py subtitle --input video.mp4 --srt <that>.srt --output out.mp4 (burn in)")
|
|
526
529
|
return emit(cmd, a.run, tool=tool)
|
|
527
530
|
|
|
@@ -602,11 +605,16 @@ def main(argv=None):
|
|
|
602
605
|
ac.add_argument("--margin", type=float, default=0.1, help="padding kept around speech (s)")
|
|
603
606
|
ac.set_defaults(fn=c_autocut)
|
|
604
607
|
|
|
605
|
-
ca = sub.add_parser("captions", help="transcribe speech to SRT (local whisper)")
|
|
606
|
-
ca.add_argument("--input", required=True); ca.add_argument("--output", required=True, help="target
|
|
608
|
+
ca = sub.add_parser("captions", help="transcribe speech to SRT/VTT (local whisper)")
|
|
609
|
+
ca.add_argument("--input", required=True); ca.add_argument("--output", required=True, help="target caption file")
|
|
607
610
|
ca.add_argument("--engine", choices=["whisper", "whispercpp", "whisperx"], default="whisper")
|
|
608
|
-
ca.add_argument("--model", default="
|
|
609
|
-
ca.add_argument("--
|
|
611
|
+
ca.add_argument("--model", default="turbo", help="model (openai-whisper default 'turbo'); .bin path for whispercpp")
|
|
612
|
+
ca.add_argument("--format", default="srt", help="srt|vtt|txt|tsv|json (openai-whisper/whisperx)")
|
|
613
|
+
ca.add_argument("--lang", default="auto")
|
|
614
|
+
ca.add_argument("--word-timestamps", dest="word_timestamps", action="store_true")
|
|
615
|
+
ca.add_argument("--highlight-words", dest="highlight_words", action="store_true", help="karaoke per-word highlight")
|
|
616
|
+
ca.add_argument("--initial-prompt", dest="initial_prompt", help="bias brand/product term spelling")
|
|
617
|
+
ca.set_defaults(fn=c_captions)
|
|
610
618
|
|
|
611
619
|
sub.add_parser("presets", help="list channel aspect presets").set_defaults(fn=c_presets)
|
|
612
620
|
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ Distilled from the reference library's R-category projects.
|
|
|
9
9
|
|
|
10
10
|
| Engine | What / how | License | Reach for it when |
|
|
11
11
|
|---|---|---|---|
|
|
12
|
-
| openai-whisper |
|
|
12
|
+
| openai-whisper | `whisper IN --model turbo --output_format srt --output_dir DIR` (default model is now **`turbo`** = large-v3-turbo; formats `txt/vtt/srt/tsv/json`) | MIT | the default; simplest install (`pip install openai-whisper`) |
|
|
13
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
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
15
|
|
|
@@ -38,4 +38,24 @@ Model size trades speed for accuracy: `tiny` / `base` (fast, rough) -> `small` /
|
|
|
38
38
|
API is ever wired in instead, the global spend-first rule applies - state the cost.
|
|
39
39
|
- Keep caption text inside the safe area (see `maggie-video` channel presets).
|
|
40
40
|
|
|
41
|
+
## Caption-shaping flags (built into openai-whisper)
|
|
42
|
+
|
|
43
|
+
The core CLI already does what we previously routed to whisperX or post-processing
|
|
44
|
+
(`maggie_video.py captions` exposes these):
|
|
45
|
+
|
|
46
|
+
- `--word_timestamps True` - word-level timings (whisperX still wins on alignment +
|
|
47
|
+
diarization, but core whisper now has them).
|
|
48
|
+
- `--highlight_words True` - **karaoke-style** per-word highlight in SRT/VTT (the
|
|
49
|
+
social-caption effect); render the ASS with ffmpeg `subtitles`.
|
|
50
|
+
- `--max_line_width` / `--max_line_count` / `--max_words_per_line` - native line
|
|
51
|
+
wrapping so captions fit the channel safe-area, no post-processing.
|
|
52
|
+
- `--initial_prompt "<brand, product names>"` - bias the model toward correct brand/
|
|
53
|
+
jargon spelling (the concrete mitigation for the mishears-brand-terms risk; still
|
|
54
|
+
human-review).
|
|
55
|
+
- Anti-hallucination over music/silence: `--hallucination_silence_threshold`,
|
|
56
|
+
`--compression_ratio_threshold`, `--logprob_threshold`, `--no_speech_threshold` -
|
|
57
|
+
worth setting for music-bed marketing clips.
|
|
58
|
+
- `--task translate` (foreign audio -> English subs), `--clip_timestamps` (only given
|
|
59
|
+
ranges).
|
|
60
|
+
|
|
41
61
|
See `maggie-video` for the subcommands and the burn-in / soft-mux / compile options.
|
|
@@ -15,8 +15,12 @@ the image is reconstructed by replaying the stack. Borrow this as a maggie "imag
|
|
|
15
15
|
recipe": a small JSON `[ {op, params}, ... ]` stored next to the original and replayed
|
|
16
16
|
by `maggie_image.py` (each op -> an ImageMagick/sharp command). It is **reproducible,
|
|
17
17
|
auditable, re-editable, and batchable** - apply one "look" recipe to every photo in a
|
|
18
|
-
campaign for consistent brand grading. `darktable-cli <
|
|
19
|
-
from such a stack (
|
|
18
|
+
campaign for consistent brand grading. `darktable-cli <input> [sidecar.xmp]
|
|
19
|
+
<output-dir>` renders from such a stack (XMP optional/auto-discovered, final arg is a
|
|
20
|
+
**directory**; GPL binary, arm's-length). Two source details worth adopting: **params
|
|
21
|
+
versioning** (each module keeps `legacy_params` for forward-compatible migration -
|
|
22
|
+
version your recipe ops) and **"Styles" = saved reusable history** (name a look once,
|
|
23
|
+
apply to many).
|
|
20
24
|
|
|
21
25
|
## LLM-generatable scene / diagram formats
|
|
22
26
|
|
|
@@ -75,3 +79,26 @@ only: penpot (MPL), tldraw (custom), krita/krita-ai-diffusion (GPL), drawio icon
|
|
|
75
79
|
MIT and freely borrowable as ideas (still reimplement, don't bulk-copy): excalidraw,
|
|
76
80
|
Method-Draw, rough.js, Pinta. drawio code Apache-2.0; its diagram output is
|
|
77
81
|
unrestricted.
|
|
82
|
+
|
|
83
|
+
## From full-source study (net-new)
|
|
84
|
+
|
|
85
|
+
- **tldraw** - the LLM mutation surface is `editor.createShapes/updateShapes/
|
|
86
|
+
deleteShapes` (batch partial mutations); the store ships schema **migration
|
|
87
|
+
sequences**, a composable **validation layer**, and a **side-effects manager**
|
|
88
|
+
(before/after create/change/delete hooks). `parentId` can be a page *or* a shape;
|
|
89
|
+
pages carry a fractional `index`. Adopt the batch-mutation + validate + migrate
|
|
90
|
+
shape as the model for an agent-written canvas doc.
|
|
91
|
+
- **penpot** - **design tokens are W3C DTCG** (import/export + aliasing), components
|
|
92
|
+
have **variants**, shapes carry **flex/grid layout**; the agent boundary is an
|
|
93
|
+
**official MCP server talking to a sandboxed in-app plugin over WebSocket** - a
|
|
94
|
+
strong arm's-length model for making a design doc agent-writable (mirrors Maggie's
|
|
95
|
+
own MCP posture).
|
|
96
|
+
- **drawio** - the `mxConstants` STYLE_* key table is a **validated allow-list** for
|
|
97
|
+
LLM-emitted styles; `mxEdgeStyle` routing + hierarchical/tree/organic **auto-layout**
|
|
98
|
+
mean a generated node+edge list can self-position; `.drawio` pages are often
|
|
99
|
+
**deflate+base64** compressed.
|
|
100
|
+
- **krita** - an **adjustment/filter layer holds a serialized filter config** = a
|
|
101
|
+
non-destructive recipe living in the layer stack; ops are **mask/selection-scoped**;
|
|
102
|
+
`.kra` is `zip(manifest XML + per-layer blobs)`; a **Python scripting API** and a
|
|
103
|
+
JSON-manifest plugin boundary let a generative backend register like any filter -
|
|
104
|
+
the "AI as a swappable backend" posture.
|
|
@@ -128,3 +128,24 @@ maggie tool maggie_video.py compile --spec timeline.json # declarative timelin
|
|
|
128
128
|
maggie tool maggie_video.py --run encode --input in.mov --output out.mp4 # actually runs
|
|
129
129
|
maggie tool maggie_video.py presets
|
|
130
130
|
```
|
|
131
|
+
|
|
132
|
+
## More filters worth reaching for (confirmed in libavfilter)
|
|
133
|
+
|
|
134
|
+
- **Green-screen / keying** (UGC, product on a brand background):
|
|
135
|
+
`chromakey=0x00FF00:0.1:0.0` (or `colorkey`), clean fringes with `despill`.
|
|
136
|
+
- **Fade to/from black** (distinct from clip crossfade): `fade=t=in:st=0:d=0.5`,
|
|
137
|
+
`fade=t=out:st=9.5:d=0.5`.
|
|
138
|
+
- **Brand color grade / LUT**: `lut3d=brand.cube`, or `curves`/`eq`/`colorbalance`/`hue`.
|
|
139
|
+
- **Karaoke captions**: `subtitles=in.ass` with a full ASS file (word highlight) -
|
|
140
|
+
pairs with whisper `--highlight_words` ([captions](captions-transcription.md)).
|
|
141
|
+
- **Focus/credits**: `gblur` + `vignette`; `scroll` for scrolling outros.
|
|
142
|
+
- **GPU fast path** when hardware is present: `overlay_cuda`/`overlay_vulkan`,
|
|
143
|
+
`xfade_opencl` for long renders.
|
|
144
|
+
|
|
145
|
+
## Target-filesize social presets (HandBrake idea)
|
|
146
|
+
|
|
147
|
+
Beyond aspect, encode to **fit under a platform's upload cap**: pick a two-pass video
|
|
148
|
+
bitrate from the cap and duration - `bitrate_bits = size_bytes * 8 / duration - audio`
|
|
149
|
+
- e.g. "25 MB / 30 s / 1080p". A named preset bundles `(upload cap, max duration) ->
|
|
150
|
+
resolution + bitrate`. (HandBrake ships exactly these as "Social 25 MB 30 Seconds"
|
|
151
|
+
presets.)
|
|
@@ -119,3 +119,26 @@ nannou - licenses in the table above. Capture pipeline: see
|
|
|
119
119
|
prompting, ControlNet/IP-Adapter conditioning), see
|
|
120
120
|
[AI media generation](ai-media-generation.md) and
|
|
121
121
|
[design editor patterns](design-editor-patterns.md).
|
|
122
|
+
|
|
123
|
+
### Headless capture + 3D export (from full-source study)
|
|
124
|
+
|
|
125
|
+
How to render these engines **without a screen**, and extra output formats worth
|
|
126
|
+
knowing:
|
|
127
|
+
|
|
128
|
+
- **Babylon.js `NullEngine`** (Apache-2.0) - first-class **server-side headless**
|
|
129
|
+
rendering (no GPU/canvas); the cleanest headless story for render-automation jobs,
|
|
130
|
+
plus a built-in **procedural-texture library** (brick/cloud/fire/marble/noise/wood),
|
|
131
|
+
`CreateScreenshotUsingRenderTarget` for 4K+ stills from a small viewport, and
|
|
132
|
+
**NodeMaterial** (shaders serialize to JSON - an LLM-emittable look). This is the
|
|
133
|
+
concrete reason to pick Babylon over three for automation.
|
|
134
|
+
- **three.js** (MIT) - `readRenderTargetPixels()`/`...Async` read pixels from an
|
|
135
|
+
offscreen `WebGLRenderTarget` (true headless high-res, not on-screen canvas);
|
|
136
|
+
**SVGRenderer** emits a 3D scene as editable SVG; **USDZExporter** turns a 3D product
|
|
137
|
+
into a free **iOS AR Quick Look** asset; **GPUComputationRenderer** (ping-pong FBO
|
|
138
|
+
sim: flocking/fluid) = a living, seeded generative hero background.
|
|
139
|
+
- **openFrameworks** (MIT) - `ofFbo` offscreen framebuffer for arbitrary-res stills;
|
|
140
|
+
**ofxEmscripten** compiles a desktop sketch to WebAssembly for the browser;
|
|
141
|
+
**ofxSvg** parses + emits structured SVG.
|
|
142
|
+
- **Processing** - `PSurfaceNone` (headless) and `createGraphics(big)` (render larger
|
|
143
|
+
than the screen -> high-res still). License at feature granularity: core raster is
|
|
144
|
+
LGPL, but **SVG/PDF export is in non-core (GPL)** - reimplement, don't vendor.
|
|
@@ -103,6 +103,36 @@ inkscape in.svg --export-id=badge --export-id-only --export-type=png --export-fi
|
|
|
103
103
|
inkscape in.svg --export-type=png --export-dpi=300 --export-area-page --export-filename=print.png
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
**From the real source (GitLab `inkscape/inkscape`, 1.5-dev — the GitHub mirror is a
|
|
107
|
+
stub; CLI flags above all confirmed current against `man/inkscape.pod.in`).** Three
|
|
108
|
+
net-new capabilities worth adopting, all still arm's-length (no GPL vendored):
|
|
109
|
+
|
|
110
|
+
- **Actions pipeline + batch (`--actions` / `--shell`).** One invocation can run an
|
|
111
|
+
ordered list of verbs, so a cleanup+flatten+export is a single call instead of a
|
|
112
|
+
chain. `maggie_image.py svg-export` can emit this for a "clean then export" job:
|
|
113
|
+
```bash
|
|
114
|
+
inkscape in.svg \
|
|
115
|
+
--actions="select-all;object-to-path;vacuum-defs;export-plain-svg;export-filename:flat.svg;export-do"
|
|
116
|
+
# batch many files through one long-lived process (no per-file startup cost):
|
|
117
|
+
inkscape --shell < actions.txt # one actions line per file, ending in export-do
|
|
118
|
+
```
|
|
119
|
+
Useful actions: `object-to-path` / `all-objects-to-paths` (outline shapes+text),
|
|
120
|
+
`path-simplify`, boolean `path-union` / `path-difference` / `path-intersection`,
|
|
121
|
+
`vacuum-defs` (drop unused defs — shrinks file), and the `export-*` action family.
|
|
122
|
+
`inkscape --action-list` prints every verb.
|
|
123
|
+
- **Geometry query (`--query-all` / `--query-id`).** Discover element ids and their
|
|
124
|
+
bounding boxes *before* `--export-id` slicing, so a generated slice list is correct:
|
|
125
|
+
```bash
|
|
126
|
+
inkscape in.svg --query-all # id,x,y,w,h per object (CSV) — feed --export-id
|
|
127
|
+
inkscape in.svg --query-id=badge --query-x --query-y --query-width --query-height
|
|
128
|
+
```
|
|
129
|
+
`maggie_image.py svg-query` emits this (pairs with the per-id export above).
|
|
130
|
+
- **Extra export flags:** `--export-page=all|N` + `--export-margin` (multi-page PDF/
|
|
131
|
+
SVG slicing), `--export-overwrite`, `--export-png-color-mode=Gray_8|RGBA_16` and
|
|
132
|
+
`--export-png-use-dithering`, `--export-pdf-version=1.5` / `--export-ps-level=3`,
|
|
133
|
+
`--export-latex` (PDF+LaTeX overlay for papers), `--export-plain-svg` (strip
|
|
134
|
+
Inkscape namespaces for clean handoff).
|
|
135
|
+
|
|
106
136
|
## Layered composite + named looks (Pinta vocabulary)
|
|
107
137
|
|
|
108
138
|
Blend modes map from a raster editor's layer stack onto ImageMagick `-compose`:
|
|
@@ -121,3 +151,16 @@ non-destructive edit recipe): `grayscale` (`-colorspace Gray`), `sepia`
|
|
|
121
151
|
`maggie_image.py composite` and `maggie_image.py look` generate these. For a
|
|
122
152
|
reusable, batchable edit *recipe* (a JSON stack of named ops applied to many photos),
|
|
123
153
|
see [design editor patterns](design-editor-patterns.md).
|
|
154
|
+
|
|
155
|
+
## More ImageMagick ops worth baking in (confirmed in source)
|
|
156
|
+
|
|
157
|
+
- **`-auto-orient`** first in every pipeline - fixes sideways phone photos by EXIF
|
|
158
|
+
(`maggie_image.py` resize/social/trim already prepend it).
|
|
159
|
+
- **`-thumbnail WxH`** - metadata-stripping fast resize; a better default than
|
|
160
|
+
`-resize` for web/OG thumbnails.
|
|
161
|
+
- **`-fuzz N% -trim +repage`** - trim a uniform border or cut a product/logo out of
|
|
162
|
+
its background (`maggie_image.py trim`).
|
|
163
|
+
- **`-clut` / `-hald-clut`** - repeatable LUT brand grading (raster equivalent of
|
|
164
|
+
ffmpeg `lut3d`).
|
|
165
|
+
- **`-distort perspective '<src coords> <dst coords>'`** - place a screenshot into a
|
|
166
|
+
device mockup.
|
|
@@ -78,3 +78,25 @@ apply.
|
|
|
78
78
|
`libx264 -crf 14-20 -preset slow/medium -pix_fmt yuv420p -movflags +faststart`,
|
|
79
79
|
even dimensions, plus 960px / 800x500 GIF deliverables. For transparent output,
|
|
80
80
|
Revideo uses per-format pixel formats (`yuva420p` WebM, `yuva444p10le` ProRes).
|
|
81
|
+
|
|
82
|
+
## Remotion's real renderer (from source) and the ideas to borrow
|
|
83
|
+
|
|
84
|
+
Source study (Remotion License - concept only, no vendoring) shows Remotion is **not**
|
|
85
|
+
the base64-PNG pipe we lump with Revideo/Motion Canvas: it drives headless Chromium
|
|
86
|
+
over CDP, captures frames via **shared memory**, and encodes with a **per-platform
|
|
87
|
+
native Rust sidecar that statically links FFmpeg** (a long-running streaming process),
|
|
88
|
+
plus a bundled `ffmpeg` for the final stitch. Borrowable ideas (improve our Part C):
|
|
89
|
+
|
|
90
|
+
- **A readiness gate, not just a fake clock.** `delayRender()`/`continueRender()`: a
|
|
91
|
+
component declares async work pending and the renderer blocks the screenshot until
|
|
92
|
+
all handles clear, reporting **labeled** open handles on timeout. Add an explicit
|
|
93
|
+
readiness handshake (plus a per-frame `await document.fonts.ready`) to Part C so no
|
|
94
|
+
frame is captured before fonts/data/images are ready - prevents FOUT/half-loaded
|
|
95
|
+
frames.
|
|
96
|
+
- **Tab-pool concurrency**: open N browser pages, each seeking a different frame in
|
|
97
|
+
parallel (frame range / N).
|
|
98
|
+
- **Chunked parallel encode + seamless concat**: split the frame range, encode chunks
|
|
99
|
+
independently, concat without re-encode - throughput for long renders.
|
|
100
|
+
- **Kept-open, LRU-cached source-video decoders**: for embedded video assets, keep the
|
|
101
|
+
decoder open and cache frames so repeated seeks to a timestamp are cheap (our flat
|
|
102
|
+
timeline currently punts this to raw ffmpeg `-ss`).
|
|
@@ -161,10 +161,28 @@ GPL = concept only):
|
|
|
161
161
|
`blendMode` values on overlay clips: Porter-Duff (`Over, Under, In, Out, ATop, Copy,
|
|
162
162
|
Matte`) + blends (`Multiply, Screen, Overlay, SoftLight, HardLight, ColorBurn,
|
|
163
163
|
ColorDodge, Difference, Exclusion, Hue, Saturation, Color, Luminosity`).
|
|
164
|
-
- **Xsheet tabular timing** (opentoonz/tahoma2d, BSD
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
- **Xsheet tabular timing** (opentoonz/tahoma2d, **BSD - code reusable with
|
|
165
|
+
attribution, a direct port target**). An optional rows=frames / columns=assets
|
|
166
|
+
exposure grid (cells = a drawing exposed for N frames; a *hold* is the same cell
|
|
167
|
+
repeated). The source implements this (and more of our model than we first credited)
|
|
168
|
+
in `TXsheet` / `TXshCellColumn` (`std::vector<TXshCell> m_cells`) / `TXshCell`
|
|
169
|
+
(`{level, TFrameId}`) / `TFrameId` (`{frame, letter}`), plus the cell-op vocabulary
|
|
170
|
+
(`reverseCells, swingCells, stepCells, reframeCells, timeStretch`). It also backs, as
|
|
171
|
+
BSD code: the **decomposed transform channels** (`TStageObject::Channel` T_X/T_Y/
|
|
172
|
+
T_Angle/T_ScaleX/... each a keyframe curve, with parenting + `T_Path` follow-spline),
|
|
173
|
+
a **richer easing enum** (`TDoubleKeyframe::Type` = Constant / Linear / SpeedInOut /
|
|
174
|
+
EaseInOut / EaseInOutPercentage / Exponential / Expression / File), **nested
|
|
175
|
+
sub-xsheets** (`TXshChildLevel` = precomp), and a **node-graph fx schematic**
|
|
176
|
+
(`FxDag`/`TFx`). So the easing/transform/nested-clip/effect-graph extensions above
|
|
177
|
+
have a BSD implementation to port, not only GPL concept sources.
|
|
178
|
+
- **Strip/effect model + full blend set** (blender VSE `Strip`, concept). The Video
|
|
179
|
+
Sequence Editor's `Strip` (input1/input2, effect strips CROSS/WIPE/GLOW/SPEED/
|
|
180
|
+
ADJUSTMENT/TEXT, a Photoshop-complete `StripBlendMode`) and **F-curve modifier stack**
|
|
181
|
+
(noise/cycles/limits/stepped) + **drivers** (property-driven channels) are strong
|
|
182
|
+
concept borrows for overlay/effect clips.
|
|
183
|
+
- **OTIO interchange** (kdenlive, concept). Document a mapping to OpenTimelineIO (the
|
|
184
|
+
ASWF-standard timeline interchange): gap=blank, stack=track, externalReference=asset
|
|
185
|
+
src - so the model can round-trip with real NLEs.
|
|
168
186
|
- **Optional node-graph "effect graph"** (Natron, concept) for branched layered effects
|
|
169
187
|
(mask -> blur -> merge) that a linear track can't express - heavy; a documented
|
|
170
188
|
escape hatch, not the default.
|