@bendyline/squisq-video 2.3.12 → 2.3.14

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MediaScheduleOptions, Doc, Theme, VideoPresentation, VideoPipSize, VideoPipShape, VideoPipPosition, ViewportPreset } from '@bendyline/squisq/schemas';
1
+ import { MediaScheduleOptions, Doc, Theme, MotionSpec, VideoPresentation, VideoPipSize, VideoPipShape, VideoPipPosition, ViewportPreset } from '@bendyline/squisq/schemas';
2
2
  export { fetchFile } from '@ffmpeg/util';
3
3
 
4
4
  /**
@@ -263,6 +263,8 @@ interface RenderHtmlOptions {
263
263
  captionStyle?: 'standard' | 'social';
264
264
  /** Theme override for the capture player. Omitted values resolve from the Doc. */
265
265
  theme?: Theme;
266
+ /** Motion profile override. Omitted values resolve from the Doc, then the theme. */
267
+ motion?: MotionSpec | null;
266
268
  /** Player-level video placement override. Omitted values resolve from Doc frontmatter. */
267
269
  videoPresentation?: VideoPresentation;
268
270
  /** Picture-in-picture size override. Omitted values resolve from Doc frontmatter. */
@@ -510,4 +512,21 @@ declare function ffmpegGifOutputArgs(options: GifOutputOptions): string[];
510
512
  */
511
513
  declare function framesToMp4Wasm(frames: Uint8Array[], audio: Uint8Array | null, options?: VideoExportOptions): Promise<EncoderResult>;
512
514
 
513
- export { type AudioTimelineClip, type ComputeAudioTimelineOptions, DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, type DashboardResolutionId, type DashboardResolutionPreset, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, MAX_DASHBOARD_IMAGE_DIMENSION, MAX_DASHBOARD_IMAGE_PIXELS, MIN_DASHBOARD_IMAGE_DIMENSION, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderHtmlOptions, type ResolveDashboardDimensionsInput, type ResolvedDashboardDimensions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, dashboardFamilyForDimensions, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, framesToMp4Wasm, generateRenderHtml, resolveDashboardDimensions, resolveDimensions, resolveFfmpegWasmLoad, validateDashboardImageDimensions, validateVideoExportOptions };
515
+ /**
516
+ * Frame clock — the one place that turns a frame index into a timeline time.
517
+ *
518
+ * Every offline capture path (CLI pipe/memory transports, browser export)
519
+ * must agree on which instant a frame shows, otherwise a resumed or
520
+ * deduplicated render drifts from a fresh one. `i / fps` in floating point is
521
+ * not exactly reproducible across code paths, so the time is rounded to a
522
+ * microsecond: `frameTimeSeconds(370, 30)` equals `frameTimeSeconds(37, 3)`.
523
+ *
524
+ * Contract: a frame's pixels are a function of its time only. Consumers seek
525
+ * the player to this time and pause every animation before capturing.
526
+ */
527
+ /** Timeline second shown by frame `index` at `fps`, rounded to a microsecond. */
528
+ declare function frameTimeSeconds(index: number, fps: number): number;
529
+ /** Number of frames needed to cover `durationSeconds` at `fps` (partial frames round up). */
530
+ declare function frameCountFor(durationSeconds: number, fps: number): number;
531
+
532
+ export { type AudioTimelineClip, type ComputeAudioTimelineOptions, DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, type DashboardResolutionId, type DashboardResolutionPreset, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, MAX_DASHBOARD_IMAGE_DIMENSION, MAX_DASHBOARD_IMAGE_PIXELS, MIN_DASHBOARD_IMAGE_DIMENSION, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderHtmlOptions, type ResolveDashboardDimensionsInput, type ResolvedDashboardDimensions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, dashboardFamilyForDimensions, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, frameCountFor, frameTimeSeconds, framesToMp4Wasm, generateRenderHtml, resolveDashboardDimensions, resolveDimensions, resolveFfmpegWasmLoad, validateDashboardImageDimensions, validateVideoExportOptions };
package/dist/index.js CHANGED
@@ -196,6 +196,7 @@ function generateRenderHtml(doc, options) {
196
196
  height = 1080,
197
197
  captionStyle,
198
198
  theme,
199
+ motion,
199
200
  videoPresentation,
200
201
  pipSize,
201
202
  pipShape,
@@ -222,9 +223,14 @@ function generateRenderHtml(doc, options) {
222
223
  const imageMapJson = escapeForScript(JSON.stringify(imageMap));
223
224
  const audioMapJson = hasAudio ? escapeForScript(JSON.stringify(audioMap)) : "null";
224
225
  const playerOptionLines = [
226
+ // Always pin the composed viewport to the export size. The page also
227
+ // pins #squisq-root to it, but an explicit viewport is what stops
228
+ // portrait/custom renders from falling back to the landscape preset.
229
+ ` viewport: ${JSON.stringify({ width, height })}`,
225
230
  ` animationsEnabled: ${JSON.stringify(animationsEnabled)}`,
226
231
  captionStyle ? ` captionStyle: ${JSON.stringify(captionStyle)}` : null,
227
232
  theme ? ` theme: ${escapeForScript(JSON.stringify(theme))}` : null,
233
+ motion != null ? ` motion: ${escapeForScript(JSON.stringify(motion))}` : null,
228
234
  videoPresentation ? ` videoPresentation: ${JSON.stringify(videoPresentation)}` : null,
229
235
  pipSize ? ` pipSize: ${JSON.stringify(pipSize)}` : null,
230
236
  pipShape ? ` pipShape: ${JSON.stringify(pipShape)}` : null,
@@ -512,6 +518,26 @@ async function framesToMp4Wasm(frames, audio, options = {}) {
512
518
  ffmpeg.terminate();
513
519
  }
514
520
  }
521
+
522
+ // src/frameClock.ts
523
+ function frameTimeSeconds(index, fps) {
524
+ if (!Number.isSafeInteger(index) || index < 0) {
525
+ throw new RangeError("Frame index must be a non-negative integer.");
526
+ }
527
+ if (!Number.isFinite(fps) || fps <= 0) {
528
+ throw new RangeError("Frames per second must be a positive number.");
529
+ }
530
+ return Math.round(index * 1e6 / fps) / 1e6;
531
+ }
532
+ function frameCountFor(durationSeconds, fps) {
533
+ if (!Number.isFinite(durationSeconds) || durationSeconds < 0) {
534
+ throw new RangeError("Duration must be a non-negative number of seconds.");
535
+ }
536
+ if (!Number.isFinite(fps) || fps <= 0) {
537
+ throw new RangeError("Frames per second must be a positive number.");
538
+ }
539
+ return Math.max(0, Math.ceil(durationSeconds * fps - 1e-9)) + 0;
540
+ }
515
541
  export {
516
542
  DASHBOARD_RESOLUTIONS,
517
543
  DEFAULT_DASHBOARD_RESOLUTION,
@@ -533,6 +559,8 @@ export {
533
559
  ffmpegGifPaletteApplicationGraph,
534
560
  ffmpegGifPaletteGenerationFilter,
535
561
  ffmpegVideoQualityArgs,
562
+ frameCountFor,
563
+ frameTimeSeconds,
536
564
  framesToMp4Wasm,
537
565
  generateRenderHtml,
538
566
  resolveDashboardDimensions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video",
3
- "version": "2.3.12",
3
+ "version": "2.3.14",
4
4
  "description": "Cross-runtime video and animated-GIF helpers with browser-based ffmpeg.wasm encoding for Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -48,7 +48,7 @@
48
48
  "typecheck": "tsc --noEmit"
49
49
  },
50
50
  "dependencies": {
51
- "@bendyline/squisq": "2.11.9",
51
+ "@bendyline/squisq": "2.11.10",
52
52
  "@ffmpeg/ffmpeg": "0.12.15",
53
53
  "@ffmpeg/util": "0.12.2"
54
54
  },