@bendyline/squisq 2.11.5 → 2.11.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/{chunk-AIB5WEK2.js → chunk-3YNFMFNK.js} +30 -345
- package/dist/chunk-44BJ42GT.js +165 -0
- package/dist/chunk-EF353KGH.js +332 -0
- package/dist/{chunk-67L6WRJN.js → chunk-GHOPHGOK.js} +28 -1
- package/dist/{chunk-ZSU5DJLX.js → chunk-IYZTF4VO.js} +318 -6
- package/dist/{chunk-2P44EZLI.js → chunk-SKKBRLLM.js} +4 -2
- package/dist/{chunk-NUS5YAI5.js → chunk-SOIV7KDO.js} +13 -12
- package/dist/{chunk-NKNMGIIN.js → chunk-SVN7T6BK.js} +0 -3
- package/dist/{chunk-DQRELU4S.js → chunk-VU4T74LI.js} +1 -1
- package/dist/{chunk-RXYKU6EX.js → chunk-Y4D6TDZT.js} +212 -3
- package/dist/{chunk-XV4SF2N5.js → chunk-YGGYYBJS.js} +3 -2
- package/dist/chunk-ZP5F7LUH.js +223 -0
- package/dist/doc/index.js +8 -10
- package/dist/generate/index.d.ts +61 -1
- package/dist/generate/index.js +14 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +56 -38
- package/dist/jsonForm/index.js +3 -4
- package/dist/narration/index.js +5 -6
- package/dist/schemas/index.js +17 -19
- package/dist/timing/index.d.ts +29 -1
- package/dist/timing/index.js +6 -4
- package/dist/transform/index.d.ts +97 -2
- package/dist/transform/index.js +17 -6
- package/package.json +1 -1
- package/dist/chunk-BAOV476U.js +0 -214
- package/dist/chunk-XOFT65EX.js +0 -34
|
@@ -26,15 +26,6 @@ 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
|
-
|
|
46
|
-
|
|
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-
|
|
71
|
-
import {
|
|
72
|
-
estimateSpokenWordCount
|
|
73
|
-
} from "./chunk-67L6WRJN.js";
|
|
68
|
+
} from "./chunk-GHOPHGOK.js";
|
|
74
69
|
import {
|
|
75
70
|
SeededRandom,
|
|
76
71
|
hashString
|
|
@@ -3184,7 +3179,7 @@ function imageWithCaption(input, context) {
|
|
|
3184
3179
|
return layers;
|
|
3185
3180
|
}
|
|
3186
3181
|
if (caption) {
|
|
3187
|
-
const captionBgY = captionPosition === "top" ? "
|
|
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: "
|
|
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 ? "
|
|
3873
|
-
const captionHeight = layout.stackColumns ? "
|
|
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" ? "
|
|
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: "
|
|
4468
|
+
height: "10%"
|
|
4474
4469
|
}
|
|
4475
4470
|
});
|
|
4476
4471
|
layers.push({
|
|
@@ -10499,6 +10494,7 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10499
10494
|
blocksBySegment.get(segmentIndex).push({ block, originalIndex: index });
|
|
10500
10495
|
});
|
|
10501
10496
|
const expandedBlocks = new Array(blocks.length);
|
|
10497
|
+
const splitParts = /* @__PURE__ */ new Map();
|
|
10502
10498
|
for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
|
|
10503
10499
|
const audioSegment = audioSegments[segmentIndex];
|
|
10504
10500
|
if (!audioSegment) {
|
|
@@ -10660,6 +10656,7 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10660
10656
|
const partDuration = block.duration / numParts;
|
|
10661
10657
|
if (partDuration >= MIN_TRANSITION_GAP) {
|
|
10662
10658
|
block.duration = partDuration;
|
|
10659
|
+
const parts = [];
|
|
10663
10660
|
for (let p = 1; p < numParts; p++) {
|
|
10664
10661
|
const splitBlock = {
|
|
10665
10662
|
id: `${block.id}-split-${p}`,
|
|
@@ -10673,13 +10670,23 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10673
10670
|
transition: { type: "dissolve", duration: 1 },
|
|
10674
10671
|
template: block.template
|
|
10675
10672
|
};
|
|
10676
|
-
|
|
10673
|
+
parts.push(splitBlock);
|
|
10677
10674
|
}
|
|
10675
|
+
splitParts.set(block, parts);
|
|
10678
10676
|
}
|
|
10679
10677
|
}
|
|
10680
10678
|
}
|
|
10681
10679
|
}
|
|
10682
10680
|
}
|
|
10681
|
+
if (splitParts.size > 0) {
|
|
10682
|
+
const sequenced = [];
|
|
10683
|
+
for (const block of expandedBlocks) {
|
|
10684
|
+
sequenced.push(block);
|
|
10685
|
+
const parts = block ? splitParts.get(block) : void 0;
|
|
10686
|
+
if (parts) sequenced.push(...parts);
|
|
10687
|
+
}
|
|
10688
|
+
expandedBlocks.splice(0, expandedBlocks.length, ...sequenced);
|
|
10689
|
+
}
|
|
10683
10690
|
return mergeShortBlocks ? expandedBlocks.filter((block) => block && block.duration > 0) : expandedBlocks.filter(Boolean);
|
|
10684
10691
|
}
|
|
10685
10692
|
function getAvailableTemplates() {
|
|
@@ -11960,320 +11967,6 @@ function buildStartBlock(markdownDoc, rootBlocks, fileName) {
|
|
|
11960
11967
|
return config;
|
|
11961
11968
|
}
|
|
11962
11969
|
|
|
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
11970
|
export {
|
|
12278
11971
|
expandPersistentLayers,
|
|
12279
11972
|
getPersistentLayersFromTheme,
|
|
@@ -12399,13 +12092,5 @@ export {
|
|
|
12399
12092
|
flattenRenderableBlocks,
|
|
12400
12093
|
countBlocks,
|
|
12401
12094
|
getBlockDepth,
|
|
12402
|
-
getBlockBodyText2 as getBlockBodyText
|
|
12403
|
-
estimateSyllables,
|
|
12404
|
-
buildNarrationScript,
|
|
12405
|
-
expectedSyllablesAt,
|
|
12406
|
-
wordPosAtExpectedSyllables,
|
|
12407
|
-
wordIndexAtChar,
|
|
12408
|
-
wordIndexAtTime,
|
|
12409
|
-
buildNarrationTimingJson,
|
|
12410
|
-
parseNarrationTimingJson
|
|
12095
|
+
getBlockBodyText2 as getBlockBodyText
|
|
12411
12096
|
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
estimateTimeFromText
|
|
3
|
+
} from "./chunk-GHOPHGOK.js";
|
|
4
|
+
|
|
5
|
+
// src/timing/captions.ts
|
|
6
|
+
var DEFAULT_VERSION = 1;
|
|
7
|
+
var DEFAULT_TARGET_WORDS = 10;
|
|
8
|
+
var DEFAULT_MIN_DURATION = 1.5;
|
|
9
|
+
var DEFAULT_MAX_DURATION = 6;
|
|
10
|
+
var DEFAULT_WORDS_PER_SECOND = 2.5;
|
|
11
|
+
function phrasesFromTiming(source, segmentIndex, targetWords) {
|
|
12
|
+
const { segment, timing } = source;
|
|
13
|
+
if (!timing || timing.bookmarks.length === 0) return [];
|
|
14
|
+
const segmentEnd = segment.startTime + segment.duration;
|
|
15
|
+
const phrases = [];
|
|
16
|
+
let words = [];
|
|
17
|
+
let phraseText = "";
|
|
18
|
+
for (let index = 0; index < timing.bookmarks.length; index += 1) {
|
|
19
|
+
const bookmark = timing.bookmarks[index];
|
|
20
|
+
const text = bookmark.textFragment?.trim() ?? "";
|
|
21
|
+
if (!text) continue;
|
|
22
|
+
const next = timing.bookmarks[index + 1];
|
|
23
|
+
const startTime = Math.min(
|
|
24
|
+
segmentEnd,
|
|
25
|
+
Math.max(segment.startTime, segment.startTime + bookmark.time)
|
|
26
|
+
);
|
|
27
|
+
const endTime = Math.min(
|
|
28
|
+
segmentEnd,
|
|
29
|
+
Math.max(startTime, segment.startTime + (next?.time ?? timing.duration))
|
|
30
|
+
);
|
|
31
|
+
const word = {
|
|
32
|
+
text,
|
|
33
|
+
startTime,
|
|
34
|
+
endTime
|
|
35
|
+
};
|
|
36
|
+
words.push(word);
|
|
37
|
+
phraseText += `${phraseText ? " " : ""}${text}`;
|
|
38
|
+
const flush = /[.!?]$/.test(text) && words.length >= 4 || /[,;:]$/.test(text) && words.length >= targetWords - 2 || words.length >= targetWords + 3 || index === timing.bookmarks.length - 1;
|
|
39
|
+
if (flush) {
|
|
40
|
+
phrases.push({
|
|
41
|
+
text: phraseText,
|
|
42
|
+
startTime: words[0].startTime,
|
|
43
|
+
endTime: words[words.length - 1].endTime,
|
|
44
|
+
audioSegment: segmentIndex,
|
|
45
|
+
words
|
|
46
|
+
});
|
|
47
|
+
words = [];
|
|
48
|
+
phraseText = "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return phrases;
|
|
52
|
+
}
|
|
53
|
+
function phrasesFromText(source, segmentIndex, options) {
|
|
54
|
+
const { segment } = source;
|
|
55
|
+
const sourceText = source.text?.trim();
|
|
56
|
+
if (!sourceText) return [];
|
|
57
|
+
const prefix = source.spokenPrefix ?? "";
|
|
58
|
+
const prefixWords = prefix.split(/\s+/).filter(Boolean).length;
|
|
59
|
+
const prefixDuration = Math.min(
|
|
60
|
+
segment.duration,
|
|
61
|
+
prefixWords / options.wordsPerSecond + (prefix.match(/\n\n/g)?.length ?? 0) * 0.5
|
|
62
|
+
);
|
|
63
|
+
const contentDuration = Math.max(0, segment.duration - prefixDuration);
|
|
64
|
+
const phrases = [];
|
|
65
|
+
const prefixText = prefix.replace(/\n+/g, " ").trim();
|
|
66
|
+
if (prefixText && prefixDuration > 0.5) {
|
|
67
|
+
phrases.push({
|
|
68
|
+
text: prefixText,
|
|
69
|
+
startTime: segment.startTime,
|
|
70
|
+
endTime: Math.min(segment.startTime + segment.duration, segment.startTime + prefixDuration),
|
|
71
|
+
audioSegment: segmentIndex
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const sentences = sourceText.replace(/\n+/g, " ").split(/(?<=[.!?])\s+/).map((sentence) => sentence.trim()).filter(Boolean);
|
|
75
|
+
let phraseText = "";
|
|
76
|
+
let phraseWordCount = 0;
|
|
77
|
+
let phraseStartOffset = 0;
|
|
78
|
+
let currentOffset = 0;
|
|
79
|
+
const flush = () => {
|
|
80
|
+
if (!phraseText) return;
|
|
81
|
+
const startTime = segment.startTime + prefixDuration + estimateTimeFromText(sourceText, phraseStartOffset, contentDuration);
|
|
82
|
+
const estimatedEnd = segment.startTime + prefixDuration + estimateTimeFromText(sourceText, currentOffset, contentDuration);
|
|
83
|
+
phrases.push({
|
|
84
|
+
text: phraseText,
|
|
85
|
+
startTime,
|
|
86
|
+
endTime: Math.min(
|
|
87
|
+
segment.startTime + segment.duration,
|
|
88
|
+
Math.min(
|
|
89
|
+
Math.max(startTime + options.minPhraseDuration, estimatedEnd),
|
|
90
|
+
startTime + options.maxPhraseDuration
|
|
91
|
+
)
|
|
92
|
+
),
|
|
93
|
+
audioSegment: segmentIndex
|
|
94
|
+
});
|
|
95
|
+
phraseText = "";
|
|
96
|
+
phraseWordCount = 0;
|
|
97
|
+
phraseStartOffset = currentOffset;
|
|
98
|
+
};
|
|
99
|
+
for (const sentence of sentences) {
|
|
100
|
+
const sentenceWords = sentence.split(/\s+/).filter(Boolean).length;
|
|
101
|
+
if (phraseWordCount > 0 && phraseWordCount + sentenceWords > options.targetWordsPerPhrase * 1.5) {
|
|
102
|
+
flush();
|
|
103
|
+
}
|
|
104
|
+
phraseText += `${phraseText ? " " : ""}${sentence}`;
|
|
105
|
+
phraseWordCount += sentenceWords;
|
|
106
|
+
currentOffset += sentence.length + 1;
|
|
107
|
+
if (phraseWordCount >= options.targetWordsPerPhrase) flush();
|
|
108
|
+
}
|
|
109
|
+
flush();
|
|
110
|
+
return phrases;
|
|
111
|
+
}
|
|
112
|
+
function buildCaptionTrack(sources, options = {}) {
|
|
113
|
+
const targetWordsPerPhrase = options.targetWordsPerPhrase ?? DEFAULT_TARGET_WORDS;
|
|
114
|
+
const textOptions = {
|
|
115
|
+
targetWordsPerPhrase,
|
|
116
|
+
minPhraseDuration: options.minPhraseDuration ?? DEFAULT_MIN_DURATION,
|
|
117
|
+
maxPhraseDuration: options.maxPhraseDuration ?? DEFAULT_MAX_DURATION,
|
|
118
|
+
wordsPerSecond: options.wordsPerSecond ?? DEFAULT_WORDS_PER_SECOND
|
|
119
|
+
};
|
|
120
|
+
if (!Number.isInteger(targetWordsPerPhrase) || targetWordsPerPhrase < 1) {
|
|
121
|
+
throw new Error("targetWordsPerPhrase must be a positive integer");
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isFinite(textOptions.wordsPerSecond) || textOptions.wordsPerSecond <= 0) {
|
|
124
|
+
throw new Error("wordsPerSecond must be a positive finite number");
|
|
125
|
+
}
|
|
126
|
+
if (!Number.isFinite(textOptions.minPhraseDuration) || !Number.isFinite(textOptions.maxPhraseDuration) || textOptions.minPhraseDuration < 0 || textOptions.maxPhraseDuration < textOptions.minPhraseDuration) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"caption phrase durations must be finite with max greater than or equal to min"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const phrases = sources.flatMap((source, segmentIndex) => {
|
|
132
|
+
const timed = phrasesFromTiming(source, segmentIndex, targetWordsPerPhrase);
|
|
133
|
+
return timed.length > 0 ? timed : phrasesFromText(source, segmentIndex, textOptions);
|
|
134
|
+
});
|
|
135
|
+
if (options.closePhraseGaps ?? true) {
|
|
136
|
+
for (let index = 0; index < phrases.length - 1; index += 1) {
|
|
137
|
+
if (phrases[index].audioSegment === phrases[index + 1].audioSegment) {
|
|
138
|
+
phrases[index].endTime = Math.max(phrases[index].startTime, phrases[index + 1].startTime);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (let segmentIndex = 0; segmentIndex < sources.length; segmentIndex += 1) {
|
|
142
|
+
const segment = sources[segmentIndex].segment;
|
|
143
|
+
let last;
|
|
144
|
+
for (let index = phrases.length - 1; index >= 0; index -= 1) {
|
|
145
|
+
if (phrases[index].audioSegment === segmentIndex) {
|
|
146
|
+
last = phrases[index];
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (last) {
|
|
151
|
+
const segmentEnd = segment.startTime + segment.duration;
|
|
152
|
+
last.endTime = Math.min(segmentEnd, Math.max(last.endTime, segmentEnd - 0.05));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
phrases,
|
|
158
|
+
...options.generatedAt ? { generatedAt: options.generatedAt } : {},
|
|
159
|
+
version: options.version ?? DEFAULT_VERSION
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
buildCaptionTrack
|
|
165
|
+
};
|