@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.
- package/dist/align/align.cjs +38 -19
- package/dist/align/align.d.cts +12 -1
- package/dist/align/align.d.ts +12 -1
- package/dist/align/align.js +38 -19
- package/dist/align/ctc/Aligner.cjs +800 -0
- package/dist/align/ctc/Aligner.d.cts +56 -0
- package/dist/align/ctc/Aligner.d.ts +56 -0
- package/dist/align/ctc/Aligner.js +789 -0
- package/dist/align/ctc/align.cjs +189 -0
- package/dist/align/ctc/align.d.cts +14 -0
- package/dist/align/ctc/align.d.ts +14 -0
- package/dist/align/ctc/align.js +132 -0
- package/dist/{process/mime.cjs → align/ctc/emissions.cjs} +7 -20
- package/dist/align/ctc/emissions.d.cts +6 -0
- package/dist/align/ctc/emissions.d.ts +6 -0
- package/dist/align/ctc/emissions.js +7 -0
- package/dist/align/ctc/forcedAlign.cjs +273 -0
- package/dist/align/ctc/forcedAlign.d.cts +21 -0
- package/dist/align/ctc/forcedAlign.d.ts +21 -0
- package/dist/align/ctc/forcedAlign.js +248 -0
- package/dist/align/ctc/greedyDecode.cjs +61 -0
- package/dist/align/ctc/greedyDecode.d.cts +15 -0
- package/dist/align/ctc/greedyDecode.d.ts +15 -0
- package/dist/align/ctc/greedyDecode.js +37 -0
- package/dist/align/ctc/mediaOverlay.cjs +248 -0
- package/dist/align/ctc/mediaOverlay.d.cts +16 -0
- package/dist/align/ctc/mediaOverlay.d.ts +16 -0
- package/dist/align/ctc/mediaOverlay.js +224 -0
- package/dist/align/ctc/report.cjs +66 -0
- package/dist/align/ctc/report.d.cts +79 -0
- package/dist/align/ctc/report.d.ts +79 -0
- package/dist/align/ctc/report.js +42 -0
- package/dist/align/ctc/search.cjs +437 -0
- package/dist/align/ctc/search.d.cts +46 -0
- package/dist/align/ctc/search.d.ts +46 -0
- package/dist/align/ctc/search.js +412 -0
- package/dist/align/getSentenceRanges.cjs +9 -3
- package/dist/align/getSentenceRanges.d.cts +1 -0
- package/dist/align/getSentenceRanges.d.ts +1 -0
- package/dist/align/getSentenceRanges.js +9 -3
- package/dist/align/parse.cjs +17 -4
- package/dist/align/parse.d.cts +8 -4
- package/dist/align/parse.d.ts +8 -4
- package/dist/align/parse.js +19 -4
- package/dist/align/search.cjs +76 -23
- package/dist/align/search.d.cts +8 -4
- package/dist/align/search.d.ts +8 -4
- package/dist/align/search.js +76 -24
- package/dist/align/slugify.cjs +3 -2
- package/dist/align/slugify.d.cts +1 -1
- package/dist/align/slugify.d.ts +1 -1
- package/dist/align/slugify.js +3 -2
- package/dist/cli/bin.cjs +204 -114
- package/dist/cli/bin.js +194 -103
- package/dist/common/ffmpeg.cjs +10 -5
- package/dist/common/ffmpeg.js +10 -5
- package/dist/common/logging.cjs +33 -1
- package/dist/common/logging.js +33 -1
- package/dist/emit/emit.cjs +105 -0
- package/dist/emit/emit.d.cts +15 -0
- package/dist/emit/emit.d.ts +15 -0
- package/dist/emit/emit.js +86 -0
- package/dist/emit/fs.cjs +227 -0
- package/dist/emit/fs.d.cts +22 -0
- package/dist/emit/fs.d.ts +22 -0
- package/dist/emit/fs.js +162 -0
- package/dist/emit/parse.cjs +68 -0
- package/dist/emit/parse.d.cts +26 -0
- package/dist/emit/parse.d.ts +26 -0
- package/dist/emit/parse.js +58 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/markup/model.cjs +0 -16
- package/dist/markup/model.d.cts +1 -1
- package/dist/markup/model.d.ts +1 -1
- package/dist/markup/model.js +0 -12
- package/dist/markup/parseDom.cjs +13 -14
- package/dist/markup/parseDom.d.cts +2 -2
- package/dist/markup/parseDom.d.ts +2 -2
- package/dist/markup/parseDom.js +13 -18
- package/dist/markup/resolvedPos.d.cts +1 -1
- package/dist/markup/resolvedPos.d.ts +1 -1
- package/dist/markup/segmentation.cjs +3 -1
- package/dist/markup/segmentation.js +3 -1
- package/dist/markup/serializeDom.cjs +1 -1
- package/dist/markup/serializeDom.d.cts +1 -1
- package/dist/markup/serializeDom.d.ts +1 -1
- package/dist/markup/serializeDom.js +1 -1
- package/dist/markup/transform.cjs +5 -2
- package/dist/markup/transform.d.cts +1 -1
- package/dist/markup/transform.d.ts +1 -1
- package/dist/markup/transform.js +5 -4
- package/dist/{model-CZ2mMHJO.d.cts → model-CeOMDDgt.d.cts} +3 -11
- package/dist/{model-CZ2mMHJO.d.ts → model-CeOMDDgt.d.ts} +3 -11
- package/dist/process/processAudiobook.cjs +12 -23
- package/dist/process/processAudiobook.js +14 -30
- package/dist/process/ranges.cjs +2 -2
- package/dist/process/ranges.js +2 -2
- package/dist/readium/guidedNavigation.cjs +8 -8
- package/dist/readium/guidedNavigation.js +8 -8
- package/dist/readium/manifest.cjs +5 -4
- package/dist/readium/manifest.js +5 -4
- package/dist/snapshot/ctc/snapshot.cjs +261 -0
- package/dist/snapshot/ctc/snapshot.d.cts +6 -0
- package/dist/snapshot/ctc/snapshot.d.ts +6 -0
- package/dist/snapshot/ctc/snapshot.js +200 -0
- package/dist/snapshot/parse.cjs +29 -16
- package/dist/snapshot/parse.d.cts +14 -10
- package/dist/snapshot/parse.d.ts +14 -10
- package/dist/snapshot/parse.js +32 -17
- package/dist/transcribe/parse.d.cts +6 -6
- package/dist/transcribe/parse.d.ts +6 -6
- package/dist/transcribe/transcribe.cjs +4 -2
- package/dist/transcribe/transcribe.js +4 -2
- package/package.json +6 -4
- package/dist/process/mime.d.cts +0 -3
- package/dist/process/mime.d.ts +0 -3
- 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 };
|