@bendyline/squisq-cli 2.8.9 → 2.8.11

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.
@@ -141,7 +141,7 @@ async function framesToMp4Native(ffmpegPath, frames, audio, outputPath, options
141
141
  }
142
142
  args.push(outputPath);
143
143
  await runFfmpeg(ffmpegPath, args, {
144
- timeoutMs: 6e5,
144
+ timeoutMs: Math.max(6e5, frames.length * 250),
145
145
  failureMessage: "ffmpeg failed",
146
146
  signal: options.signal
147
147
  });
@@ -227,6 +227,7 @@ async function framesToGifNativeBytes(ffmpegPath, frames, options = {}) {
227
227
  }
228
228
 
229
229
  export {
230
+ lastMeaningfulFfmpegLine,
230
231
  runFfmpeg,
231
232
  GIF_EXPORT_DEFAULTS,
232
233
  framesToMp4Native,
@@ -143,7 +143,7 @@ async function framesToMp4Native(ffmpegPath, frames, audio, outputPath, options
143
143
  }
144
144
  args.push(outputPath);
145
145
  await runFfmpeg(ffmpegPath, args, {
146
- timeoutMs: 6e5,
146
+ timeoutMs: Math.max(6e5, frames.length * 250),
147
147
  failureMessage: "ffmpeg failed",
148
148
  signal: options.signal
149
149
  });
@@ -229,6 +229,7 @@ async function framesToGifNativeBytes(ffmpegPath, frames, options = {}) {
229
229
  }
230
230
 
231
231
  export {
232
+ lastMeaningfulFfmpegLine,
232
233
  runFfmpeg,
233
234
  GIF_EXPORT_DEFAULTS,
234
235
  framesToMp4Native,
package/dist/index.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  renderDocToDashboardPng,
10
10
  renderDocToGif,
11
11
  renderDocToMp4
12
- } from "./chunk-XD2ICI5G.js";
13
- import "./chunk-GGVASRPG.js";
12
+ } from "./chunk-6MZC4YQV.js";
13
+ import "./chunk-Y5XKUWJ5.js";
14
14
 
15
15
  // src/index.ts
16
16
  import { createRequire } from "module";
@@ -319,8 +319,11 @@ import { validateVideoExportOptions } from "@bendyline/squisq-video";
319
319
  var VALID_QUALITIES = ["draft", "normal", "high"];
320
320
  var VALID_ORIENTATIONS = ["landscape", "portrait"];
321
321
  var VALID_CAPTIONS = ["off", "standard", "social"];
322
+ var VALID_MOTION = ["calm", "documentary", "vibrant"];
322
323
  var VALID_FORMATS2 = ["mp4", "gif"];
323
324
  var VALID_GIF_DITHERS = ["bayer", "sierra2_4a", "none"];
325
+ var VALID_FRAME_TRANSPORTS = ["pipe", "memory"];
326
+ var VALID_CAPTURE_FORMATS = ["png", "jpeg"];
324
327
  function registerVideoCommand(program2) {
325
328
  program2.command("video").description("Render a squisq document to MP4 video or animated GIF").argument("<input>", "Path to .md/.json file, .zip/.dbk container, or folder").argument("[output]", "Output .mp4 or .gif path (default: <input>.<format>)").option("-o, --output <path>", "Output .mp4 or .gif path (format inferred from extension)").option("--format <format>", `Output format: ${VALID_FORMATS2.join(", ")} (default: mp4)`).option("--fps <number>", "Frames per second (default: 30 for MP4, 10 for GIF)").option(
326
329
  "--quality <level>",
@@ -336,13 +339,28 @@ function registerVideoCommand(program2) {
336
339
  "--no-auto-templates",
337
340
  "Disable content-aware template auto-picking for unannotated headings"
338
341
  ).option("-t, --theme <id>", "Squisq theme ID to apply (e.g., documentary, cinematic, bold)").option(
342
+ "--motion <profile>",
343
+ `Motion profile: ${VALID_MOTION.join(", ")} (default: the doc's, then the theme's)`
344
+ ).option(
339
345
  "--transform <style>",
340
346
  "Transform style to apply before rendering (e.g., documentary, magazine, minimal)"
341
347
  ).option(
342
348
  "--cover-preroll <seconds>",
343
349
  "Seconds of cover-slide pre-roll before the story starts (default: 2)",
344
350
  "2"
345
- ).option("--width <pixels>", "Override video width (must be even for MP4)").option("--height <pixels>", "Override video height (must be even for MP4)").option("--overwrite", "Replace an existing output file (default: refuse and exit non-zero)").option("--animations", "Enable slide animations/transitions (GIF default: disabled)").option("--no-animations", "Disable slide animations/transitions").option("--loop <count>", "GIF repeat count: 0 forever, -1 no loop (default: 0)").option("--max-colors <count>", "GIF palette size from 2 to 256 (default: 256)").option(
351
+ ).option("--width <pixels>", "Override video width (must be even for MP4)").option("--height <pixels>", "Override video height (must be even for MP4)").option("--overwrite", "Replace an existing output file (default: refuse and exit non-zero)").option(
352
+ "--frame-transport <mode>",
353
+ `MP4 frame delivery: ${VALID_FRAME_TRANSPORTS.join(", ")} (default: pipe, streams frames to ffmpeg)`
354
+ ).option(
355
+ "--capture-format <format>",
356
+ `MP4 still format captured per frame: ${VALID_CAPTURE_FORMATS.join(", ")} (default: png for high quality, jpeg otherwise)`
357
+ ).option(
358
+ "--frames-dir <dir>",
359
+ "MP4 only: spool every captured frame here and keep it after the render"
360
+ ).option(
361
+ "--resume",
362
+ "MP4 only: reuse frames an identical earlier render spooled into --frames-dir"
363
+ ).option("--animations", "Enable slide animations/transitions (GIF default: disabled)").option("--no-animations", "Disable slide animations/transitions").option("--loop <count>", "GIF repeat count: 0 forever, -1 no loop (default: 0)").option("--max-colors <count>", "GIF palette size from 2 to 256 (default: 256)").option(
346
364
  "--dither <algorithm>",
347
365
  `GIF dithering: ${VALID_GIF_DITHERS.join(", ")} (default: sierra2_4a)`
348
366
  ).option("--bayer-scale <number>", "GIF Bayer dither strength from 0 to 5 (default: 3)").action(async (inputPath, outputArg, opts) => {
@@ -401,10 +419,37 @@ async function runVideo(inputPath, opts) {
401
419
  if (!VALID_CAPTIONS.includes(captions)) {
402
420
  throw new Error(`Invalid captions "${captions}". Valid: ${VALID_CAPTIONS.join(", ")}`);
403
421
  }
422
+ const motion = opts.motion;
423
+ if (motion !== void 0 && !VALID_MOTION.includes(motion)) {
424
+ throw new Error(`Invalid motion "${motion}". Valid: ${VALID_MOTION.join(", ")}`);
425
+ }
404
426
  const coverPreRoll = Number(opts.coverPreroll ?? "2");
405
427
  if (!Number.isFinite(coverPreRoll) || coverPreRoll < 0) {
406
428
  throw new Error("Cover pre-roll must be a number of seconds >= 0");
407
429
  }
430
+ if (outputFormat === "gif" && (opts.frameTransport || opts.captureFormat || opts.framesDir || opts.resume)) {
431
+ throw new Error(
432
+ "--frame-transport, --capture-format, --frames-dir, and --resume only apply to MP4 output"
433
+ );
434
+ }
435
+ const frameTransport = opts.frameTransport ?? "pipe";
436
+ if (!VALID_FRAME_TRANSPORTS.includes(frameTransport)) {
437
+ throw new Error(
438
+ `Invalid frame transport "${frameTransport}". Valid: ${VALID_FRAME_TRANSPORTS.join(", ")}`
439
+ );
440
+ }
441
+ const captureFormat = opts.captureFormat;
442
+ if (captureFormat !== void 0 && !VALID_CAPTURE_FORMATS.includes(captureFormat)) {
443
+ throw new Error(
444
+ `Invalid capture format "${captureFormat}". Valid: ${VALID_CAPTURE_FORMATS.join(", ")}`
445
+ );
446
+ }
447
+ if (opts.resume && !opts.framesDir) {
448
+ throw new Error("--resume requires --frames-dir");
449
+ }
450
+ if ((opts.framesDir || captureFormat) && frameTransport !== "pipe") {
451
+ throw new Error("--frames-dir and --capture-format require --frame-transport pipe");
452
+ }
408
453
  const animationsEnabled = opts.animations ?? outputFormat === "mp4";
409
454
  const loop = opts.loop === void 0 ? 0 : Number(opts.loop);
410
455
  const maxColors = opts.maxColors === void 0 ? 256 : Number(opts.maxColors);
@@ -461,8 +506,8 @@ async function runVideo(inputPath, opts) {
461
506
  if (opts.theme) {
462
507
  doc = { ...doc, themeId: opts.theme };
463
508
  }
464
- const motionLabel = animationsEnabled ? "animations on" : "animations off";
465
- const qualityLabel = outputFormat === "mp4" ? `, quality: ${quality}` : "";
509
+ const motionLabel = animationsEnabled ? `animations on${motion ? ` (${motion})` : ""}` : "animations off";
510
+ const qualityLabel = outputFormat === "mp4" ? `, quality: ${quality}, transport: ${frameTransport}` : "";
466
511
  console.error(
467
512
  `Rendering ${outputFormat.toUpperCase()}: ${fps} fps${qualityLabel}, orientation: ${orientation}, captions: ${captions}, ${motionLabel}`
468
513
  );
@@ -473,6 +518,7 @@ async function runVideo(inputPath, opts) {
473
518
  width,
474
519
  height,
475
520
  captionStyle: captions,
521
+ motion,
476
522
  coverPreRoll,
477
523
  animationsEnabled,
478
524
  onProgress: (phase, percent) => writeProgress(phase, percent, 100)
@@ -485,15 +531,20 @@ async function runVideo(inputPath, opts) {
485
531
  bayerScale
486
532
  }) : await renderDocToMp4(doc, container, {
487
533
  ...sharedRenderOptions,
488
- quality
534
+ quality,
535
+ frameTransport,
536
+ captureFormat,
537
+ framesDir: opts.framesDir ? resolve2(opts.framesDir) : void 0,
538
+ resume: opts.resume
489
539
  });
490
540
  clearProgress();
491
541
  const warnings = "warnings" in result2 && Array.isArray(result2.warnings) ? result2.warnings : [];
492
542
  for (const warning of warnings) {
493
543
  console.error(` \u26A0 ${warning}`);
494
544
  }
545
+ const reused = "reusedFrameCount" in result2 && result2.reusedFrameCount ? `, ${result2.reusedFrameCount} reused from ${opts.framesDir}` : "";
495
546
  console.error(
496
- ` \u2713 ${outputPath} (${result2.duration.toFixed(1)}s, ${result2.frameCount} frames)`
547
+ ` \u2713 ${outputPath} (${result2.duration.toFixed(1)}s, ${result2.frameCount} frames${reused})`
497
548
  );
498
549
  console.error("Done.");
499
550
  }
@@ -5,7 +5,7 @@ import {
5
5
  framesToGifNativeBytes,
6
6
  framesToMp4Native,
7
7
  framesToMp4NativeBytes
8
- } from "./chunk-GGVASRPG.js";
8
+ } from "./chunk-Y5XKUWJ5.js";
9
9
  export {
10
10
  GIF_EXPORT_DEFAULTS,
11
11
  framesToGifNative,
@@ -4,7 +4,7 @@ import {
4
4
  framesToGifNativeBytes,
5
5
  framesToMp4Native,
6
6
  framesToMp4NativeBytes
7
- } from "./chunk-Z3BHD4AF.js";
7
+ } from "./chunk-RQSP3OPR.js";
8
8
  export {
9
9
  GIF_EXPORT_DEFAULTS,
10
10
  framesToGifNative,