@bendyline/squisq-video 2.2.6 → 2.2.7

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
@@ -168,8 +168,9 @@ declare function resolveFfmpegWasmLoad(config: FfmpegWasmLoadConfig | undefined,
168
168
  * concatenates the segment files in order.
169
169
  * - Timed media clips (`block.media` + `doc.documentMedia`) are placed at
170
170
  * their **absolute** doc-timeline positions via the shared
171
- * `resolveMediaSchedule()` helper (the same one the CLI calls), honouring
172
- * each clip's trim window (`sourceIn` / `absoluteEnd - absoluteStart`).
171
+ * `resolveMediaSchedule()` helper, honouring each clip's trim window
172
+ * (`sourceIn` / `absoluteEnd - absoluteStart`). This includes the audio
173
+ * stream carried by scheduled video clips unless explicitly disabled.
173
174
  * - Every start time is shifted by `coverPreRoll` so a cover pre-roll padding
174
175
  * (silent leading frames) keeps audio in sync.
175
176
  */
@@ -184,6 +185,20 @@ interface AudioTimelineClip {
184
185
  sourceInSec: number;
185
186
  /** Played length in seconds (the trimmed window of the source). */
186
187
  durationSec: number;
188
+ /**
189
+ * Video sources may legitimately contain no audio stream. The browser mixer
190
+ * probes them independently and skips only the silent ones. Omitted means an
191
+ * authored audio/narration source, whose decode failure remains an error.
192
+ */
193
+ sourceKind?: 'video';
194
+ }
195
+ interface ComputeAudioTimelineOptions {
196
+ /**
197
+ * Include the audio stream carried by scheduled video clips. Defaults to
198
+ * true for composed MP4 export. Consumers that cannot demux video audio may
199
+ * explicitly retain the legacy audio-only behavior.
200
+ */
201
+ includeVideoAudio?: boolean;
187
202
  }
188
203
  /**
189
204
  * Flatten a doc's narration + timed-media audio into absolute-timed clips.
@@ -196,7 +211,7 @@ interface AudioTimelineClip {
196
211
  * @param coverPreRoll - Leading silent padding (seconds) added ahead of every
197
212
  * clip, matching the cover-slide pre-roll frames. Default 0.
198
213
  */
199
- declare function computeAudioTimeline(doc: Doc, coverPreRoll?: number): AudioTimelineClip[];
214
+ declare function computeAudioTimeline(doc: Doc, coverPreRoll?: number, options?: ComputeAudioTimelineOptions): AudioTimelineClip[];
200
215
 
201
216
  /**
202
217
  * Render HTML Generation for Video Frame Capture
@@ -343,4 +358,4 @@ declare function ffmpegGifOutputArgs(options: GifOutputOptions): string[];
343
358
  */
344
359
  declare function framesToMp4Wasm(frames: Uint8Array[], audio: Uint8Array | null, options?: VideoExportOptions): Promise<EncoderResult>;
345
360
 
346
- export { type AudioTimelineClip, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderHtmlOptions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, framesToMp4Wasm, generateRenderHtml, resolveDimensions, resolveFfmpegWasmLoad, validateVideoExportOptions };
361
+ export { type AudioTimelineClip, type ComputeAudioTimelineOptions, type EncoderResult, FFMPEG_WASM_SETUP_HINT, type FfmpegWasmLoadConfig, type GifDither, type GifFilterOptions, type GifOutputOptions, ORIENTATION_DIMENSIONS, QUALITY_PRESETS, type QualityPreset, type RenderHtmlOptions, type VideoExportOptions, type VideoOrientation, type VideoQuality, audioBitrateArg, bitrateForQuality, computeAudioTimeline, ffmpegAudioMuxArgs, ffmpegGifFilterGraph, ffmpegGifOutputArgs, ffmpegGifPaletteApplicationArgs, ffmpegGifPaletteApplicationGraph, ffmpegGifPaletteGenerationFilter, ffmpegVideoQualityArgs, framesToMp4Wasm, generateRenderHtml, resolveDimensions, resolveFfmpegWasmLoad, validateVideoExportOptions };
package/dist/index.js CHANGED
@@ -65,8 +65,10 @@ function resolveFfmpegWasmLoad(config, context, defaults) {
65
65
 
66
66
  // src/audioTimeline.ts
67
67
  import { resolveMediaSchedule } from "@bendyline/squisq/schemas";
68
- function computeAudioTimeline(doc, coverPreRoll = 0) {
68
+ import { flattenRenderableBlocks, materializeBlockLayers } from "@bendyline/squisq/doc";
69
+ function computeAudioTimeline(doc, coverPreRoll = 0, options = {}) {
69
70
  const preRoll = safeSeconds(coverPreRoll);
71
+ const includeVideoAudio = options.includeVideoAudio ?? true;
70
72
  const clips = [];
71
73
  let cursor = 0;
72
74
  for (const seg of doc.audio?.segments ?? []) {
@@ -77,7 +79,7 @@ function computeAudioTimeline(doc, coverPreRoll = 0) {
77
79
  cursor += durationSec;
78
80
  }
79
81
  for (const clip of resolveMediaSchedule(doc)) {
80
- if (clip.kind !== "audio") continue;
82
+ if (clip.kind !== "audio" && !includeVideoAudio) continue;
81
83
  if (!Number.isFinite(clip.absoluteStart) || !Number.isFinite(clip.absoluteEnd)) continue;
82
84
  const durationSec = Math.max(0, clip.absoluteEnd - clip.absoluteStart);
83
85
  if (durationSec <= 0 || !clip.src) continue;
@@ -85,11 +87,47 @@ function computeAudioTimeline(doc, coverPreRoll = 0) {
85
87
  src: clip.src,
86
88
  startSec: safeSeconds(clip.absoluteStart) + preRoll,
87
89
  sourceInSec: safeSeconds(clip.sourceIn),
88
- durationSec
90
+ durationSec,
91
+ ...clip.kind === "video" ? { sourceKind: "video" } : {}
92
+ });
93
+ }
94
+ if (includeVideoAudio) {
95
+ const blocks = flattenRenderableBlocks(doc.blocks);
96
+ const existing = new Set(clips.map(timelineKey));
97
+ blocks.forEach((block, blockIndex) => {
98
+ const materialized = materializeBlockLayers(block, {
99
+ blockIndex,
100
+ totalBlocks: blocks.length,
101
+ customTemplates: doc.customTemplates,
102
+ persistentLayers: false
103
+ });
104
+ for (const layer of materialized.layers) {
105
+ if (layer.type !== "video" || !layer.content.src) continue;
106
+ const startAt = safeSeconds(layer.content.startAt ?? 0);
107
+ const sourceInSec = safeSeconds(layer.content.clipStart);
108
+ const clipDuration = Math.max(0, layer.content.clipEnd - sourceInSec);
109
+ const blockRemainder = Math.max(0, safeSeconds(block.duration) - startAt);
110
+ const durationSec = layer.content.spillover ? clipDuration : Math.min(clipDuration, blockRemainder);
111
+ if (durationSec <= 0) continue;
112
+ const candidate = {
113
+ src: layer.content.src,
114
+ startSec: safeSeconds(block.startTime) + startAt + preRoll,
115
+ sourceInSec,
116
+ durationSec,
117
+ sourceKind: "video"
118
+ };
119
+ const key = timelineKey(candidate);
120
+ if (existing.has(key)) continue;
121
+ existing.add(key);
122
+ clips.push(candidate);
123
+ }
89
124
  });
90
125
  }
91
126
  return clips;
92
127
  }
128
+ function timelineKey(clip) {
129
+ return `${clip.src}\0${clip.startSec}\0${clip.sourceInSec}\0${clip.durationSec}`;
130
+ }
93
131
  function safeSeconds(value) {
94
132
  return Number.isFinite(value) && value > 0 ? value : 0;
95
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video",
3
- "version": "2.2.6",
3
+ "version": "2.2.7",
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.4.2",
51
+ "@bendyline/squisq": "2.4.3",
52
52
  "@ffmpeg/ffmpeg": "0.12.15",
53
53
  "@ffmpeg/util": "0.12.2"
54
54
  },