@bendyline/squisq-cli 2.8.9 → 2.8.10
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 +11 -0
- package/dist/{api-QLYK5AQE.js → api-O34CQ6ZP.js} +4 -2
- package/dist/api.d.ts +109 -1
- package/dist/api.js +430 -82
- package/dist/{chunk-XD2ICI5G.js → chunk-4FJZYJC3.js} +433 -85
- package/dist/{chunk-Z3BHD4AF.js → chunk-RQSP3OPR.js} +2 -1
- package/dist/{chunk-GGVASRPG.js → chunk-Y5XKUWJ5.js} +2 -1
- package/dist/index.js +48 -6
- package/dist/{nativeEncoder-PMVSFLMN.js → nativeEncoder-RILHXTXI.js} +1 -1
- package/dist/{nativeEncoder-RZ3T4Z6S.js → nativeEncoder-T43J5UMP.js} +1 -1
- package/dist/squisq-player.full.global.js +344 -344
- package/dist/squisq-player.global.js +64 -64
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,12 +89,18 @@ squisq video doc.json -o out.mp4
|
|
|
89
89
|
| `--width` / `--height` | Dimension overrides in pixels — **must be even** | MP4 1080p; GIF 960×540 |
|
|
90
90
|
| `--overwrite` | Replace an existing output file (otherwise refuse and exit non-zero) | off |
|
|
91
91
|
| `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
|
|
92
|
+
| `--frame-transport` | MP4 frame delivery: `pipe` (stream stills to ffmpeg) or `memory` | `pipe` |
|
|
93
|
+
| `--capture-format` | MP4 still captured per frame: `png` or `jpeg` (pipe only) | high `png`, else `jpeg` |
|
|
94
|
+
| `--frames-dir` | MP4 only: spool every captured still here and keep it after the run | none |
|
|
95
|
+
| `--resume` | MP4 only: reuse stills an identical render spooled in `--frames-dir` | off |
|
|
92
96
|
|
|
93
97
|
Notes:
|
|
94
98
|
|
|
95
99
|
- **Dimensions must be even.** `--width 851` is rejected immediately — before the document is read or a browser launches — with `Video width must be an even number of pixels (got 851) … Use 850 or 852.` Odd values are rejected rather than rounded, so a render never silently ships at a size you did not ask for. The rule is the same for MP4 and GIF, and matches browser export.
|
|
96
100
|
- **An existing output file is never overwritten by default.** The check runs before rendering, so a colliding path costs you a second rather than a full capture. Pass `--overwrite` to replace.
|
|
97
101
|
- FFmpeg failures report the one relevant line (e.g. `[libx264] width not divisible by 2 (851x480) (exit code 1)`) rather than dumping the whole command line and stderr buffer.
|
|
102
|
+
- **MP4 frames stream to ffmpeg as they are captured.** Memory stays flat for a story of any length; `--frame-transport memory` restores the old retain-then-encode path, which is bounded to a few seconds of 1080p photo slides. `--frames-dir` keeps every still (plus a `manifest.json` naming the doc, size, rate, captions and animation settings), and `--resume` skips re-capturing stills that an identical render already spooled there.
|
|
103
|
+
- **Portrait and custom sizes are composed natively.** The render page pins the player's viewport to the export size; a player bundle that ignores it fails the run before capture instead of producing a letterboxed landscape layout.
|
|
98
104
|
|
|
99
105
|
### `squisq image <input> [output]`
|
|
100
106
|
|
|
@@ -230,8 +236,13 @@ const result = await renderDocToMp4(doc, container, {
|
|
|
230
236
|
captionStyle: 'social', // 'off' | 'standard' | 'social' (default 'off')
|
|
231
237
|
animationsEnabled: false, // static slide changes; media/timing remain active
|
|
232
238
|
coverPreRoll: 2, // seconds of cover-slide pre-roll (default 0)
|
|
239
|
+
frameTransport: 'pipe', // 'pipe' (default; streams stills to ffmpeg) | 'memory'
|
|
240
|
+
captureFormat: 'png', // 'png' | 'jpeg' (default: png for high quality, jpeg otherwise)
|
|
241
|
+
framesDir: './frames', // optional: keep every still + manifest; with resume: true, reuse them
|
|
242
|
+
onFrame: ({ index, totalFrames, captureMs, reused }) => {}, // per-frame throughput hook
|
|
233
243
|
onProgress: (phase, pct) => console.log(`${phase}: ${pct}%`),
|
|
234
244
|
});
|
|
245
|
+
// result: { duration, frameCount, outputPath, reusedFrameCount?, framesDir? }
|
|
235
246
|
|
|
236
247
|
console.log(`Rendered ${result.frameCount} frames (${result.duration}s) → ${result.outputPath}`);
|
|
237
248
|
```
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
convert,
|
|
10
10
|
createCliRegistry,
|
|
11
11
|
extractThumbnails,
|
|
12
|
+
ffmpegPipeArgs,
|
|
12
13
|
prepareConversion,
|
|
13
14
|
readInput,
|
|
14
15
|
renderDocCoverToPng,
|
|
@@ -17,14 +18,14 @@ import {
|
|
|
17
18
|
renderDocToMp4,
|
|
18
19
|
resolveDashboardDimensions,
|
|
19
20
|
validateDashboardImageDimensions
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-4FJZYJC3.js";
|
|
21
22
|
import {
|
|
22
23
|
GIF_EXPORT_DEFAULTS,
|
|
23
24
|
framesToGifNative,
|
|
24
25
|
framesToGifNativeBytes,
|
|
25
26
|
framesToMp4Native,
|
|
26
27
|
framesToMp4NativeBytes
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-Y5XKUWJ5.js";
|
|
28
29
|
export {
|
|
29
30
|
CapturedFrameBudgetError,
|
|
30
31
|
ConversionError,
|
|
@@ -36,6 +37,7 @@ export {
|
|
|
36
37
|
convert,
|
|
37
38
|
createCliRegistry,
|
|
38
39
|
extractThumbnails,
|
|
40
|
+
ffmpegPipeArgs,
|
|
39
41
|
framesToGifNative,
|
|
40
42
|
framesToGifNativeBytes,
|
|
41
43
|
framesToMp4Native,
|
package/dist/api.d.ts
CHANGED
|
@@ -8,6 +8,66 @@ import { FormatRegistry, FormatId, ConvertOptions, ConvertSource, ConversionResu
|
|
|
8
8
|
export { ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionResult, ConvertOptions, ConvertSource, FormatDefinition, FormatId, FormatRegistry, NormalizedInput, PreparedConversion, PreparedExportOptions } from '@bendyline/squisq-formats';
|
|
9
9
|
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Frame sinks for offline MP4 rendering.
|
|
13
|
+
*
|
|
14
|
+
* The capture loop produces one encoded still (PNG or JPEG) per output frame
|
|
15
|
+
* and hands it to a sink. Two kinds exist:
|
|
16
|
+
*
|
|
17
|
+
* - {@link createFfmpegPipeSink} streams stills into one long-running ffmpeg
|
|
18
|
+
* process over stdin (`image2pipe`) so x264 encodes as frames arrive and
|
|
19
|
+
* memory stays flat however long the document is. This replaced the
|
|
20
|
+
* retain-every-PNG-then-encode design, whose 256 MB budget held only a few
|
|
21
|
+
* seconds of 1080p photo slides.
|
|
22
|
+
* - {@link openDirectoryFrameStore} spools stills to a directory next to a
|
|
23
|
+
* manifest, so a render can be inspected frame by frame and resumed after
|
|
24
|
+
* an interruption without re-capturing frames already on disk.
|
|
25
|
+
*
|
|
26
|
+
* Related: api.ts (`renderDocToMp4`), runFfmpeg.ts (stderr digest),
|
|
27
|
+
* @bendyline/squisq-video ffmpegArgs.ts (quality and mux flags).
|
|
28
|
+
*
|
|
29
|
+
* Gotchas: the pipe sink honours stdin backpressure, so a slow encoder slows
|
|
30
|
+
* capture rather than buffering frames. A wall-clock timeout would fire on
|
|
31
|
+
* every long render, so the sink watches for silence instead: a frame write
|
|
32
|
+
* or an ffmpeg stderr line counts as activity.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Still-image format captured from the browser for each frame. */
|
|
36
|
+
type CaptureFormat = 'png' | 'jpeg';
|
|
37
|
+
/** Everything the pipe encoder needs; mirrors the memory path's ffmpeg flags. */
|
|
38
|
+
interface FfmpegPipeSinkOptions {
|
|
39
|
+
ffmpegPath: string;
|
|
40
|
+
outputPath: string;
|
|
41
|
+
fps: number;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
quality: VideoQuality;
|
|
45
|
+
captureFormat: CaptureFormat;
|
|
46
|
+
/** Pre-mixed audio file to mux, or null for a silent video. */
|
|
47
|
+
audioPath: string | null;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
/** Fail when neither a frame nor encoder output arrives for this long (default 120 s). */
|
|
50
|
+
idleTimeoutMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/** ffmpeg arguments for the streaming (`image2pipe`) H.264 encode. Pure; unit-tested. */
|
|
53
|
+
declare function ffmpegPipeArgs(options: Omit<FfmpegPipeSinkOptions, 'ffmpegPath' | 'signal' | 'idleTimeoutMs'>): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Identity of a spooled render. A spool is reusable only when every value
|
|
56
|
+
* matches: a different doc, size, rate, caption or animation setting changes
|
|
57
|
+
* the pixels, and a different capture format changes the bytes on disk.
|
|
58
|
+
*/
|
|
59
|
+
interface FrameManifest {
|
|
60
|
+
generatedBy: 'squisq-cli';
|
|
61
|
+
docHash: string;
|
|
62
|
+
fps: number;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
captionStyle: string | null;
|
|
66
|
+
animationsEnabled: boolean;
|
|
67
|
+
coverPreRoll: number;
|
|
68
|
+
captureFormat: CaptureFormat;
|
|
69
|
+
}
|
|
70
|
+
|
|
11
71
|
/**
|
|
12
72
|
* CLI format registry.
|
|
13
73
|
*
|
|
@@ -286,6 +346,36 @@ interface RenderDocToMp4Options {
|
|
|
286
346
|
captionStyle?: 'off' | 'standard' | 'social';
|
|
287
347
|
/** Render layer animations and block transitions (default: true). */
|
|
288
348
|
animationsEnabled?: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* How captured stills reach the encoder (default: 'pipe').
|
|
351
|
+
*
|
|
352
|
+
* - 'pipe' streams each still into one long-running ffmpeg process as it is
|
|
353
|
+
* captured, so memory stays flat for documents of any length.
|
|
354
|
+
* - 'memory' retains every still until capture completes, then encodes.
|
|
355
|
+
* Bounded by {@link MAX_CAPTURED_FRAME_BYTES} (a few seconds of 1080p
|
|
356
|
+
* photo slides); kept for callers that depend on the old semantics.
|
|
357
|
+
*/
|
|
358
|
+
frameTransport?: 'pipe' | 'memory';
|
|
359
|
+
/**
|
|
360
|
+
* Still-image format captured from the browser per frame (default: 'png'
|
|
361
|
+
* for high quality, otherwise 'jpeg'). Chromium encodes a 1080p PNG in
|
|
362
|
+
* hundreds of milliseconds but a JPEG in tens, and JPEG stills are
|
|
363
|
+
* converted to limited-range yuv420p and indistinguishable after H.264 at
|
|
364
|
+
* draft/normal CRF; PNG keeps the capture lossless for the high preset.
|
|
365
|
+
* Pipe transport only.
|
|
366
|
+
*/
|
|
367
|
+
captureFormat?: CaptureFormat;
|
|
368
|
+
/**
|
|
369
|
+
* Spool every captured still into this directory next to a manifest, and
|
|
370
|
+
* keep them after the render for inspection. With `resume`, a spool whose
|
|
371
|
+
* manifest matches this render supplies its frames instead of re-capturing
|
|
372
|
+
* them. Pipe transport only.
|
|
373
|
+
*/
|
|
374
|
+
framesDir?: string;
|
|
375
|
+
/** Reuse frames already present in `framesDir` from an identical render. */
|
|
376
|
+
resume?: boolean;
|
|
377
|
+
/** Per-frame callback for hosts that measure throughput or tee frames. Pipe transport only. */
|
|
378
|
+
onFrame?: (frame: RenderedFrameInfo) => void;
|
|
289
379
|
/**
|
|
290
380
|
* Seconds of cover-slide pre-roll before the story starts (default: 0).
|
|
291
381
|
*
|
|
@@ -330,6 +420,20 @@ interface RenderDocToGifOptions {
|
|
|
330
420
|
/** Progress callback. */
|
|
331
421
|
onProgress?: (phase: string, percent: number) => void;
|
|
332
422
|
}
|
|
423
|
+
/** One output frame as it passes through the render loop. */
|
|
424
|
+
interface RenderedFrameInfo {
|
|
425
|
+
/** Zero-based index in the output sequence; cover pre-roll frames come first. */
|
|
426
|
+
index: number;
|
|
427
|
+
/** Timeline second rendered (0 during the cover pre-roll). */
|
|
428
|
+
time: number;
|
|
429
|
+
isCover: boolean;
|
|
430
|
+
/** Frames the render emits in total, pre-roll included. */
|
|
431
|
+
totalFrames: number;
|
|
432
|
+
/** True when the still came from a resumed spool instead of a capture. */
|
|
433
|
+
reused: boolean;
|
|
434
|
+
/** Milliseconds spent seeking and capturing (0 when reused). */
|
|
435
|
+
captureMs: number;
|
|
436
|
+
}
|
|
333
437
|
/** Result returned by renderDocToMp4. */
|
|
334
438
|
interface RenderDocToMp4Result {
|
|
335
439
|
/** Duration of the rendered video in seconds (including pre-roll). */
|
|
@@ -338,6 +442,10 @@ interface RenderDocToMp4Result {
|
|
|
338
442
|
frameCount: number;
|
|
339
443
|
/** Output file path. */
|
|
340
444
|
outputPath: string;
|
|
445
|
+
/** Frames supplied by a resumed spool instead of a capture (pipe transport). */
|
|
446
|
+
reusedFrameCount?: number;
|
|
447
|
+
/** Spool directory holding every captured still, when one was requested. */
|
|
448
|
+
framesDir?: string;
|
|
341
449
|
}
|
|
342
450
|
/** Result returned by renderDocToGif. */
|
|
343
451
|
interface RenderDocToGifResult extends RenderDocToMp4Result {
|
|
@@ -442,4 +550,4 @@ interface ExtractThumbnailsOptions {
|
|
|
442
550
|
*/
|
|
443
551
|
declare function extractThumbnails(options: ExtractThumbnailsOptions): Promise<void>;
|
|
444
552
|
|
|
445
|
-
export { CapturedFrameBudgetError, type CliConvertOptions, type ExtractThumbnailsOptions, GIF_EXPORT_DEFAULTS, type GifExportOptions, type GifFormatOptions, MAX_CAPTURED_FRAME_BYTES, type Mp4FormatOptions, type NativeVideoExportOptions, type PngFormatOptions, type ReadInputResult, type RenderCoverPngOptions, type RenderCoverPngResult, type RenderDashboardPngOptions, type RenderDashboardPngResult, type RenderDocToGifOptions, type RenderDocToGifResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type ThumbnailSpec, convert, createCliRegistry, extractThumbnails, framesToGifNative, framesToGifNativeBytes, framesToMp4Native, framesToMp4NativeBytes, prepareConversion, readInput, renderDocCoverToPng, renderDocToDashboardPng, renderDocToGif, renderDocToMp4 };
|
|
553
|
+
export { type CaptureFormat, CapturedFrameBudgetError, type CliConvertOptions, type ExtractThumbnailsOptions, type FrameManifest, GIF_EXPORT_DEFAULTS, type GifExportOptions, type GifFormatOptions, MAX_CAPTURED_FRAME_BYTES, type Mp4FormatOptions, type NativeVideoExportOptions, type PngFormatOptions, type ReadInputResult, type RenderCoverPngOptions, type RenderCoverPngResult, type RenderDashboardPngOptions, type RenderDashboardPngResult, type RenderDocToGifOptions, type RenderDocToGifResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type RenderedFrameInfo, type ThumbnailSpec, convert, createCliRegistry, extractThumbnails, ffmpegPipeArgs, framesToGifNative, framesToGifNativeBytes, framesToMp4Native, framesToMp4NativeBytes, prepareConversion, readInput, renderDocCoverToPng, renderDocToDashboardPng, renderDocToGif, renderDocToMp4 };
|