@storyteller-platform/align 0.1.57 → 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
package/dist/align/align.cjs
CHANGED
|
@@ -81,16 +81,16 @@ module.exports = __toCommonJS(align_exports);
|
|
|
81
81
|
var import_node_crypto = require("node:crypto");
|
|
82
82
|
var import_node_fs = require("node:fs");
|
|
83
83
|
var import_promises = require("node:fs/promises");
|
|
84
|
-
var import_node_os = require("node:os");
|
|
85
84
|
var import_node_path = require("node:path");
|
|
86
85
|
var import_posix = require("node:path/posix");
|
|
87
86
|
var import_shared = require("@readium/shared");
|
|
88
87
|
var import_itertools = require("itertools");
|
|
89
88
|
var import_memoize = __toESM(require("memoize"), 1);
|
|
90
89
|
var import_yazl = require("yazl");
|
|
91
|
-
var import_audiobook = require("@storyteller-platform/audiobook");
|
|
92
90
|
var import_epub = require("@storyteller-platform/epub");
|
|
91
|
+
var import_fs = require("@storyteller-platform/fs");
|
|
93
92
|
var import_ghost_story = require("@storyteller-platform/ghost-story");
|
|
93
|
+
var import_media_types = require("@storyteller-platform/media-types");
|
|
94
94
|
var import_ffmpeg = require("../common/ffmpeg.cjs");
|
|
95
95
|
var import_parseDom = require("../markup/parseDom.cjs");
|
|
96
96
|
var import_segmentation = require("../markup/segmentation.cjs");
|
|
@@ -105,8 +105,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
105
105
|
var _stack2 = [];
|
|
106
106
|
try {
|
|
107
107
|
const outFormat = options.outFormat ?? "epub";
|
|
108
|
-
const epubPath = outFormat === "epub" ? (0,
|
|
109
|
-
(0, import_node_os.tmpdir)(),
|
|
108
|
+
const epubPath = outFormat === "epub" ? (0, import_fs.scratchPath)(
|
|
110
109
|
`storyteller-platform-align-${(0, import_node_crypto.randomUUID)()}`,
|
|
111
110
|
(0, import_node_path.basename)(output)
|
|
112
111
|
) : input;
|
|
@@ -124,7 +123,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
124
123
|
await (0, import_promises.copyFile)(input, epubPath);
|
|
125
124
|
}
|
|
126
125
|
const audiobookFiles = await (0, import_promises.readdir)(audiobookDir).then(
|
|
127
|
-
(filenames) => filenames.filter((f) =>
|
|
126
|
+
(filenames) => filenames.filter((f) => import_media_types.MediaType.fromPath(f)?.kind === "audio").map((f) => (0, import_node_path.join)(audiobookDir, f))
|
|
128
127
|
);
|
|
129
128
|
const epub = __using(_stack2, await import_epub.Epub.from(epubPath));
|
|
130
129
|
const transcriptions = await (0, import_promises.readdir)(transcriptionsDir).then(
|
|
@@ -172,8 +171,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
172
171
|
),
|
|
173
172
|
guidedNavigationDocuments
|
|
174
173
|
);
|
|
175
|
-
const tmpArchivePath = (0,
|
|
176
|
-
(0, import_node_os.tmpdir)(),
|
|
174
|
+
const tmpArchivePath = (0, import_fs.scratchPath)(
|
|
177
175
|
`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
|
|
178
176
|
);
|
|
179
177
|
const { promise, resolve } = Promise.withResolvers();
|
|
@@ -235,6 +233,9 @@ class Aligner {
|
|
|
235
233
|
this.languageOverride = languageOverride;
|
|
236
234
|
this.logger = logger;
|
|
237
235
|
this.transcription = concatTranscriptions(transcriptions, audiofiles);
|
|
236
|
+
audiofiles.forEach((audiofile, index) => {
|
|
237
|
+
this.audiofileTranscripts[audiofile] = transcriptions[index]?.transcript ?? "";
|
|
238
|
+
});
|
|
238
239
|
this.getChapterSentences = (0, import_memoize.default)(this.getChapterSentences.bind(this));
|
|
239
240
|
this.granularity = granularity ?? "sentence";
|
|
240
241
|
this.textRef = textRef ?? "id-fragment";
|
|
@@ -246,6 +247,7 @@ class Aligner {
|
|
|
246
247
|
granularity;
|
|
247
248
|
textRef;
|
|
248
249
|
audioFileDurations = {};
|
|
250
|
+
audiofileTranscripts = {};
|
|
249
251
|
report = {
|
|
250
252
|
chapters: [],
|
|
251
253
|
unalignedChapters: [],
|
|
@@ -369,7 +371,7 @@ class Aligner {
|
|
|
369
371
|
const duration = await (0, import_ffmpeg.getTrackDuration)(audiofile);
|
|
370
372
|
this.totalDuration += duration;
|
|
371
373
|
const audio = await (0, import_promises.readFile)(audiofile);
|
|
372
|
-
const mediaType =
|
|
374
|
+
const mediaType = import_media_types.MediaType.fromPath(base)?.mime;
|
|
373
375
|
await this.epub.addManifestItem(
|
|
374
376
|
{
|
|
375
377
|
id,
|
|
@@ -386,7 +388,7 @@ class Aligner {
|
|
|
386
388
|
{
|
|
387
389
|
id: mediaOverlayId,
|
|
388
390
|
href: `MediaOverlays/${chapterStem}.smil`,
|
|
389
|
-
mediaType:
|
|
391
|
+
mediaType: import_media_types.MediaType.SMIL.mime
|
|
390
392
|
},
|
|
391
393
|
createMediaOverlay(
|
|
392
394
|
chapter,
|
|
@@ -414,7 +416,7 @@ class Aligner {
|
|
|
414
416
|
value: import_epub.Epub.formatSmilDuration(chapterDuration)
|
|
415
417
|
});
|
|
416
418
|
}
|
|
417
|
-
addChapterReport(chapter, chapterSentences, sentenceRanges, startSentence, endSentence, mapping, transcriptionOffset, endTranscriptionOffset) {
|
|
419
|
+
addChapterReport(spineIndex, chapter, chapterSentences, sentenceRanges, startSentence, endSentence, mapping, transcriptionOffset, endTranscriptionOffset, score) {
|
|
418
420
|
const audioFiles = sentenceRanges.reduce(
|
|
419
421
|
(acc, range) => {
|
|
420
422
|
const existing = acc.find(
|
|
@@ -438,6 +440,8 @@ class Aligner {
|
|
|
438
440
|
const mappedEndTranscriptionOffset = cursor.map(endTranscriptionOffset);
|
|
439
441
|
this.report.chapters.push({
|
|
440
442
|
href: chapter.href,
|
|
443
|
+
score,
|
|
444
|
+
spineIndex,
|
|
441
445
|
transcriptionOffset: mappedTranscriptionOffset,
|
|
442
446
|
endTranscriptionOffset: mappedEndTranscriptionOffset,
|
|
443
447
|
transcriptionContext: {
|
|
@@ -505,7 +509,7 @@ class Aligner {
|
|
|
505
509
|
}
|
|
506
510
|
}
|
|
507
511
|
}
|
|
508
|
-
async alignChapter(chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline, mapping) {
|
|
512
|
+
async alignChapter(spineIndex, chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline, mapping) {
|
|
509
513
|
const timing = (0, import_ghost_story.createTiming)();
|
|
510
514
|
timing.start("read contents");
|
|
511
515
|
const manifest = await this.epub.getManifest();
|
|
@@ -525,7 +529,8 @@ class Aligner {
|
|
|
525
529
|
wordRanges,
|
|
526
530
|
transcriptionOffset: endTranscriptionOffset,
|
|
527
531
|
firstFoundSentence,
|
|
528
|
-
lastFoundSentence
|
|
532
|
+
lastFoundSentence,
|
|
533
|
+
score
|
|
529
534
|
} = await (0, import_getSentenceRanges.getSentenceRanges)(
|
|
530
535
|
transcriptionText,
|
|
531
536
|
mappedTimeline,
|
|
@@ -544,7 +549,7 @@ class Aligner {
|
|
|
544
549
|
import_epub.Epub.addLinkToXhtmlHead(chapterXml, {
|
|
545
550
|
rel: "stylesheet",
|
|
546
551
|
href: storytellerStylesheetUrl,
|
|
547
|
-
type:
|
|
552
|
+
type: import_media_types.MediaType.CSS.mime
|
|
548
553
|
});
|
|
549
554
|
this.alignedChapters.push({
|
|
550
555
|
chapter,
|
|
@@ -555,6 +560,7 @@ class Aligner {
|
|
|
555
560
|
endOffset: endTranscriptionOffset
|
|
556
561
|
});
|
|
557
562
|
this.addChapterReport(
|
|
563
|
+
spineIndex,
|
|
558
564
|
chapter,
|
|
559
565
|
chapterSentences,
|
|
560
566
|
sentenceRanges,
|
|
@@ -562,7 +568,8 @@ class Aligner {
|
|
|
562
568
|
lastFoundSentence,
|
|
563
569
|
mapping,
|
|
564
570
|
transcriptionOffset,
|
|
565
|
-
endTranscriptionOffset
|
|
571
|
+
endTranscriptionOffset,
|
|
572
|
+
score
|
|
566
573
|
);
|
|
567
574
|
return {
|
|
568
575
|
lastSentenceRange: sentenceRanges.at(-1) ?? null,
|
|
@@ -614,7 +621,8 @@ class Aligner {
|
|
|
614
621
|
if (manifest[chapterId]?.properties?.includes("nav")) {
|
|
615
622
|
this.report.unalignedChapters.push({
|
|
616
623
|
href: spineItem.href,
|
|
617
|
-
reason: "is-nav"
|
|
624
|
+
reason: "is-nav",
|
|
625
|
+
spineIndex: index
|
|
618
626
|
});
|
|
619
627
|
continue;
|
|
620
628
|
}
|
|
@@ -629,7 +637,8 @@ class Aligner {
|
|
|
629
637
|
this.logger?.info(`Chapter #${index} has no text; skipping`);
|
|
630
638
|
this.report.unalignedChapters.push({
|
|
631
639
|
href: spineItem.href,
|
|
632
|
-
reason: "no-text"
|
|
640
|
+
reason: "no-text",
|
|
641
|
+
spineIndex: index
|
|
633
642
|
});
|
|
634
643
|
continue;
|
|
635
644
|
}
|
|
@@ -640,7 +649,8 @@ class Aligner {
|
|
|
640
649
|
);
|
|
641
650
|
this.report.unalignedChapters.push({
|
|
642
651
|
href: spineItem.href,
|
|
643
|
-
reason: "too-short"
|
|
652
|
+
reason: "too-short",
|
|
653
|
+
spineIndex: index
|
|
644
654
|
});
|
|
645
655
|
continue;
|
|
646
656
|
}
|
|
@@ -655,6 +665,9 @@ class Aligner {
|
|
|
655
665
|
this.report.unalignedChapters.push({
|
|
656
666
|
href: spineItem.href,
|
|
657
667
|
reason: "not-found",
|
|
668
|
+
spineIndex: index,
|
|
669
|
+
chapterSentenceCount: chapterSentences.length,
|
|
670
|
+
score: 0,
|
|
658
671
|
start: chapterSentences.slice(0, 3).map((s) => s.text).join("").slice(0, 80),
|
|
659
672
|
end: chapterSentences.slice(-3).map((s) => s.text).join("").slice(-80)
|
|
660
673
|
});
|
|
@@ -665,6 +678,7 @@ class Aligner {
|
|
|
665
678
|
continue;
|
|
666
679
|
}
|
|
667
680
|
const result = await this.alignChapter(
|
|
681
|
+
index,
|
|
668
682
|
chapterId,
|
|
669
683
|
transcriptionText,
|
|
670
684
|
Math.max(start, 0),
|
|
@@ -730,7 +744,12 @@ class Aligner {
|
|
|
730
744
|
}
|
|
731
745
|
for (const audiofile of this.audiofiles) {
|
|
732
746
|
if (!this.report.audioFiles.some(({ filepath }) => filepath === audiofile)) {
|
|
733
|
-
this.
|
|
747
|
+
const transcript = this.audiofileTranscripts[audiofile]?.trim();
|
|
748
|
+
this.report.unalignedAudioFiles.push({
|
|
749
|
+
filepath: audiofile,
|
|
750
|
+
duration: this.audioFileDurations[audiofile] ?? null,
|
|
751
|
+
...transcript ? { transcription: { text: transcript.slice(0, 2e3) } } : {}
|
|
752
|
+
});
|
|
734
753
|
}
|
|
735
754
|
}
|
|
736
755
|
await this.epub.addMetadata({
|
|
@@ -747,7 +766,7 @@ class Aligner {
|
|
|
747
766
|
{
|
|
748
767
|
id: "storyteller_readaloud_styles",
|
|
749
768
|
href: "Styles/storyteller-readaloud.css",
|
|
750
|
-
mediaType:
|
|
769
|
+
mediaType: import_media_types.MediaType.CSS.mime
|
|
751
770
|
},
|
|
752
771
|
`
|
|
753
772
|
.-epub-media-overlay-active {
|
package/dist/align/align.d.cts
CHANGED
|
@@ -13,6 +13,8 @@ interface AudioFileContext {
|
|
|
13
13
|
}
|
|
14
14
|
interface ChapterReport {
|
|
15
15
|
href: string;
|
|
16
|
+
score: number;
|
|
17
|
+
spineIndex: number;
|
|
16
18
|
transcriptionOffset: number;
|
|
17
19
|
endTranscriptionOffset: number;
|
|
18
20
|
transcriptionContext: {
|
|
@@ -42,10 +44,14 @@ interface ChapterReport {
|
|
|
42
44
|
type UnalignedChapterReason = "too-short" | "not-found" | "is-nav" | "no-text";
|
|
43
45
|
interface UnalignedChapterReport {
|
|
44
46
|
href: string;
|
|
47
|
+
spineIndex: number;
|
|
45
48
|
reason: Exclude<UnalignedChapterReason, "not-found">;
|
|
46
49
|
}
|
|
47
50
|
interface UnalignedNotFoundChapterReport {
|
|
48
51
|
href: string;
|
|
52
|
+
spineIndex: number;
|
|
53
|
+
chapterSentenceCount: number;
|
|
54
|
+
score: number;
|
|
49
55
|
reason: "not-found";
|
|
50
56
|
start: string;
|
|
51
57
|
end: string;
|
|
@@ -61,6 +67,10 @@ interface AudioFileReport {
|
|
|
61
67
|
}
|
|
62
68
|
interface UnalignedAudioFileReport {
|
|
63
69
|
filepath: string;
|
|
70
|
+
duration?: number | null;
|
|
71
|
+
transcription?: {
|
|
72
|
+
text: string;
|
|
73
|
+
};
|
|
64
74
|
}
|
|
65
75
|
interface Report {
|
|
66
76
|
chapters: ChapterReport[];
|
|
@@ -90,6 +100,7 @@ declare class Aligner {
|
|
|
90
100
|
private granularity;
|
|
91
101
|
private textRef;
|
|
92
102
|
private audioFileDurations;
|
|
103
|
+
private audiofileTranscripts;
|
|
93
104
|
report: Report;
|
|
94
105
|
constructor(epub: Epub, audiofiles: string[], transcriptions: Pick<RecognitionResult, "transcript" | "timeline">[], granularity: "sentence" | "word" | null | undefined, textRef: "id-fragment" | "text-fragment" | null | undefined, languageOverride?: (Intl.Locale | null) | undefined, logger?: (Logger | null) | undefined);
|
|
95
106
|
private getChapterSentences;
|
|
@@ -107,4 +118,4 @@ declare class Aligner {
|
|
|
107
118
|
}
|
|
108
119
|
declare function concatTranscriptions(transcriptions: Pick<RecognitionResult, "transcript" | "timeline">[], audiofiles: string[]): StorytellerTranscription;
|
|
109
120
|
|
|
110
|
-
export { type AlignOptions, Aligner, align, concatTranscriptions };
|
|
121
|
+
export { type AlignOptions, Aligner, type AudioFileContext, type AudioFileReport, type ChapterReport, type Report, type UnalignedAudioFileReport, type UnalignedChapterReason, type UnalignedChapterReport, type UnalignedNotFoundChapterReport, align, concatTranscriptions };
|
package/dist/align/align.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface AudioFileContext {
|
|
|
13
13
|
}
|
|
14
14
|
interface ChapterReport {
|
|
15
15
|
href: string;
|
|
16
|
+
score: number;
|
|
17
|
+
spineIndex: number;
|
|
16
18
|
transcriptionOffset: number;
|
|
17
19
|
endTranscriptionOffset: number;
|
|
18
20
|
transcriptionContext: {
|
|
@@ -42,10 +44,14 @@ interface ChapterReport {
|
|
|
42
44
|
type UnalignedChapterReason = "too-short" | "not-found" | "is-nav" | "no-text";
|
|
43
45
|
interface UnalignedChapterReport {
|
|
44
46
|
href: string;
|
|
47
|
+
spineIndex: number;
|
|
45
48
|
reason: Exclude<UnalignedChapterReason, "not-found">;
|
|
46
49
|
}
|
|
47
50
|
interface UnalignedNotFoundChapterReport {
|
|
48
51
|
href: string;
|
|
52
|
+
spineIndex: number;
|
|
53
|
+
chapterSentenceCount: number;
|
|
54
|
+
score: number;
|
|
49
55
|
reason: "not-found";
|
|
50
56
|
start: string;
|
|
51
57
|
end: string;
|
|
@@ -61,6 +67,10 @@ interface AudioFileReport {
|
|
|
61
67
|
}
|
|
62
68
|
interface UnalignedAudioFileReport {
|
|
63
69
|
filepath: string;
|
|
70
|
+
duration?: number | null;
|
|
71
|
+
transcription?: {
|
|
72
|
+
text: string;
|
|
73
|
+
};
|
|
64
74
|
}
|
|
65
75
|
interface Report {
|
|
66
76
|
chapters: ChapterReport[];
|
|
@@ -90,6 +100,7 @@ declare class Aligner {
|
|
|
90
100
|
private granularity;
|
|
91
101
|
private textRef;
|
|
92
102
|
private audioFileDurations;
|
|
103
|
+
private audiofileTranscripts;
|
|
93
104
|
report: Report;
|
|
94
105
|
constructor(epub: Epub, audiofiles: string[], transcriptions: Pick<RecognitionResult, "transcript" | "timeline">[], granularity: "sentence" | "word" | null | undefined, textRef: "id-fragment" | "text-fragment" | null | undefined, languageOverride?: (Intl.Locale | null) | undefined, logger?: (Logger | null) | undefined);
|
|
95
106
|
private getChapterSentences;
|
|
@@ -107,4 +118,4 @@ declare class Aligner {
|
|
|
107
118
|
}
|
|
108
119
|
declare function concatTranscriptions(transcriptions: Pick<RecognitionResult, "transcript" | "timeline">[], audiofiles: string[]): StorytellerTranscription;
|
|
109
120
|
|
|
110
|
-
export { type AlignOptions, Aligner, align, concatTranscriptions };
|
|
121
|
+
export { type AlignOptions, Aligner, type AudioFileContext, type AudioFileReport, type ChapterReport, type Report, type UnalignedAudioFileReport, type UnalignedChapterReason, type UnalignedChapterReport, type UnalignedNotFoundChapterReport, align, concatTranscriptions };
|
package/dist/align/align.js
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
rm,
|
|
14
14
|
writeFile
|
|
15
15
|
} from "node:fs/promises";
|
|
16
|
-
import { tmpdir } from "node:os";
|
|
17
16
|
import {
|
|
18
17
|
basename as autoBasename,
|
|
19
18
|
dirname as autoDirname,
|
|
@@ -26,14 +25,15 @@ import { LocalizedString } from "@readium/shared";
|
|
|
26
25
|
import { enumerate, max } from "itertools";
|
|
27
26
|
import memoize from "memoize";
|
|
28
27
|
import { ZipFile } from "yazl";
|
|
29
|
-
import { isAudioFile, lookupAudioMime } from "@storyteller-platform/audiobook";
|
|
30
28
|
import {
|
|
31
29
|
Epub
|
|
32
30
|
} from "@storyteller-platform/epub";
|
|
31
|
+
import { scratchPath } from "@storyteller-platform/fs";
|
|
33
32
|
import {
|
|
34
33
|
createAggregator,
|
|
35
34
|
createTiming
|
|
36
35
|
} from "@storyteller-platform/ghost-story";
|
|
36
|
+
import { MediaType } from "@storyteller-platform/media-types";
|
|
37
37
|
import { getTrackDuration } from "../common/ffmpeg.js";
|
|
38
38
|
import { parseDom } from "../markup/parseDom.js";
|
|
39
39
|
import { segmentChapter } from "../markup/segmentation.js";
|
|
@@ -60,8 +60,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
60
60
|
var _stack2 = [];
|
|
61
61
|
try {
|
|
62
62
|
const outFormat = options.outFormat ?? "epub";
|
|
63
|
-
const epubPath = outFormat === "epub" ?
|
|
64
|
-
tmpdir(),
|
|
63
|
+
const epubPath = outFormat === "epub" ? scratchPath(
|
|
65
64
|
`storyteller-platform-align-${randomUUID()}`,
|
|
66
65
|
autoBasename(output)
|
|
67
66
|
) : input;
|
|
@@ -79,7 +78,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
79
78
|
await copyFile(input, epubPath);
|
|
80
79
|
}
|
|
81
80
|
const audiobookFiles = await readdir(audiobookDir).then(
|
|
82
|
-
(filenames) => filenames.filter((f) =>
|
|
81
|
+
(filenames) => filenames.filter((f) => MediaType.fromPath(f)?.kind === "audio").map((f) => autoJoin(audiobookDir, f))
|
|
83
82
|
);
|
|
84
83
|
const epub = __using(_stack2, await Epub.from(epubPath));
|
|
85
84
|
const transcriptions = await readdir(transcriptionsDir).then(
|
|
@@ -127,8 +126,7 @@ async function align(input, output, transcriptionsDir, audiobookDir, options) {
|
|
|
127
126
|
),
|
|
128
127
|
guidedNavigationDocuments
|
|
129
128
|
);
|
|
130
|
-
const tmpArchivePath =
|
|
131
|
-
tmpdir(),
|
|
129
|
+
const tmpArchivePath = scratchPath(
|
|
132
130
|
`storyteller-platform-epub-${randomUUID()}`
|
|
133
131
|
);
|
|
134
132
|
const { promise, resolve } = Promise.withResolvers();
|
|
@@ -190,6 +188,9 @@ class Aligner {
|
|
|
190
188
|
this.languageOverride = languageOverride;
|
|
191
189
|
this.logger = logger;
|
|
192
190
|
this.transcription = concatTranscriptions(transcriptions, audiofiles);
|
|
191
|
+
audiofiles.forEach((audiofile, index) => {
|
|
192
|
+
this.audiofileTranscripts[audiofile] = transcriptions[index]?.transcript ?? "";
|
|
193
|
+
});
|
|
193
194
|
this.getChapterSentences = memoize(this.getChapterSentences.bind(this));
|
|
194
195
|
this.granularity = granularity ?? "sentence";
|
|
195
196
|
this.textRef = textRef ?? "id-fragment";
|
|
@@ -201,6 +202,7 @@ class Aligner {
|
|
|
201
202
|
granularity;
|
|
202
203
|
textRef;
|
|
203
204
|
audioFileDurations = {};
|
|
205
|
+
audiofileTranscripts = {};
|
|
204
206
|
report = {
|
|
205
207
|
chapters: [],
|
|
206
208
|
unalignedChapters: [],
|
|
@@ -324,7 +326,7 @@ class Aligner {
|
|
|
324
326
|
const duration = await getTrackDuration(audiofile);
|
|
325
327
|
this.totalDuration += duration;
|
|
326
328
|
const audio = await readFile(audiofile);
|
|
327
|
-
const mediaType =
|
|
329
|
+
const mediaType = MediaType.fromPath(base)?.mime;
|
|
328
330
|
await this.epub.addManifestItem(
|
|
329
331
|
{
|
|
330
332
|
id,
|
|
@@ -341,7 +343,7 @@ class Aligner {
|
|
|
341
343
|
{
|
|
342
344
|
id: mediaOverlayId,
|
|
343
345
|
href: `MediaOverlays/${chapterStem}.smil`,
|
|
344
|
-
mediaType:
|
|
346
|
+
mediaType: MediaType.SMIL.mime
|
|
345
347
|
},
|
|
346
348
|
createMediaOverlay(
|
|
347
349
|
chapter,
|
|
@@ -369,7 +371,7 @@ class Aligner {
|
|
|
369
371
|
value: Epub.formatSmilDuration(chapterDuration)
|
|
370
372
|
});
|
|
371
373
|
}
|
|
372
|
-
addChapterReport(chapter, chapterSentences, sentenceRanges, startSentence, endSentence, mapping, transcriptionOffset, endTranscriptionOffset) {
|
|
374
|
+
addChapterReport(spineIndex, chapter, chapterSentences, sentenceRanges, startSentence, endSentence, mapping, transcriptionOffset, endTranscriptionOffset, score) {
|
|
373
375
|
const audioFiles = sentenceRanges.reduce(
|
|
374
376
|
(acc, range) => {
|
|
375
377
|
const existing = acc.find(
|
|
@@ -393,6 +395,8 @@ class Aligner {
|
|
|
393
395
|
const mappedEndTranscriptionOffset = cursor.map(endTranscriptionOffset);
|
|
394
396
|
this.report.chapters.push({
|
|
395
397
|
href: chapter.href,
|
|
398
|
+
score,
|
|
399
|
+
spineIndex,
|
|
396
400
|
transcriptionOffset: mappedTranscriptionOffset,
|
|
397
401
|
endTranscriptionOffset: mappedEndTranscriptionOffset,
|
|
398
402
|
transcriptionContext: {
|
|
@@ -460,7 +464,7 @@ class Aligner {
|
|
|
460
464
|
}
|
|
461
465
|
}
|
|
462
466
|
}
|
|
463
|
-
async alignChapter(chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline, mapping) {
|
|
467
|
+
async alignChapter(spineIndex, chapterId, transcriptionText, transcriptionOffset, transcriptionEndOffset, locale, mappedTimeline, mapping) {
|
|
464
468
|
const timing = createTiming();
|
|
465
469
|
timing.start("read contents");
|
|
466
470
|
const manifest = await this.epub.getManifest();
|
|
@@ -480,7 +484,8 @@ class Aligner {
|
|
|
480
484
|
wordRanges,
|
|
481
485
|
transcriptionOffset: endTranscriptionOffset,
|
|
482
486
|
firstFoundSentence,
|
|
483
|
-
lastFoundSentence
|
|
487
|
+
lastFoundSentence,
|
|
488
|
+
score
|
|
484
489
|
} = await getSentenceRanges(
|
|
485
490
|
transcriptionText,
|
|
486
491
|
mappedTimeline,
|
|
@@ -499,7 +504,7 @@ class Aligner {
|
|
|
499
504
|
Epub.addLinkToXhtmlHead(chapterXml, {
|
|
500
505
|
rel: "stylesheet",
|
|
501
506
|
href: storytellerStylesheetUrl,
|
|
502
|
-
type:
|
|
507
|
+
type: MediaType.CSS.mime
|
|
503
508
|
});
|
|
504
509
|
this.alignedChapters.push({
|
|
505
510
|
chapter,
|
|
@@ -510,6 +515,7 @@ class Aligner {
|
|
|
510
515
|
endOffset: endTranscriptionOffset
|
|
511
516
|
});
|
|
512
517
|
this.addChapterReport(
|
|
518
|
+
spineIndex,
|
|
513
519
|
chapter,
|
|
514
520
|
chapterSentences,
|
|
515
521
|
sentenceRanges,
|
|
@@ -517,7 +523,8 @@ class Aligner {
|
|
|
517
523
|
lastFoundSentence,
|
|
518
524
|
mapping,
|
|
519
525
|
transcriptionOffset,
|
|
520
|
-
endTranscriptionOffset
|
|
526
|
+
endTranscriptionOffset,
|
|
527
|
+
score
|
|
521
528
|
);
|
|
522
529
|
return {
|
|
523
530
|
lastSentenceRange: sentenceRanges.at(-1) ?? null,
|
|
@@ -569,7 +576,8 @@ class Aligner {
|
|
|
569
576
|
if (manifest[chapterId]?.properties?.includes("nav")) {
|
|
570
577
|
this.report.unalignedChapters.push({
|
|
571
578
|
href: spineItem.href,
|
|
572
|
-
reason: "is-nav"
|
|
579
|
+
reason: "is-nav",
|
|
580
|
+
spineIndex: index
|
|
573
581
|
});
|
|
574
582
|
continue;
|
|
575
583
|
}
|
|
@@ -584,7 +592,8 @@ class Aligner {
|
|
|
584
592
|
this.logger?.info(`Chapter #${index} has no text; skipping`);
|
|
585
593
|
this.report.unalignedChapters.push({
|
|
586
594
|
href: spineItem.href,
|
|
587
|
-
reason: "no-text"
|
|
595
|
+
reason: "no-text",
|
|
596
|
+
spineIndex: index
|
|
588
597
|
});
|
|
589
598
|
continue;
|
|
590
599
|
}
|
|
@@ -595,7 +604,8 @@ class Aligner {
|
|
|
595
604
|
);
|
|
596
605
|
this.report.unalignedChapters.push({
|
|
597
606
|
href: spineItem.href,
|
|
598
|
-
reason: "too-short"
|
|
607
|
+
reason: "too-short",
|
|
608
|
+
spineIndex: index
|
|
599
609
|
});
|
|
600
610
|
continue;
|
|
601
611
|
}
|
|
@@ -610,6 +620,9 @@ class Aligner {
|
|
|
610
620
|
this.report.unalignedChapters.push({
|
|
611
621
|
href: spineItem.href,
|
|
612
622
|
reason: "not-found",
|
|
623
|
+
spineIndex: index,
|
|
624
|
+
chapterSentenceCount: chapterSentences.length,
|
|
625
|
+
score: 0,
|
|
613
626
|
start: chapterSentences.slice(0, 3).map((s) => s.text).join("").slice(0, 80),
|
|
614
627
|
end: chapterSentences.slice(-3).map((s) => s.text).join("").slice(-80)
|
|
615
628
|
});
|
|
@@ -620,6 +633,7 @@ class Aligner {
|
|
|
620
633
|
continue;
|
|
621
634
|
}
|
|
622
635
|
const result = await this.alignChapter(
|
|
636
|
+
index,
|
|
623
637
|
chapterId,
|
|
624
638
|
transcriptionText,
|
|
625
639
|
Math.max(start, 0),
|
|
@@ -685,7 +699,12 @@ class Aligner {
|
|
|
685
699
|
}
|
|
686
700
|
for (const audiofile of this.audiofiles) {
|
|
687
701
|
if (!this.report.audioFiles.some(({ filepath }) => filepath === audiofile)) {
|
|
688
|
-
this.
|
|
702
|
+
const transcript = this.audiofileTranscripts[audiofile]?.trim();
|
|
703
|
+
this.report.unalignedAudioFiles.push({
|
|
704
|
+
filepath: audiofile,
|
|
705
|
+
duration: this.audioFileDurations[audiofile] ?? null,
|
|
706
|
+
...transcript ? { transcription: { text: transcript.slice(0, 2e3) } } : {}
|
|
707
|
+
});
|
|
689
708
|
}
|
|
690
709
|
}
|
|
691
710
|
await this.epub.addMetadata({
|
|
@@ -702,7 +721,7 @@ class Aligner {
|
|
|
702
721
|
{
|
|
703
722
|
id: "storyteller_readaloud_styles",
|
|
704
723
|
href: "Styles/storyteller-readaloud.css",
|
|
705
|
-
mediaType:
|
|
724
|
+
mediaType: MediaType.CSS.mime
|
|
706
725
|
},
|
|
707
726
|
`
|
|
708
727
|
.-epub-media-overlay-active {
|