@bendyline/squisq-cli 2.8.10 → 2.8.12
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 +1 -0
- package/dist/{api-O34CQ6ZP.js → api-P7LOLIZT.js} +1 -1
- package/dist/api.d.ts +11 -1
- package/dist/api.js +52 -4
- package/dist/{chunk-4FJZYJC3.js → chunk-6MZC4YQV.js} +55 -7
- package/dist/index.js +11 -2
- package/dist/squisq-player.full.global.js +439 -439
- package/dist/squisq-player.global.js +71 -71
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ squisq video doc.json -o out.mp4
|
|
|
84
84
|
| `--dither` | GIF dithering: `bayer`, `sierra2_4a`, or `none` | `sierra2_4a` |
|
|
85
85
|
| `--bayer-scale` | Ordered Bayer strength, 0–5 | 3 |
|
|
86
86
|
| `-t, --theme` | Squisq theme id to apply | none |
|
|
87
|
+
| `--motion` | Motion profile: `calm`, `documentary` or `vibrant` | doc's, then theme's |
|
|
87
88
|
| `--transform` | Transform style to apply before rendering | none |
|
|
88
89
|
| `--cover-preroll` | Seconds of cover-slide pre-roll before the story starts | 2 |
|
|
89
90
|
| `--width` / `--height` | Dimension overrides in pixels — **must be even** | MP4 1080p; GIF 960×540 |
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Doc } from '@bendyline/squisq/schemas';
|
|
1
|
+
import { Doc, MotionSpec } from '@bendyline/squisq/schemas';
|
|
2
2
|
import { DashboardStyleId } from '@bendyline/squisq/doc';
|
|
3
3
|
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
4
4
|
export { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
@@ -63,6 +63,8 @@ interface FrameManifest {
|
|
|
63
63
|
width: number;
|
|
64
64
|
height: number;
|
|
65
65
|
captionStyle: string | null;
|
|
66
|
+
/** JSON of the motion spec the frames were captured with (`null` = doc/theme default). */
|
|
67
|
+
motion?: string | null;
|
|
66
68
|
animationsEnabled: boolean;
|
|
67
69
|
coverPreRoll: number;
|
|
68
70
|
captureFormat: CaptureFormat;
|
|
@@ -344,6 +346,12 @@ interface RenderDocToMp4Options {
|
|
|
344
346
|
height?: number;
|
|
345
347
|
/** Caption mode to bake into the video (default: off). */
|
|
346
348
|
captionStyle?: 'off' | 'standard' | 'social';
|
|
349
|
+
/**
|
|
350
|
+
* Motion profile override: `calm` (pre-profile output), `documentary` or
|
|
351
|
+
* `vibrant`, or a spec with overrides. Omitted → the doc's `motion` /
|
|
352
|
+
* frontmatter `squisq-motion`, then the theme's `renderStyle.motionProfile`.
|
|
353
|
+
*/
|
|
354
|
+
motion?: MotionSpec | null;
|
|
347
355
|
/** Render layer animations and block transitions (default: true). */
|
|
348
356
|
animationsEnabled?: boolean;
|
|
349
357
|
/**
|
|
@@ -405,6 +413,8 @@ interface RenderDocToGifOptions {
|
|
|
405
413
|
height?: number;
|
|
406
414
|
/** Caption mode to bake into the GIF (default: standard). */
|
|
407
415
|
captionStyle?: 'off' | 'standard' | 'social';
|
|
416
|
+
/** Motion profile override (see {@link RenderDocToMp4Options.motion}). */
|
|
417
|
+
motion?: MotionSpec | null;
|
|
408
418
|
/** Seconds of cover-slide pre-roll (default: 0). */
|
|
409
419
|
coverPreRoll?: number;
|
|
410
420
|
/** Render layer animations and block transitions (default: false). */
|
package/dist/api.js
CHANGED
|
@@ -1128,6 +1128,7 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1128
1128
|
height,
|
|
1129
1129
|
includeAudio,
|
|
1130
1130
|
captionStyle,
|
|
1131
|
+
motion,
|
|
1131
1132
|
animationsEnabled,
|
|
1132
1133
|
displayMode,
|
|
1133
1134
|
dashboard,
|
|
@@ -1188,6 +1189,7 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1188
1189
|
width,
|
|
1189
1190
|
height,
|
|
1190
1191
|
captionStyle,
|
|
1192
|
+
motion,
|
|
1191
1193
|
animationsEnabled,
|
|
1192
1194
|
displayMode,
|
|
1193
1195
|
dashboard
|
|
@@ -1198,7 +1200,14 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1198
1200
|
signal?.throwIfAborted();
|
|
1199
1201
|
let browser;
|
|
1200
1202
|
try {
|
|
1201
|
-
browser = await chromium.launch({
|
|
1203
|
+
browser = await chromium.launch({
|
|
1204
|
+
headless: true,
|
|
1205
|
+
// Keyframe animations must be sampled by the main thread: the
|
|
1206
|
+
// compositor rasterises a transforming layer differently depending on
|
|
1207
|
+
// whether it caught the animation running, which made mid-motion stills
|
|
1208
|
+
// differ between otherwise identical renders.
|
|
1209
|
+
args: ["--disable-threaded-animation"]
|
|
1210
|
+
});
|
|
1202
1211
|
} catch (err) {
|
|
1203
1212
|
signal?.throwIfAborted();
|
|
1204
1213
|
const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
@@ -1248,6 +1257,13 @@ Page errors:
|
|
|
1248
1257
|
`The standalone player failed to boot in headless Chromium. Render API did not initialize within ${RENDER_BOOT_TIMEOUT_MS / 1e3} seconds.${errorDetail}`
|
|
1249
1258
|
);
|
|
1250
1259
|
}
|
|
1260
|
+
signal?.throwIfAborted();
|
|
1261
|
+
await page.evaluate(
|
|
1262
|
+
() => Promise.race([
|
|
1263
|
+
document.fonts?.ready ?? Promise.resolve(void 0),
|
|
1264
|
+
new Promise((resolve2) => setTimeout(resolve2, 1e4))
|
|
1265
|
+
]).then(() => void 0)
|
|
1266
|
+
);
|
|
1251
1267
|
renderAPI = await page.evaluateHandle(() => {
|
|
1252
1268
|
const root = document.getElementById("squisq-root");
|
|
1253
1269
|
const player = window.SquisqPlayer;
|
|
@@ -1334,7 +1350,17 @@ async function iterateDocFrames(session, options, visit) {
|
|
|
1334
1350
|
};
|
|
1335
1351
|
}
|
|
1336
1352
|
async function captureDocFrames(doc, container, options) {
|
|
1337
|
-
const {
|
|
1353
|
+
const {
|
|
1354
|
+
fps,
|
|
1355
|
+
width,
|
|
1356
|
+
height,
|
|
1357
|
+
captionStyle,
|
|
1358
|
+
motion,
|
|
1359
|
+
coverPreRoll,
|
|
1360
|
+
animationsEnabled,
|
|
1361
|
+
onProgress,
|
|
1362
|
+
signal
|
|
1363
|
+
} = options;
|
|
1338
1364
|
signal?.throwIfAborted();
|
|
1339
1365
|
resolveAppliedCoverPreRoll(coverPreRoll, true);
|
|
1340
1366
|
const ffmpegPath = await requireFfmpeg(signal);
|
|
@@ -1343,7 +1369,16 @@ async function captureDocFrames(doc, container, options) {
|
|
|
1343
1369
|
return await withRenderPage(
|
|
1344
1370
|
doc,
|
|
1345
1371
|
container,
|
|
1346
|
-
{
|
|
1372
|
+
{
|
|
1373
|
+
signal,
|
|
1374
|
+
width,
|
|
1375
|
+
height,
|
|
1376
|
+
includeAudio: true,
|
|
1377
|
+
captionStyle,
|
|
1378
|
+
motion,
|
|
1379
|
+
animationsEnabled,
|
|
1380
|
+
onProgress
|
|
1381
|
+
},
|
|
1347
1382
|
async (session) => {
|
|
1348
1383
|
const loop = await iterateDocFrames(
|
|
1349
1384
|
session,
|
|
@@ -1378,6 +1413,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1378
1413
|
throw new Error("resume requires framesDir so there is a spool to resume from");
|
|
1379
1414
|
}
|
|
1380
1415
|
const captionStyle = options.captionStyle === "off" ? void 0 : options.captionStyle;
|
|
1416
|
+
const motion = options.motion ?? null;
|
|
1381
1417
|
const animationsEnabled = options.animationsEnabled ?? true;
|
|
1382
1418
|
const ffmpegPath = await requireFfmpeg(signal);
|
|
1383
1419
|
const outputPath = resolvePath(options.outputPath);
|
|
@@ -1389,6 +1425,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1389
1425
|
width,
|
|
1390
1426
|
height,
|
|
1391
1427
|
captionStyle: captionStyle ?? null,
|
|
1428
|
+
motion: motion === null ? null : JSON.stringify(motion),
|
|
1392
1429
|
animationsEnabled,
|
|
1393
1430
|
coverPreRoll,
|
|
1394
1431
|
captureFormat
|
|
@@ -1406,7 +1443,16 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1406
1443
|
const { loop, activeSink } = await withRenderPage(
|
|
1407
1444
|
doc,
|
|
1408
1445
|
container,
|
|
1409
|
-
{
|
|
1446
|
+
{
|
|
1447
|
+
signal,
|
|
1448
|
+
width,
|
|
1449
|
+
height,
|
|
1450
|
+
includeAudio: true,
|
|
1451
|
+
captionStyle,
|
|
1452
|
+
motion,
|
|
1453
|
+
animationsEnabled,
|
|
1454
|
+
onProgress
|
|
1455
|
+
},
|
|
1410
1456
|
async (session) => {
|
|
1411
1457
|
const hasCover = coverPreRoll > 0 ? await session.renderAPI.evaluate((api) => api.hasCoverBlock()) : false;
|
|
1412
1458
|
const appliedCoverPreRoll = resolveAppliedCoverPreRoll(coverPreRoll, hasCover);
|
|
@@ -1606,6 +1652,7 @@ async function renderDocToMp4(doc, container, options) {
|
|
|
1606
1652
|
width: dimensions.width,
|
|
1607
1653
|
height: dimensions.height,
|
|
1608
1654
|
captionStyle: options.captionStyle === "off" ? void 0 : options.captionStyle,
|
|
1655
|
+
motion: options.motion ?? null,
|
|
1609
1656
|
coverPreRoll: options.coverPreRoll ?? 0,
|
|
1610
1657
|
animationsEnabled: options.animationsEnabled ?? true,
|
|
1611
1658
|
onProgress: options.onProgress,
|
|
@@ -1669,6 +1716,7 @@ async function renderDocToGif(doc, container, options) {
|
|
|
1669
1716
|
width,
|
|
1670
1717
|
height,
|
|
1671
1718
|
captionStyle: options.captionStyle === "off" ? void 0 : options.captionStyle ?? "standard",
|
|
1719
|
+
motion: options.motion ?? null,
|
|
1672
1720
|
coverPreRoll: options.coverPreRoll ?? 0,
|
|
1673
1721
|
animationsEnabled: options.animationsEnabled ?? false,
|
|
1674
1722
|
onProgress: options.onProgress,
|
|
@@ -542,7 +542,7 @@ function mp4Format() {
|
|
|
542
542
|
const animationsEnabled = typeof mp4Opts.animationsEnabled === "boolean" ? mp4Opts.animationsEnabled : MP4_DEFAULTS.animationsEnabled;
|
|
543
543
|
const outputPath = join2(tmpdir(), `squisq-mp4-${randomBytes(8).toString("hex")}.mp4`);
|
|
544
544
|
try {
|
|
545
|
-
const { renderDocToMp4: renderDocToMp42 } = await import("./api-
|
|
545
|
+
const { renderDocToMp4: renderDocToMp42 } = await import("./api-P7LOLIZT.js");
|
|
546
546
|
await renderDocToMp42(input.doc, input.container, {
|
|
547
547
|
outputPath,
|
|
548
548
|
fps,
|
|
@@ -583,7 +583,7 @@ function gifFormat() {
|
|
|
583
583
|
const defaultHeight = portrait ? GIF_DEFAULTS.width : GIF_DEFAULTS.height;
|
|
584
584
|
const outputPath = join2(tmpdir(), `squisq-gif-${randomBytes(8).toString("hex")}.gif`);
|
|
585
585
|
try {
|
|
586
|
-
const { renderDocToGif: renderDocToGif2 } = await import("./api-
|
|
586
|
+
const { renderDocToGif: renderDocToGif2 } = await import("./api-P7LOLIZT.js");
|
|
587
587
|
const result = await renderDocToGif2(input.doc, input.container, {
|
|
588
588
|
outputPath,
|
|
589
589
|
fps: typeof gifOpts.fps === "number" ? gifOpts.fps : GIF_DEFAULTS.fps,
|
|
@@ -626,7 +626,7 @@ function pngFormat() {
|
|
|
626
626
|
async exportDoc(input, options) {
|
|
627
627
|
options.signal?.throwIfAborted();
|
|
628
628
|
const pngOpts = options.formatOptions?.png ?? {};
|
|
629
|
-
const { renderDocToDashboardPng: renderDocToDashboardPng2 } = await import("./api-
|
|
629
|
+
const { renderDocToDashboardPng: renderDocToDashboardPng2 } = await import("./api-P7LOLIZT.js");
|
|
630
630
|
const result = await renderDocToDashboardPng2(input.doc, input.container, {
|
|
631
631
|
resolution: pngOpts.resolution,
|
|
632
632
|
width: pngOpts.width,
|
|
@@ -1125,6 +1125,7 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1125
1125
|
height,
|
|
1126
1126
|
includeAudio,
|
|
1127
1127
|
captionStyle,
|
|
1128
|
+
motion,
|
|
1128
1129
|
animationsEnabled,
|
|
1129
1130
|
displayMode,
|
|
1130
1131
|
dashboard,
|
|
@@ -1185,6 +1186,7 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1185
1186
|
width,
|
|
1186
1187
|
height,
|
|
1187
1188
|
captionStyle,
|
|
1189
|
+
motion,
|
|
1188
1190
|
animationsEnabled,
|
|
1189
1191
|
displayMode,
|
|
1190
1192
|
dashboard
|
|
@@ -1195,7 +1197,14 @@ async function withRenderPage(doc, container, options, fn) {
|
|
|
1195
1197
|
signal?.throwIfAborted();
|
|
1196
1198
|
let browser;
|
|
1197
1199
|
try {
|
|
1198
|
-
browser = await chromium.launch({
|
|
1200
|
+
browser = await chromium.launch({
|
|
1201
|
+
headless: true,
|
|
1202
|
+
// Keyframe animations must be sampled by the main thread: the
|
|
1203
|
+
// compositor rasterises a transforming layer differently depending on
|
|
1204
|
+
// whether it caught the animation running, which made mid-motion stills
|
|
1205
|
+
// differ between otherwise identical renders.
|
|
1206
|
+
args: ["--disable-threaded-animation"]
|
|
1207
|
+
});
|
|
1199
1208
|
} catch (err) {
|
|
1200
1209
|
signal?.throwIfAborted();
|
|
1201
1210
|
const detail = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
@@ -1245,6 +1254,13 @@ Page errors:
|
|
|
1245
1254
|
`The standalone player failed to boot in headless Chromium. Render API did not initialize within ${RENDER_BOOT_TIMEOUT_MS / 1e3} seconds.${errorDetail}`
|
|
1246
1255
|
);
|
|
1247
1256
|
}
|
|
1257
|
+
signal?.throwIfAborted();
|
|
1258
|
+
await page.evaluate(
|
|
1259
|
+
() => Promise.race([
|
|
1260
|
+
document.fonts?.ready ?? Promise.resolve(void 0),
|
|
1261
|
+
new Promise((resolve2) => setTimeout(resolve2, 1e4))
|
|
1262
|
+
]).then(() => void 0)
|
|
1263
|
+
);
|
|
1248
1264
|
renderAPI = await page.evaluateHandle(() => {
|
|
1249
1265
|
const root = document.getElementById("squisq-root");
|
|
1250
1266
|
const player = window.SquisqPlayer;
|
|
@@ -1331,7 +1347,17 @@ async function iterateDocFrames(session, options, visit) {
|
|
|
1331
1347
|
};
|
|
1332
1348
|
}
|
|
1333
1349
|
async function captureDocFrames(doc, container, options) {
|
|
1334
|
-
const {
|
|
1350
|
+
const {
|
|
1351
|
+
fps,
|
|
1352
|
+
width,
|
|
1353
|
+
height,
|
|
1354
|
+
captionStyle,
|
|
1355
|
+
motion,
|
|
1356
|
+
coverPreRoll,
|
|
1357
|
+
animationsEnabled,
|
|
1358
|
+
onProgress,
|
|
1359
|
+
signal
|
|
1360
|
+
} = options;
|
|
1335
1361
|
signal?.throwIfAborted();
|
|
1336
1362
|
resolveAppliedCoverPreRoll(coverPreRoll, true);
|
|
1337
1363
|
const ffmpegPath = await requireFfmpeg(signal);
|
|
@@ -1340,7 +1366,16 @@ async function captureDocFrames(doc, container, options) {
|
|
|
1340
1366
|
return await withRenderPage(
|
|
1341
1367
|
doc,
|
|
1342
1368
|
container,
|
|
1343
|
-
{
|
|
1369
|
+
{
|
|
1370
|
+
signal,
|
|
1371
|
+
width,
|
|
1372
|
+
height,
|
|
1373
|
+
includeAudio: true,
|
|
1374
|
+
captionStyle,
|
|
1375
|
+
motion,
|
|
1376
|
+
animationsEnabled,
|
|
1377
|
+
onProgress
|
|
1378
|
+
},
|
|
1344
1379
|
async (session) => {
|
|
1345
1380
|
const loop = await iterateDocFrames(
|
|
1346
1381
|
session,
|
|
@@ -1375,6 +1410,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1375
1410
|
throw new Error("resume requires framesDir so there is a spool to resume from");
|
|
1376
1411
|
}
|
|
1377
1412
|
const captionStyle = options.captionStyle === "off" ? void 0 : options.captionStyle;
|
|
1413
|
+
const motion = options.motion ?? null;
|
|
1378
1414
|
const animationsEnabled = options.animationsEnabled ?? true;
|
|
1379
1415
|
const ffmpegPath = await requireFfmpeg(signal);
|
|
1380
1416
|
const outputPath = resolvePath(options.outputPath);
|
|
@@ -1386,6 +1422,7 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1386
1422
|
width,
|
|
1387
1423
|
height,
|
|
1388
1424
|
captionStyle: captionStyle ?? null,
|
|
1425
|
+
motion: motion === null ? null : JSON.stringify(motion),
|
|
1389
1426
|
animationsEnabled,
|
|
1390
1427
|
coverPreRoll,
|
|
1391
1428
|
captureFormat
|
|
@@ -1403,7 +1440,16 @@ async function renderDocToMp4Piped(doc, container, options, render) {
|
|
|
1403
1440
|
const { loop, activeSink } = await withRenderPage(
|
|
1404
1441
|
doc,
|
|
1405
1442
|
container,
|
|
1406
|
-
{
|
|
1443
|
+
{
|
|
1444
|
+
signal,
|
|
1445
|
+
width,
|
|
1446
|
+
height,
|
|
1447
|
+
includeAudio: true,
|
|
1448
|
+
captionStyle,
|
|
1449
|
+
motion,
|
|
1450
|
+
animationsEnabled,
|
|
1451
|
+
onProgress
|
|
1452
|
+
},
|
|
1407
1453
|
async (session) => {
|
|
1408
1454
|
const hasCover = coverPreRoll > 0 ? await session.renderAPI.evaluate((api) => api.hasCoverBlock()) : false;
|
|
1409
1455
|
const appliedCoverPreRoll = resolveAppliedCoverPreRoll(coverPreRoll, hasCover);
|
|
@@ -1603,6 +1649,7 @@ async function renderDocToMp4(doc, container, options) {
|
|
|
1603
1649
|
width: dimensions.width,
|
|
1604
1650
|
height: dimensions.height,
|
|
1605
1651
|
captionStyle: options.captionStyle === "off" ? void 0 : options.captionStyle,
|
|
1652
|
+
motion: options.motion ?? null,
|
|
1606
1653
|
coverPreRoll: options.coverPreRoll ?? 0,
|
|
1607
1654
|
animationsEnabled: options.animationsEnabled ?? true,
|
|
1608
1655
|
onProgress: options.onProgress,
|
|
@@ -1666,6 +1713,7 @@ async function renderDocToGif(doc, container, options) {
|
|
|
1666
1713
|
width,
|
|
1667
1714
|
height,
|
|
1668
1715
|
captionStyle: options.captionStyle === "off" ? void 0 : options.captionStyle ?? "standard",
|
|
1716
|
+
motion: options.motion ?? null,
|
|
1669
1717
|
coverPreRoll: options.coverPreRoll ?? 0,
|
|
1670
1718
|
animationsEnabled: options.animationsEnabled ?? false,
|
|
1671
1719
|
onProgress: options.onProgress,
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
renderDocToDashboardPng,
|
|
10
10
|
renderDocToGif,
|
|
11
11
|
renderDocToMp4
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6MZC4YQV.js";
|
|
13
13
|
import "./chunk-Y5XKUWJ5.js";
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
@@ -319,6 +319,7 @@ 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"];
|
|
324
325
|
var VALID_FRAME_TRANSPORTS = ["pipe", "memory"];
|
|
@@ -338,6 +339,9 @@ function registerVideoCommand(program2) {
|
|
|
338
339
|
"--no-auto-templates",
|
|
339
340
|
"Disable content-aware template auto-picking for unannotated headings"
|
|
340
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(
|
|
341
345
|
"--transform <style>",
|
|
342
346
|
"Transform style to apply before rendering (e.g., documentary, magazine, minimal)"
|
|
343
347
|
).option(
|
|
@@ -415,6 +419,10 @@ async function runVideo(inputPath, opts) {
|
|
|
415
419
|
if (!VALID_CAPTIONS.includes(captions)) {
|
|
416
420
|
throw new Error(`Invalid captions "${captions}". Valid: ${VALID_CAPTIONS.join(", ")}`);
|
|
417
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
|
+
}
|
|
418
426
|
const coverPreRoll = Number(opts.coverPreroll ?? "2");
|
|
419
427
|
if (!Number.isFinite(coverPreRoll) || coverPreRoll < 0) {
|
|
420
428
|
throw new Error("Cover pre-roll must be a number of seconds >= 0");
|
|
@@ -498,7 +506,7 @@ async function runVideo(inputPath, opts) {
|
|
|
498
506
|
if (opts.theme) {
|
|
499
507
|
doc = { ...doc, themeId: opts.theme };
|
|
500
508
|
}
|
|
501
|
-
const motionLabel = animationsEnabled ?
|
|
509
|
+
const motionLabel = animationsEnabled ? `animations on${motion ? ` (${motion})` : ""}` : "animations off";
|
|
502
510
|
const qualityLabel = outputFormat === "mp4" ? `, quality: ${quality}, transport: ${frameTransport}` : "";
|
|
503
511
|
console.error(
|
|
504
512
|
`Rendering ${outputFormat.toUpperCase()}: ${fps} fps${qualityLabel}, orientation: ${orientation}, captions: ${captions}, ${motionLabel}`
|
|
@@ -510,6 +518,7 @@ async function runVideo(inputPath, opts) {
|
|
|
510
518
|
width,
|
|
511
519
|
height,
|
|
512
520
|
captionStyle: captions,
|
|
521
|
+
motion,
|
|
513
522
|
coverPreRoll,
|
|
514
523
|
animationsEnabled,
|
|
515
524
|
onProgress: (phase, percent) => writeProgress(phase, percent, 100)
|