@shotstack/shotstack-canvas 2.0.4 → 2.0.6

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
@@ -1,13 +1,13 @@
1
- # @shotstack/shotstack-canvas
2
-
3
- One package → identical text shaping/wrapping/animation on Web & Node.
4
- - HarfBuzz WASM for shaping and glyph outlines (via `harfbuzzjs`).
5
- - Device-independent draw-ops.
6
- - Painters: Canvas2D (web) and node-canvas (node).
7
- - Deterministic time-driven animations.
8
-
9
- ## Install
10
-
11
- ```bash
12
- pnpm add @shotstack/shotstack-canvas
13
- # or npm i / yarn add
1
+ # @shotstack/shotstack-canvas
2
+
3
+ One package → identical text shaping/wrapping/animation on Web & Node.
4
+ - HarfBuzz WASM for shaping and glyph outlines (via `harfbuzzjs`).
5
+ - Device-independent draw-ops.
6
+ - Painters: Canvas2D (web) and node-canvas (node).
7
+ - Deterministic time-driven animations.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @shotstack/shotstack-canvas
13
+ # or npm i / yarn add
@@ -362,6 +362,7 @@ __export(entry_node_exports, {
362
362
  RichCaptionRenderer: () => RichCaptionRenderer,
363
363
  WordTimingStore: () => WordTimingStore,
364
364
  arcToCubicBeziers: () => arcToCubicBeziers,
365
+ breakIntoLines: () => breakIntoLines,
365
366
  calculateAnimationStatesForGroup: () => calculateAnimationStatesForGroup,
366
367
  commandsToPathString: () => commandsToPathString,
367
368
  computeSimplePathBounds: () => computeSimplePathBounds,
@@ -581,7 +582,7 @@ var richCaptionFontSchema = import_zod.z.object({
581
582
  });
582
583
  var richCaptionActiveSchema = import_zod2.richCaptionActiveSchema.extend({
583
584
  font: import_zod.z.object({
584
- color: import_zod.z.string().regex(HEX6).default("#ffff00"),
585
+ color: import_zod.z.string().regex(HEX6).default("#ffffff"),
585
586
  background: import_zod.z.string().regex(HEX6).optional(),
586
587
  opacity: import_zod.z.number().min(0).max(1).default(1)
587
588
  }).optional(),
@@ -4328,7 +4329,7 @@ function groupWordsByPause(store, pauseThreshold = 500) {
4328
4329
  }
4329
4330
  return groups;
4330
4331
  }
4331
- function breakIntoLines(wordWidths, maxWidth, maxLines, spaceWidth) {
4332
+ function breakIntoLines(wordWidths, maxWidth, spaceWidth) {
4332
4333
  const lines = [];
4333
4334
  let currentLine = [];
4334
4335
  let currentWidth = 0;
@@ -4341,15 +4342,12 @@ function breakIntoLines(wordWidths, maxWidth, maxLines, spaceWidth) {
4341
4342
  } else {
4342
4343
  if (currentLine.length > 0) {
4343
4344
  lines.push(currentLine);
4344
- if (lines.length >= maxLines) {
4345
- return lines;
4346
- }
4347
4345
  }
4348
4346
  currentLine = [i];
4349
4347
  currentWidth = wordWidth;
4350
4348
  }
4351
4349
  }
4352
- if (currentLine.length > 0 && lines.length < maxLines) {
4350
+ if (currentLine.length > 0) {
4353
4351
  lines.push(currentLine);
4354
4352
  }
4355
4353
  return lines;
@@ -4382,6 +4380,13 @@ function transformText(text, transform) {
4382
4380
  return text;
4383
4381
  }
4384
4382
  }
4383
+ function splitIntoChunks(arr, chunkSize) {
4384
+ const chunks = [];
4385
+ for (let i = 0; i < arr.length; i += chunkSize) {
4386
+ chunks.push(arr.slice(i, i + chunkSize));
4387
+ }
4388
+ return chunks;
4389
+ }
4385
4390
  var CaptionLayoutEngine = class {
4386
4391
  fontRegistry;
4387
4392
  cache;
@@ -4468,31 +4473,34 @@ var CaptionLayoutEngine = class {
4468
4473
  const spaceWord = await this.measureWord(" ", measurementConfig);
4469
4474
  spaceWidth = spaceWord.width + config.wordSpacing;
4470
4475
  }
4471
- const groups = wordGroups.map((indices) => {
4476
+ const groups = wordGroups.flatMap((indices) => {
4472
4477
  const groupWidths = indices.map((i) => store.widths[i]);
4473
- const lineIndices = breakIntoLines(
4478
+ const allLines = breakIntoLines(
4474
4479
  groupWidths,
4475
4480
  pixelMaxWidth,
4476
- config.maxLines,
4477
4481
  spaceWidth
4478
4482
  );
4479
- const lines = lineIndices.map((lineWordIndices, lineIndex) => {
4480
- const actualIndices = lineWordIndices.map((i) => indices[i]);
4481
- const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
4483
+ const lineChunks = splitIntoChunks(allLines, config.maxLines);
4484
+ return lineChunks.map((chunkLines) => {
4485
+ const lines = chunkLines.map((lineWordIndices, lineIndex) => {
4486
+ const actualIndices = lineWordIndices.map((i) => indices[i]);
4487
+ const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
4488
+ return {
4489
+ wordIndices: actualIndices,
4490
+ x: 0,
4491
+ y: lineIndex * config.fontSize * config.lineHeight,
4492
+ width: lineWidth,
4493
+ height: config.fontSize
4494
+ };
4495
+ });
4496
+ const allWordIndices = lines.flatMap((l) => l.wordIndices);
4482
4497
  return {
4483
- wordIndices: actualIndices,
4484
- x: 0,
4485
- y: lineIndex * config.fontSize * config.lineHeight,
4486
- width: lineWidth,
4487
- height: config.fontSize
4498
+ wordIndices: allWordIndices,
4499
+ startTime: store.startTimes[allWordIndices[0]],
4500
+ endTime: store.endTimes[allWordIndices[allWordIndices.length - 1]],
4501
+ lines
4488
4502
  };
4489
4503
  });
4490
- return {
4491
- wordIndices: lines.flatMap((l) => l.wordIndices),
4492
- startTime: store.startTimes[indices[0]],
4493
- endTime: store.endTimes[indices[indices.length - 1]],
4494
- lines
4495
- };
4496
4504
  });
4497
4505
  const calculateGroupY = (group) => {
4498
4506
  const totalHeight = group.lines.length * config.fontSize * config.lineHeight;
@@ -4871,7 +4879,7 @@ function extractFontConfig(asset) {
4871
4879
  size: font?.size ?? 24,
4872
4880
  weight: String(font?.weight ?? "400"),
4873
4881
  baseColor: font?.color ?? "#ffffff",
4874
- activeColor: active?.color ?? "#ffff00",
4882
+ activeColor: active?.color ?? "#ffffff",
4875
4883
  baseOpacity: font?.opacity ?? 1,
4876
4884
  activeOpacity: active?.opacity ?? 1,
4877
4885
  letterSpacing: asset.style?.letterSpacing ?? 0
@@ -5607,6 +5615,7 @@ var NodeRawEncoder = class _NodeRawEncoder {
5607
5615
  preset = "ultrafast",
5608
5616
  profile = "high"
5609
5617
  } = config;
5618
+ const hasAlpha = config.hasAlpha ?? false;
5610
5619
  const args = [
5611
5620
  "-y",
5612
5621
  "-f",
@@ -5620,32 +5629,49 @@ var NodeRawEncoder = class _NodeRawEncoder {
5620
5629
  "-thread_queue_size",
5621
5630
  "512",
5622
5631
  "-i",
5623
- "pipe:0",
5624
- "-c:v",
5625
- "libx264",
5626
- "-preset",
5627
- preset,
5628
- "-tune",
5629
- "stillimage",
5630
- "-crf",
5631
- String(crf),
5632
- "-profile:v",
5633
- profile,
5634
- "-g",
5635
- "300",
5636
- "-bf",
5637
- "2",
5638
- "-threads",
5639
- "0",
5640
- "-pix_fmt",
5641
- "yuv420p",
5642
- "-r",
5643
- String(fps),
5644
- "-movflags",
5645
- "+faststart"
5632
+ "pipe:0"
5646
5633
  ];
5634
+ if (hasAlpha) {
5635
+ args.push(
5636
+ "-c:v",
5637
+ "prores_ks",
5638
+ "-profile:v",
5639
+ "4444",
5640
+ "-pix_fmt",
5641
+ "yuva444p10le",
5642
+ "-vendor",
5643
+ "apl0",
5644
+ "-r",
5645
+ String(fps)
5646
+ );
5647
+ } else {
5648
+ args.push(
5649
+ "-c:v",
5650
+ "libx264",
5651
+ "-preset",
5652
+ preset,
5653
+ "-tune",
5654
+ "stillimage",
5655
+ "-crf",
5656
+ String(crf),
5657
+ "-profile:v",
5658
+ profile,
5659
+ "-g",
5660
+ "300",
5661
+ "-bf",
5662
+ "2",
5663
+ "-threads",
5664
+ "0",
5665
+ "-pix_fmt",
5666
+ "yuv420p",
5667
+ "-r",
5668
+ String(fps),
5669
+ "-movflags",
5670
+ "+faststart"
5671
+ );
5672
+ }
5647
5673
  if (this.outputToMemory) {
5648
- args.push("-f", "mp4", "pipe:1");
5674
+ args.push("-f", hasAlpha ? "mov" : "mp4", "pipe:1");
5649
5675
  } else {
5650
5676
  args.push(this.outputPath);
5651
5677
  }
@@ -5905,6 +5931,8 @@ var RichCaptionRenderer = class {
5905
5931
  animationStyle,
5906
5932
  animationSpeed
5907
5933
  );
5934
+ const bgColor = options?.bgColor;
5935
+ const hasAlpha = !bgColor;
5908
5936
  const encoder = new NodeRawEncoder();
5909
5937
  await encoder.configure(
5910
5938
  {
@@ -5914,7 +5942,8 @@ var RichCaptionRenderer = class {
5914
5942
  duration,
5915
5943
  crf: options?.crf ?? 23,
5916
5944
  preset: options?.preset ?? "ultrafast",
5917
- profile: options?.profile ?? "high"
5945
+ profile: options?.profile ?? "high",
5946
+ hasAlpha
5918
5947
  },
5919
5948
  {
5920
5949
  outputPath,
@@ -5926,7 +5955,6 @@ var RichCaptionRenderer = class {
5926
5955
  height: this.height,
5927
5956
  pixelRatio: this.pixelRatio
5928
5957
  });
5929
- const bgColor = options?.bgColor ?? "#000000";
5930
5958
  const totalStart = performance.now();
5931
5959
  let framesProcessed = 0;
5932
5960
  let lastPct = -1;
@@ -5940,7 +5968,7 @@ var RichCaptionRenderer = class {
5940
5968
  height: this.height * this.pixelRatio,
5941
5969
  pixelRatio: this.pixelRatio,
5942
5970
  clear: true,
5943
- bg: { color: bgColor, opacity: 1, radius: 0 }
5971
+ ...bgColor ? { bg: { color: bgColor, opacity: 1, radius: 0 } } : {}
5944
5972
  };
5945
5973
  await painter.render([beginOp, ...captionOps]);
5946
5974
  const rawResult = painter.toRawRGBA();
@@ -6569,6 +6597,7 @@ async function createTextEngine(opts = {}) {
6569
6597
  RichCaptionRenderer,
6570
6598
  WordTimingStore,
6571
6599
  arcToCubicBeziers,
6600
+ breakIntoLines,
6572
6601
  calculateAnimationStatesForGroup,
6573
6602
  commandsToPathString,
6574
6603
  computeSimplePathBounds,
@@ -670,6 +670,7 @@ declare class WordTimingStore {
670
670
  }
671
671
  declare function findWordAtTime(store: WordTimingStore, timeMs: number): number;
672
672
  declare function groupWordsByPause(store: WordTimingStore, pauseThreshold?: number): number[][];
673
+ declare function breakIntoLines(wordWidths: number[], maxWidth: number, spaceWidth: number): number[][];
673
674
  declare class CaptionLayoutEngine {
674
675
  private fontRegistry;
675
676
  private cache;
@@ -1206,6 +1207,7 @@ interface VideoEncoderConfig {
1206
1207
  hardwareAcceleration?: "prefer-hardware" | "prefer-software" | "no-preference";
1207
1208
  crf?: number;
1208
1209
  preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
1210
+ hasAlpha?: boolean;
1209
1211
  }
1210
1212
  interface VideoEncoderProgress {
1211
1213
  framesEncoded: number;
@@ -1314,4 +1316,4 @@ declare function createTextEngine(opts?: {
1314
1316
  destroy(): void;
1315
1317
  }>;
1316
1318
 
1317
- export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, type MoveToCommand, NodeRawEncoder, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, RichCaptionRenderer, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createNodePainter, createNodeRawEncoder, createRichCaptionRenderer, createTextEngine, createVideoEncoder, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, isDrawCaptionWordOp, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };
1319
+ export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, type MoveToCommand, NodeRawEncoder, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, RichCaptionRenderer, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, breakIntoLines, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createNodePainter, createNodeRawEncoder, createRichCaptionRenderer, createTextEngine, createVideoEncoder, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, isDrawCaptionWordOp, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };
@@ -670,6 +670,7 @@ declare class WordTimingStore {
670
670
  }
671
671
  declare function findWordAtTime(store: WordTimingStore, timeMs: number): number;
672
672
  declare function groupWordsByPause(store: WordTimingStore, pauseThreshold?: number): number[][];
673
+ declare function breakIntoLines(wordWidths: number[], maxWidth: number, spaceWidth: number): number[][];
673
674
  declare class CaptionLayoutEngine {
674
675
  private fontRegistry;
675
676
  private cache;
@@ -1206,6 +1207,7 @@ interface VideoEncoderConfig {
1206
1207
  hardwareAcceleration?: "prefer-hardware" | "prefer-software" | "no-preference";
1207
1208
  crf?: number;
1208
1209
  preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
1210
+ hasAlpha?: boolean;
1209
1211
  }
1210
1212
  interface VideoEncoderProgress {
1211
1213
  framesEncoded: number;
@@ -1314,4 +1316,4 @@ declare function createTextEngine(opts?: {
1314
1316
  destroy(): void;
1315
1317
  }>;
1316
1318
 
1317
- export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, type MoveToCommand, NodeRawEncoder, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, RichCaptionRenderer, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createNodePainter, createNodeRawEncoder, createRichCaptionRenderer, createTextEngine, createVideoEncoder, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, isDrawCaptionWordOp, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };
1319
+ export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, type MoveToCommand, NodeRawEncoder, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, RichCaptionRenderer, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, breakIntoLines, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createNodePainter, createNodeRawEncoder, createRichCaptionRenderer, createTextEngine, createVideoEncoder, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, isDrawCaptionWordOp, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };
@@ -193,7 +193,7 @@ var richCaptionFontSchema = z.object({
193
193
  });
194
194
  var richCaptionActiveSchema = baseCaptionActiveSchema.extend({
195
195
  font: z.object({
196
- color: z.string().regex(HEX6).default("#ffff00"),
196
+ color: z.string().regex(HEX6).default("#ffffff"),
197
197
  background: z.string().regex(HEX6).optional(),
198
198
  opacity: z.number().min(0).max(1).default(1)
199
199
  }).optional(),
@@ -3939,7 +3939,7 @@ function groupWordsByPause(store, pauseThreshold = 500) {
3939
3939
  }
3940
3940
  return groups;
3941
3941
  }
3942
- function breakIntoLines(wordWidths, maxWidth, maxLines, spaceWidth) {
3942
+ function breakIntoLines(wordWidths, maxWidth, spaceWidth) {
3943
3943
  const lines = [];
3944
3944
  let currentLine = [];
3945
3945
  let currentWidth = 0;
@@ -3952,15 +3952,12 @@ function breakIntoLines(wordWidths, maxWidth, maxLines, spaceWidth) {
3952
3952
  } else {
3953
3953
  if (currentLine.length > 0) {
3954
3954
  lines.push(currentLine);
3955
- if (lines.length >= maxLines) {
3956
- return lines;
3957
- }
3958
3955
  }
3959
3956
  currentLine = [i];
3960
3957
  currentWidth = wordWidth;
3961
3958
  }
3962
3959
  }
3963
- if (currentLine.length > 0 && lines.length < maxLines) {
3960
+ if (currentLine.length > 0) {
3964
3961
  lines.push(currentLine);
3965
3962
  }
3966
3963
  return lines;
@@ -3993,6 +3990,13 @@ function transformText(text, transform) {
3993
3990
  return text;
3994
3991
  }
3995
3992
  }
3993
+ function splitIntoChunks(arr, chunkSize) {
3994
+ const chunks = [];
3995
+ for (let i = 0; i < arr.length; i += chunkSize) {
3996
+ chunks.push(arr.slice(i, i + chunkSize));
3997
+ }
3998
+ return chunks;
3999
+ }
3996
4000
  var CaptionLayoutEngine = class {
3997
4001
  fontRegistry;
3998
4002
  cache;
@@ -4079,31 +4083,34 @@ var CaptionLayoutEngine = class {
4079
4083
  const spaceWord = await this.measureWord(" ", measurementConfig);
4080
4084
  spaceWidth = spaceWord.width + config.wordSpacing;
4081
4085
  }
4082
- const groups = wordGroups.map((indices) => {
4086
+ const groups = wordGroups.flatMap((indices) => {
4083
4087
  const groupWidths = indices.map((i) => store.widths[i]);
4084
- const lineIndices = breakIntoLines(
4088
+ const allLines = breakIntoLines(
4085
4089
  groupWidths,
4086
4090
  pixelMaxWidth,
4087
- config.maxLines,
4088
4091
  spaceWidth
4089
4092
  );
4090
- const lines = lineIndices.map((lineWordIndices, lineIndex) => {
4091
- const actualIndices = lineWordIndices.map((i) => indices[i]);
4092
- const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
4093
+ const lineChunks = splitIntoChunks(allLines, config.maxLines);
4094
+ return lineChunks.map((chunkLines) => {
4095
+ const lines = chunkLines.map((lineWordIndices, lineIndex) => {
4096
+ const actualIndices = lineWordIndices.map((i) => indices[i]);
4097
+ const lineWidth = actualIndices.reduce((sum, idx) => sum + store.widths[idx], 0) + (actualIndices.length - 1) * spaceWidth;
4098
+ return {
4099
+ wordIndices: actualIndices,
4100
+ x: 0,
4101
+ y: lineIndex * config.fontSize * config.lineHeight,
4102
+ width: lineWidth,
4103
+ height: config.fontSize
4104
+ };
4105
+ });
4106
+ const allWordIndices = lines.flatMap((l) => l.wordIndices);
4093
4107
  return {
4094
- wordIndices: actualIndices,
4095
- x: 0,
4096
- y: lineIndex * config.fontSize * config.lineHeight,
4097
- width: lineWidth,
4098
- height: config.fontSize
4108
+ wordIndices: allWordIndices,
4109
+ startTime: store.startTimes[allWordIndices[0]],
4110
+ endTime: store.endTimes[allWordIndices[allWordIndices.length - 1]],
4111
+ lines
4099
4112
  };
4100
4113
  });
4101
- return {
4102
- wordIndices: lines.flatMap((l) => l.wordIndices),
4103
- startTime: store.startTimes[indices[0]],
4104
- endTime: store.endTimes[indices[indices.length - 1]],
4105
- lines
4106
- };
4107
4114
  });
4108
4115
  const calculateGroupY = (group) => {
4109
4116
  const totalHeight = group.lines.length * config.fontSize * config.lineHeight;
@@ -4482,7 +4489,7 @@ function extractFontConfig(asset) {
4482
4489
  size: font?.size ?? 24,
4483
4490
  weight: String(font?.weight ?? "400"),
4484
4491
  baseColor: font?.color ?? "#ffffff",
4485
- activeColor: active?.color ?? "#ffff00",
4492
+ activeColor: active?.color ?? "#ffffff",
4486
4493
  baseOpacity: font?.opacity ?? 1,
4487
4494
  activeOpacity: active?.opacity ?? 1,
4488
4495
  letterSpacing: asset.style?.letterSpacing ?? 0
@@ -5218,6 +5225,7 @@ var NodeRawEncoder = class _NodeRawEncoder {
5218
5225
  preset = "ultrafast",
5219
5226
  profile = "high"
5220
5227
  } = config;
5228
+ const hasAlpha = config.hasAlpha ?? false;
5221
5229
  const args = [
5222
5230
  "-y",
5223
5231
  "-f",
@@ -5231,32 +5239,49 @@ var NodeRawEncoder = class _NodeRawEncoder {
5231
5239
  "-thread_queue_size",
5232
5240
  "512",
5233
5241
  "-i",
5234
- "pipe:0",
5235
- "-c:v",
5236
- "libx264",
5237
- "-preset",
5238
- preset,
5239
- "-tune",
5240
- "stillimage",
5241
- "-crf",
5242
- String(crf),
5243
- "-profile:v",
5244
- profile,
5245
- "-g",
5246
- "300",
5247
- "-bf",
5248
- "2",
5249
- "-threads",
5250
- "0",
5251
- "-pix_fmt",
5252
- "yuv420p",
5253
- "-r",
5254
- String(fps),
5255
- "-movflags",
5256
- "+faststart"
5242
+ "pipe:0"
5257
5243
  ];
5244
+ if (hasAlpha) {
5245
+ args.push(
5246
+ "-c:v",
5247
+ "prores_ks",
5248
+ "-profile:v",
5249
+ "4444",
5250
+ "-pix_fmt",
5251
+ "yuva444p10le",
5252
+ "-vendor",
5253
+ "apl0",
5254
+ "-r",
5255
+ String(fps)
5256
+ );
5257
+ } else {
5258
+ args.push(
5259
+ "-c:v",
5260
+ "libx264",
5261
+ "-preset",
5262
+ preset,
5263
+ "-tune",
5264
+ "stillimage",
5265
+ "-crf",
5266
+ String(crf),
5267
+ "-profile:v",
5268
+ profile,
5269
+ "-g",
5270
+ "300",
5271
+ "-bf",
5272
+ "2",
5273
+ "-threads",
5274
+ "0",
5275
+ "-pix_fmt",
5276
+ "yuv420p",
5277
+ "-r",
5278
+ String(fps),
5279
+ "-movflags",
5280
+ "+faststart"
5281
+ );
5282
+ }
5258
5283
  if (this.outputToMemory) {
5259
- args.push("-f", "mp4", "pipe:1");
5284
+ args.push("-f", hasAlpha ? "mov" : "mp4", "pipe:1");
5260
5285
  } else {
5261
5286
  args.push(this.outputPath);
5262
5287
  }
@@ -5516,6 +5541,8 @@ var RichCaptionRenderer = class {
5516
5541
  animationStyle,
5517
5542
  animationSpeed
5518
5543
  );
5544
+ const bgColor = options?.bgColor;
5545
+ const hasAlpha = !bgColor;
5519
5546
  const encoder = new NodeRawEncoder();
5520
5547
  await encoder.configure(
5521
5548
  {
@@ -5525,7 +5552,8 @@ var RichCaptionRenderer = class {
5525
5552
  duration,
5526
5553
  crf: options?.crf ?? 23,
5527
5554
  preset: options?.preset ?? "ultrafast",
5528
- profile: options?.profile ?? "high"
5555
+ profile: options?.profile ?? "high",
5556
+ hasAlpha
5529
5557
  },
5530
5558
  {
5531
5559
  outputPath,
@@ -5537,7 +5565,6 @@ var RichCaptionRenderer = class {
5537
5565
  height: this.height,
5538
5566
  pixelRatio: this.pixelRatio
5539
5567
  });
5540
- const bgColor = options?.bgColor ?? "#000000";
5541
5568
  const totalStart = performance.now();
5542
5569
  let framesProcessed = 0;
5543
5570
  let lastPct = -1;
@@ -5551,7 +5578,7 @@ var RichCaptionRenderer = class {
5551
5578
  height: this.height * this.pixelRatio,
5552
5579
  pixelRatio: this.pixelRatio,
5553
5580
  clear: true,
5554
- bg: { color: bgColor, opacity: 1, radius: 0 }
5581
+ ...bgColor ? { bg: { color: bgColor, opacity: 1, radius: 0 } } : {}
5555
5582
  };
5556
5583
  await painter.render([beginOp, ...captionOps]);
5557
5584
  const rawResult = painter.toRawRGBA();
@@ -6179,6 +6206,7 @@ export {
6179
6206
  RichCaptionRenderer,
6180
6207
  WordTimingStore,
6181
6208
  arcToCubicBeziers,
6209
+ breakIntoLines,
6182
6210
  calculateAnimationStatesForGroup,
6183
6211
  commandsToPathString,
6184
6212
  computeSimplePathBounds,
@@ -670,6 +670,7 @@ declare class WordTimingStore {
670
670
  }
671
671
  declare function findWordAtTime(store: WordTimingStore, timeMs: number): number;
672
672
  declare function groupWordsByPause(store: WordTimingStore, pauseThreshold?: number): number[][];
673
+ declare function breakIntoLines(wordWidths: number[], maxWidth: number, spaceWidth: number): number[][];
673
674
  declare class CaptionLayoutEngine {
674
675
  private fontRegistry;
675
676
  private cache;
@@ -1147,6 +1148,7 @@ interface VideoEncoderConfig {
1147
1148
  hardwareAcceleration?: "prefer-hardware" | "prefer-software" | "no-preference";
1148
1149
  crf?: number;
1149
1150
  preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
1151
+ hasAlpha?: boolean;
1150
1152
  }
1151
1153
  interface VideoEncoderProgress {
1152
1154
  framesEncoded: number;
@@ -1259,4 +1261,4 @@ declare function createTextEngine(opts?: {
1259
1261
  destroy(): void;
1260
1262
  }>;
1261
1263
 
1262
- export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, MediaRecorderFallback, type MoveToCommand, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, WebCodecsEncoder, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createMediaRecorderFallback, createTextEngine, createVideoEncoder, createWebCodecsEncoder, createWebPainter, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, initResvg, isDrawCaptionWordOp, isMediaRecorderSupported, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };
1264
+ export { type AnimationDirection, type AnimationStyle, type ArcCommand, type BackgroundConfig, type BoundingBox, type CanvasRichCaptionAsset, CanvasRichCaptionAssetSchema, type CanvasRichTextAsset, CanvasRichTextAssetSchema, type CanvasSvgAsset, CanvasSvgAssetSchema, type CaptionGroup, type CaptionLayout, type CaptionLayoutConfig, CaptionLayoutEngine, type CaptionLine, type ClosePathCommand, type CubicBezierCommand, type DrawOp, type EngineInit, type FastVideoOptions, type FastVideoResult, type FontConfig, FontRegistry, type FrameSchedule, type Glyph, type GradientSpec, type IVideoEncoder, type LineToCommand, MediaRecorderFallback, type MoveToCommand, type NormalizedPathCommand, type ParsedPathCommand, type PathCommandType, type Point2D, type PositionedWord, type QuadraticBezierCommand, type RGBA, type RenderFrame, type RenderStats, type Renderer, type ResvgRenderOptions, type ResvgRenderResult, type RichCaptionGeneratorConfig, type RichCaptionRendererOptions, type ShadowConfig, type ShapedLine, type ShapedWord, type ShapedWordGlyph, type ShotstackRichTextAsset, type ShotstackSvgAsset, type StrokeConfig, type StrokeSpec, type ValidAsset, type VideoEncoderCapabilities, type VideoEncoderConfig, type VideoEncoderProgress, WebCodecsEncoder, type WordAnimationConfig, type WordAnimationState, type WordTiming, WordTimingStore, arcToCubicBeziers, breakIntoLines, calculateAnimationStatesForGroup, commandsToPathString, computeSimplePathBounds, createDefaultGeneratorConfig, createFrameSchedule, createMediaRecorderFallback, createTextEngine, createVideoEncoder, createWebCodecsEncoder, createWebPainter, detectPlatform, detectSubtitleFormat, findWordAtTime, generateRichCaptionDrawOps, generateRichCaptionFrame, generateShapePathData, getDefaultAnimationConfig, getDrawCaptionWordOps, getEncoderCapabilities, getEncoderWarning, groupWordsByPause, initResvg, isDrawCaptionWordOp, isMediaRecorderSupported, isRTLText, isWebCodecsH264Supported, normalizePath, normalizePathString, parseSubtitleToWords, parseSvgPath, quadraticToCubic, renderSvgAssetToPng, renderSvgToPng, richCaptionAssetSchema, shapeToSvgString };