@storyteller-platform/align 0.1.56 → 0.1.58

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 (118) hide show
  1. package/dist/align/align.cjs +38 -19
  2. package/dist/align/align.d.cts +12 -1
  3. package/dist/align/align.d.ts +12 -1
  4. package/dist/align/align.js +38 -19
  5. package/dist/align/ctc/Aligner.cjs +800 -0
  6. package/dist/align/ctc/Aligner.d.cts +56 -0
  7. package/dist/align/ctc/Aligner.d.ts +56 -0
  8. package/dist/align/ctc/Aligner.js +789 -0
  9. package/dist/align/ctc/align.cjs +189 -0
  10. package/dist/align/ctc/align.d.cts +14 -0
  11. package/dist/align/ctc/align.d.ts +14 -0
  12. package/dist/align/ctc/align.js +132 -0
  13. package/dist/{process/mime.cjs → align/ctc/emissions.cjs} +7 -20
  14. package/dist/align/ctc/emissions.d.cts +6 -0
  15. package/dist/align/ctc/emissions.d.ts +6 -0
  16. package/dist/align/ctc/emissions.js +7 -0
  17. package/dist/align/ctc/forcedAlign.cjs +273 -0
  18. package/dist/align/ctc/forcedAlign.d.cts +21 -0
  19. package/dist/align/ctc/forcedAlign.d.ts +21 -0
  20. package/dist/align/ctc/forcedAlign.js +248 -0
  21. package/dist/align/ctc/greedyDecode.cjs +61 -0
  22. package/dist/align/ctc/greedyDecode.d.cts +15 -0
  23. package/dist/align/ctc/greedyDecode.d.ts +15 -0
  24. package/dist/align/ctc/greedyDecode.js +37 -0
  25. package/dist/align/ctc/mediaOverlay.cjs +248 -0
  26. package/dist/align/ctc/mediaOverlay.d.cts +16 -0
  27. package/dist/align/ctc/mediaOverlay.d.ts +16 -0
  28. package/dist/align/ctc/mediaOverlay.js +224 -0
  29. package/dist/align/ctc/report.cjs +66 -0
  30. package/dist/align/ctc/report.d.cts +79 -0
  31. package/dist/align/ctc/report.d.ts +79 -0
  32. package/dist/align/ctc/report.js +42 -0
  33. package/dist/align/ctc/search.cjs +437 -0
  34. package/dist/align/ctc/search.d.cts +46 -0
  35. package/dist/align/ctc/search.d.ts +46 -0
  36. package/dist/align/ctc/search.js +412 -0
  37. package/dist/align/getSentenceRanges.cjs +9 -3
  38. package/dist/align/getSentenceRanges.d.cts +1 -0
  39. package/dist/align/getSentenceRanges.d.ts +1 -0
  40. package/dist/align/getSentenceRanges.js +9 -3
  41. package/dist/align/parse.cjs +17 -4
  42. package/dist/align/parse.d.cts +8 -4
  43. package/dist/align/parse.d.ts +8 -4
  44. package/dist/align/parse.js +19 -4
  45. package/dist/align/search.cjs +76 -23
  46. package/dist/align/search.d.cts +8 -4
  47. package/dist/align/search.d.ts +8 -4
  48. package/dist/align/search.js +76 -24
  49. package/dist/align/slugify.cjs +3 -2
  50. package/dist/align/slugify.d.cts +1 -1
  51. package/dist/align/slugify.d.ts +1 -1
  52. package/dist/align/slugify.js +3 -2
  53. package/dist/cli/bin.cjs +204 -114
  54. package/dist/cli/bin.js +194 -103
  55. package/dist/common/ffmpeg.cjs +10 -5
  56. package/dist/common/ffmpeg.js +10 -5
  57. package/dist/common/logging.cjs +33 -1
  58. package/dist/common/logging.js +33 -1
  59. package/dist/emit/emit.cjs +105 -0
  60. package/dist/emit/emit.d.cts +15 -0
  61. package/dist/emit/emit.d.ts +15 -0
  62. package/dist/emit/emit.js +86 -0
  63. package/dist/emit/fs.cjs +227 -0
  64. package/dist/emit/fs.d.cts +22 -0
  65. package/dist/emit/fs.d.ts +22 -0
  66. package/dist/emit/fs.js +162 -0
  67. package/dist/emit/parse.cjs +68 -0
  68. package/dist/emit/parse.d.cts +26 -0
  69. package/dist/emit/parse.d.ts +26 -0
  70. package/dist/emit/parse.js +58 -0
  71. package/dist/index.d.cts +1 -1
  72. package/dist/index.d.ts +1 -1
  73. package/dist/markup/model.cjs +0 -16
  74. package/dist/markup/model.d.cts +1 -1
  75. package/dist/markup/model.d.ts +1 -1
  76. package/dist/markup/model.js +0 -12
  77. package/dist/markup/parseDom.cjs +13 -14
  78. package/dist/markup/parseDom.d.cts +2 -2
  79. package/dist/markup/parseDom.d.ts +2 -2
  80. package/dist/markup/parseDom.js +13 -18
  81. package/dist/markup/resolvedPos.d.cts +1 -1
  82. package/dist/markup/resolvedPos.d.ts +1 -1
  83. package/dist/markup/segmentation.cjs +3 -1
  84. package/dist/markup/segmentation.js +3 -1
  85. package/dist/markup/serializeDom.cjs +1 -1
  86. package/dist/markup/serializeDom.d.cts +1 -1
  87. package/dist/markup/serializeDom.d.ts +1 -1
  88. package/dist/markup/serializeDom.js +1 -1
  89. package/dist/markup/transform.cjs +5 -2
  90. package/dist/markup/transform.d.cts +1 -1
  91. package/dist/markup/transform.d.ts +1 -1
  92. package/dist/markup/transform.js +5 -4
  93. package/dist/{model-CZ2mMHJO.d.cts → model-CeOMDDgt.d.cts} +3 -11
  94. package/dist/{model-CZ2mMHJO.d.ts → model-CeOMDDgt.d.ts} +3 -11
  95. package/dist/process/processAudiobook.cjs +12 -23
  96. package/dist/process/processAudiobook.js +14 -30
  97. package/dist/process/ranges.cjs +2 -2
  98. package/dist/process/ranges.js +2 -2
  99. package/dist/readium/guidedNavigation.cjs +8 -8
  100. package/dist/readium/guidedNavigation.js +8 -8
  101. package/dist/readium/manifest.cjs +5 -4
  102. package/dist/readium/manifest.js +5 -4
  103. package/dist/snapshot/ctc/snapshot.cjs +261 -0
  104. package/dist/snapshot/ctc/snapshot.d.cts +6 -0
  105. package/dist/snapshot/ctc/snapshot.d.ts +6 -0
  106. package/dist/snapshot/ctc/snapshot.js +200 -0
  107. package/dist/snapshot/parse.cjs +29 -16
  108. package/dist/snapshot/parse.d.cts +14 -10
  109. package/dist/snapshot/parse.d.ts +14 -10
  110. package/dist/snapshot/parse.js +32 -17
  111. package/dist/transcribe/parse.d.cts +6 -6
  112. package/dist/transcribe/parse.d.ts +6 -6
  113. package/dist/transcribe/transcribe.cjs +4 -2
  114. package/dist/transcribe/transcribe.js +4 -2
  115. package/package.json +6 -4
  116. package/dist/process/mime.d.cts +0 -3
  117. package/dist/process/mime.d.ts +0 -3
  118. package/dist/process/mime.js +0 -24
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var search_exports = {};
20
+ __export(search_exports, {
21
+ computeEvidenceRange: () => computeEvidenceRange,
22
+ findBoundariesInDocument: () => findBoundariesInDocument,
23
+ findCtcBoundaries: () => findCtcBoundaries
24
+ });
25
+ module.exports = __toCommonJS(search_exports);
26
+ var import_itertools = require("itertools");
27
+ var import_greedyDecode = require("./greedyDecode.cjs");
28
+ const NGRAM_SIZE = 10;
29
+ const RANSAC_ITERATIONS = 500;
30
+ const MIN_INLIERS = 10;
31
+ const MAX_PAIR_SEPARATION_REQUIREMENT = 200;
32
+ const MIN_CONFIDENCE = 0.5;
33
+ const MIN_COVERAGE = 0.3;
34
+ const REFINE_COUNT = 25;
35
+ const ANCHOR_SPACING = 2e3;
36
+ const BOUNDARY_PAD_FRAMES = 500;
37
+ const EVIDENCE_GAP_MULTIPLIER = 3;
38
+ const EVIDENCE_GAP_FLOOR = 500;
39
+ const EVIDENCE_GAP_PERCENTILE = 0.99;
40
+ const MIN_EDGE_CLUSTER_OFFSETS = 10;
41
+ const EDGE_EXTENSION_MAX_GAP = 1e3;
42
+ const EDGE_EXTENSION_WINDOW = 250;
43
+ const EDGE_EXTENSION_PAIR_SEPARATION = 50;
44
+ const ANCHOR_CONTEXT = 40;
45
+ const ANCHOR_AGREEMENT_FRACTION = 0.4;
46
+ const ANCHOR_AGREEMENT_FLOOR = 0.05;
47
+ const ANCHOR_CALIBRATION_MINIMUM = 5;
48
+ const ngramIndexes = /* @__PURE__ */ new WeakMap();
49
+ const decodedEmissions = /* @__PURE__ */ new WeakMap();
50
+ function median(values) {
51
+ const sorted = Array.from(values).sort((a, b) => a - b);
52
+ return sorted[Math.floor(sorted.length / 2)];
53
+ }
54
+ function getNgramIndex(document) {
55
+ const cached = ngramIndexes.get(document);
56
+ if (cached) return cached;
57
+ const { text, positions } = document;
58
+ const index = /* @__PURE__ */ new Map();
59
+ for (const i of (0, import_itertools.range)(text.length - NGRAM_SIZE + 1)) {
60
+ const ngram = text.slice(i, i + NGRAM_SIZE);
61
+ const position = positions[i];
62
+ const entries = index.get(ngram);
63
+ if (entries) {
64
+ entries.push(position);
65
+ } else {
66
+ index.set(ngram, [position]);
67
+ }
68
+ }
69
+ ngramIndexes.set(document, index);
70
+ return index;
71
+ }
72
+ function collectMatches(query, document, excludedRanges) {
73
+ const index = getNgramIndex(document);
74
+ const queryNgramOffsets = /* @__PURE__ */ new Map();
75
+ const matches = [];
76
+ for (const i of (0, import_itertools.range)(query.length - NGRAM_SIZE + 1)) {
77
+ const queryNgram = query.slice(i, i + NGRAM_SIZE);
78
+ const offsets = queryNgramOffsets.get(queryNgram);
79
+ if (offsets) offsets.push(i);
80
+ else queryNgramOffsets.set(queryNgram, [i]);
81
+ const positions = index.get(queryNgram);
82
+ if (!positions) continue;
83
+ for (const position of positions) {
84
+ if (excludedRanges.some(
85
+ ([start, end]) => start <= position && position < end
86
+ )) {
87
+ continue;
88
+ }
89
+ matches.push({ offset: i, position });
90
+ }
91
+ }
92
+ return { matches, queryNgramOffsets };
93
+ }
94
+ function fitLine(matches, pairSeparation, { minSlope, maxSlope, inlierTolerance }) {
95
+ let seed = 625341585;
96
+ const random = () => {
97
+ seed = Math.imul(seed, 1103515245) + 12345 & 2147483647;
98
+ return seed / 2147483648;
99
+ };
100
+ let best = null;
101
+ for (let i = 0; i < RANSAC_ITERATIONS; i++) {
102
+ const a = matches[Math.floor(random() * matches.length)];
103
+ const b = matches[Math.floor(random() * matches.length)];
104
+ if (Math.abs(b.offset - a.offset) < pairSeparation) continue;
105
+ const slope2 = (b.position - a.position) / (b.offset - a.offset);
106
+ if (slope2 < minSlope || slope2 > maxSlope) continue;
107
+ const intercept2 = a.position - slope2 * a.offset;
108
+ let count = 0;
109
+ for (const m of matches) {
110
+ const predicted = intercept2 + slope2 * m.offset;
111
+ if (Math.abs(m.position - predicted) <= inlierTolerance) count++;
112
+ }
113
+ if (!best || count > best.count) {
114
+ best = { slope: slope2, intercept: intercept2, count };
115
+ }
116
+ }
117
+ if (!best || best.count < MIN_INLIERS) return null;
118
+ const inliers = matches.filter((m) => {
119
+ const predicted = best.intercept + best.slope * m.offset;
120
+ return Math.abs(m.position - predicted) <= inlierTolerance;
121
+ });
122
+ const sorted = inliers.sort((a, b) => a.offset - b.offset);
123
+ const stride = Math.max(Math.floor(sorted.length / 2), 1);
124
+ const slopes = [];
125
+ for (let i = 0; i + stride < sorted.length; i++) {
126
+ const lo = sorted[i];
127
+ const hi = sorted[i + stride];
128
+ if (hi.offset - lo.offset < pairSeparation) continue;
129
+ slopes.push((hi.position - lo.position) / (hi.offset - lo.offset));
130
+ }
131
+ const slope = slopes.length ? median(slopes) : best.slope;
132
+ const intercept = sorted.length ? median(sorted.map((m) => m.position - slope * m.offset)) : best.intercept;
133
+ return {
134
+ slope,
135
+ intercept,
136
+ inliers
137
+ };
138
+ }
139
+ function bigramSimilarity(a, b) {
140
+ if (a.length < 2 || b.length < 2) return 0;
141
+ const bigrams = /* @__PURE__ */ new Map();
142
+ for (const i of (0, import_itertools.range)(a.length - 1)) {
143
+ const bigram = a.slice(i, i + 2);
144
+ bigrams.set(bigram, (bigrams.get(bigram) ?? 0) + 1);
145
+ }
146
+ let shared = 0;
147
+ for (const i of (0, import_itertools.range)(b.length - 1)) {
148
+ const bigram = b.slice(i, i + 2);
149
+ const remaining = bigrams.get(bigram) ?? 0;
150
+ if (remaining > 0) {
151
+ shared++;
152
+ bigrams.set(bigram, remaining - 1);
153
+ }
154
+ }
155
+ return shared / Math.max(a.length - 1, b.length - 1);
156
+ }
157
+ function positionToDocumentIndex(document, position) {
158
+ const { positions } = document;
159
+ let low = 0;
160
+ let high = positions.length - 1;
161
+ while (low < high) {
162
+ const mid = low + high >> 1;
163
+ if (positions[mid] < position) low = mid + 1;
164
+ else high = mid;
165
+ }
166
+ return low;
167
+ }
168
+ function contextAgreement(match, query, document) {
169
+ const index = positionToDocumentIndex(document, match.position);
170
+ const queryLeft = query.slice(
171
+ Math.max(0, match.offset - ANCHOR_CONTEXT),
172
+ match.offset
173
+ );
174
+ const documentLeft = document.text.slice(
175
+ Math.max(0, index - ANCHOR_CONTEXT),
176
+ index
177
+ );
178
+ const queryRight = query.slice(
179
+ match.offset + NGRAM_SIZE,
180
+ match.offset + NGRAM_SIZE + ANCHOR_CONTEXT
181
+ );
182
+ const documentRight = document.text.slice(
183
+ index + NGRAM_SIZE,
184
+ index + NGRAM_SIZE + ANCHOR_CONTEXT
185
+ );
186
+ return Math.max(
187
+ bigramSimilarity(queryLeft, documentLeft),
188
+ bigramSimilarity(queryRight, documentRight)
189
+ );
190
+ }
191
+ function selectAnchors(query, inliers, queryNgramOffsets, document, slope, intercept) {
192
+ const documentNgramIndex = getNgramIndex(document);
193
+ const agreements = /* @__PURE__ */ new Map();
194
+ const agreement = (match) => {
195
+ let score = agreements.get(match);
196
+ if (score === void 0) {
197
+ score = contextAgreement(match, query, document);
198
+ agreements.set(match, score);
199
+ }
200
+ return score;
201
+ };
202
+ const select = (minAgreement) => {
203
+ const anchors2 = [];
204
+ for (let i = 0; i < query.length; i += ANCHOR_SPACING) {
205
+ const windowStart = Math.max(0, i - ANCHOR_SPACING / 2);
206
+ const windowEnd = Math.min(query.length, i + ANCHOR_SPACING / 2);
207
+ const candidates = inliers.filter(({ offset }) => windowStart <= offset && offset < windowEnd).sort(
208
+ ({ offset: a }, { offset: b }) => Math.abs(a - i) - Math.abs(b - i)
209
+ );
210
+ for (const c of candidates) {
211
+ const gram = query.slice(c.offset, c.offset + NGRAM_SIZE);
212
+ if (documentNgramIndex.get(gram)?.length !== 1) {
213
+ continue;
214
+ }
215
+ if (queryNgramOffsets.get(gram)?.length !== 1) {
216
+ continue;
217
+ }
218
+ if (minAgreement !== null && agreement(c) < minAgreement) {
219
+ continue;
220
+ }
221
+ anchors2.push(c);
222
+ break;
223
+ }
224
+ }
225
+ return anchors2;
226
+ };
227
+ let anchors = select(null);
228
+ if (anchors.length >= ANCHOR_CALIBRATION_MINIMUM) {
229
+ const threshold = Math.max(
230
+ ANCHOR_AGREEMENT_FRACTION * median(anchors.map(agreement)),
231
+ ANCHOR_AGREEMENT_FLOOR
232
+ );
233
+ anchors = select(threshold);
234
+ }
235
+ const monotonicAnchors = [];
236
+ for (const a of anchors) {
237
+ while (true) {
238
+ const b = monotonicAnchors.at(-1);
239
+ if (!b || a.position - b.position >= a.offset - b.offset) {
240
+ monotonicAnchors.push(a);
241
+ break;
242
+ }
243
+ const aDist = Math.abs(a.position - (intercept + slope * a.offset));
244
+ const bDist = Math.abs(b.position - (intercept + slope * b.offset));
245
+ if (aDist > bDist) break;
246
+ else monotonicAnchors.pop();
247
+ }
248
+ }
249
+ return monotonicAnchors;
250
+ }
251
+ function fitLocalLine(points, fallbackSlope) {
252
+ const stride = Math.max(Math.floor(points.length / 2), 1);
253
+ const slopes = [];
254
+ for (const i of (0, import_itertools.range)(0, points.length - stride, stride)) {
255
+ const lo = points[i];
256
+ const hi = points[i + stride];
257
+ if (hi.offset - lo.offset < EDGE_EXTENSION_PAIR_SEPARATION) continue;
258
+ slopes.push((hi.position - lo.position) / (hi.offset - lo.offset));
259
+ }
260
+ const slope = slopes.length ? median(slopes) : fallbackSlope;
261
+ const intercept = median(points.map((m) => m.position - slope * m.offset));
262
+ return { slope, intercept };
263
+ }
264
+ function extendEdge(sortedMatches, sortedInliers, globalSlope, inlierTolerance) {
265
+ const lastInlier = sortedInliers.at(-1);
266
+ if (!lastInlier) return [];
267
+ const window = sortedInliers.slice(-EDGE_EXTENSION_WINDOW);
268
+ const accepted = [];
269
+ let lastAcceptedOffset = lastInlier.offset;
270
+ let local = fitLocalLine(window, globalSlope);
271
+ for (const match of sortedMatches) {
272
+ if (match.offset <= lastInlier.offset) continue;
273
+ if (match.offset - lastAcceptedOffset > EDGE_EXTENSION_MAX_GAP) break;
274
+ const predicted = local.intercept + local.slope * match.offset;
275
+ if (Math.abs(match.position - predicted) > inlierTolerance) continue;
276
+ accepted.push(match);
277
+ window.push(match);
278
+ if (window.length > EDGE_EXTENSION_WINDOW) window.shift();
279
+ lastAcceptedOffset = match.offset;
280
+ local = fitLocalLine(window, globalSlope);
281
+ }
282
+ return accepted;
283
+ }
284
+ function extendInlierEdges(matches, sortedInliers, slope, inlierTolerance) {
285
+ const byOffset = matches.toSorted((a, b) => a.offset - b.offset);
286
+ const tail = extendEdge(byOffset, sortedInliers, slope, inlierTolerance);
287
+ function mirror(m) {
288
+ return { offset: -m.offset, position: -m.position };
289
+ }
290
+ const head = extendEdge(
291
+ byOffset.map(mirror).reverse(),
292
+ sortedInliers.map(mirror).reverse(),
293
+ slope,
294
+ inlierTolerance
295
+ ).map(mirror).reverse();
296
+ return [...head, ...sortedInliers, ...tail];
297
+ }
298
+ function computeEvidenceRange(offsets, queryLength) {
299
+ if (offsets.length < 2) {
300
+ return { evidenceRange: [0, queryLength], matchedRange: [0, queryLength] };
301
+ }
302
+ const gaps = [];
303
+ for (let i = 1; i < offsets.length; i++) {
304
+ gaps.push(offsets[i] - offsets[i - 1]);
305
+ }
306
+ const sortedGaps = gaps.toSorted((a, b) => a - b);
307
+ const p99 = sortedGaps[Math.min(
308
+ sortedGaps.length - 1,
309
+ Math.floor(sortedGaps.length * EVIDENCE_GAP_PERCENTILE)
310
+ )];
311
+ const threshold = Math.max(EVIDENCE_GAP_MULTIPLIER * p99, EVIDENCE_GAP_FLOOR);
312
+ const clusters = [];
313
+ let clusterStart = 0;
314
+ for (const [i, gap] of (0, import_itertools.enumerate)(gaps, 1)) {
315
+ if (gap <= threshold) continue;
316
+ clusters.push([clusterStart, i - 1]);
317
+ clusterStart = i;
318
+ }
319
+ clusters.push([clusterStart, offsets.length - 1]);
320
+ let firstCluster = 0;
321
+ let lastCluster = clusters.length - 1;
322
+ while (firstCluster < lastCluster && clusters[firstCluster][1] - clusters[firstCluster][0] + 1 < MIN_EDGE_CLUSTER_OFFSETS) {
323
+ firstCluster++;
324
+ }
325
+ while (lastCluster > firstCluster && clusters[lastCluster][1] - clusters[lastCluster][0] + 1 < MIN_EDGE_CLUSTER_OFFSETS) {
326
+ lastCluster--;
327
+ }
328
+ const first = offsets[clusters[firstCluster][0]];
329
+ const last = offsets[clusters[lastCluster][1]] + NGRAM_SIZE;
330
+ return {
331
+ matchedRange: [first, last],
332
+ evidenceRange: [
333
+ first > threshold ? first - threshold : 0,
334
+ queryLength - last > threshold ? last + threshold : queryLength
335
+ ]
336
+ };
337
+ }
338
+ function findBoundariesInDocument(query, document, options) {
339
+ const gramCount = Math.max(query.length - NGRAM_SIZE + 1, 0);
340
+ const pairSeparation = Math.min(
341
+ Math.max(Math.floor(gramCount / 4), 20),
342
+ MAX_PAIR_SEPARATION_REQUIREMENT
343
+ );
344
+ const { matches, queryNgramOffsets } = collectMatches(
345
+ query,
346
+ document,
347
+ options.excludedRanges
348
+ );
349
+ if (matches.length < MIN_INLIERS) return null;
350
+ const line = fitLine(matches, pairSeparation, options);
351
+ if (!line) return null;
352
+ const { slope, intercept, inliers } = line;
353
+ const byOffset = extendInlierEdges(
354
+ matches,
355
+ inliers.sort((a, b) => a.offset - b.offset),
356
+ slope,
357
+ options.inlierTolerance
358
+ );
359
+ const inlierOffsets = Array.from(
360
+ new Set(byOffset.map(({ offset }) => offset))
361
+ );
362
+ const { evidenceRange, matchedRange } = computeEvidenceRange(
363
+ inlierOffsets,
364
+ query.length
365
+ );
366
+ const head = byOffset.slice(0, REFINE_COUNT);
367
+ const tail = byOffset.slice(-REFINE_COUNT);
368
+ const extrapolatedStart = Math.min(
369
+ ...head.map((m) => m.position - slope * m.offset)
370
+ );
371
+ const extrapolatedEnd = Math.max(
372
+ ...tail.map((m) => m.position + slope * (query.length - m.offset))
373
+ );
374
+ const startPad = Math.max(
375
+ BOUNDARY_PAD_FRAMES,
376
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
377
+ 0.25 * (head[0].position - extrapolatedStart)
378
+ );
379
+ const start = extrapolatedStart - startPad;
380
+ const endPad = Math.max(
381
+ BOUNDARY_PAD_FRAMES,
382
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
383
+ 0.25 * (extrapolatedEnd - tail.at(-1).position)
384
+ );
385
+ const end = extrapolatedEnd + endPad;
386
+ if (end <= start) return null;
387
+ const matchedGrams = new Set(matches.map((m) => m.offset)).size;
388
+ const inlierGrams = inlierOffsets.length;
389
+ const confidence = inlierGrams / matchedGrams;
390
+ if (confidence < MIN_CONFIDENCE) return null;
391
+ const first = byOffset[0];
392
+ const last = byOffset[byOffset.length - 1];
393
+ const coverage = (last.offset - first.offset + NGRAM_SIZE) / query.length;
394
+ if (coverage < MIN_COVERAGE) return null;
395
+ const anchors = selectAnchors(
396
+ query,
397
+ inliers,
398
+ queryNgramOffsets,
399
+ document,
400
+ slope,
401
+ intercept
402
+ );
403
+ return {
404
+ start: Math.round(start),
405
+ end: Math.round(end),
406
+ confidence,
407
+ anchors,
408
+ evidenceRange,
409
+ matchedRange
410
+ };
411
+ }
412
+ async function findCtcBoundaries(query, emissionsReader, excludedRanges) {
413
+ let document = decodedEmissions.get(emissionsReader);
414
+ if (!document) {
415
+ const { text, frames } = await (0, import_greedyDecode.ctcGreedyDecode)(emissionsReader);
416
+ document = { text, positions: frames };
417
+ decodedEmissions.set(emissionsReader, document);
418
+ }
419
+ const result = findBoundariesInDocument(query, document, {
420
+ excludedRanges,
421
+ minSlope: 2,
422
+ maxSlope: 15,
423
+ inlierTolerance: 2500
424
+ });
425
+ if (!result) return null;
426
+ return {
427
+ ...result,
428
+ start: Math.max(result.start, 0),
429
+ end: Math.min(result.end, emissionsReader.numFrames - 1)
430
+ };
431
+ }
432
+ // Annotate the CommonJS export names for ESM import in node:
433
+ 0 && (module.exports = {
434
+ computeEvidenceRange,
435
+ findBoundariesInDocument,
436
+ findCtcBoundaries
437
+ });
@@ -0,0 +1,46 @@
1
+ import { EmissionsReader } from '../../emit/fs.cjs';
2
+ import '@storyteller-platform/ghost-story';
3
+ import './emissions.cjs';
4
+
5
+ /**
6
+ * A searchable document. `text` holds the matchable characters, and
7
+ * `positions[i]` is the position of `text[i]` in whatever units the caller
8
+ * wants boundaries expressed in — emission frames for CTC decodings,
9
+ * original character offsets for transcription text, etc.
10
+ */
11
+ type PositionedDocument = {
12
+ text: string;
13
+ positions: number[];
14
+ };
15
+ interface BoundaryFitOptions {
16
+ minSlope: number;
17
+ maxSlope: number;
18
+ inlierTolerance: number;
19
+ excludedRanges: [number, number][];
20
+ }
21
+ type Match = {
22
+ offset: number;
23
+ position: number;
24
+ };
25
+ declare function computeEvidenceRange(offsets: number[], queryLength: number): {
26
+ evidenceRange: [number, number];
27
+ matchedRange: [number, number];
28
+ };
29
+ declare function findBoundariesInDocument(query: string, document: PositionedDocument, options: BoundaryFitOptions): {
30
+ start: number;
31
+ end: number;
32
+ confidence: number;
33
+ anchors: Match[];
34
+ evidenceRange: [number, number];
35
+ matchedRange: [number, number];
36
+ } | null;
37
+ declare function findCtcBoundaries(query: string, emissionsReader: EmissionsReader, excludedRanges: [number, number][]): Promise<{
38
+ start: number;
39
+ end: number;
40
+ confidence: number;
41
+ anchors: Match[];
42
+ evidenceRange: [number, number];
43
+ matchedRange: [number, number];
44
+ } | null>;
45
+
46
+ export { type BoundaryFitOptions, type Match, type PositionedDocument, computeEvidenceRange, findBoundariesInDocument, findCtcBoundaries };
@@ -0,0 +1,46 @@
1
+ import { EmissionsReader } from '../../emit/fs.js';
2
+ import '@storyteller-platform/ghost-story';
3
+ import './emissions.js';
4
+
5
+ /**
6
+ * A searchable document. `text` holds the matchable characters, and
7
+ * `positions[i]` is the position of `text[i]` in whatever units the caller
8
+ * wants boundaries expressed in — emission frames for CTC decodings,
9
+ * original character offsets for transcription text, etc.
10
+ */
11
+ type PositionedDocument = {
12
+ text: string;
13
+ positions: number[];
14
+ };
15
+ interface BoundaryFitOptions {
16
+ minSlope: number;
17
+ maxSlope: number;
18
+ inlierTolerance: number;
19
+ excludedRanges: [number, number][];
20
+ }
21
+ type Match = {
22
+ offset: number;
23
+ position: number;
24
+ };
25
+ declare function computeEvidenceRange(offsets: number[], queryLength: number): {
26
+ evidenceRange: [number, number];
27
+ matchedRange: [number, number];
28
+ };
29
+ declare function findBoundariesInDocument(query: string, document: PositionedDocument, options: BoundaryFitOptions): {
30
+ start: number;
31
+ end: number;
32
+ confidence: number;
33
+ anchors: Match[];
34
+ evidenceRange: [number, number];
35
+ matchedRange: [number, number];
36
+ } | null;
37
+ declare function findCtcBoundaries(query: string, emissionsReader: EmissionsReader, excludedRanges: [number, number][]): Promise<{
38
+ start: number;
39
+ end: number;
40
+ confidence: number;
41
+ anchors: Match[];
42
+ evidenceRange: [number, number];
43
+ matchedRange: [number, number];
44
+ } | null>;
45
+
46
+ export { type BoundaryFitOptions, type Match, type PositionedDocument, computeEvidenceRange, findBoundariesInDocument, findCtcBoundaries };