@bendyline/squisq-video 2.2.1 → 2.2.2

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 CHANGED
@@ -22,7 +22,7 @@ npm install @bendyline/squisq-video
22
22
  | `computeAudioTimeline(doc, coverPreRoll?)` | Flatten a doc's narration + timed media into absolute-timed audio clips (browser export and CLI mix share this) |
23
23
  | `bitrateForQuality(quality, width, height)` | Target H.264 bitrate (`w*h*bitsPerPixel`) — the single source of truth for WebCodecs bitrate |
24
24
  | `ffmpegVideoQualityArgs`, `audioBitrateArg`, `ffmpegAudioMuxArgs` | Shared FFmpeg flags; audio muxing pads short narration so it cannot truncate the video |
25
- | `ffmpegGifFilterGraph`, `ffmpegGifOutputArgs` | Shared global-palette GIF filters and muxer flags used by native and browser exporters |
25
+ | `ffmpegGifFilterGraph`, `ffmpegGifOutputArgs`, `ffmpegGifPalette*` helpers | Shared global-palette GIF filters, including bounded two-pass palette generation/application |
26
26
  | `QUALITY_PRESETS`, `ORIENTATION_DIMENSIONS`, `resolveDimensions` | Quality/dimension presets and helpers |
27
27
  | `validateVideoExportOptions(options)` | Fail-fast runtime validation for FPS, dimensions, quality, and orientation |
28
28
  | `VideoExportOptions`, `VideoQuality`, `VideoOrientation`, `GifDither`, `QualityPreset`, `EncoderResult` | Shared video and GIF types |
package/dist/index.d.ts CHANGED
@@ -296,6 +296,12 @@ declare function audioBitrateArg(quality: VideoQuality): string;
296
296
  * audio longer than the video is trimmed while shorter audio becomes silence.
297
297
  */
298
298
  declare function ffmpegAudioMuxArgs(bitrate: string | number): string[];
299
+ /** First pass of the bounded-memory GIF workflow: video -> one palette image. */
300
+ declare function ffmpegGifPaletteGenerationFilter(options: GifFilterOptions): string;
301
+ /** Second pass of the bounded-memory GIF workflow: video + palette -> GIF frames. */
302
+ declare function ffmpegGifPaletteApplicationGraph(options: GifFilterOptions): string;
303
+ /** Output arguments for the second, two-input pass of GIF encoding. */
304
+ declare function ffmpegGifPaletteApplicationArgs(options: GifOutputOptions): string[];
299
305
  /**
300
306
  * Build a high-quality, compression-friendly GIF palette filter graph.
301
307
  *
@@ -327,4 +333,4 @@ declare function ffmpegGifOutputArgs(options: GifOutputOptions): string[];
327
333
  */
328
334
  declare function framesToMp4Wasm(frames: Uint8Array[], audio: Uint8Array | null, options?: VideoExportOptions): Promise<EncoderResult>;
329
335
 
330
- 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, ffmpegVideoQualityArgs, framesToMp4Wasm, generateRenderHtml, resolveDimensions, resolveFfmpegWasmLoad, validateVideoExportOptions };
336
+ 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 };
package/dist/index.js CHANGED
@@ -205,7 +205,7 @@ function audioBitrateArg(quality) {
205
205
  function ffmpegAudioMuxArgs(bitrate) {
206
206
  return ["-c:a", "aac", "-b:a", String(bitrate), "-af", "apad", "-shortest"];
207
207
  }
208
- function ffmpegGifFilterGraph(options) {
208
+ function resolveGifFilterOptions(options) {
209
209
  const { width, height } = options;
210
210
  const maxColors = options.maxColors ?? 256;
211
211
  const dither = options.dither ?? "sierra2_4a";
@@ -227,14 +227,49 @@ function ffmpegGifFilterGraph(options) {
227
227
  if (!Number.isSafeInteger(bayerScale) || bayerScale < 0 || bayerScale > 5) {
228
228
  throw new RangeError("GIF bayerScale must be an integer between 0 and 5.");
229
229
  }
230
- const ditherArgs = dither === "bayer" ? `dither=bayer:bayer_scale=${bayerScale}` : `dither=${dither}`;
231
- return `[0:v]scale=${width}:${height}:force_original_aspect_ratio=decrease:flags=lanczos,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black,split[gif_frames][gif_palette_source];[gif_palette_source]palettegen=stats_mode=diff:max_colors=${maxColors}:reserve_transparent=0[gif_palette];[gif_frames][gif_palette]paletteuse=${ditherArgs}:diff_mode=rectangle[gif_out]`;
230
+ return {
231
+ width,
232
+ height,
233
+ maxColors,
234
+ ditherArgs: dither === "bayer" ? `dither=bayer:bayer_scale=${bayerScale}` : `dither=${dither}`
235
+ };
232
236
  }
233
- function ffmpegGifOutputArgs(options) {
237
+ function gifScaleFilter({ width, height }) {
238
+ return `scale=${width}:${height}:force_original_aspect_ratio=decrease:flags=lanczos,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`;
239
+ }
240
+ function ffmpegGifPaletteGenerationFilter(options) {
241
+ const resolved = resolveGifFilterOptions(options);
242
+ return `${gifScaleFilter(resolved)},palettegen=stats_mode=diff:max_colors=${resolved.maxColors}:reserve_transparent=0`;
243
+ }
244
+ function ffmpegGifPaletteApplicationGraph(options) {
245
+ const resolved = resolveGifFilterOptions(options);
246
+ return `[0:v]${gifScaleFilter(resolved)}[gif_frames];[gif_frames][1:v]paletteuse=${resolved.ditherArgs}:diff_mode=rectangle[gif_out]`;
247
+ }
248
+ function ffmpegGifPaletteApplicationArgs(options) {
249
+ const loop = resolveGifLoop(options);
250
+ return [
251
+ "-filter_complex",
252
+ ffmpegGifPaletteApplicationGraph(options),
253
+ "-map",
254
+ "[gif_out]",
255
+ "-an",
256
+ "-loop",
257
+ String(loop)
258
+ ];
259
+ }
260
+ function ffmpegGifFilterGraph(options) {
261
+ const resolved = resolveGifFilterOptions(options);
262
+ return `[0:v]${gifScaleFilter(resolved)},split[gif_frames][gif_palette_source];[gif_palette_source]palettegen=stats_mode=diff:max_colors=${resolved.maxColors}:reserve_transparent=0[gif_palette];[gif_frames][gif_palette]paletteuse=${resolved.ditherArgs}:diff_mode=rectangle[gif_out]`;
263
+ }
264
+ function resolveGifLoop(options) {
234
265
  const loop = options.loop ?? 0;
235
266
  if (!Number.isSafeInteger(loop) || loop < -1 || loop > 65535) {
236
267
  throw new RangeError("GIF loop must be an integer between -1 and 65535.");
237
268
  }
269
+ return loop;
270
+ }
271
+ function ffmpegGifOutputArgs(options) {
272
+ const loop = resolveGifLoop(options);
238
273
  return [
239
274
  "-filter_complex",
240
275
  ffmpegGifFilterGraph(options),
@@ -337,6 +372,9 @@ export {
337
372
  ffmpegAudioMuxArgs,
338
373
  ffmpegGifFilterGraph,
339
374
  ffmpegGifOutputArgs,
375
+ ffmpegGifPaletteApplicationArgs,
376
+ ffmpegGifPaletteApplicationGraph,
377
+ ffmpegGifPaletteGenerationFilter,
340
378
  ffmpegVideoQualityArgs,
341
379
  framesToMp4Wasm,
342
380
  generateRenderHtml,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
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.3.1",
51
+ "@bendyline/squisq": "2.3.2",
52
52
  "@ffmpeg/ffmpeg": "0.12.15",
53
53
  "@ffmpeg/util": "0.12.2"
54
54
  },