@storyteller-platform/align 0.1.19 → 0.1.20
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 +105 -44
- package/dist/align/align.d.cts +2 -0
- package/dist/align/align.d.ts +2 -0
- package/dist/align/align.js +108 -45
- package/dist/align/getSentenceRanges.cjs +116 -68
- package/dist/align/getSentenceRanges.d.cts +35 -5
- package/dist/align/getSentenceRanges.d.ts +35 -5
- package/dist/align/getSentenceRanges.js +113 -67
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/markup/markup.cjs +18 -1
- package/dist/markup/markup.d.cts +1 -1
- package/dist/markup/markup.d.ts +1 -1
- package/dist/markup/markup.js +18 -1
- package/dist/markup/serializeDom.cjs +80 -53
- package/dist/markup/serializeDom.d.cts +3 -4
- package/dist/markup/serializeDom.d.ts +3 -4
- package/dist/markup/serializeDom.js +79 -51
- package/package.json +2 -2
package/dist/align/align.cjs
CHANGED
|
@@ -81,6 +81,7 @@ module.exports = __toCommonJS(align_exports);
|
|
|
81
81
|
var import_promises = require("node:fs/promises");
|
|
82
82
|
var import_node_path = require("node:path");
|
|
83
83
|
var import_posix = require("node:path/posix");
|
|
84
|
+
var import_itertools = require("itertools");
|
|
84
85
|
var import_memoize = __toESM(require("memoize"), 1);
|
|
85
86
|
var import_audiobook = require("@storyteller-platform/audiobook");
|
|
86
87
|
var import_epub = require("@storyteller-platform/epub");
|
|
@@ -173,10 +174,11 @@ class Aligner {
|
|
|
173
174
|
primaryLocale: this.languageOverride ?? await this.epub.getLanguage()
|
|
174
175
|
}
|
|
175
176
|
);
|
|
176
|
-
return segmentation.
|
|
177
|
+
return segmentation.filter((s) => s.text.match(/\S/));
|
|
177
178
|
}
|
|
178
179
|
async writeAlignedChapter(alignedChapter) {
|
|
179
|
-
const { chapter, sentenceRanges, xml } = alignedChapter;
|
|
180
|
+
const { chapter, sentenceRanges, wordRanges, xml } = alignedChapter;
|
|
181
|
+
const wordRangeMap = new Map(wordRanges.map((w) => [w[0].sentenceId, w]));
|
|
180
182
|
const audiofiles = Array.from(
|
|
181
183
|
new Set(sentenceRanges.map(({ audiofile }) => audiofile))
|
|
182
184
|
);
|
|
@@ -209,7 +211,12 @@ class Aligner {
|
|
|
209
211
|
href: `MediaOverlays/${chapterStem}.smil`,
|
|
210
212
|
mediaType: "application/smil+xml"
|
|
211
213
|
},
|
|
212
|
-
createMediaOverlay(
|
|
214
|
+
createMediaOverlay(
|
|
215
|
+
chapter,
|
|
216
|
+
this.granularity,
|
|
217
|
+
sentenceRanges,
|
|
218
|
+
wordRangeMap
|
|
219
|
+
),
|
|
213
220
|
"xml"
|
|
214
221
|
);
|
|
215
222
|
await this.epub.updateManifestItem(chapter.id, {
|
|
@@ -246,17 +253,17 @@ class Aligner {
|
|
|
246
253
|
},
|
|
247
254
|
firstMatchedSentenceId: startSentence,
|
|
248
255
|
firstMatchedSentenceContext: {
|
|
249
|
-
prevSentence: chapterSentences[startSentence - 1] ?? null,
|
|
256
|
+
prevSentence: chapterSentences[startSentence - 1]?.text ?? null,
|
|
250
257
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
251
|
-
matchedSentence: chapterSentences[startSentence],
|
|
252
|
-
nextSentence: chapterSentences[startSentence + 1] ?? null
|
|
258
|
+
matchedSentence: chapterSentences[startSentence].text,
|
|
259
|
+
nextSentence: chapterSentences[startSentence + 1]?.text ?? null
|
|
253
260
|
},
|
|
254
261
|
lastMatchedSentenceId: endSentence,
|
|
255
262
|
lastMatchedSentenceContext: {
|
|
256
|
-
prevSentence: chapterSentences[endSentence - 1] ?? null,
|
|
263
|
+
prevSentence: chapterSentences[endSentence - 1]?.text ?? null,
|
|
257
264
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
258
|
-
matchedSentence: chapterSentences[endSentence],
|
|
259
|
-
nextSentence: chapterSentences[endSentence + 1] ?? null
|
|
265
|
+
matchedSentence: chapterSentences[endSentence].text,
|
|
266
|
+
nextSentence: chapterSentences[endSentence + 1]?.text ?? null
|
|
260
267
|
},
|
|
261
268
|
chapterSentenceCount: chapterSentences.length,
|
|
262
269
|
alignedSentenceCount: sentenceRanges.length,
|
|
@@ -277,7 +284,7 @@ class Aligner {
|
|
|
277
284
|
}, [])
|
|
278
285
|
});
|
|
279
286
|
}
|
|
280
|
-
async alignChapter(chapterId, transcriptionOffset, transcriptionEndOffset, locale,
|
|
287
|
+
async alignChapter(chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline) {
|
|
281
288
|
const timing = (0, import_ghost_story.createTiming)();
|
|
282
289
|
timing.start("read contents");
|
|
283
290
|
const manifest = await this.epub.getManifest();
|
|
@@ -294,14 +301,18 @@ class Aligner {
|
|
|
294
301
|
timing.start("align sentences");
|
|
295
302
|
const {
|
|
296
303
|
sentenceRanges,
|
|
304
|
+
wordRanges,
|
|
297
305
|
transcriptionOffset: endTranscriptionOffset,
|
|
298
306
|
firstFoundSentence,
|
|
299
307
|
lastFoundSentence
|
|
300
308
|
} = await (0, import_getSentenceRanges.getSentenceRanges)(
|
|
301
|
-
|
|
309
|
+
transcriptionText,
|
|
310
|
+
mappedTimeline,
|
|
302
311
|
chapterSentences,
|
|
312
|
+
chapterId,
|
|
303
313
|
transcriptionOffset,
|
|
304
314
|
transcriptionEndOffset,
|
|
315
|
+
this.granularity,
|
|
305
316
|
locale
|
|
306
317
|
);
|
|
307
318
|
timing.end("align sentences");
|
|
@@ -318,8 +329,9 @@ class Aligner {
|
|
|
318
329
|
chapter,
|
|
319
330
|
xml: chapterXml,
|
|
320
331
|
sentenceRanges,
|
|
321
|
-
|
|
322
|
-
|
|
332
|
+
wordRanges,
|
|
333
|
+
startOffset: transcriptionOffset,
|
|
334
|
+
endOffset: endTranscriptionOffset
|
|
323
335
|
});
|
|
324
336
|
this.addChapterReport(
|
|
325
337
|
chapter,
|
|
@@ -357,6 +369,7 @@ class Aligner {
|
|
|
357
369
|
this.transcription.transcript,
|
|
358
370
|
locale
|
|
359
371
|
);
|
|
372
|
+
const mappedTimeline = (0, import_getSentenceRanges.mapTranscriptionTimeline)(this.transcription, mapping);
|
|
360
373
|
for (let index = 0; index < spine.length; index++) {
|
|
361
374
|
onProgress?.(index / spine.length);
|
|
362
375
|
const spineItem = spine[index];
|
|
@@ -371,7 +384,7 @@ class Aligner {
|
|
|
371
384
|
const slugifiedChapterSentences = [];
|
|
372
385
|
for (const chapterSentence of chapterSentences) {
|
|
373
386
|
slugifiedChapterSentences.push(
|
|
374
|
-
(await (0, import_slugify.slugify)(chapterSentence, locale)).result
|
|
387
|
+
(await (0, import_slugify.slugify)(chapterSentence.text, locale)).result
|
|
375
388
|
);
|
|
376
389
|
}
|
|
377
390
|
if (chapterSentences.length === 0) {
|
|
@@ -379,7 +392,7 @@ class Aligner {
|
|
|
379
392
|
continue;
|
|
380
393
|
}
|
|
381
394
|
if (chapterSentences.length < 2 && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
382
|
-
chapterSentences[0].
|
|
395
|
+
chapterSentences[0].words.length < 4) {
|
|
383
396
|
this.logger?.info(
|
|
384
397
|
`Chapter #${index} is fewer than four words; skipping`
|
|
385
398
|
);
|
|
@@ -399,14 +412,13 @@ class Aligner {
|
|
|
399
412
|
if (start === end) {
|
|
400
413
|
continue;
|
|
401
414
|
}
|
|
402
|
-
const transcriptionOffset = mapping.invert().map(Math.max(start, 0), -1);
|
|
403
|
-
const endOffset = mapping.invert().map(Math.min(end, transcriptionText.length), 1);
|
|
404
415
|
const result = await this.alignChapter(
|
|
405
416
|
chapterId,
|
|
406
|
-
|
|
407
|
-
|
|
417
|
+
transcriptionText,
|
|
418
|
+
Math.max(start, 0),
|
|
419
|
+
Math.min(end, transcriptionText.length),
|
|
408
420
|
locale,
|
|
409
|
-
|
|
421
|
+
mappedTimeline
|
|
410
422
|
);
|
|
411
423
|
this.timing.add(result.timing.summary());
|
|
412
424
|
}
|
|
@@ -426,16 +438,36 @@ class Aligner {
|
|
|
426
438
|
}
|
|
427
439
|
return firstAudiofileIndexA - firstAudiofileIndexB;
|
|
428
440
|
});
|
|
429
|
-
|
|
441
|
+
const sentenceRanges = [];
|
|
442
|
+
const chapterSentenceCounts = {};
|
|
430
443
|
for (const alignedChapter of audioOrderedChapters) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
444
|
+
sentenceRanges.push(...alignedChapter.sentenceRanges);
|
|
445
|
+
const sentences = await this.getChapterSentences(
|
|
446
|
+
alignedChapter.chapter.id
|
|
434
447
|
);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
448
|
+
chapterSentenceCounts[alignedChapter.chapter.id] = sentences.length;
|
|
449
|
+
}
|
|
450
|
+
const interpolated = await (0, import_getSentenceRanges.interpolateSentenceRanges)(
|
|
451
|
+
sentenceRanges,
|
|
452
|
+
chapterSentenceCounts
|
|
453
|
+
);
|
|
454
|
+
const expanded = (0, import_getSentenceRanges.expandEmptySentenceRanges)(interpolated);
|
|
455
|
+
const collapsed = await (0, import_getSentenceRanges.collapseSentenceRangeGaps)(expanded);
|
|
456
|
+
let collapsedStart = 0;
|
|
457
|
+
for (const alignedChapter of audioOrderedChapters) {
|
|
458
|
+
const sentences = await this.getChapterSentences(
|
|
459
|
+
alignedChapter.chapter.id
|
|
460
|
+
);
|
|
461
|
+
const finalSentenceRanges = collapsed.slice(
|
|
462
|
+
collapsedStart,
|
|
463
|
+
collapsedStart + sentences.length - 1
|
|
464
|
+
);
|
|
465
|
+
alignedChapter.sentenceRanges = finalSentenceRanges;
|
|
466
|
+
for (const [i, wordRanges] of (0, import_itertools.enumerate)(alignedChapter.wordRanges)) {
|
|
467
|
+
alignedChapter.wordRanges[i] = (0, import_getSentenceRanges.expandEmptySentenceRanges)(wordRanges);
|
|
468
|
+
}
|
|
438
469
|
await this.writeAlignedChapter(alignedChapter);
|
|
470
|
+
collapsedStart += sentences.length - 1;
|
|
439
471
|
}
|
|
440
472
|
await this.epub.addMetadata({
|
|
441
473
|
type: "meta",
|
|
@@ -463,7 +495,7 @@ class Aligner {
|
|
|
463
495
|
return this.timing;
|
|
464
496
|
}
|
|
465
497
|
}
|
|
466
|
-
function createMediaOverlay(chapter, sentenceRanges) {
|
|
498
|
+
function createMediaOverlay(chapter, granularity, sentenceRanges, wordRanges) {
|
|
467
499
|
return [
|
|
468
500
|
import_epub.Epub.createXmlElement(
|
|
469
501
|
"smil",
|
|
@@ -481,24 +513,53 @@ function createMediaOverlay(chapter, sentenceRanges) {
|
|
|
481
513
|
"epub:textref": `../${chapter.href}`,
|
|
482
514
|
"epub:type": "chapter"
|
|
483
515
|
},
|
|
484
|
-
sentenceRanges.map(
|
|
485
|
-
(
|
|
486
|
-
|
|
516
|
+
sentenceRanges.map((sentenceRange) => {
|
|
517
|
+
if (granularity === "sentence" || !wordRanges.has(sentenceRange.id)) {
|
|
518
|
+
return import_epub.Epub.createXmlElement(
|
|
519
|
+
"par",
|
|
520
|
+
{
|
|
521
|
+
id: `${chapter.id}-s${sentenceRange.id}`
|
|
522
|
+
},
|
|
523
|
+
[
|
|
524
|
+
import_epub.Epub.createXmlElement("text", {
|
|
525
|
+
src: `../${chapter.href}#${chapter.id}-s${sentenceRange.id}`
|
|
526
|
+
}),
|
|
527
|
+
import_epub.Epub.createXmlElement("audio", {
|
|
528
|
+
src: `../Audio/${(0, import_posix.basename)(sentenceRange.audiofile)}`,
|
|
529
|
+
clipBegin: `${sentenceRange.start.toFixed(3)}s`,
|
|
530
|
+
clipEnd: `${sentenceRange.end.toFixed(3)}s`
|
|
531
|
+
})
|
|
532
|
+
]
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const words = wordRanges.get(sentenceRange.id);
|
|
536
|
+
return import_epub.Epub.createXmlElement(
|
|
537
|
+
"seq",
|
|
487
538
|
{
|
|
488
|
-
id: `${chapter.id}-s${sentenceRange.id}
|
|
539
|
+
id: `${chapter.id}-s${sentenceRange.id}`,
|
|
540
|
+
"epub:type": "text-range-small",
|
|
541
|
+
"epub:textref": `../${chapter.href}#${chapter.id}-s${sentenceRange.id}`
|
|
489
542
|
},
|
|
490
|
-
|
|
491
|
-
import_epub.Epub.createXmlElement(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
543
|
+
words.map(
|
|
544
|
+
(word) => import_epub.Epub.createXmlElement(
|
|
545
|
+
"par",
|
|
546
|
+
{
|
|
547
|
+
id: `${chapter.id}-s${sentenceRange.id}-w${word.id}`
|
|
548
|
+
},
|
|
549
|
+
[
|
|
550
|
+
import_epub.Epub.createXmlElement("text", {
|
|
551
|
+
src: `../${chapter.href}#${chapter.id}-s${sentenceRange.id}-w${word.id}`
|
|
552
|
+
}),
|
|
553
|
+
import_epub.Epub.createXmlElement("audio", {
|
|
554
|
+
src: `../Audio/${(0, import_posix.basename)(word.audiofile)}`,
|
|
555
|
+
clipBegin: `${word.start.toFixed(3)}s`,
|
|
556
|
+
clipEnd: `${word.end.toFixed(3)}s`
|
|
557
|
+
})
|
|
558
|
+
]
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
);
|
|
562
|
+
})
|
|
502
563
|
)
|
|
503
564
|
])
|
|
504
565
|
]
|
package/dist/align/align.d.cts
CHANGED
|
@@ -3,6 +3,8 @@ import { Logger } from 'pino';
|
|
|
3
3
|
import { Epub } from '@storyteller-platform/epub';
|
|
4
4
|
import { RecognitionResult } from '@storyteller-platform/ghost-story/recognition';
|
|
5
5
|
import { StorytellerTranscription } from './getSentenceRanges.cjs';
|
|
6
|
+
import '@echogarden/text-segmentation';
|
|
7
|
+
import '@storyteller-platform/transliteration';
|
|
6
8
|
|
|
7
9
|
interface AudioFileContext {
|
|
8
10
|
start: number;
|
package/dist/align/align.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Logger } from 'pino';
|
|
|
3
3
|
import { Epub } from '@storyteller-platform/epub';
|
|
4
4
|
import { RecognitionResult } from '@storyteller-platform/ghost-story/recognition';
|
|
5
5
|
import { StorytellerTranscription } from './getSentenceRanges.js';
|
|
6
|
+
import '@echogarden/text-segmentation';
|
|
7
|
+
import '@storyteller-platform/transliteration';
|
|
6
8
|
|
|
7
9
|
interface AudioFileContext {
|
|
8
10
|
start: number;
|
package/dist/align/align.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname as autoDirname, join as autoJoin } from "node:path";
|
|
7
7
|
import { basename, dirname, parse, relative } from "node:path/posix";
|
|
8
|
+
import { enumerate } from "itertools";
|
|
8
9
|
import memoize from "memoize";
|
|
9
10
|
import { isAudioFile, lookupAudioMime } from "@storyteller-platform/audiobook";
|
|
10
11
|
import {
|
|
@@ -17,10 +18,12 @@ import {
|
|
|
17
18
|
import { getTrackDuration } from "../common/ffmpeg.js";
|
|
18
19
|
import { getXhtmlSegmentation } from "../markup/segmentation.js";
|
|
19
20
|
import {
|
|
21
|
+
collapseSentenceRangeGaps,
|
|
20
22
|
expandEmptySentenceRanges,
|
|
21
23
|
getChapterDuration,
|
|
22
24
|
getSentenceRanges,
|
|
23
|
-
interpolateSentenceRanges
|
|
25
|
+
interpolateSentenceRanges,
|
|
26
|
+
mapTranscriptionTimeline
|
|
24
27
|
} from "./getSentenceRanges.js";
|
|
25
28
|
import { findBoundaries } from "./search.js";
|
|
26
29
|
import { slugify } from "./slugify.js";
|
|
@@ -107,10 +110,11 @@ class Aligner {
|
|
|
107
110
|
primaryLocale: this.languageOverride ?? await this.epub.getLanguage()
|
|
108
111
|
}
|
|
109
112
|
);
|
|
110
|
-
return segmentation.
|
|
113
|
+
return segmentation.filter((s) => s.text.match(/\S/));
|
|
111
114
|
}
|
|
112
115
|
async writeAlignedChapter(alignedChapter) {
|
|
113
|
-
const { chapter, sentenceRanges, xml } = alignedChapter;
|
|
116
|
+
const { chapter, sentenceRanges, wordRanges, xml } = alignedChapter;
|
|
117
|
+
const wordRangeMap = new Map(wordRanges.map((w) => [w[0].sentenceId, w]));
|
|
114
118
|
const audiofiles = Array.from(
|
|
115
119
|
new Set(sentenceRanges.map(({ audiofile }) => audiofile))
|
|
116
120
|
);
|
|
@@ -143,7 +147,12 @@ class Aligner {
|
|
|
143
147
|
href: `MediaOverlays/${chapterStem}.smil`,
|
|
144
148
|
mediaType: "application/smil+xml"
|
|
145
149
|
},
|
|
146
|
-
createMediaOverlay(
|
|
150
|
+
createMediaOverlay(
|
|
151
|
+
chapter,
|
|
152
|
+
this.granularity,
|
|
153
|
+
sentenceRanges,
|
|
154
|
+
wordRangeMap
|
|
155
|
+
),
|
|
147
156
|
"xml"
|
|
148
157
|
);
|
|
149
158
|
await this.epub.updateManifestItem(chapter.id, {
|
|
@@ -180,17 +189,17 @@ class Aligner {
|
|
|
180
189
|
},
|
|
181
190
|
firstMatchedSentenceId: startSentence,
|
|
182
191
|
firstMatchedSentenceContext: {
|
|
183
|
-
prevSentence: chapterSentences[startSentence - 1] ?? null,
|
|
192
|
+
prevSentence: chapterSentences[startSentence - 1]?.text ?? null,
|
|
184
193
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
185
|
-
matchedSentence: chapterSentences[startSentence],
|
|
186
|
-
nextSentence: chapterSentences[startSentence + 1] ?? null
|
|
194
|
+
matchedSentence: chapterSentences[startSentence].text,
|
|
195
|
+
nextSentence: chapterSentences[startSentence + 1]?.text ?? null
|
|
187
196
|
},
|
|
188
197
|
lastMatchedSentenceId: endSentence,
|
|
189
198
|
lastMatchedSentenceContext: {
|
|
190
|
-
prevSentence: chapterSentences[endSentence - 1] ?? null,
|
|
199
|
+
prevSentence: chapterSentences[endSentence - 1]?.text ?? null,
|
|
191
200
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
192
|
-
matchedSentence: chapterSentences[endSentence],
|
|
193
|
-
nextSentence: chapterSentences[endSentence + 1] ?? null
|
|
201
|
+
matchedSentence: chapterSentences[endSentence].text,
|
|
202
|
+
nextSentence: chapterSentences[endSentence + 1]?.text ?? null
|
|
194
203
|
},
|
|
195
204
|
chapterSentenceCount: chapterSentences.length,
|
|
196
205
|
alignedSentenceCount: sentenceRanges.length,
|
|
@@ -211,7 +220,7 @@ class Aligner {
|
|
|
211
220
|
}, [])
|
|
212
221
|
});
|
|
213
222
|
}
|
|
214
|
-
async alignChapter(chapterId, transcriptionOffset, transcriptionEndOffset, locale,
|
|
223
|
+
async alignChapter(chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline) {
|
|
215
224
|
const timing = createTiming();
|
|
216
225
|
timing.start("read contents");
|
|
217
226
|
const manifest = await this.epub.getManifest();
|
|
@@ -228,14 +237,18 @@ class Aligner {
|
|
|
228
237
|
timing.start("align sentences");
|
|
229
238
|
const {
|
|
230
239
|
sentenceRanges,
|
|
240
|
+
wordRanges,
|
|
231
241
|
transcriptionOffset: endTranscriptionOffset,
|
|
232
242
|
firstFoundSentence,
|
|
233
243
|
lastFoundSentence
|
|
234
244
|
} = await getSentenceRanges(
|
|
235
|
-
|
|
245
|
+
transcriptionText,
|
|
246
|
+
mappedTimeline,
|
|
236
247
|
chapterSentences,
|
|
248
|
+
chapterId,
|
|
237
249
|
transcriptionOffset,
|
|
238
250
|
transcriptionEndOffset,
|
|
251
|
+
this.granularity,
|
|
239
252
|
locale
|
|
240
253
|
);
|
|
241
254
|
timing.end("align sentences");
|
|
@@ -252,8 +265,9 @@ class Aligner {
|
|
|
252
265
|
chapter,
|
|
253
266
|
xml: chapterXml,
|
|
254
267
|
sentenceRanges,
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
wordRanges,
|
|
269
|
+
startOffset: transcriptionOffset,
|
|
270
|
+
endOffset: endTranscriptionOffset
|
|
257
271
|
});
|
|
258
272
|
this.addChapterReport(
|
|
259
273
|
chapter,
|
|
@@ -291,6 +305,7 @@ class Aligner {
|
|
|
291
305
|
this.transcription.transcript,
|
|
292
306
|
locale
|
|
293
307
|
);
|
|
308
|
+
const mappedTimeline = mapTranscriptionTimeline(this.transcription, mapping);
|
|
294
309
|
for (let index = 0; index < spine.length; index++) {
|
|
295
310
|
onProgress?.(index / spine.length);
|
|
296
311
|
const spineItem = spine[index];
|
|
@@ -305,7 +320,7 @@ class Aligner {
|
|
|
305
320
|
const slugifiedChapterSentences = [];
|
|
306
321
|
for (const chapterSentence of chapterSentences) {
|
|
307
322
|
slugifiedChapterSentences.push(
|
|
308
|
-
(await slugify(chapterSentence, locale)).result
|
|
323
|
+
(await slugify(chapterSentence.text, locale)).result
|
|
309
324
|
);
|
|
310
325
|
}
|
|
311
326
|
if (chapterSentences.length === 0) {
|
|
@@ -313,7 +328,7 @@ class Aligner {
|
|
|
313
328
|
continue;
|
|
314
329
|
}
|
|
315
330
|
if (chapterSentences.length < 2 && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
316
|
-
chapterSentences[0].
|
|
331
|
+
chapterSentences[0].words.length < 4) {
|
|
317
332
|
this.logger?.info(
|
|
318
333
|
`Chapter #${index} is fewer than four words; skipping`
|
|
319
334
|
);
|
|
@@ -333,14 +348,13 @@ class Aligner {
|
|
|
333
348
|
if (start === end) {
|
|
334
349
|
continue;
|
|
335
350
|
}
|
|
336
|
-
const transcriptionOffset = mapping.invert().map(Math.max(start, 0), -1);
|
|
337
|
-
const endOffset = mapping.invert().map(Math.min(end, transcriptionText.length), 1);
|
|
338
351
|
const result = await this.alignChapter(
|
|
339
352
|
chapterId,
|
|
340
|
-
|
|
341
|
-
|
|
353
|
+
transcriptionText,
|
|
354
|
+
Math.max(start, 0),
|
|
355
|
+
Math.min(end, transcriptionText.length),
|
|
342
356
|
locale,
|
|
343
|
-
|
|
357
|
+
mappedTimeline
|
|
344
358
|
);
|
|
345
359
|
this.timing.add(result.timing.summary());
|
|
346
360
|
}
|
|
@@ -360,16 +374,36 @@ class Aligner {
|
|
|
360
374
|
}
|
|
361
375
|
return firstAudiofileIndexA - firstAudiofileIndexB;
|
|
362
376
|
});
|
|
363
|
-
|
|
377
|
+
const sentenceRanges = [];
|
|
378
|
+
const chapterSentenceCounts = {};
|
|
364
379
|
for (const alignedChapter of audioOrderedChapters) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
380
|
+
sentenceRanges.push(...alignedChapter.sentenceRanges);
|
|
381
|
+
const sentences = await this.getChapterSentences(
|
|
382
|
+
alignedChapter.chapter.id
|
|
368
383
|
);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
384
|
+
chapterSentenceCounts[alignedChapter.chapter.id] = sentences.length;
|
|
385
|
+
}
|
|
386
|
+
const interpolated = await interpolateSentenceRanges(
|
|
387
|
+
sentenceRanges,
|
|
388
|
+
chapterSentenceCounts
|
|
389
|
+
);
|
|
390
|
+
const expanded = expandEmptySentenceRanges(interpolated);
|
|
391
|
+
const collapsed = await collapseSentenceRangeGaps(expanded);
|
|
392
|
+
let collapsedStart = 0;
|
|
393
|
+
for (const alignedChapter of audioOrderedChapters) {
|
|
394
|
+
const sentences = await this.getChapterSentences(
|
|
395
|
+
alignedChapter.chapter.id
|
|
396
|
+
);
|
|
397
|
+
const finalSentenceRanges = collapsed.slice(
|
|
398
|
+
collapsedStart,
|
|
399
|
+
collapsedStart + sentences.length - 1
|
|
400
|
+
);
|
|
401
|
+
alignedChapter.sentenceRanges = finalSentenceRanges;
|
|
402
|
+
for (const [i, wordRanges] of enumerate(alignedChapter.wordRanges)) {
|
|
403
|
+
alignedChapter.wordRanges[i] = expandEmptySentenceRanges(wordRanges);
|
|
404
|
+
}
|
|
372
405
|
await this.writeAlignedChapter(alignedChapter);
|
|
406
|
+
collapsedStart += sentences.length - 1;
|
|
373
407
|
}
|
|
374
408
|
await this.epub.addMetadata({
|
|
375
409
|
type: "meta",
|
|
@@ -397,7 +431,7 @@ class Aligner {
|
|
|
397
431
|
return this.timing;
|
|
398
432
|
}
|
|
399
433
|
}
|
|
400
|
-
function createMediaOverlay(chapter, sentenceRanges) {
|
|
434
|
+
function createMediaOverlay(chapter, granularity, sentenceRanges, wordRanges) {
|
|
401
435
|
return [
|
|
402
436
|
Epub.createXmlElement(
|
|
403
437
|
"smil",
|
|
@@ -415,24 +449,53 @@ function createMediaOverlay(chapter, sentenceRanges) {
|
|
|
415
449
|
"epub:textref": `../${chapter.href}`,
|
|
416
450
|
"epub:type": "chapter"
|
|
417
451
|
},
|
|
418
|
-
sentenceRanges.map(
|
|
419
|
-
(
|
|
420
|
-
|
|
452
|
+
sentenceRanges.map((sentenceRange) => {
|
|
453
|
+
if (granularity === "sentence" || !wordRanges.has(sentenceRange.id)) {
|
|
454
|
+
return Epub.createXmlElement(
|
|
455
|
+
"par",
|
|
456
|
+
{
|
|
457
|
+
id: `${chapter.id}-s${sentenceRange.id}`
|
|
458
|
+
},
|
|
459
|
+
[
|
|
460
|
+
Epub.createXmlElement("text", {
|
|
461
|
+
src: `../${chapter.href}#${chapter.id}-s${sentenceRange.id}`
|
|
462
|
+
}),
|
|
463
|
+
Epub.createXmlElement("audio", {
|
|
464
|
+
src: `../Audio/${basename(sentenceRange.audiofile)}`,
|
|
465
|
+
clipBegin: `${sentenceRange.start.toFixed(3)}s`,
|
|
466
|
+
clipEnd: `${sentenceRange.end.toFixed(3)}s`
|
|
467
|
+
})
|
|
468
|
+
]
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const words = wordRanges.get(sentenceRange.id);
|
|
472
|
+
return Epub.createXmlElement(
|
|
473
|
+
"seq",
|
|
421
474
|
{
|
|
422
|
-
id: `${chapter.id}-s${sentenceRange.id}
|
|
475
|
+
id: `${chapter.id}-s${sentenceRange.id}`,
|
|
476
|
+
"epub:type": "text-range-small",
|
|
477
|
+
"epub:textref": `../${chapter.href}#${chapter.id}-s${sentenceRange.id}`
|
|
423
478
|
},
|
|
424
|
-
|
|
425
|
-
Epub.createXmlElement(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
479
|
+
words.map(
|
|
480
|
+
(word) => Epub.createXmlElement(
|
|
481
|
+
"par",
|
|
482
|
+
{
|
|
483
|
+
id: `${chapter.id}-s${sentenceRange.id}-w${word.id}`
|
|
484
|
+
},
|
|
485
|
+
[
|
|
486
|
+
Epub.createXmlElement("text", {
|
|
487
|
+
src: `../${chapter.href}#${chapter.id}-s${sentenceRange.id}-w${word.id}`
|
|
488
|
+
}),
|
|
489
|
+
Epub.createXmlElement("audio", {
|
|
490
|
+
src: `../Audio/${basename(word.audiofile)}`,
|
|
491
|
+
clipBegin: `${word.start.toFixed(3)}s`,
|
|
492
|
+
clipEnd: `${word.end.toFixed(3)}s`
|
|
493
|
+
})
|
|
494
|
+
]
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
);
|
|
498
|
+
})
|
|
436
499
|
)
|
|
437
500
|
])
|
|
438
501
|
]
|