@bendyline/squisq 2.11.4 → 2.11.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.
Files changed (34) hide show
  1. package/dist/chunk-44BJ42GT.js +165 -0
  2. package/dist/chunk-764WHWKM.js +332 -0
  3. package/dist/{chunk-XV4SF2N5.js → chunk-BUJ5RV2P.js} +1 -1
  4. package/dist/{chunk-PJRVODWW.js → chunk-C3NAY5YP.js} +7 -2
  5. package/dist/{chunk-67L6WRJN.js → chunk-GHOPHGOK.js} +28 -1
  6. package/dist/{chunk-NV7LNKS2.js → chunk-GRGLZF4A.js} +18 -30
  7. package/dist/{chunk-ZSU5DJLX.js → chunk-J2IPSQKP.js} +104 -4
  8. package/dist/{chunk-NKNMGIIN.js → chunk-SVN7T6BK.js} +0 -3
  9. package/dist/{chunk-4UNTPQ4J.js → chunk-UUI35INI.js} +4 -2
  10. package/dist/{chunk-DQRELU4S.js → chunk-VU4T74LI.js} +1 -1
  11. package/dist/{chunk-LI547QHN.js → chunk-WAGJK36Q.js} +19 -346
  12. package/dist/{chunk-RXYKU6EX.js → chunk-Y4D6TDZT.js} +212 -3
  13. package/dist/chunk-ZP5F7LUH.js +223 -0
  14. package/dist/doc/index.js +9 -11
  15. package/dist/generate/index.d.ts +61 -1
  16. package/dist/generate/index.js +14 -1
  17. package/dist/index.d.ts +5 -4
  18. package/dist/index.js +57 -39
  19. package/dist/jsonForm/index.js +3 -4
  20. package/dist/narration/index.js +6 -7
  21. package/dist/proof/index.d.ts +3 -45
  22. package/dist/recommend/index.d.ts +6 -0
  23. package/dist/recommend/index.js +1 -1
  24. package/dist/review/index.d.ts +141 -0
  25. package/dist/review/index.js +29 -0
  26. package/dist/schemas/index.js +17 -19
  27. package/dist/timing/index.d.ts +29 -1
  28. package/dist/timing/index.js +6 -4
  29. package/dist/transform/index.d.ts +63 -2
  30. package/dist/transform/index.js +17 -6
  31. package/dist/types-BQWu0gzi.d.ts +46 -0
  32. package/package.json +6 -1
  33. package/dist/chunk-BAOV476U.js +0 -214
  34. package/dist/chunk-XOFT65EX.js +0 -34
@@ -2,18 +2,24 @@ import {
2
2
  extractContent,
3
3
  mapElementToBlock,
4
4
  stripMarkdown
5
- } from "./chunk-NKNMGIIN.js";
5
+ } from "./chunk-SVN7T6BK.js";
6
+ import {
7
+ markdownToDoc
8
+ } from "./chunk-WAGJK36Q.js";
6
9
  import {
7
10
  cloneAndFreezeData,
8
11
  isTemplateBlock
9
- } from "./chunk-BAOV476U.js";
12
+ } from "./chunk-Y4D6TDZT.js";
13
+ import {
14
+ parseMarkdown
15
+ } from "./chunk-CW24CH7I.js";
10
16
  import {
11
17
  extractPlainText,
12
18
  getChildren
13
19
  } from "./chunk-X7BNDUNI.js";
14
20
  import {
15
21
  estimateTimeFromText
16
- } from "./chunk-67L6WRJN.js";
22
+ } from "./chunk-GHOPHGOK.js";
17
23
  import {
18
24
  SeededRandom,
19
25
  hashString
@@ -839,6 +845,98 @@ function firstTitle(doc) {
839
845
  return void 0;
840
846
  }
841
847
 
848
+ // src/transform/narratedSegment.ts
849
+ function transformNarratedSegment(input, options = {}) {
850
+ const seed = options.seed ?? hashString(`${input.articleId}-${input.segmentId}`);
851
+ const heading = input.title.replace(/[\r\n]+/g, " ").trim() || input.segmentId;
852
+ const source = markdownToDoc(parseMarkdown(`# ${heading}
853
+
854
+ ${input.text}`), {
855
+ articleId: `${input.articleId}:${input.segmentId}`,
856
+ defaultDuration: Math.max(3, input.duration)
857
+ });
858
+ const sourceDoc = {
859
+ ...source,
860
+ articleId: input.articleId,
861
+ duration: input.duration,
862
+ audio: {
863
+ segments: [
864
+ {
865
+ src: input.audioSrc,
866
+ name: input.audioName ?? input.segmentId,
867
+ duration: input.duration,
868
+ startTime: 0
869
+ }
870
+ ]
871
+ }
872
+ };
873
+ const transformed = applyTransform(sourceDoc, options.style ?? "documentary", {
874
+ seed,
875
+ images: input.images,
876
+ themeId: options.themeId,
877
+ registry: options.registry,
878
+ overrides: {
879
+ ...options.minConfidence === void 0 ? {} : { minConfidence: options.minConfidence },
880
+ ...options.slidesPerMinute === void 0 ? {} : { budget: { slidesPerMinute: options.slidesPerMinute } }
881
+ }
882
+ });
883
+ let blocks = transformed.doc.blocks;
884
+ if (input.includeTitleBlock) {
885
+ const title = {
886
+ template: "title",
887
+ id: `${input.segmentId}-title`,
888
+ duration: 4,
889
+ audioSegment: 0,
890
+ title: input.title
891
+ };
892
+ blocks = [title, ...blocks];
893
+ }
894
+ blocks = interleaveVideoBlocks(blocks, input.videos ?? [], input.segmentId);
895
+ const timedBlocks = allocateTiming(blocks, input.duration).map((block) => ({
896
+ ...block,
897
+ audioSegment: 0
898
+ }));
899
+ return {
900
+ ...transformed,
901
+ seed,
902
+ doc: {
903
+ ...transformed.doc,
904
+ articleId: input.articleId,
905
+ duration: input.duration,
906
+ audio: sourceDoc.audio,
907
+ blocks: timedBlocks
908
+ }
909
+ };
910
+ }
911
+ function interleaveVideoBlocks(blocks, videos, segmentId) {
912
+ if (videos.length === 0) return blocks;
913
+ const result = [...blocks];
914
+ videos.forEach((video, index) => {
915
+ const block = {
916
+ template: "videoWithCaption",
917
+ id: `${segmentId}-video-${index}`,
918
+ duration: Math.max(3, video.clipEnd - video.clipStart),
919
+ audioSegment: 0,
920
+ videoSrc: video.src,
921
+ posterSrc: video.posterSrc,
922
+ videoAlt: video.alt ?? "",
923
+ clipStart: video.clipStart,
924
+ clipEnd: video.clipEnd,
925
+ sourceDuration: video.sourceDuration,
926
+ caption: video.alt,
927
+ captionPosition: "bottom",
928
+ videoCredit: video.credit,
929
+ videoLicense: video.license
930
+ };
931
+ const insertion = Math.min(
932
+ result.length,
933
+ Math.round((index + 1) * result.length / (videos.length + 1))
934
+ );
935
+ result.splice(insertion + index, 0, block);
936
+ });
937
+ return result;
938
+ }
939
+
842
940
  export {
843
941
  DEFAULT_TRANSFORM_STYLE_ID,
844
942
  createTransformStyleRegistry,
@@ -847,5 +945,7 @@ export {
847
945
  getTransformStyleSummaries,
848
946
  extractDocImages,
849
947
  analyzeBlocks,
850
- applyTransform
948
+ allocateTiming,
949
+ applyTransform,
950
+ transformNarratedSegment
851
951
  };
@@ -538,9 +538,6 @@ function cleanDescription(sentence, value) {
538
538
  for (const pattern of prepositionPatterns) {
539
539
  desc = desc.replace(pattern, "");
540
540
  }
541
- if (desc === sentence) {
542
- desc = sentence.replace(value, "").trim();
543
- }
544
541
  desc = desc.replace(/^[,:\-–—]+\s*/, "").replace(/\s*[,:\-–—]+$/, "");
545
542
  desc = desc.replace(
546
543
  /\s+(?:of|on|in|at|by|to|for|from|with|about|over|under|between|than|into|onto|upon)\s*([,.])/gi,
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  buildNarrationScript,
3
3
  expectedSyllablesAt,
4
- flattenRenderableBlocks,
5
4
  wordPosAtExpectedSyllables
6
- } from "./chunk-LI547QHN.js";
5
+ } from "./chunk-764WHWKM.js";
6
+ import {
7
+ flattenRenderableBlocks
8
+ } from "./chunk-WAGJK36Q.js";
7
9
 
8
10
  // src/narration/types.ts
9
11
  var DEFAULT_FEATURE_CONFIG = Object.freeze({
@@ -2,7 +2,7 @@ import {
2
2
  DEFAULT_THEME,
3
3
  applySurface,
4
4
  resolveFontFamily
5
- } from "./chunk-RXYKU6EX.js";
5
+ } from "./chunk-Y4D6TDZT.js";
6
6
 
7
7
  // src/jsonForm/chooseControl.ts
8
8
  var ENUM_SEGMENTED_LIMIT = 4;
@@ -21,20 +21,11 @@ import {
21
21
  parseAsciiTimeline,
22
22
  pickAutoTemplate,
23
23
  profileBlockContents
24
- } from "./chunk-PJRVODWW.js";
24
+ } from "./chunk-C3NAY5YP.js";
25
25
  import {
26
26
  DEFAULT_THEME,
27
27
  FRONTMATTER_CUSTOM_TEMPLATES_KEY,
28
28
  FRONTMATTER_CUSTOM_THEMES_KEY,
29
- oklchDarken,
30
- pickContrastingText,
31
- relativeLuminance,
32
- resolveFontFamily,
33
- validateCustomTemplateDefinition,
34
- validateTheme,
35
- withAlpha
36
- } from "./chunk-RXYKU6EX.js";
37
- import {
38
29
  VIEWPORT_PRESETS,
39
30
  cloneData,
40
31
  createTemplateContext,
@@ -42,8 +33,15 @@ import {
42
33
  getViewportOrientation,
43
34
  isPersistentLayerTemplate,
44
35
  isTemplateBlock,
45
- scaledFontSize2 as scaledFontSize
46
- } from "./chunk-BAOV476U.js";
36
+ oklchDarken,
37
+ pickContrastingText,
38
+ relativeLuminance,
39
+ resolveFontFamily,
40
+ scaledFontSize2 as scaledFontSize,
41
+ validateCustomTemplateDefinition,
42
+ validateTheme,
43
+ withAlpha
44
+ } from "./chunk-Y4D6TDZT.js";
47
45
  import {
48
46
  KNOWN_BLOCK_META_KEYS,
49
47
  TABLE_FED_TEMPLATES,
@@ -67,10 +65,7 @@ import {
67
65
  } from "./chunk-4VOD55SX.js";
68
66
  import {
69
67
  estimateReadingTime
70
- } from "./chunk-XOFT65EX.js";
71
- import {
72
- estimateSpokenWordCount
73
- } from "./chunk-67L6WRJN.js";
68
+ } from "./chunk-GHOPHGOK.js";
74
69
  import {
75
70
  SeededRandom,
76
71
  hashString
@@ -1930,7 +1925,7 @@ function autoTemplatePreservesContent(templateName, contents) {
1930
1925
  case "leftFeature":
1931
1926
  case "rightFeature":
1932
1927
  case "statHighlight":
1933
- return true;
1928
+ return nodes.length > 0 && nodes.every((node) => node.type === "paragraph");
1934
1929
  default:
1935
1930
  return false;
1936
1931
  }
@@ -3184,7 +3179,7 @@ function imageWithCaption(input, context) {
3184
3179
  return layers;
3185
3180
  }
3186
3181
  if (caption) {
3187
- const captionBgY = captionPosition === "top" ? "7%" : captionPosition === "center" ? "42%" : "68%";
3182
+ const captionBgY = captionPosition === "top" ? "8%" : captionPosition === "center" ? "43%" : "69%";
3188
3183
  const captionY = captionPosition === "top" ? "13%" : captionPosition === "center" ? "48%" : "74%";
3189
3184
  layers.push({
3190
3185
  type: "shape",
@@ -3197,7 +3192,7 @@ function imageWithCaption(input, context) {
3197
3192
  x: 0,
3198
3193
  y: captionBgY,
3199
3194
  width: "100%",
3200
- height: "16%"
3195
+ height: "10%"
3201
3196
  }
3202
3197
  });
3203
3198
  layers.push({
@@ -3869,8 +3864,8 @@ function photoGrid(input, context) {
3869
3864
  if (caption) {
3870
3865
  const captionFontSize = themedFontSize(layout.stackColumns ? 24 : 26, context, false);
3871
3866
  const captionY = layout.stackColumns ? "78%" : "74%";
3872
- const captionBgY = layout.stackColumns ? "72%" : "68%";
3873
- const captionHeight = layout.stackColumns ? "13%" : "16%";
3867
+ const captionBgY = layout.stackColumns ? "73%" : "70%";
3868
+ const captionHeight = layout.stackColumns ? "10%" : "8%";
3874
3869
  const bg = theme.colors.background;
3875
3870
  layers.push({
3876
3871
  type: "shape",
@@ -4457,7 +4452,7 @@ function videoWithCaption(input, context) {
4457
4452
  ];
4458
4453
  if (caption) {
4459
4454
  const bg = theme.colors.background;
4460
- const captionBgY = captionPosition === "top" ? "7%" : captionPosition === "center" ? "42%" : "68%";
4455
+ const captionBgY = captionPosition === "top" ? "8%" : captionPosition === "center" ? "43%" : "69%";
4461
4456
  const captionY = captionPosition === "top" ? "13%" : captionPosition === "center" ? "48%" : "74%";
4462
4457
  layers.push({
4463
4458
  type: "shape",
@@ -4470,7 +4465,7 @@ function videoWithCaption(input, context) {
4470
4465
  x: 0,
4471
4466
  y: captionBgY,
4472
4467
  width: "100%",
4473
- height: "16%"
4468
+ height: "10%"
4474
4469
  }
4475
4470
  });
4476
4471
  layers.push({
@@ -11960,320 +11955,6 @@ function buildStartBlock(markdownDoc, rootBlocks, fileName) {
11960
11955
  return config;
11961
11956
  }
11962
11957
 
11963
- // src/narration/syllables.ts
11964
- var BOUNDARY_PUNCT = /^[.,!?;:'"()[\]{}]+|[.,!?;:'"()[\]{}]+$/g;
11965
- var SYLLABLES_PER_SPOKEN_WORD = 1.4;
11966
- function estimateSyllables(token) {
11967
- const stripped = token.replace(BOUNDARY_PUNCT, "");
11968
- if (!stripped) return 1;
11969
- if (/^[-+]?[\d.,]+$/.test(stripped)) {
11970
- const num = parseFloat(stripped.replace(/,/g, ""));
11971
- if (!Number.isNaN(num) && Number.isFinite(num)) {
11972
- return Math.max(1, Math.round(estimateSpokenWordCount(stripped) * SYLLABLES_PER_SPOKEN_WORD));
11973
- }
11974
- }
11975
- if (/^[A-Z]{2,5}$/.test(stripped) && !/[AEIOUY]/.test(stripped)) {
11976
- return stripped.length;
11977
- }
11978
- const parts = stripped.toLowerCase().split(/[^a-z']+/).filter((p) => p.length > 0);
11979
- if (parts.length === 0) return 1;
11980
- let total = 0;
11981
- for (const part of parts) {
11982
- total += countAlphaSyllables(part.replace(/'/g, ""));
11983
- }
11984
- return Math.max(1, total);
11985
- }
11986
- function countAlphaSyllables(word) {
11987
- if (!word) return 0;
11988
- const groups = word.match(/[aeiouy]+/g);
11989
- let count = groups ? groups.length : 0;
11990
- if (count === 0) return 1;
11991
- if (count > 1 && /[^aeiouy]e$/.test(word)) {
11992
- count -= 1;
11993
- }
11994
- if (count > 1 && /[^aeiouy]ed$/.test(word) && !/[td]ed$/.test(word)) {
11995
- count -= 1;
11996
- }
11997
- if (count > 1 && /[^aeiouy]es$/.test(word) && !/(?:[sxz]|[cs]h)es$/.test(word)) {
11998
- count -= 1;
11999
- }
12000
- if (/[^aeiouy]le$/.test(word)) {
12001
- count += 1;
12002
- }
12003
- return Math.max(1, count);
12004
- }
12005
-
12006
- // src/narration/script.ts
12007
- function blockSpokenText(block, includeTitles) {
12008
- const title = includeTitles && block.title ? block.title.trim() : "";
12009
- const body = getBlockBodyText2(block);
12010
- if (title && body) return `${title}
12011
- ${body}`;
12012
- return title || body;
12013
- }
12014
- function buildNarrationScript(doc, options) {
12015
- const includeTitles = options?.includeTitles ?? true;
12016
- const separator = options?.blockSeparator ?? "\n\n";
12017
- const pieces = [];
12018
- for (const block of flattenRenderableBlocks(doc.blocks)) {
12019
- const text = blockSpokenText(block, includeTitles);
12020
- if (!text) continue;
12021
- pieces.push({
12022
- blockId: block.id,
12023
- heading: block.title || void 0,
12024
- text
12025
- });
12026
- }
12027
- let sourceText = "";
12028
- const blocks = [];
12029
- for (let i = 0; i < pieces.length; i++) {
12030
- if (i > 0) sourceText += separator;
12031
- const charStart = sourceText.length;
12032
- sourceText += pieces[i].text;
12033
- blocks.push({
12034
- blockId: pieces[i].blockId,
12035
- ...pieces[i].heading !== void 0 ? { heading: pieces[i].heading } : {},
12036
- tokenStart: 0,
12037
- tokenEnd: 0,
12038
- charStart,
12039
- charEnd: sourceText.length
12040
- });
12041
- }
12042
- const tokens = [];
12043
- const tokenRe = /\S+/g;
12044
- let blockIndex = 0;
12045
- let match;
12046
- while ((match = tokenRe.exec(sourceText)) !== null) {
12047
- const charOffset = match.index;
12048
- const charEnd = charOffset + match[0].length;
12049
- while (blockIndex < blocks.length - 1 && charOffset >= blocks[blockIndex].charEnd) {
12050
- blockIndex++;
12051
- }
12052
- const spoken = hasSpokenContent(match[0]);
12053
- tokens.push({
12054
- text: match[0],
12055
- charOffset,
12056
- charEnd,
12057
- blockId: blocks[blockIndex]?.blockId ?? "",
12058
- blockIndex,
12059
- syllables: spoken ? estimateSyllables(match[0]) : 0,
12060
- spokenWordEquiv: spoken ? estimateSpokenWordCount(stripBoundaryPunct(match[0])) : 0,
12061
- pauseAfter: 0,
12062
- spoken
12063
- });
12064
- }
12065
- for (const range of blocks) {
12066
- range.tokenStart = tokens.length;
12067
- range.tokenEnd = 0;
12068
- }
12069
- for (let i = 0; i < tokens.length; i++) {
12070
- const range = blocks[tokens[i].blockIndex];
12071
- if (range) {
12072
- if (i < range.tokenStart) range.tokenStart = i;
12073
- range.tokenEnd = i + 1;
12074
- }
12075
- }
12076
- for (const range of blocks) {
12077
- if (range.tokenStart > range.tokenEnd) {
12078
- range.tokenStart = range.tokenEnd;
12079
- }
12080
- }
12081
- for (let i = 0; i < tokens.length; i++) {
12082
- const token = tokens[i];
12083
- const next = tokens[i + 1];
12084
- if (!next || next.blockIndex !== token.blockIndex) {
12085
- token.pauseAfter = 3;
12086
- } else if (sourceText.slice(token.charEnd, next.charOffset).includes("\n")) {
12087
- token.pauseAfter = 2;
12088
- } else if (endsWithClausePunctuation(token.text)) {
12089
- token.pauseAfter = 1;
12090
- }
12091
- }
12092
- const cumulativeSyllables = new Array(tokens.length + 1);
12093
- cumulativeSyllables[0] = 0;
12094
- for (let i = 0; i < tokens.length; i++) {
12095
- cumulativeSyllables[i + 1] = cumulativeSyllables[i] + tokens[i].syllables;
12096
- }
12097
- return {
12098
- sourceText,
12099
- tokens,
12100
- blocks,
12101
- totalSyllables: cumulativeSyllables[tokens.length],
12102
- cumulativeSyllables
12103
- };
12104
- }
12105
- var TRAILING_CLOSERS = /["')\]}»”’]+$/;
12106
- function hasSpokenContent(token) {
12107
- return /[\p{L}\p{N}]/u.test(token);
12108
- }
12109
- function stripBoundaryPunct(token) {
12110
- return token.replace(/^[.,!?;:'"()[\]{}]+|[.,!?;:'"()[\]{}]+$/g, "");
12111
- }
12112
- function endsWithClausePunctuation(token) {
12113
- const bare = token.replace(TRAILING_CLOSERS, "");
12114
- const last = bare.charAt(bare.length - 1);
12115
- if (!last) return false;
12116
- if (last === ",") {
12117
- return true;
12118
- }
12119
- return last === "." || last === ";" || last === ":" || last === "!" || last === "?";
12120
- }
12121
- function clamp2(value, min, max) {
12122
- return value < min ? min : value > max ? max : value;
12123
- }
12124
- function expectedSyllablesAt(script, wordPos) {
12125
- const n = script.tokens.length;
12126
- if (n === 0) return 0;
12127
- const pos = clamp2(wordPos, 0, n);
12128
- const idx = Math.floor(pos);
12129
- if (idx >= n) return script.totalSyllables;
12130
- return script.cumulativeSyllables[idx] + (pos - idx) * script.tokens[idx].syllables;
12131
- }
12132
- function wordPosAtExpectedSyllables(script, syllables) {
12133
- const n = script.tokens.length;
12134
- if (n === 0) return 0;
12135
- const target = clamp2(syllables, 0, script.totalSyllables);
12136
- let lo = 0;
12137
- let hi = n;
12138
- while (lo < hi) {
12139
- const mid = lo + hi + 1 >> 1;
12140
- if (script.cumulativeSyllables[mid] <= target) lo = mid;
12141
- else hi = mid - 1;
12142
- }
12143
- if (lo >= n) return n;
12144
- const within = target - script.cumulativeSyllables[lo];
12145
- const sylls = script.tokens[lo].syllables;
12146
- return lo + (sylls > 0 ? within / sylls : 0);
12147
- }
12148
- function wordIndexAtChar(script, charOffset) {
12149
- const tokens = script.tokens;
12150
- if (tokens.length === 0) return 0;
12151
- let lo = 0;
12152
- let hi = tokens.length - 1;
12153
- while (lo < hi) {
12154
- const mid = lo + hi >> 1;
12155
- if (tokens[mid].charEnd <= charOffset) lo = mid + 1;
12156
- else hi = mid;
12157
- }
12158
- return lo;
12159
- }
12160
- function wordIndexAtTime(words, tSec) {
12161
- if (words.length === 0) return 0;
12162
- let lo = 0;
12163
- let hi = words.length - 1;
12164
- while (lo < hi) {
12165
- const mid = lo + hi + 1 >> 1;
12166
- if (words[mid].tSec <= tSec) lo = mid;
12167
- else hi = mid - 1;
12168
- }
12169
- return words[lo].tSec <= tSec ? lo : 0;
12170
- }
12171
-
12172
- // src/narration/sidecar.ts
12173
- function buildNarrationTimingJson(script, alignment, durationSec, options) {
12174
- const bookmarks = alignment.words.map((word) => {
12175
- const token = script.tokens[word.tokenIndex];
12176
- return {
12177
- id: `word-${word.tokenIndex}`,
12178
- time: word.tSec,
12179
- charOffset: token?.charOffset ?? 0,
12180
- textFragment: token?.text ?? ""
12181
- };
12182
- });
12183
- const blocks = alignment.blocks.map((range) => ({
12184
- ...range.blockId ? { blockId: range.blockId } : {},
12185
- ...range.heading !== void 0 ? { heading: range.heading } : {},
12186
- blockIndex: range.blockIndex,
12187
- charStart: range.charStart,
12188
- charEnd: range.charEnd,
12189
- startSec: range.startSec,
12190
- endSec: range.endSec
12191
- }));
12192
- return {
12193
- version: 3,
12194
- sourceText: script.sourceText,
12195
- duration: Number.isFinite(durationSec) && durationSec >= 0 ? durationSec : 0,
12196
- bookmarks,
12197
- blocks,
12198
- // Write only what `parseNarrationTimingJson` will read back, so the codec
12199
- // is a round-trip pair. A non-finite value would serialize to `null`
12200
- // (JSON has no NaN) and be dropped on parse anyway — omitting it here
12201
- // keeps the emitted sidecar honest rather than carrying a dead field.
12202
- ...finiteNumber(options?.cameraOffsetSec) ? { cameraOffsetSec: options.cameraOffsetSec } : {},
12203
- generator: {
12204
- name: "squisq-teleprompter",
12205
- method: "dsp-align",
12206
- ...options?.baseWpm !== void 0 ? { baseWpm: options.baseWpm } : {}
12207
- }
12208
- };
12209
- }
12210
- function finiteNonNegative(value) {
12211
- return typeof value === "number" && Number.isFinite(value) && value >= 0;
12212
- }
12213
- function finiteNumber(value) {
12214
- return typeof value === "number" && Number.isFinite(value);
12215
- }
12216
- function parseNarrationTimingJson(data) {
12217
- try {
12218
- const text = typeof data === "string" ? data : new TextDecoder().decode(data instanceof Uint8Array ? data : new Uint8Array(data));
12219
- const parsed = JSON.parse(text);
12220
- if (typeof parsed !== "object" || parsed === null) return null;
12221
- const raw = parsed;
12222
- if (typeof raw.sourceText !== "string" || !finiteNonNegative(raw.duration)) return null;
12223
- const bookmarks = Array.isArray(raw.bookmarks) ? raw.bookmarks.flatMap((b) => {
12224
- if (typeof b !== "object" || b === null) return [];
12225
- const bookmark = b;
12226
- if (!finiteNonNegative(bookmark.time) || !finiteNonNegative(bookmark.charOffset)) {
12227
- return [];
12228
- }
12229
- return [
12230
- {
12231
- id: typeof bookmark.id === "string" ? bookmark.id : `bookmark-${bookmark.charOffset}`,
12232
- time: bookmark.time,
12233
- charOffset: bookmark.charOffset,
12234
- ...typeof bookmark.textFragment === "string" ? { textFragment: bookmark.textFragment } : {}
12235
- }
12236
- ];
12237
- }) : [];
12238
- let blocks = Array.isArray(raw.blocks) ? raw.blocks.flatMap((b) => {
12239
- if (typeof b !== "object" || b === null) return [];
12240
- const block = b;
12241
- if (!finiteNonNegative(block.startSec) || !finiteNonNegative(block.endSec) || !finiteNonNegative(block.charStart) || !finiteNonNegative(block.charEnd)) {
12242
- return [];
12243
- }
12244
- return [
12245
- {
12246
- ...typeof block.blockId === "string" ? { blockId: block.blockId } : {},
12247
- ...typeof block.heading === "string" ? { heading: block.heading } : {},
12248
- blockIndex: finiteNonNegative(block.blockIndex) ? block.blockIndex : 0,
12249
- charStart: block.charStart,
12250
- charEnd: block.charEnd,
12251
- startSec: block.startSec,
12252
- endSec: Math.max(block.startSec, block.endSec)
12253
- }
12254
- ];
12255
- }) : [];
12256
- blocks = blocks.sort((a, b) => a.startSec - b.startSec);
12257
- let cursor = 0;
12258
- for (const block of blocks) {
12259
- if (block.startSec < cursor) block.startSec = cursor;
12260
- if (block.endSec < block.startSec) block.endSec = block.startSec;
12261
- cursor = block.startSec;
12262
- }
12263
- return {
12264
- version: 3,
12265
- sourceText: raw.sourceText,
12266
- duration: raw.duration,
12267
- bookmarks,
12268
- blocks,
12269
- ...finiteNumber(raw.cameraOffsetSec) ? { cameraOffsetSec: raw.cameraOffsetSec } : {},
12270
- ...typeof raw.generator === "object" && raw.generator !== null ? { generator: raw.generator } : {}
12271
- };
12272
- } catch {
12273
- return null;
12274
- }
12275
- }
12276
-
12277
11958
  export {
12278
11959
  expandPersistentLayers,
12279
11960
  getPersistentLayersFromTheme,
@@ -12399,13 +12080,5 @@ export {
12399
12080
  flattenRenderableBlocks,
12400
12081
  countBlocks,
12401
12082
  getBlockDepth,
12402
- getBlockBodyText2 as getBlockBodyText,
12403
- estimateSyllables,
12404
- buildNarrationScript,
12405
- expectedSyllablesAt,
12406
- wordPosAtExpectedSyllables,
12407
- wordIndexAtChar,
12408
- wordIndexAtTime,
12409
- buildNarrationTimingJson,
12410
- parseNarrationTimingJson
12083
+ getBlockBodyText2 as getBlockBodyText
12411
12084
  };