@bendyline/squisq-cli 1.2.2 → 2.0.1

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.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bendyline/squisq-cli
2
2
 
3
- Command-line tool and programmatic API for converting Squisq documents between DOCX, PPTX, PDF, XLSX, CSV, HTML, EPUB, Markdown, and container ZIP — and rendering them to MP4 video. Reads Markdown, binary documents (`.docx`/`.pptx`/`.pdf`/`.xlsx`/`.csv`/`.html`), ZIP/`.dbk` containers, folders, or pre-built Doc JSON as input.
3
+ Command-line tool and programmatic API for converting Squisq documents between DOCX, PPTX, PDF, XLSX, CSV, HTML, EPUB, Markdown, and container ZIP — and rendering them to MP4 video or animated GIF. Reads Markdown, binary documents (`.docx`/`.pptx`/`.pdf`/`.xlsx`/`.csv`/`.html`), ZIP/`.dbk` containers, folders, or pre-built Doc JSON as input.
4
4
 
5
5
  Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
6
6
 
@@ -13,7 +13,7 @@ Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
13
13
  npm install -g @bendyline/squisq-cli
14
14
  ```
15
15
 
16
- For `squisq video` (and `convert` to `mp4`) you also need:
16
+ For `squisq video` (and `convert` to `mp4` or `gif`) you also need:
17
17
 
18
18
  - ffmpeg — resolved from the `SQUISQ_FFMPEG` env var, then your `PATH` (`brew install ffmpeg` / `apt install ffmpeg` / `winget install ffmpeg`), then an optionally-installed `ffmpeg-static` package.
19
19
  - A Playwright-managed Chromium for headless frame capture (e.g. `npx playwright install chromium`)
@@ -29,55 +29,62 @@ Convert a document to one or more formats. Input can be Markdown or a binary doc
29
29
  ```bash
30
30
  squisq convert input.md -o report.docx # single file; format inferred from extension
31
31
  squisq convert report.docx -o report.md # binary input → markdown
32
- squisq convert input.md --format pptx # single format to the default output dir
32
+ squisq convert input.md --formats pptx # single format to the default output dir
33
33
  squisq convert input.md --formats docx,pptx,pdf # multiple formats
34
34
  squisq convert project.dbk --output-dir ./out --formats html,docx
35
- squisq convert input.md --theme cinematic --transform magazine --format pptx
35
+ squisq convert input.md --theme cinematic --transform magazine --formats pptx
36
36
  ```
37
37
 
38
- | Option | Description | Default |
39
- | --------------------- | ------------------------------------------------------------------------------------------------------- | ------------- |
40
- | `-o, --output <file>` | **Single** output file; format inferred from its extension (cannot combine with `--formats`/`--format`) | — |
41
- | `-d, --output-dir` | Output directory (multi-format mode) | same as input |
42
- | `-f, --formats` | Comma-separated: `docx`, `pptx`, `pdf`, `html`, `htmlzip`, `epub`, `dbk`, `md`, `xlsx`, `csv`, `mp4` | default set |
43
- | `--format <id>` | Produce a single format (alias for a one-entry `--formats`) | |
44
- | `-t, --theme` | Squisq theme id built-in or a custom theme inlined in the doc's frontmatter | none |
45
- | `--transform` | Transform style applied before export (e.g. `documentary`, `magazine`, `minimal`) | none |
46
- | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
38
+ | Option | Description | Default |
39
+ | --------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
40
+ | `-o, --output <file>` | **Single** output file; format inferred from its extension (cannot combine with `--formats`) | — |
41
+ | `-d, --output-dir` | Output directory (multi-format mode) | same as input |
42
+ | `-f, --formats` | Comma-separated: `docx`, `pptx`, `pdf`, `html`, `htmlzip`, `epub`, `dbk`, `md`, `xlsx`, `csv`, `mp4`, `gif` | default set |
43
+ | `-t, --theme` | Squisq theme id built-in or a custom theme inlined in the doc's frontmatter | none |
44
+ | `--transform` | Transform style applied before export (e.g. `documentary`, `magazine`, `minimal`) | none |
45
+ | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
47
46
 
48
- > **v1.5 breaking flag change:** `-o` is now a **single-file** destination (format inferred from the extension). The old `-o` output-**directory** behavior is now `-d, --output-dir`. A bare `convert <input>` with no `-o`/`--format`/`--formats` writes a default set that deliberately excludes `md`/`xlsx`/`csv`/`mp4`.
47
+ > **v1.5 breaking flag change:** `-o` is now a **single-file** destination (format inferred from the extension). The old `-o` output-**directory** behavior is now `-d, --output-dir`. A bare `convert <input>` with no `-o`/`--formats` writes a default set that deliberately excludes `md`/`xlsx`/`csv`/`mp4`/`gif`.
49
48
 
50
49
  Notes:
51
50
 
52
51
  - `html` produces a single self-contained file with the standalone player inlined (static mode); `htmlzip` produces a `<name>.html.zip` archive with external assets and optional audio.
53
52
  - `epub` embeds images from the input container and, when the doc has narration segments, generates EPUB 3 Media Overlays.
54
- - `xlsx` export is tables-only; `csv` export emits the first table; `mp4` requires the video toolchain (see Install).
53
+ - `xlsx` export is tables-only; `csv` export emits the first table; `mp4` and `gif` require the render toolchain (see Install).
55
54
  - `dbk` re-serializes the input container as a ZIP.
56
55
 
57
56
  ### `squisq video <input> [output]`
58
57
 
59
- Render a document to MP4: Playwright captures frames from a headless player page, native ffmpeg encodes them (H.264 + AAC when the doc has audio).
58
+ Render a document to MP4 or animated GIF. Playwright captures deterministic frames from a headless player page; native ffmpeg encodes H.264 + AAC for MP4 or a generated global palette for GIF. GIF has no audio track.
60
59
 
61
60
  ```bash
62
61
  squisq video input.md output.mp4
62
+ squisq video input.md output.gif
63
+ squisq video input.md --format gif --no-animations
63
64
  squisq video project.dbk --quality high --fps 30
64
65
  squisq video ./my-folder --orientation portrait --captions social
65
66
  squisq video input.md -t documentary --transform magazine --cover-preroll 1.5
66
67
  squisq video doc.json -o out.mp4
67
68
  ```
68
69
 
69
- | Option | Description | Default |
70
- | ---------------------- | -------------------------------------------------------------------- | --------------- |
71
- | `-o, --output` | Output MP4 path (positional `[output]` wins over the flag) | `<input>.mp4` |
72
- | `--fps` | Frames per second (1–120) | 30 |
73
- | `--quality` | `draft`, `normal`, or `high` | normal |
74
- | `--orientation` | `landscape` (1920×1080) or `portrait` (1080×1920) | landscape |
75
- | `--captions` | `off`, `standard`, or `social` | off |
76
- | `-t, --theme` | Squisq theme id to apply | none |
77
- | `--transform` | Transform style to apply before rendering | none |
78
- | `--cover-preroll` | Seconds of cover-slide pre-roll before the story starts | 2 |
79
- | `--width` / `--height` | Dimension overrides in pixels | per orientation |
80
- | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
70
+ | Option | Description | Default |
71
+ | ---------------------------------- | -------------------------------------------------------------------- | ------------------------- |
72
+ | `-o, --output` | Output `.mp4` or `.gif` path; extension selects the format | `<input>.mp4` |
73
+ | `--format` | `mp4` or `gif` | inferred, otherwise `mp4` |
74
+ | `--fps` | Frames per second (MP4 1–120; GIF 1–100) | MP4 30; GIF 10 |
75
+ | `--quality` | MP4-only quality: `draft`, `normal`, or `high` | normal |
76
+ | `--orientation` | `landscape` or `portrait` | landscape |
77
+ | `--captions` | `off`, `standard`, or `social` | off |
78
+ | `--animations` / `--no-animations` | Enable/disable layer animations and block transitions | MP4 enabled; GIF disabled |
79
+ | `--loop` | GIF repeat count (`0` forever, `-1` no loop) | 0 |
80
+ | `--max-colors` | GIF palette size, 2–256 | 256 |
81
+ | `--dither` | GIF dithering: `bayer`, `sierra2_4a`, or `none` | `sierra2_4a` |
82
+ | `--bayer-scale` | Ordered Bayer strength, 0–5 | 3 |
83
+ | `-t, --theme` | Squisq theme id to apply | none |
84
+ | `--transform` | Transform style to apply before rendering | none |
85
+ | `--cover-preroll` | Seconds of cover-slide pre-roll before the story starts | 2 |
86
+ | `--width` / `--height` | Dimension overrides in pixels | MP4 1080p; GIF 960×540 |
87
+ | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
81
88
 
82
89
  ### `squisq doctor`
83
90
 
@@ -118,11 +125,11 @@ All commands accept:
118
125
 
119
126
  ## Programmatic API
120
127
 
121
- Import `@bendyline/squisq-cli/api` to use the same pipeline as a library from Node.js — no shell-out required. Like the CLI, `renderDocToMp4` requires ffmpeg and a Playwright-managed Chromium.
128
+ Import `@bendyline/squisq-cli/api` to use the same pipeline as a library from Node.js — no shell-out required. Like the CLI, `renderDocToMp4` and `renderDocToGif` require ffmpeg and a Playwright-managed Chromium.
122
129
 
123
130
  ### `convert()`
124
131
 
125
- A pre-bound wrapper over `@bendyline/squisq-formats`' `convert()` that injects the CLI's format registry (every built-in exporter plus the CLI-only `mp4` format) and a default `resolvePlayerScript` (so HTML/player-embedding exports work out of the box). Both are overridable via `options`. `createCliRegistry()` returns that same registry for direct use.
132
+ A pre-bound wrapper over `@bendyline/squisq-formats`' `convert()` that injects the CLI's format registry (every built-in exporter plus the CLI-only `mp4` and `gif` formats) and a default `resolvePlayerScript` (so HTML/player-embedding exports work out of the box). Both are overridable via `options`. `createCliRegistry()` returns that same registry for direct use.
126
133
 
127
134
  ```ts
128
135
  import { convert, createCliRegistry, ConversionError } from '@bendyline/squisq-cli/api';
@@ -153,6 +160,7 @@ const result = await renderDocToMp4(doc, input.container, {
153
160
  quality: 'high', // 'draft' | 'normal' | 'high' (default 'normal')
154
161
  orientation: 'landscape', // 'landscape' | 'portrait' (default 'landscape')
155
162
  captionStyle: 'social', // 'standard' | 'social' — omit for no captions
163
+ animationsEnabled: false, // static slide changes; media/timing remain active
156
164
  coverPreRoll: 2, // seconds of cover-slide pre-roll (default 0)
157
165
  onProgress: (phase, pct) => console.log(`${phase}: ${pct}%`),
158
166
  });
@@ -162,6 +170,49 @@ console.log(`Rendered ${result.frameCount} frames (${result.duration}s) → ${re
162
170
 
163
171
  The container's audio segments become the MP4's audio track; timed media clips and block videos found in the doc are embedded and mixed at their scheduled positions.
164
172
 
173
+ ### `renderDocToGif`
174
+
175
+ ```ts
176
+ import { renderDocToGif } from '@bendyline/squisq-cli/api';
177
+
178
+ const result = await renderDocToGif(doc, input.container, {
179
+ outputPath: './output.gif',
180
+ fps: 10,
181
+ animationsEnabled: false,
182
+ maxColors: 256,
183
+ loop: 0,
184
+ });
185
+ ```
186
+
187
+ GIF defaults to 960×540 landscape (540×960 portrait), 10 fps, an infinite
188
+ loop, and disabled slide animations/transitions. Embedded video still advances,
189
+ but all audio is omitted and reported through `result.warnings`.
190
+
191
+ ### Native frame encoding
192
+
193
+ For callers that already have PNG frames and do not need Playwright capture,
194
+ the API exposes the native FFmpeg layer directly:
195
+
196
+ ```ts
197
+ import { framesToGifNativeBytes, framesToMp4NativeBytes } from '@bendyline/squisq-cli/api';
198
+
199
+ const mp4 = await framesToMp4NativeBytes('/usr/bin/ffmpeg', pngFrames, audioBytes, {
200
+ fps: 30,
201
+ quality: 'high',
202
+ });
203
+
204
+ const gif = await framesToGifNativeBytes('/usr/bin/ffmpeg', pngFrames, {
205
+ fps: 10,
206
+ width: 960,
207
+ height: 540,
208
+ });
209
+ ```
210
+
211
+ The path variants write to a caller-supplied output; each `Bytes` variant returns
212
+ a `Uint8Array`. MP4 pads short audio with silence so narration cannot truncate
213
+ the timeline. GIF uses a compression-friendly diff palette and changed-rectangle
214
+ application.
215
+
165
216
  ### `extractThumbnails`
166
217
 
167
218
  Extract JPEG thumbnails from the first frame of a rendered video:
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CapturedFrameBudgetError,
4
+ ConversionError,
5
+ MAX_CAPTURED_FRAME_BYTES,
6
+ MemoryContentContainer,
7
+ convert,
8
+ createCliRegistry,
9
+ extractThumbnails,
10
+ prepareConversion,
11
+ readInput,
12
+ renderDocToGif,
13
+ renderDocToMp4
14
+ } from "./chunk-FVFK4ZJU.js";
15
+ import {
16
+ GIF_EXPORT_DEFAULTS,
17
+ framesToGifNative,
18
+ framesToGifNativeBytes,
19
+ framesToMp4Native,
20
+ framesToMp4NativeBytes
21
+ } from "./chunk-JDHONKFZ.js";
22
+ export {
23
+ CapturedFrameBudgetError,
24
+ ConversionError,
25
+ GIF_EXPORT_DEFAULTS,
26
+ MAX_CAPTURED_FRAME_BYTES,
27
+ MemoryContentContainer,
28
+ convert,
29
+ createCliRegistry,
30
+ extractThumbnails,
31
+ framesToGifNative,
32
+ framesToGifNativeBytes,
33
+ framesToMp4Native,
34
+ framesToMp4NativeBytes,
35
+ prepareConversion,
36
+ readInput,
37
+ renderDocToGif,
38
+ renderDocToMp4
39
+ };
40
+ //# sourceMappingURL=api-LE75OSRQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/api.d.ts CHANGED
@@ -1,12 +1,53 @@
1
1
  import { Doc } from '@bendyline/squisq/schemas';
2
2
  import { ContentContainer } from '@bendyline/squisq/storage';
3
3
  export { MemoryContentContainer } from '@bendyline/squisq/storage';
4
- import { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';
5
- export { VideoOrientation, VideoQuality } from '@bendyline/squisq-video';
6
- import { FormatId, FormatRegistry, ConvertSource, ConvertOptions, ConversionResult } from '@bendyline/squisq-formats';
7
- export { ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionResult, ConvertOptions, ConvertSource, FormatDefinition, FormatId, FormatRegistry, NormalizedInput } from '@bendyline/squisq-formats';
4
+ import { VideoQuality, VideoOrientation, GifDither, VideoExportOptions } from '@bendyline/squisq-video';
5
+ export { GifDither, VideoOrientation, VideoQuality } from '@bendyline/squisq-video';
6
+ import { FormatRegistry, FormatId, ConvertOptions, ConvertSource, ConversionResult, PreparedConversion } from '@bendyline/squisq-formats';
7
+ export { ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionResult, ConvertOptions, ConvertSource, FormatDefinition, FormatId, FormatRegistry, NormalizedInput, PreparedConversion, PreparedExportOptions } from '@bendyline/squisq-formats';
8
8
  import { MarkdownDocument } from '@bendyline/squisq/markdown';
9
9
 
10
+ /**
11
+ * CLI format registry.
12
+ *
13
+ * The CLI extends the formats package's default registry with `mp4` and `gif`
14
+ * formats so `convert()` can produce rendered media like DOCX/PDF/HTML. These
15
+ * exports are Node-only (Playwright + FFmpeg), which is why they live here
16
+ * rather than in the browser-pure formats package.
17
+ */
18
+
19
+ interface Mp4FormatOptions {
20
+ onProgress?: (phase: string, percent: number) => void;
21
+ fps?: number;
22
+ quality?: VideoQuality;
23
+ orientation?: VideoOrientation;
24
+ width?: number;
25
+ height?: number;
26
+ captionStyle?: 'standard' | 'social';
27
+ coverPreRoll?: number;
28
+ animationsEnabled?: boolean;
29
+ }
30
+ /** Per-export options for `convert(..., 'gif')`. */
31
+ interface GifFormatOptions {
32
+ onProgress?: (phase: string, percent: number) => void;
33
+ fps?: number;
34
+ orientation?: VideoOrientation;
35
+ width?: number;
36
+ height?: number;
37
+ captionStyle?: 'standard' | 'social';
38
+ coverPreRoll?: number;
39
+ animationsEnabled?: boolean;
40
+ loop?: number;
41
+ maxColors?: number;
42
+ dither?: GifDither;
43
+ bayerScale?: number;
44
+ }
45
+ /**
46
+ * Build the CLI's format registry: every built-in format from
47
+ * `@bendyline/squisq-formats` plus the CLI-only rendered-media exporters.
48
+ */
49
+ declare function createCliRegistry(): FormatRegistry;
50
+
10
51
  /**
11
52
  * readInput
12
53
  *
@@ -37,33 +78,121 @@ interface ReadInputResult {
37
78
  /** Detected source format id (registry id, or `md`/`json`/`dbk`/`folder`). */
38
79
  sourceFormat: FormatId;
39
80
  }
81
+ interface ReadInputOptions {
82
+ /** Cancel filesystem traversal, import, or audio resolution at a bounded boundary. */
83
+ signal?: AbortSignal;
84
+ /**
85
+ * Infer a Squisq theme from an OOXML source's theme part (PPTX today).
86
+ * Default true — the importer carries it as frontmatter.
87
+ */
88
+ inferTheme?: boolean;
89
+ /** Derive custom layout templates from PPTX slide layouts. Default true. */
90
+ inferLayouts?: boolean;
91
+ }
40
92
  /**
41
93
  * Read input from a file path (markdown, JSON Doc, ZIP/DBK container, folder,
42
94
  * or an importable binary format) and resolve it to a populated
43
95
  * {@link ReadInputResult}.
44
96
  */
45
- declare function readInput(inputPath: string): Promise<ReadInputResult>;
97
+ declare function readInput(inputPath: string, options?: ReadInputOptions): Promise<ReadInputResult>;
46
98
 
47
99
  /**
48
- * CLI format registry.
100
+ * Hard retained-memory ceiling for PNG frames captured before native encoding.
49
101
  *
50
- * The CLI extends the formats package's default registry with an `mp4` format
51
- * so `convert()` can produce video the same way it produces DOCX/PDF/HTML. MP4
52
- * export is Node-only (it needs Playwright + FFmpeg), which is why it lives
53
- * here in the CLI rather than in the browser-pure formats package.
102
+ * The Node renderer currently keeps captured PNGs in memory while FFmpeg is
103
+ * prepared. Bounding the actual encoded PNG bytes prevents visually complex
104
+ * documents from exhausting the process even when their dimensions and frame
105
+ * counts satisfy higher-level render budgets.
54
106
  */
107
+ declare const MAX_CAPTURED_FRAME_BYTES: number;
108
+ /** Stable failure raised before another captured frame is retained. */
109
+ declare class CapturedFrameBudgetError extends Error {
110
+ readonly capturedBytes: number;
111
+ readonly attemptedFrameBytes: number;
112
+ readonly maximumBytes: number;
113
+ readonly code = "captured-frame-budget-exceeded";
114
+ constructor(capturedBytes: number, attemptedFrameBytes: number, maximumBytes: number);
115
+ }
55
116
 
56
117
  /**
57
- * Build the CLI's format registry: every built-in format from
58
- * `@bendyline/squisq-formats` plus the CLI-only `mp4` exporter.
118
+ * Native FFmpeg Encoder
119
+ *
120
+ * Encodes PNG frames to MP4 or animated GIF using a locally installed ffmpeg
121
+ * binary. Writes frames to a temporary directory, invokes ffmpeg as a child
122
+ * process, and reads the resulting media file.
123
+ *
124
+ * This is the Node fast path used after the CLI detects native FFmpeg. Browser
125
+ * callers use the WebCodecs/ffmpeg.wasm paths in the video packages instead.
59
126
  */
60
- declare function createCliRegistry(): FormatRegistry;
61
127
 
128
+ /** Node-native video options with cooperative and child-process cancellation. */
129
+ interface NativeVideoExportOptions extends VideoExportOptions {
130
+ signal?: AbortSignal;
131
+ }
132
+ /** Options for native animated-GIF encoding. */
133
+ interface GifExportOptions {
134
+ /** Cancel staging or encoding and terminate the native FFmpeg process. */
135
+ signal?: AbortSignal;
136
+ /** Frames per second (default: 10; GIF timing is centisecond-based). */
137
+ fps?: number;
138
+ /** Output width in pixels (default: 960 landscape / 540 portrait). */
139
+ width?: number;
140
+ /** Output height in pixels (default: 540 landscape / 960 portrait). */
141
+ height?: number;
142
+ /** Viewport orientation used for default dimensions. */
143
+ orientation?: VideoOrientation;
144
+ /** Number of repeats; 0 loops forever and -1 disables looping. */
145
+ loop?: number;
146
+ /** Palette size, between 2 and 256 (default: 256). */
147
+ maxColors?: number;
148
+ /** Palette dithering algorithm (default: sierra2_4a). */
149
+ dither?: GifDither;
150
+ /** Bayer strength when dither is bayer (0-5, default: 3). */
151
+ bayerScale?: number;
152
+ /** Encoding progress callback. */
153
+ onProgress?: (percent: number, phase: string) => void;
154
+ }
155
+ /** Compression-friendly defaults for animated GIF output. */
156
+ declare const GIF_EXPORT_DEFAULTS: {
157
+ readonly fps: 10;
158
+ readonly width: 960;
159
+ readonly height: 540;
160
+ readonly loop: 0;
161
+ readonly maxColors: 256;
162
+ readonly dither: GifDither;
163
+ };
62
164
  /**
63
- * Programmatic Video API
165
+ * Encode frame PNGs to MP4 using native ffmpeg.
64
166
  *
65
- * Provides a library-style entry point for rendering Squisq documents to MP4
66
- * from Node.js callers (e.g., Qualla's pipeline). This avoids the need to shell
167
+ * @param ffmpegPath - Absolute path to the ffmpeg binary
168
+ * @param frames - Array of PNG image bytes (one per frame)
169
+ * @param audio - Optional audio file bytes to mux
170
+ * @param outputPath - Where to write the final MP4
171
+ * @param options - Encoding options (fps, quality, dimensions, progress)
172
+ */
173
+ declare function framesToMp4Native(ffmpegPath: string, frames: Uint8Array[], audio: Uint8Array | null, outputPath: string, options?: NativeVideoExportOptions): Promise<void>;
174
+ /**
175
+ * Encode frames using native ffmpeg and return the MP4 bytes (instead of writing to disk).
176
+ * Useful when the caller needs the bytes in memory.
177
+ */
178
+ declare function framesToMp4NativeBytes(ffmpegPath: string, frames: Uint8Array[], audio: Uint8Array | null, options?: NativeVideoExportOptions): Promise<Uint8Array>;
179
+ /**
180
+ * Encode PNG frames to an animated GIF using native FFmpeg.
181
+ *
182
+ * The shared palette graph uses changed-frame statistics and rectangle-scoped
183
+ * palette application so static slide regions remain stable and compress well.
184
+ * GIF has no audio track; callers should surface that limitation before calling.
185
+ */
186
+ declare function framesToGifNative(ffmpegPath: string, frames: Uint8Array[], outputPath: string, options?: GifExportOptions): Promise<void>;
187
+ /** Encode frames with native FFmpeg and return animated-GIF bytes. */
188
+ declare function framesToGifNativeBytes(ffmpegPath: string, frames: Uint8Array[], options?: GifExportOptions): Promise<Uint8Array>;
189
+
190
+ /**
191
+ * Programmatic Rendered-Media API
192
+ *
193
+ * Provides library-style entry points for rendering Squisq documents to MP4
194
+ * or animated GIF from Node.js callers (e.g., Qualla's pipeline). This avoids
195
+ * the need to shell
67
196
  * out to the `squisq video` CLI and gives callers full control over the Doc,
68
197
  * MemoryContentContainer, and encoding options.
69
198
  *
@@ -78,28 +207,45 @@ declare function createCliRegistry(): FormatRegistry;
78
207
  * quality: 'normal',
79
208
  * orientation: 'landscape',
80
209
  * });
210
+ *
211
+ * await renderDocToGif(doc, container, {
212
+ * outputPath: '/tmp/output.gif',
213
+ * animationsEnabled: false,
214
+ * });
81
215
  */
82
216
 
217
+ /** Convert options with the CLI-only rendered-media adapters strongly typed. */
218
+ type CliConvertOptions = Omit<ConvertOptions, 'formatOptions'> & {
219
+ formatOptions?: ConvertOptions['formatOptions'] & {
220
+ mp4?: Mp4FormatOptions;
221
+ gif?: GifFormatOptions;
222
+ };
223
+ };
224
+
83
225
  /**
84
226
  * Convert a document to a target format using the CLI's format registry.
85
227
  *
86
228
  * This is a thin, pre-bound wrapper over `convert()` from
87
229
  * `@bendyline/squisq-formats`: it injects the CLI registry (which adds the
88
- * `mp4` format on top of every built-in exporter) and a default
230
+ * `mp4` and `gif` formats on top of every built-in exporter) and a default
89
231
  * `resolvePlayerScript` that lazily loads the standalone player IIFE bundle
90
232
  * (required for HTML/EPUB-style exports). Callers may override either via
91
233
  * `options`.
92
234
  *
93
235
  * @param source - A bytes / markdown / doc {@link ConvertSource}.
94
- * @param to - Target format id (`docx`, `pdf`, `pptx`, `html`, `mp4`, …).
236
+ * @param to - Target format id (`docx`, `pdf`, `pptx`, `html`, `mp4`, `gif`, …).
95
237
  * @param options - Conversion options; `registry` and `resolvePlayerScript`
96
238
  * default to the CLI's values but can be overridden.
97
239
  * @returns The encoded bytes plus mime type, suggested filename, and warnings.
98
240
  * @throws {@link ConversionError} on any failure, with a stable `code`.
99
241
  */
100
- declare function convert(source: ConvertSource, to: FormatId, options?: ConvertOptions): Promise<ConversionResult>;
242
+ declare function convert(source: ConvertSource, to: FormatId, options?: CliConvertOptions): Promise<ConversionResult>;
243
+ /** Normalize and transform once using the CLI registry, then export one or more targets. */
244
+ declare function prepareConversion(source: ConvertSource, options?: CliConvertOptions): Promise<PreparedConversion>;
101
245
  /** Options for renderDocToMp4. */
102
246
  interface RenderDocToMp4Options {
247
+ /** Cancel capture/encoding and terminate browser and FFmpeg work. */
248
+ signal?: AbortSignal;
103
249
  /** Output file path for the MP4. */
104
250
  outputPath: string;
105
251
  /** Frames per second (default: 30). */
@@ -114,6 +260,8 @@ interface RenderDocToMp4Options {
114
260
  height?: number;
115
261
  /** Caption style to bake into the video (default: none). */
116
262
  captionStyle?: 'standard' | 'social';
263
+ /** Render layer animations and block transitions (default: true). */
264
+ animationsEnabled?: boolean;
117
265
  /**
118
266
  * Seconds of cover-slide pre-roll before the story starts (default: 0).
119
267
  *
@@ -127,6 +275,37 @@ interface RenderDocToMp4Options {
127
275
  */
128
276
  onProgress?: (phase: string, percent: number) => void;
129
277
  }
278
+ /** Options for rendering a document to an animated GIF. */
279
+ interface RenderDocToGifOptions {
280
+ /** Cancel capture/encoding and terminate browser and FFmpeg work. */
281
+ signal?: AbortSignal;
282
+ /** Output file path for the GIF. */
283
+ outputPath: string;
284
+ /** Frames per second (default: 10). */
285
+ fps?: number;
286
+ /** Viewport orientation (default: landscape). */
287
+ orientation?: VideoOrientation;
288
+ /** Override output width (default: 960 landscape / 540 portrait). */
289
+ width?: number;
290
+ /** Override output height (default: 540 landscape / 960 portrait). */
291
+ height?: number;
292
+ /** Caption style to bake into the GIF (default: none). */
293
+ captionStyle?: 'standard' | 'social';
294
+ /** Seconds of cover-slide pre-roll (default: 0). */
295
+ coverPreRoll?: number;
296
+ /** Render layer animations and block transitions (default: false). */
297
+ animationsEnabled?: boolean;
298
+ /** Number of repeats; 0 loops forever and -1 disables looping. */
299
+ loop?: number;
300
+ /** Palette size, from 2 through 256 (default: 256). */
301
+ maxColors?: number;
302
+ /** Palette dithering algorithm (default: sierra2_4a). */
303
+ dither?: GifDither;
304
+ /** Bayer strength when dither is bayer (0-5, default: 3). */
305
+ bayerScale?: number;
306
+ /** Progress callback. */
307
+ onProgress?: (phase: string, percent: number) => void;
308
+ }
130
309
  /** Result returned by renderDocToMp4. */
131
310
  interface RenderDocToMp4Result {
132
311
  /** Duration of the rendered video in seconds (including pre-roll). */
@@ -136,24 +315,15 @@ interface RenderDocToMp4Result {
136
315
  /** Output file path. */
137
316
  outputPath: string;
138
317
  }
139
- /**
140
- * Render a Doc + media container to an MP4 video file.
141
- *
142
- * The container should contain audio and image files referenced by the Doc's
143
- * audio.segments[].src and block image paths. Files are embedded as base64
144
- * data URIs in a self-contained render HTML page.
145
- *
146
- * Requires:
147
- * - Playwright (chromium) — for headless frame capture
148
- * - FFmpeg — for video encoding (resolved from SQUISQ_FFMPEG, PATH, or an
149
- * optionally installed `ffmpeg-static` package — see detectFfmpeg)
150
- *
151
- * @param doc - The Doc structure to render
152
- * @param container - MemoryContentContainer with audio/image files
153
- * @param options - Rendering and encoding options
154
- * @returns Result with duration and frame count
155
- */
318
+ /** Result returned by renderDocToGif. */
319
+ interface RenderDocToGifResult extends RenderDocToMp4Result {
320
+ /** Non-fatal fidelity warnings, including GIF's lack of audio. */
321
+ warnings: string[];
322
+ }
323
+ /** Render a Doc + media container to an MP4 video file. */
156
324
  declare function renderDocToMp4(doc: Doc, container: ContentContainer, options: RenderDocToMp4Options): Promise<RenderDocToMp4Result>;
325
+ /** Render a Doc + media container to a silent animated GIF. */
326
+ declare function renderDocToGif(doc: Doc, container: ContentContainer, options: RenderDocToGifOptions): Promise<RenderDocToGifResult>;
157
327
  /** A thumbnail size specification. */
158
328
  interface ThumbnailSpec {
159
329
  /** Label for the thumbnail (used in filename: `{slug}-{width}x{height}.jpg`). */
@@ -167,7 +337,7 @@ interface ThumbnailSpec {
167
337
  }
168
338
  /** Options for extractThumbnails. */
169
339
  interface ExtractThumbnailsOptions {
170
- /** Path to the source MP4 video. */
340
+ /** Path to the source MP4 or animated GIF. */
171
341
  videoPath: string;
172
342
  /** Directory to write thumbnails into. */
173
343
  outputDir: string;
@@ -177,11 +347,13 @@ interface ExtractThumbnailsOptions {
177
347
  sizes: ThumbnailSpec[];
178
348
  /** Overwrite existing thumbnails (default: false). */
179
349
  force?: boolean;
350
+ /** Cancels FFmpeg and rejects with the caller's exact abort reason. */
351
+ signal?: AbortSignal;
180
352
  }
181
353
  /**
182
- * Extract thumbnail images from the first frame of an MP4 video.
354
+ * Extract thumbnail images from the first frame of an MP4 or animated GIF.
183
355
  * Produces JPEG files at each specified size using FFmpeg video filters.
184
356
  */
185
357
  declare function extractThumbnails(options: ExtractThumbnailsOptions): Promise<void>;
186
358
 
187
- export { type ExtractThumbnailsOptions, type ReadInputResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type ThumbnailSpec, convert, createCliRegistry, extractThumbnails, readInput, renderDocToMp4 };
359
+ export { CapturedFrameBudgetError, type CliConvertOptions, type ExtractThumbnailsOptions, GIF_EXPORT_DEFAULTS, type GifExportOptions, type GifFormatOptions, MAX_CAPTURED_FRAME_BYTES, type Mp4FormatOptions, type NativeVideoExportOptions, type ReadInputResult, type RenderDocToGifOptions, type RenderDocToGifResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type ThumbnailSpec, convert, createCliRegistry, extractThumbnails, framesToGifNative, framesToGifNativeBytes, framesToMp4Native, framesToMp4NativeBytes, prepareConversion, readInput, renderDocToGif, renderDocToMp4 };