aivee 0.1.0-alpha.0 → 0.1.0-dev.59872e1

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -21
  3. package/README.md +120 -118
  4. package/docs/architecture.md +401 -401
  5. package/docs/aspect-ratio-presets.md +72 -72
  6. package/docs/releasing.md +19 -19
  7. package/docs/usage.md +1193 -1193
  8. package/package.json +52 -52
  9. package/requirements-otio.txt +1 -1
  10. package/src/capabilities.mjs +307 -307
  11. package/src/cli.mjs +646 -646
  12. package/src/doctor.mjs +133 -133
  13. package/src/edits/analysis-to-edl.mjs +43 -43
  14. package/src/edits/detect-silence.mjs +51 -51
  15. package/src/edits/find-mistake-candidates.mjs +42 -42
  16. package/src/edits/plan-shot-placements.mjs +105 -105
  17. package/src/edits/plan-silence-cuts.mjs +49 -49
  18. package/src/edits/retime-transcript.mjs +105 -105
  19. package/src/files.mjs +80 -80
  20. package/src/index.mjs +44 -44
  21. package/src/interchange/otio.mjs +204 -204
  22. package/src/interchange/otio_bridge.py +521 -521
  23. package/src/media/analysis-provider.mjs +75 -75
  24. package/src/media/compatibility.mjs +45 -45
  25. package/src/media/ffmpeg-capabilities.mjs +129 -129
  26. package/src/media/frame-rate.mjs +58 -58
  27. package/src/media/frame-time.mjs +22 -22
  28. package/src/media/preview-assets.mjs +350 -350
  29. package/src/media/probe.mjs +149 -149
  30. package/src/media/render-cuts.mjs +90 -90
  31. package/src/media/scene-detection.mjs +107 -107
  32. package/src/media/scene-review.mjs +205 -205
  33. package/src/project/assets.mjs +1194 -1194
  34. package/src/project/create.mjs +100 -100
  35. package/src/project/presets.mjs +44 -44
  36. package/src/render/checkpoints.mjs +192 -192
  37. package/src/render/formats.mjs +163 -163
  38. package/src/render/hyperframes.mjs +590 -590
  39. package/src/render/process.mjs +111 -111
  40. package/src/render/progress.mjs +46 -46
  41. package/src/render/timeline-control.mjs +53 -53
  42. package/src/timeline/assemble.mjs +119 -119
  43. package/src/timeline/audio-effects.mjs +75 -75
  44. package/src/timeline/audio-mastering.mjs +236 -236
  45. package/src/timeline/audio-mix.mjs +105 -105
  46. package/src/timeline/audio-track-effects.mjs +139 -139
  47. package/src/timeline/audio-volume.mjs +63 -63
  48. package/src/timeline/background.mjs +8 -8
  49. package/src/timeline/blend-mode.mjs +34 -34
  50. package/src/timeline/caption-layouts.mjs +62 -61
  51. package/src/timeline/captions.mjs +80 -80
  52. package/src/timeline/edit.mjs +965 -965
  53. package/src/timeline/fingerprint.mjs +6 -6
  54. package/src/timeline/from-edl.mjs +31 -31
  55. package/src/timeline/history-store.mjs +281 -281
  56. package/src/timeline/history.mjs +69 -69
  57. package/src/timeline/inspect.mjs +117 -117
  58. package/src/timeline/keyframes.mjs +158 -158
  59. package/src/timeline/layers.mjs +18 -18
  60. package/src/timeline/mask.mjs +53 -53
  61. package/src/timeline/project.mjs +52 -52
  62. package/src/timeline/render.mjs +759 -759
  63. package/src/timeline/sidechain-render.mjs +209 -209
  64. package/src/timeline/source-fit.mjs +15 -15
  65. package/src/timeline/speech-bubble.mjs +26 -26
  66. package/src/timeline/speed-ramp.mjs +188 -188
  67. package/src/timeline/track-state.mjs +22 -22
  68. package/src/timeline/transition.mjs +34 -34
  69. package/src/timeline/validate.mjs +322 -322
  70. package/src/transcript/corrections.mjs +21 -21
  71. package/src/transcript/normalize.mjs +17 -17
  72. package/src/transcript/provider.mjs +42 -42
  73. package/src/transcript/subtitles.mjs +57 -57
  74. package/src/transcript/validate.mjs +39 -39
@@ -1,34 +1,34 @@
1
- const BLEND_MODES = new Set([
2
- 'normal',
3
- 'multiply',
4
- 'screen',
5
- 'overlay',
6
- 'darken',
7
- 'lighten',
8
- 'color-dodge',
9
- 'color-burn',
10
- 'hard-light',
11
- 'soft-light',
12
- 'difference',
13
- 'exclusion',
14
- 'hue',
15
- 'saturation',
16
- 'color',
17
- 'luminosity',
18
- ]);
19
-
20
- export function validateBlendMode(clip, trackKind) {
21
- if (clip.blendMode === undefined) return;
22
- if (trackKind !== 'video' && trackKind !== 'overlay') {
23
- throw new TypeError(`Clip ${clip.id} blendMode is supported only on video and overlay tracks.`);
24
- }
25
- if (typeof clip.blendMode !== 'string' || !BLEND_MODES.has(clip.blendMode)) {
26
- throw new TypeError(`Clip ${clip.id} blendMode must be one of: ${[...BLEND_MODES].join(', ')}.`);
27
- }
28
- }
29
-
30
- export function blendModeStyle(clip) {
31
- return clip.blendMode && clip.blendMode !== 'normal'
32
- ? `mix-blend-mode:${clip.blendMode};`
33
- : '';
34
- }
1
+ const BLEND_MODES = new Set([
2
+ 'normal',
3
+ 'multiply',
4
+ 'screen',
5
+ 'overlay',
6
+ 'darken',
7
+ 'lighten',
8
+ 'color-dodge',
9
+ 'color-burn',
10
+ 'hard-light',
11
+ 'soft-light',
12
+ 'difference',
13
+ 'exclusion',
14
+ 'hue',
15
+ 'saturation',
16
+ 'color',
17
+ 'luminosity',
18
+ ]);
19
+
20
+ export function validateBlendMode(clip, trackKind) {
21
+ if (clip.blendMode === undefined) return;
22
+ if (trackKind !== 'video' && trackKind !== 'overlay') {
23
+ throw new TypeError(`Clip ${clip.id} blendMode is supported only on video and overlay tracks.`);
24
+ }
25
+ if (typeof clip.blendMode !== 'string' || !BLEND_MODES.has(clip.blendMode)) {
26
+ throw new TypeError(`Clip ${clip.id} blendMode must be one of: ${[...BLEND_MODES].join(', ')}.`);
27
+ }
28
+ }
29
+
30
+ export function blendModeStyle(clip) {
31
+ return clip.blendMode && clip.blendMode !== 'normal'
32
+ ? `mix-blend-mode:${clip.blendMode};`
33
+ : '';
34
+ }
@@ -1,61 +1,62 @@
1
- const SOCIAL_LAYOUT = {
2
- x: 0.08, y: 0.68, width: 0.84, height: 0.24, fontSize: 64,
3
- fontWeight: 800, lineHeight: 1.05, verticalAlign: 'bottom', paddingPixels: 14,
4
- wordWrap: true,
5
- textShadow: { color: '#000000cc', blurPixels: 6, offsetXPixels: 0, offsetYPixels: 3 },
6
- color: '#ffffff', backgroundColor: '#00000000', textAlign: 'center',
7
- };
8
-
9
- const LAYOUTS = {
10
- subtitle: {
11
- x: 0.08, y: 0.76, width: 0.84, height: 0.16, fontSize: 54,
12
- fontWeight: 600, verticalAlign: 'bottom', paddingPixels: 12, wordWrap: true,
13
- textShadow: { color: '#000000b8', blurPixels: 4, offsetXPixels: 0, offsetYPixels: 2 },
14
- color: '#ffffff', backgroundColor: '#000000b8', textAlign: 'center',
15
- },
16
- social: SOCIAL_LAYOUT,
17
- socialPop: {
18
- ...SOCIAL_LAYOUT,
19
- wordHighlight: true, wordHighlightColor: '#ffe45c', wordHighlightAnimation: 'pop',
20
- },
21
- socialStack: {
22
- ...SOCIAL_LAYOUT,
23
- y: 0.64, height: 0.3, fontSize: 58, lineHeight: 1.02,
24
- wordsPerCaption: 6, wordsPerLine: 3,
25
- wordHighlight: true, wordHighlightColor: '#ffe45c', wordHighlightAnimation: 'pop',
26
- },
27
- speechBubble: {
28
- x: 0.32, y: 0.66, width: 0.62, height: 0.1, fontSize: 44,
29
- fontWeight: 600, lineHeight: 1.12, verticalAlign: 'center', paddingPixels: 22,
30
- wordWrap: true, wordsPerCaption: 6, wordsPerLine: 3,
31
- wordHighlight: true, wordHighlightColor: '#426BEB', wordHighlightAnimation: 'scale',
32
- color: '#172236', backgroundColor: '#ffffff', textAlign: 'left',
33
- textShadow: { color: '#17223622', blurPixels: 10, offsetXPixels: 0, offsetYPixels: 4 },
34
- bubble: { tailSide: 'bottom', tailPosition: 0.16, tailSizePixels: 26, cornerRadiusPixels: 28 },
35
- },
36
- lowerThird: {
37
- x: 0.08, y: 0.72, width: 0.76, height: 0.18, fontSize: 48,
38
- fontWeight: 600, verticalAlign: 'bottom', paddingPixels: 12, wordWrap: true,
39
- textShadow: { color: '#000000b8', blurPixels: 3, offsetXPixels: 0, offsetYPixels: 2 },
40
- color: '#ffffff', backgroundColor: '#000000b8', textAlign: 'left',
41
- },
42
- };
43
-
44
- export const CAPTION_LAYOUT_PRESETS = Object.freeze(Object.fromEntries(
45
- Object.entries(LAYOUTS).map(([name, layout]) => [name, Object.freeze({
46
- ...layout,
47
- ...(layout.textOutline ? { textOutline: Object.freeze({ ...layout.textOutline }) } : {}),
48
- ...(layout.textShadow ? { textShadow: Object.freeze({ ...layout.textShadow }) } : {}),
49
- })]),
50
- ));
51
-
52
- export function listCaptionLayoutPresets() {
53
- return Object.keys(CAPTION_LAYOUT_PRESETS);
54
- }
55
-
56
- export function resolveCaptionLayoutPreset(name = 'subtitle') {
57
- if (typeof name !== 'string' || !Object.hasOwn(CAPTION_LAYOUT_PRESETS, name)) {
58
- throw new TypeError(`Unknown caption layout preset: ${name}`);
59
- }
60
- return structuredClone(CAPTION_LAYOUT_PRESETS[name]);
61
- }
1
+ const SOCIAL_LAYOUT = {
2
+ x: 0.08, y: 0.68, width: 0.84, height: 0.24, fontSize: 64,
3
+ fontWeight: 800, lineHeight: 1.05, verticalAlign: 'bottom', paddingPixels: 14,
4
+ wordWrap: true,
5
+ textShadow: { color: '#000000cc', blurPixels: 6, offsetXPixels: 0, offsetYPixels: 3 },
6
+ color: '#ffffff', backgroundColor: '#00000000', textAlign: 'center',
7
+ };
8
+
9
+ const LAYOUTS = {
10
+ subtitle: {
11
+ x: 0.08, y: 0.76, width: 0.84, height: 0.16, fontSize: 54,
12
+ fontWeight: 600, verticalAlign: 'bottom', paddingPixels: 12, wordWrap: true,
13
+ textShadow: { color: '#000000b8', blurPixels: 4, offsetXPixels: 0, offsetYPixels: 2 },
14
+ color: '#ffffff', backgroundColor: '#000000b8', textAlign: 'center',
15
+ },
16
+ social: SOCIAL_LAYOUT,
17
+ socialPop: {
18
+ ...SOCIAL_LAYOUT,
19
+ wordHighlight: true, wordHighlightColor: '#ffe45c', wordHighlightAnimation: 'pop',
20
+ },
21
+ socialStack: {
22
+ ...SOCIAL_LAYOUT,
23
+ y: 0.64, height: 0.3, fontSize: 58, lineHeight: 1.02,
24
+ wordsPerCaption: 6, wordsPerLine: 3,
25
+ wordHighlight: true, wordHighlightColor: '#ffe45c', wordHighlightAnimation: 'pop',
26
+ },
27
+ speechBubble: {
28
+ x: 0.32, y: 0.66, width: 0.62, height: 0.1, fontSize: 44,
29
+ fontWeight: 600, lineHeight: 1.12, verticalAlign: 'center', paddingPixels: 22,
30
+ wordWrap: true, wordsPerCaption: 6, wordsPerLine: 3,
31
+ wordHighlight: true, wordHighlightColor: '#426BEB', wordHighlightAnimation: 'scale',
32
+ color: '#172236', backgroundColor: '#ffffff', textAlign: 'left',
33
+ textShadow: { color: '#17223622', blurPixels: 10, offsetXPixels: 0, offsetYPixels: 4 },
34
+ bubble: { tailSide: 'bottom', tailPosition: 0.16, tailSizePixels: 26, cornerRadiusPixels: 28 },
35
+ },
36
+ lowerThird: {
37
+ x: 0.08, y: 0.72, width: 0.76, height: 0.18, fontSize: 48,
38
+ fontWeight: 600, verticalAlign: 'bottom', paddingPixels: 12, wordWrap: true,
39
+ textShadow: { color: '#000000b8', blurPixels: 3, offsetXPixels: 0, offsetYPixels: 2 },
40
+ color: '#ffffff', backgroundColor: '#000000b8', textAlign: 'left',
41
+ },
42
+ };
43
+
44
+ export const CAPTION_LAYOUT_PRESETS = Object.freeze(Object.fromEntries(
45
+ Object.entries(LAYOUTS).map(([name, layout]) => [name, Object.freeze({
46
+ ...layout,
47
+ ...(layout.textOutline ? { textOutline: Object.freeze({ ...layout.textOutline }) } : {}),
48
+ ...(layout.textShadow ? { textShadow: Object.freeze({ ...layout.textShadow }) } : {}),
49
+ ...(layout.bubble ? { bubble: Object.freeze({ ...layout.bubble }) } : {}),
50
+ })]),
51
+ ));
52
+
53
+ export function listCaptionLayoutPresets() {
54
+ return Object.keys(CAPTION_LAYOUT_PRESETS);
55
+ }
56
+
57
+ export function resolveCaptionLayoutPreset(name = 'subtitle') {
58
+ if (typeof name !== 'string' || !Object.hasOwn(CAPTION_LAYOUT_PRESETS, name)) {
59
+ throw new TypeError(`Unknown caption layout preset: ${name}`);
60
+ }
61
+ return structuredClone(CAPTION_LAYOUT_PRESETS[name]);
62
+ }
@@ -1,80 +1,80 @@
1
- import { validateTranscript } from '../transcript/validate.mjs';
2
- import { resolveCaptionLayoutPreset } from './caption-layouts.mjs';
3
- import { resolveSpeechBubble } from './speech-bubble.mjs';
4
-
5
- export function createCaptionTrack(transcript, options = {}) {
6
- validateTranscript(transcript);
7
- const layout = resolveCaptionLayoutPreset(options.layoutPreset);
8
- const wordHighlight = options.wordHighlight ?? layout.wordHighlight ?? false;
9
- if (typeof wordHighlight !== 'boolean') throw new TypeError('wordHighlight must be a boolean.');
10
- if (wordHighlight && transcript.granularity === 'cue') throw new TypeError('Word highlighting requires word-level transcript timing.');
11
- const wordHighlightColor = options.wordHighlightColor ?? layout.wordHighlightColor ?? '#ffe45c';
12
- if (typeof wordHighlightColor !== 'string' || !/^#(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.test(wordHighlightColor)) {
13
- throw new TypeError('wordHighlightColor must be a hex color.');
14
- }
15
- if (!wordHighlight && options.wordHighlightColor !== undefined) throw new TypeError('wordHighlightColor requires wordHighlight: true.');
16
- const wordHighlightAnimation = options.wordHighlightAnimation ?? layout.wordHighlightAnimation ?? 'scale';
17
- if (!['scale', 'pop'].includes(wordHighlightAnimation)) throw new TypeError('wordHighlightAnimation must be scale or pop.');
18
- if (!wordHighlight && options.wordHighlightAnimation !== undefined) throw new TypeError('wordHighlightAnimation requires wordHighlight: true.');
19
- const wordsPerCaption = options.wordsPerCaption ?? layout.wordsPerCaption ?? 6;
20
- if (!Number.isSafeInteger(wordsPerCaption) || wordsPerCaption < 1 || wordsPerCaption > 20) throw new TypeError('wordsPerCaption must be an integer from 1 to 20.');
21
- const wordsPerLine = options.wordsPerLine ?? layout.wordsPerLine;
22
- if (wordsPerLine !== undefined && (!Number.isSafeInteger(wordsPerLine) || wordsPerLine < 1 || wordsPerLine > 20)) {
23
- throw new TypeError('wordsPerLine must be an integer from 1 to 20.');
24
- }
25
- const wordWrap = options.wordWrap ?? layout.wordWrap ?? false;
26
- if (typeof wordWrap !== 'boolean') throw new TypeError('wordWrap must be a boolean.');
27
- const maxGapSeconds = options.maxGapSeconds ?? 0.8;
28
- if (!Number.isFinite(maxGapSeconds) || maxGapSeconds < 0) throw new TypeError('maxGapSeconds must be a non-negative number.');
29
- const bubbleSource = options.bubble ?? layout.bubble;
30
- const bubble = bubbleSource === undefined ? undefined : resolveSpeechBubble(bubbleSource, layout.bubble);
31
-
32
- const groups = [];
33
- let group = [];
34
- for (const word of transcript.words) {
35
- if (group.length && (group.length >= wordsPerCaption || word.start - group.at(-1).end > maxGapSeconds || /[.!?]$/.test(group.at(-1).text))) {
36
- groups.push(group);
37
- group = [];
38
- }
39
- group.push(word);
40
- }
41
- if (group.length) groups.push(group);
42
-
43
- const clips = groups.map((words, index) => ({
44
- id: `caption-${String(index + 1).padStart(3, '0')}`,
45
- timelineStart: words[0].start,
46
- durationSeconds: words.at(-1).end - words[0].start,
47
- text: words.map(({ text }) => text).join(' '),
48
- ...(wordsPerLine !== undefined ? { wordsPerLine } : {}),
49
- ...(wordHighlight ? {
50
- wordTimings: words.map((word) => ({
51
- text: word.text,
52
- start: word.start - words[0].start,
53
- end: word.end - words[0].start,
54
- })),
55
- wordHighlightColor,
56
- wordHighlightAnimation,
57
- } : {}),
58
- x: options.x ?? layout.x,
59
- y: options.y ?? layout.y,
60
- width: options.width ?? layout.width,
61
- height: options.height ?? layout.height,
62
- fontSize: options.fontSize ?? layout.fontSize,
63
- ...(options.fontFamily !== undefined ? { fontFamily: options.fontFamily } : {}),
64
- fontWeight: options.fontWeight ?? layout.fontWeight,
65
- ...(options.lineHeight !== undefined || layout.lineHeight !== undefined ? { lineHeight: options.lineHeight ?? layout.lineHeight } : {}),
66
- verticalAlign: options.verticalAlign ?? layout.verticalAlign,
67
- wordWrap,
68
- paddingPixels: options.paddingPixels ?? layout.paddingPixels,
69
- textOutline: options.textOutline ?? layout.textOutline,
70
- textShadow: options.textShadow ?? layout.textShadow,
71
- color: options.color ?? layout.color,
72
- backgroundColor: options.backgroundColor ?? layout.backgroundColor,
73
- textAlign: options.textAlign ?? layout.textAlign,
74
- ...(bubble === undefined ? {} : { bubble: structuredClone(bubble) }),
75
- ...(options.fadeInSeconds ? { fadeInSeconds: options.fadeInSeconds } : {}),
76
- ...(options.fadeOutSeconds ? { fadeOutSeconds: options.fadeOutSeconds } : {}),
77
- }));
78
-
79
- return { id: options.trackId ?? 'captions', kind: 'overlay', order: options.order ?? 100, clips };
80
- }
1
+ import { validateTranscript } from '../transcript/validate.mjs';
2
+ import { resolveCaptionLayoutPreset } from './caption-layouts.mjs';
3
+ import { resolveSpeechBubble } from './speech-bubble.mjs';
4
+
5
+ export function createCaptionTrack(transcript, options = {}) {
6
+ validateTranscript(transcript);
7
+ const layout = resolveCaptionLayoutPreset(options.layoutPreset);
8
+ const wordHighlight = options.wordHighlight ?? layout.wordHighlight ?? false;
9
+ if (typeof wordHighlight !== 'boolean') throw new TypeError('wordHighlight must be a boolean.');
10
+ if (wordHighlight && transcript.granularity === 'cue') throw new TypeError('Word highlighting requires word-level transcript timing.');
11
+ const wordHighlightColor = options.wordHighlightColor ?? layout.wordHighlightColor ?? '#ffe45c';
12
+ if (typeof wordHighlightColor !== 'string' || !/^#(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.test(wordHighlightColor)) {
13
+ throw new TypeError('wordHighlightColor must be a hex color.');
14
+ }
15
+ if (!wordHighlight && options.wordHighlightColor !== undefined) throw new TypeError('wordHighlightColor requires wordHighlight: true.');
16
+ const wordHighlightAnimation = options.wordHighlightAnimation ?? layout.wordHighlightAnimation ?? 'scale';
17
+ if (!['scale', 'pop'].includes(wordHighlightAnimation)) throw new TypeError('wordHighlightAnimation must be scale or pop.');
18
+ if (!wordHighlight && options.wordHighlightAnimation !== undefined) throw new TypeError('wordHighlightAnimation requires wordHighlight: true.');
19
+ const wordsPerCaption = options.wordsPerCaption ?? layout.wordsPerCaption ?? 6;
20
+ if (!Number.isSafeInteger(wordsPerCaption) || wordsPerCaption < 1 || wordsPerCaption > 20) throw new TypeError('wordsPerCaption must be an integer from 1 to 20.');
21
+ const wordsPerLine = options.wordsPerLine ?? layout.wordsPerLine;
22
+ if (wordsPerLine !== undefined && (!Number.isSafeInteger(wordsPerLine) || wordsPerLine < 1 || wordsPerLine > 20)) {
23
+ throw new TypeError('wordsPerLine must be an integer from 1 to 20.');
24
+ }
25
+ const wordWrap = options.wordWrap ?? layout.wordWrap ?? false;
26
+ if (typeof wordWrap !== 'boolean') throw new TypeError('wordWrap must be a boolean.');
27
+ const maxGapSeconds = options.maxGapSeconds ?? 0.8;
28
+ if (!Number.isFinite(maxGapSeconds) || maxGapSeconds < 0) throw new TypeError('maxGapSeconds must be a non-negative number.');
29
+ const bubbleSource = options.bubble ?? layout.bubble;
30
+ const bubble = bubbleSource === undefined ? undefined : resolveSpeechBubble(bubbleSource, layout.bubble);
31
+
32
+ const groups = [];
33
+ let group = [];
34
+ for (const word of transcript.words) {
35
+ if (group.length && (group.length >= wordsPerCaption || word.start - group.at(-1).end > maxGapSeconds || /[.!?]$/.test(group.at(-1).text))) {
36
+ groups.push(group);
37
+ group = [];
38
+ }
39
+ group.push(word);
40
+ }
41
+ if (group.length) groups.push(group);
42
+
43
+ const clips = groups.map((words, index) => ({
44
+ id: `caption-${String(index + 1).padStart(3, '0')}`,
45
+ timelineStart: words[0].start,
46
+ durationSeconds: words.at(-1).end - words[0].start,
47
+ text: words.map(({ text }) => text).join(' '),
48
+ ...(wordsPerLine !== undefined ? { wordsPerLine } : {}),
49
+ ...(wordHighlight ? {
50
+ wordTimings: words.map((word) => ({
51
+ text: word.text,
52
+ start: word.start - words[0].start,
53
+ end: word.end - words[0].start,
54
+ })),
55
+ wordHighlightColor,
56
+ wordHighlightAnimation,
57
+ } : {}),
58
+ x: options.x ?? layout.x,
59
+ y: options.y ?? layout.y,
60
+ width: options.width ?? layout.width,
61
+ height: options.height ?? layout.height,
62
+ fontSize: options.fontSize ?? layout.fontSize,
63
+ ...(options.fontFamily !== undefined ? { fontFamily: options.fontFamily } : {}),
64
+ fontWeight: options.fontWeight ?? layout.fontWeight,
65
+ ...(options.lineHeight !== undefined || layout.lineHeight !== undefined ? { lineHeight: options.lineHeight ?? layout.lineHeight } : {}),
66
+ verticalAlign: options.verticalAlign ?? layout.verticalAlign,
67
+ wordWrap,
68
+ paddingPixels: options.paddingPixels ?? layout.paddingPixels,
69
+ textOutline: options.textOutline ?? layout.textOutline,
70
+ textShadow: options.textShadow ?? layout.textShadow,
71
+ color: options.color ?? layout.color,
72
+ backgroundColor: options.backgroundColor ?? layout.backgroundColor,
73
+ textAlign: options.textAlign ?? layout.textAlign,
74
+ ...(bubble === undefined ? {} : { bubble: structuredClone(bubble) }),
75
+ ...(options.fadeInSeconds ? { fadeInSeconds: options.fadeInSeconds } : {}),
76
+ ...(options.fadeOutSeconds ? { fadeOutSeconds: options.fadeOutSeconds } : {}),
77
+ }));
78
+
79
+ return { id: options.trackId ?? 'captions', kind: 'overlay', order: options.order ?? 100, clips };
80
+ }