@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.
@@ -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.4
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
- elif a.engine == "whisperx":
515
- tool = "whisperx"
516
- cmd = [tool, a.input, "--model", a.model, "--output_format", "srt",
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
- print(f"# whisperx writes {os.path.join(out_dir, base)}.srt in --output_dir")
519
- else: # openai-whisper (local, pip install openai-whisper)
520
- tool = "whisper"
521
- cmd = [tool, a.input, "--model", a.model, "--output_format", "srt",
522
- "--output_dir", out_dir] + lang
523
- print(f"# openai-whisper writes {os.path.join(out_dir, base)}.srt in --output_dir")
524
- print("# transcription is local/offline (no paid API); a large model is slower but better.")
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 .srt")
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="base", help="model name, or the .bin path for whispercpp")
609
- ca.add_argument("--lang", default="auto"); ca.set_defaults(fn=c_captions)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.8.15",
3
+ "version": "0.8.17",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 | The reference Python CLI: `whisper IN --model base --output_format srt --output_dir DIR` | MIT | the default; simplest install (`pip install 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 <in> <sidecar.xmp> <out>` renders
19
- from such a stack (GPL binary, arm's-length).
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). An optional rows=frames /
165
- columns=assets exposure grid (cells = a drawing exposed for N frames, with holds) -
166
- an LLM- and spreadsheet-friendly way to author precise timing, convertible to this
167
- JSON timeline.
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.