@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.
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,261 @@
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 __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+ var __using = (stack, value, async) => {
24
+ if (value != null) {
25
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
26
+ var dispose, inner;
27
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
28
+ if (dispose === void 0) {
29
+ dispose = value[__knownSymbol("dispose")];
30
+ if (async) inner = dispose;
31
+ }
32
+ if (typeof dispose !== "function") __typeError("Object not disposable");
33
+ if (inner) dispose = function() {
34
+ try {
35
+ inner.call(this);
36
+ } catch (e) {
37
+ return Promise.reject(e);
38
+ }
39
+ };
40
+ stack.push([async, dispose, value]);
41
+ } else if (async) {
42
+ stack.push([async]);
43
+ }
44
+ return value;
45
+ };
46
+ var __callDispose = (stack, error, hasError) => {
47
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
48
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
49
+ };
50
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
51
+ var next = (it) => {
52
+ while (it = stack.pop()) {
53
+ try {
54
+ var result = it[1] && it[1].call(it[2]);
55
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
56
+ } catch (e) {
57
+ fail(e);
58
+ }
59
+ }
60
+ if (hasError) throw error;
61
+ };
62
+ return next();
63
+ };
64
+ var snapshot_exports = {};
65
+ __export(snapshot_exports, {
66
+ createAlignmentSnapshot: () => createAlignmentSnapshot,
67
+ snapshotCtcAlignment: () => snapshotCtcAlignment
68
+ });
69
+ module.exports = __toCommonJS(snapshot_exports);
70
+ var import_promises = require("node:fs/promises");
71
+ var import_node_path = require("node:path");
72
+ var import_posix = require("node:path/posix");
73
+ var import_epub = require("@storyteller-platform/epub");
74
+ var import_greedyDecode = require("../../align/ctc/greedyDecode.cjs");
75
+ var import_slugify = require("../../align/slugify.cjs");
76
+ var import_fs = require("../../emit/fs.cjs");
77
+ var import_parseDom = require("../../markup/parseDom.cjs");
78
+ var import_segmentation = require("../../markup/segmentation.cjs");
79
+ var import_transform = require("../../markup/transform.cjs");
80
+ async function snapshotCtcAlignment(epubPath, emissionsPath, outputPath) {
81
+ var _stack = [];
82
+ try {
83
+ const emissionFilepaths = await (0, import_promises.readdir)(emissionsPath).then(
84
+ (filenames) => filenames.filter((f) => f.endsWith(".json")).map((f) => (0, import_node_path.join)(emissionsPath, f))
85
+ );
86
+ const epub = __using(_stack, await import_epub.Epub.from(epubPath));
87
+ const snapshot = await createAlignmentSnapshot(
88
+ epub,
89
+ emissionFilepaths,
90
+ "id-fragment"
91
+ );
92
+ await (0, import_promises.writeFile)(outputPath, snapshot, { encoding: "utf-8" });
93
+ } catch (_) {
94
+ var _error = _, _hasError = true;
95
+ } finally {
96
+ __callDispose(_stack, _error, _hasError);
97
+ }
98
+ }
99
+ function secondsToFrame(seconds, timings) {
100
+ const chunk = Math.floor(seconds / timings.secondsPerChunk);
101
+ const localFrame = Math.round(
102
+ (seconds - chunk * timings.secondsPerChunk) / timings.secondsPerFrame
103
+ );
104
+ return chunk * timings.framesPerChunk + Math.min(localFrame, timings.framesPerChunk - 1);
105
+ }
106
+ async function loadDecodedEmissions(filepath) {
107
+ const emissionsReader = await import_fs.EmissionsReader.from(filepath);
108
+ const { text, frames } = await (0, import_greedyDecode.ctcGreedyDecode)(emissionsReader);
109
+ return {
110
+ text,
111
+ frames,
112
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113
+ ...emissionsReader.timings[0]
114
+ };
115
+ }
116
+ async function createAlignmentSnapshot(epub, emissionFilepaths, textRef, transliterate) {
117
+ let newSnapshot = "";
118
+ const decodedEmissionsCache = /* @__PURE__ */ new Map();
119
+ function getDecodedEmissions(filepath) {
120
+ let cached = decodedEmissionsCache.get(filepath);
121
+ if (!cached) {
122
+ cached = loadDecodedEmissions(filepath);
123
+ decodedEmissionsCache.set(filepath, cached);
124
+ }
125
+ return cached;
126
+ }
127
+ const locale = await epub.getLanguage() ?? new Intl.Locale("en-US");
128
+ const spine = await epub.getSpineItems();
129
+ const manifest = await epub.getManifest();
130
+ const mediaOverlayItems = spine.map((item) => item.mediaOverlay).filter((mediaOverlayId) => !!mediaOverlayId).map((id) => manifest[id]);
131
+ const mediaOverlays = [];
132
+ for (const item of mediaOverlayItems) {
133
+ const contents = await epub.readItemContents(item.id, "utf-8");
134
+ const parsed = import_epub.Epub.xmlParser.parse(contents);
135
+ mediaOverlays.push(parsed);
136
+ const smil = import_epub.Epub.findXmlChildByName("smil", parsed);
137
+ if (!smil) continue;
138
+ const body = import_epub.Epub.findXmlChildByName("body", import_epub.Epub.getXmlChildren(smil));
139
+ if (!body) continue;
140
+ const seq = import_epub.Epub.findXmlChildByName("seq", import_epub.Epub.getXmlChildren(body));
141
+ if (!seq) continue;
142
+ const textref = seq[":@"]?.["@_epub:textref"];
143
+ if (!textref) continue;
144
+ newSnapshot += `// ${(0, import_posix.basename)(textref)}
145
+
146
+ `;
147
+ const chapterContents = await epub.readFileContents(
148
+ textref,
149
+ item.href,
150
+ "utf-8"
151
+ );
152
+ const chapterXml = import_epub.Epub.xhtmlParser.parse(chapterContents);
153
+ const original = (0, import_parseDom.parseDom)(import_epub.Epub.getXhtmlBody(chapterXml));
154
+ const inlined = (0, import_transform.inlineFootnotes)(original);
155
+ const lifted = (0, import_transform.liftText)(inlined.root);
156
+ const segmentation = await (0, import_segmentation.segmentChapter)(lifted.result, {
157
+ primaryLocale: new Intl.Locale("en-US")
158
+ });
159
+ let lastChapterSentence = -1;
160
+ const chapterSentences = segmentation.filter((s) => s.text.match(/\S/));
161
+ const pars = findAll("par", import_epub.Epub.getXmlChildren(seq));
162
+ for (const par of pars) {
163
+ newSnapshot += `
164
+ `;
165
+ const text = import_epub.Epub.findXmlChildByName("text", import_epub.Epub.getXmlChildren(par));
166
+ if (!text) continue;
167
+ const audio = import_epub.Epub.findXmlChildByName("audio", import_epub.Epub.getXmlChildren(par));
168
+ if (!audio) continue;
169
+ const textSrc = text[":@"]?.["@_src"];
170
+ if (!textSrc) continue;
171
+ const result = textRef === "id-fragment" ? getTextSentenceIndexByIdFragment(textSrc) : getTextSentenceIndexByTextFragment(
172
+ textSrc,
173
+ chapterSentences,
174
+ lastChapterSentence
175
+ );
176
+ if (result === null) continue;
177
+ const { fragment, sentenceId } = result;
178
+ const textSentence = chapterSentences[sentenceId]?.text.replace(/\n/, "");
179
+ if (!textSentence) continue;
180
+ lastChapterSentence = sentenceId;
181
+ if (textRef === "text-fragment") {
182
+ newSnapshot += `${fragment}
183
+ `;
184
+ }
185
+ newSnapshot += `Text: ${transliterate ? (await (0, import_slugify.slugify)(textSentence, locale)).result : textSentence}
186
+ `;
187
+ const audioSrc = audio[":@"]?.["@_src"];
188
+ if (!audioSrc) continue;
189
+ const audioStart = audio[":@"]?.["@_clipBegin"];
190
+ const audioEnd = audio[":@"]?.["@_clipEnd"];
191
+ if (!audioStart || !audioEnd) continue;
192
+ const audioStartTime = parseFloat(audioStart.slice(0, -1));
193
+ const audioEndTime = parseFloat(audioEnd.slice(0, -1));
194
+ const audioFilename = (0, import_posix.basename)(audioSrc, (0, import_posix.extname)(audioSrc));
195
+ const emissionFilepath = emissionFilepaths.find((f) => {
196
+ const withoutJson = (0, import_node_path.basename)(f, (0, import_node_path.extname)(f));
197
+ return (0, import_node_path.basename)(withoutJson, (0, import_node_path.extname)(withoutJson)) === audioFilename;
198
+ });
199
+ if (!emissionFilepath) continue;
200
+ const decoded = await getDecodedEmissions(emissionFilepath);
201
+ const startFrame = secondsToFrame(audioStartTime, decoded);
202
+ const endFrame = secondsToFrame(audioEndTime, decoded);
203
+ let decodedSentence = "";
204
+ for (let i = 0; i < decoded.frames.length; i++) {
205
+ const frame = decoded.frames[i];
206
+ if (frame > endFrame) break;
207
+ if (frame >= startFrame) {
208
+ decodedSentence += decoded.text[i];
209
+ }
210
+ }
211
+ newSnapshot += `Audio: ${decodedSentence}
212
+ `;
213
+ }
214
+ newSnapshot += `
215
+ `;
216
+ }
217
+ return newSnapshot;
218
+ }
219
+ function getTextSentenceIndexByTextFragment(textSrc, chapterSentences, lastChapterSentence) {
220
+ const textSrcMatch = textSrc.match(/#:~:text=(.+)$/);
221
+ if (!textSrcMatch) return null;
222
+ const textFragment = textSrcMatch[1];
223
+ if (textFragment === void 0) return null;
224
+ const textFragmentParts = textFragment.split(",");
225
+ const textFragmentPrefix = textFragmentParts[0]?.endsWith("-") ? decodeURIComponent(textFragmentParts[0]).slice(0, -1) : "";
226
+ const textFragmentStart = decodeURIComponent(
227
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
228
+ textFragmentPrefix ? textFragmentParts[1] : textFragmentParts[0]
229
+ );
230
+ const textSentenceIndex = chapterSentences.slice(lastChapterSentence + 1).findIndex((s, i) => {
231
+ const prev = chapterSentences[lastChapterSentence + i];
232
+ return (!prev || prev.text.replace("\n", " ").toLowerCase().endsWith(textFragmentPrefix)) && s.text.replace("\n", " ").toLowerCase().startsWith(textFragmentStart);
233
+ });
234
+ if (textSentenceIndex === -1) return null;
235
+ return {
236
+ fragment: textSrcMatch[0],
237
+ sentenceId: textSentenceIndex + lastChapterSentence + 1
238
+ };
239
+ }
240
+ function getTextSentenceIndexByIdFragment(textSrc) {
241
+ const match = textSrc.match(/#.*s([0-9]+)$/);
242
+ if (!match) return null;
243
+ const [fragment, sentenceId] = match;
244
+ return { fragment, sentenceId: parseInt(sentenceId, 10) };
245
+ }
246
+ function findAll(name, xml) {
247
+ const result = [];
248
+ for (const child of xml) {
249
+ if (import_epub.Epub.isXmlTextNode(child)) continue;
250
+ if (import_epub.Epub.getXmlElementName(child) === name) {
251
+ result.push(child);
252
+ }
253
+ result.push(...findAll(name, import_epub.Epub.getXmlChildren(child)));
254
+ }
255
+ return result;
256
+ }
257
+ // Annotate the CommonJS export names for ESM import in node:
258
+ 0 && (module.exports = {
259
+ createAlignmentSnapshot,
260
+ snapshotCtcAlignment
261
+ });
@@ -0,0 +1,6 @@
1
+ import { Epub } from '@storyteller-platform/epub';
2
+
3
+ declare function snapshotCtcAlignment(epubPath: string, emissionsPath: string, outputPath: string): Promise<void>;
4
+ declare function createAlignmentSnapshot(epub: Epub, emissionFilepaths: string[], textRef: "id-fragment" | "text-fragment", transliterate?: boolean): Promise<string>;
5
+
6
+ export { createAlignmentSnapshot, snapshotCtcAlignment };
@@ -0,0 +1,6 @@
1
+ import { Epub } from '@storyteller-platform/epub';
2
+
3
+ declare function snapshotCtcAlignment(epubPath: string, emissionsPath: string, outputPath: string): Promise<void>;
4
+ declare function createAlignmentSnapshot(epub: Epub, emissionFilepaths: string[], textRef: "id-fragment" | "text-fragment", transliterate?: boolean): Promise<string>;
5
+
6
+ export { createAlignmentSnapshot, snapshotCtcAlignment };
@@ -0,0 +1,200 @@
1
+ import {
2
+ __callDispose,
3
+ __using
4
+ } from "../../chunk-BIEQXUOY.js";
5
+ import { readdir, writeFile } from "node:fs/promises";
6
+ import { basename, extname, join } from "node:path";
7
+ import {
8
+ basename as posixBasename,
9
+ extname as posixExtname
10
+ } from "node:path/posix";
11
+ import {
12
+ Epub
13
+ } from "@storyteller-platform/epub";
14
+ import { ctcGreedyDecode } from "../../align/ctc/greedyDecode.js";
15
+ import { slugify } from "../../align/slugify.js";
16
+ import { EmissionsReader } from "../../emit/fs.js";
17
+ import { parseDom } from "../../markup/parseDom.js";
18
+ import { segmentChapter } from "../../markup/segmentation.js";
19
+ import { inlineFootnotes, liftText } from "../../markup/transform.js";
20
+ async function snapshotCtcAlignment(epubPath, emissionsPath, outputPath) {
21
+ var _stack = [];
22
+ try {
23
+ const emissionFilepaths = await readdir(emissionsPath).then(
24
+ (filenames) => filenames.filter((f) => f.endsWith(".json")).map((f) => join(emissionsPath, f))
25
+ );
26
+ const epub = __using(_stack, await Epub.from(epubPath));
27
+ const snapshot = await createAlignmentSnapshot(
28
+ epub,
29
+ emissionFilepaths,
30
+ "id-fragment"
31
+ );
32
+ await writeFile(outputPath, snapshot, { encoding: "utf-8" });
33
+ } catch (_) {
34
+ var _error = _, _hasError = true;
35
+ } finally {
36
+ __callDispose(_stack, _error, _hasError);
37
+ }
38
+ }
39
+ function secondsToFrame(seconds, timings) {
40
+ const chunk = Math.floor(seconds / timings.secondsPerChunk);
41
+ const localFrame = Math.round(
42
+ (seconds - chunk * timings.secondsPerChunk) / timings.secondsPerFrame
43
+ );
44
+ return chunk * timings.framesPerChunk + Math.min(localFrame, timings.framesPerChunk - 1);
45
+ }
46
+ async function loadDecodedEmissions(filepath) {
47
+ const emissionsReader = await EmissionsReader.from(filepath);
48
+ const { text, frames } = await ctcGreedyDecode(emissionsReader);
49
+ return {
50
+ text,
51
+ frames,
52
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
53
+ ...emissionsReader.timings[0]
54
+ };
55
+ }
56
+ async function createAlignmentSnapshot(epub, emissionFilepaths, textRef, transliterate) {
57
+ let newSnapshot = "";
58
+ const decodedEmissionsCache = /* @__PURE__ */ new Map();
59
+ function getDecodedEmissions(filepath) {
60
+ let cached = decodedEmissionsCache.get(filepath);
61
+ if (!cached) {
62
+ cached = loadDecodedEmissions(filepath);
63
+ decodedEmissionsCache.set(filepath, cached);
64
+ }
65
+ return cached;
66
+ }
67
+ const locale = await epub.getLanguage() ?? new Intl.Locale("en-US");
68
+ const spine = await epub.getSpineItems();
69
+ const manifest = await epub.getManifest();
70
+ const mediaOverlayItems = spine.map((item) => item.mediaOverlay).filter((mediaOverlayId) => !!mediaOverlayId).map((id) => manifest[id]);
71
+ const mediaOverlays = [];
72
+ for (const item of mediaOverlayItems) {
73
+ const contents = await epub.readItemContents(item.id, "utf-8");
74
+ const parsed = Epub.xmlParser.parse(contents);
75
+ mediaOverlays.push(parsed);
76
+ const smil = Epub.findXmlChildByName("smil", parsed);
77
+ if (!smil) continue;
78
+ const body = Epub.findXmlChildByName("body", Epub.getXmlChildren(smil));
79
+ if (!body) continue;
80
+ const seq = Epub.findXmlChildByName("seq", Epub.getXmlChildren(body));
81
+ if (!seq) continue;
82
+ const textref = seq[":@"]?.["@_epub:textref"];
83
+ if (!textref) continue;
84
+ newSnapshot += `// ${posixBasename(textref)}
85
+
86
+ `;
87
+ const chapterContents = await epub.readFileContents(
88
+ textref,
89
+ item.href,
90
+ "utf-8"
91
+ );
92
+ const chapterXml = Epub.xhtmlParser.parse(chapterContents);
93
+ const original = parseDom(Epub.getXhtmlBody(chapterXml));
94
+ const inlined = inlineFootnotes(original);
95
+ const lifted = liftText(inlined.root);
96
+ const segmentation = await segmentChapter(lifted.result, {
97
+ primaryLocale: new Intl.Locale("en-US")
98
+ });
99
+ let lastChapterSentence = -1;
100
+ const chapterSentences = segmentation.filter((s) => s.text.match(/\S/));
101
+ const pars = findAll("par", Epub.getXmlChildren(seq));
102
+ for (const par of pars) {
103
+ newSnapshot += `
104
+ `;
105
+ const text = Epub.findXmlChildByName("text", Epub.getXmlChildren(par));
106
+ if (!text) continue;
107
+ const audio = Epub.findXmlChildByName("audio", Epub.getXmlChildren(par));
108
+ if (!audio) continue;
109
+ const textSrc = text[":@"]?.["@_src"];
110
+ if (!textSrc) continue;
111
+ const result = textRef === "id-fragment" ? getTextSentenceIndexByIdFragment(textSrc) : getTextSentenceIndexByTextFragment(
112
+ textSrc,
113
+ chapterSentences,
114
+ lastChapterSentence
115
+ );
116
+ if (result === null) continue;
117
+ const { fragment, sentenceId } = result;
118
+ const textSentence = chapterSentences[sentenceId]?.text.replace(/\n/, "");
119
+ if (!textSentence) continue;
120
+ lastChapterSentence = sentenceId;
121
+ if (textRef === "text-fragment") {
122
+ newSnapshot += `${fragment}
123
+ `;
124
+ }
125
+ newSnapshot += `Text: ${transliterate ? (await slugify(textSentence, locale)).result : textSentence}
126
+ `;
127
+ const audioSrc = audio[":@"]?.["@_src"];
128
+ if (!audioSrc) continue;
129
+ const audioStart = audio[":@"]?.["@_clipBegin"];
130
+ const audioEnd = audio[":@"]?.["@_clipEnd"];
131
+ if (!audioStart || !audioEnd) continue;
132
+ const audioStartTime = parseFloat(audioStart.slice(0, -1));
133
+ const audioEndTime = parseFloat(audioEnd.slice(0, -1));
134
+ const audioFilename = posixBasename(audioSrc, posixExtname(audioSrc));
135
+ const emissionFilepath = emissionFilepaths.find((f) => {
136
+ const withoutJson = basename(f, extname(f));
137
+ return basename(withoutJson, extname(withoutJson)) === audioFilename;
138
+ });
139
+ if (!emissionFilepath) continue;
140
+ const decoded = await getDecodedEmissions(emissionFilepath);
141
+ const startFrame = secondsToFrame(audioStartTime, decoded);
142
+ const endFrame = secondsToFrame(audioEndTime, decoded);
143
+ let decodedSentence = "";
144
+ for (let i = 0; i < decoded.frames.length; i++) {
145
+ const frame = decoded.frames[i];
146
+ if (frame > endFrame) break;
147
+ if (frame >= startFrame) {
148
+ decodedSentence += decoded.text[i];
149
+ }
150
+ }
151
+ newSnapshot += `Audio: ${decodedSentence}
152
+ `;
153
+ }
154
+ newSnapshot += `
155
+ `;
156
+ }
157
+ return newSnapshot;
158
+ }
159
+ function getTextSentenceIndexByTextFragment(textSrc, chapterSentences, lastChapterSentence) {
160
+ const textSrcMatch = textSrc.match(/#:~:text=(.+)$/);
161
+ if (!textSrcMatch) return null;
162
+ const textFragment = textSrcMatch[1];
163
+ if (textFragment === void 0) return null;
164
+ const textFragmentParts = textFragment.split(",");
165
+ const textFragmentPrefix = textFragmentParts[0]?.endsWith("-") ? decodeURIComponent(textFragmentParts[0]).slice(0, -1) : "";
166
+ const textFragmentStart = decodeURIComponent(
167
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
168
+ textFragmentPrefix ? textFragmentParts[1] : textFragmentParts[0]
169
+ );
170
+ const textSentenceIndex = chapterSentences.slice(lastChapterSentence + 1).findIndex((s, i) => {
171
+ const prev = chapterSentences[lastChapterSentence + i];
172
+ return (!prev || prev.text.replace("\n", " ").toLowerCase().endsWith(textFragmentPrefix)) && s.text.replace("\n", " ").toLowerCase().startsWith(textFragmentStart);
173
+ });
174
+ if (textSentenceIndex === -1) return null;
175
+ return {
176
+ fragment: textSrcMatch[0],
177
+ sentenceId: textSentenceIndex + lastChapterSentence + 1
178
+ };
179
+ }
180
+ function getTextSentenceIndexByIdFragment(textSrc) {
181
+ const match = textSrc.match(/#.*s([0-9]+)$/);
182
+ if (!match) return null;
183
+ const [fragment, sentenceId] = match;
184
+ return { fragment, sentenceId: parseInt(sentenceId, 10) };
185
+ }
186
+ function findAll(name, xml) {
187
+ const result = [];
188
+ for (const child of xml) {
189
+ if (Epub.isXmlTextNode(child)) continue;
190
+ if (Epub.getXmlElementName(child) === name) {
191
+ result.push(child);
192
+ }
193
+ result.push(...findAll(name, Epub.getXmlChildren(child)));
194
+ }
195
+ return result;
196
+ }
197
+ export {
198
+ createAlignmentSnapshot,
199
+ snapshotCtcAlignment
200
+ };
@@ -25,22 +25,35 @@ module.exports = __toCommonJS(parse_exports);
25
25
  var import_core = require("@optique/core");
26
26
  var import_run = require("@optique/run");
27
27
  var import_parse = require("../common/parse.cjs");
28
- const snapshotParser = (0, import_core.object)("Snapshot", {
29
- transcriptions: (0, import_core.option)(
30
- "--transcriptions",
31
- (0, import_run.path)({ mustExist: true, type: "directory" })
32
- ),
33
- epub: (0, import_core.option)(
34
- "--epub",
35
- (0, import_run.path)({ mustExist: true, type: "file", extensions: [".epub"] }),
36
- {
37
- description: import_core.message`Path to an EPUB file to snapshot. This EPUB must have Media Overlays and audio files corresponding to the transcription files passed to --transcriptions.`
38
- }
39
- ),
40
- output: (0, import_core.argument)((0, import_run.path)({ type: "file", metavar: "OUTPUT_PATH" }), {
41
- description: import_core.message`Path to save the snapshot.`
42
- })
43
- });
28
+ const snapshotParser = (0, import_core.merge)(
29
+ (0, import_core.object)("Snapshot", {
30
+ epub: (0, import_core.option)(
31
+ "--epub",
32
+ (0, import_run.path)({ mustExist: true, type: "file", extensions: [".epub"] }),
33
+ {
34
+ description: import_core.message`Path to an EPUB file to snapshot. This EPUB must have Media Overlays and audio files corresponding to the transcription files passed to --transcriptions.`
35
+ }
36
+ ),
37
+ output: (0, import_core.argument)((0, import_run.path)({ type: "file", metavar: "OUTPUT_PATH" }), {
38
+ description: import_core.message`Path to save the snapshot.`
39
+ })
40
+ }),
41
+ (0, import_core.or)(
42
+ (0, import_core.object)({
43
+ transcriptions: (0, import_core.option)(
44
+ "--transcriptions",
45
+ (0, import_run.path)({ mustExist: true, type: "directory" })
46
+ )
47
+ }),
48
+ (0, import_core.object)({
49
+ ctc: (0, import_core.flag)("--ctc"),
50
+ emissions: (0, import_core.option)(
51
+ "--emissions",
52
+ (0, import_run.path)({ mustExist: true, type: "directory" })
53
+ )
54
+ })
55
+ )
56
+ );
44
57
  const snapshotCommand = (0, import_core.command)(
45
58
  "snapshot",
46
59
  (0, import_core.merge)(
@@ -1,24 +1,28 @@
1
1
  import * as _optique_core from '@optique/core';
2
2
 
3
3
  declare const snapshotParser: _optique_core.Parser<"sync", {
4
- readonly transcriptions: string;
5
4
  readonly epub: string;
6
5
  readonly output: string;
7
- }, {
8
- readonly transcriptions: _optique_core.ValueParserResult<string> | undefined;
9
- readonly epub: _optique_core.ValueParserResult<string> | undefined;
10
- readonly output: _optique_core.ValueParserResult<string> | undefined;
11
- }>;
6
+ } & ({
7
+ readonly transcriptions: string;
8
+ } | {
9
+ readonly ctc: true;
10
+ readonly emissions: string;
11
+ }), Record<string | symbol, unknown>>;
12
12
  declare const snapshotCommand: _optique_core.Parser<"sync", {
13
13
  readonly action: "snapshot";
14
- } & {
15
- readonly transcriptions: string;
14
+ } & (({
16
15
  readonly epub: string;
17
16
  readonly output: string;
18
- } & {
17
+ } & ({
18
+ readonly transcriptions: string;
19
+ } | {
20
+ readonly ctc: true;
21
+ readonly emissions: string;
22
+ })) & {
19
23
  readonly noProgress: boolean;
20
24
  readonly logLevel: "silent" | "debug" | "info" | "warn" | "error";
21
25
  readonly time: boolean;
22
- }, ["matched", string] | ["parsing", Record<string | symbol, unknown>] | undefined>;
26
+ }), ["matched", string] | ["parsing", Record<string | symbol, unknown>] | undefined>;
23
27
 
24
28
  export { snapshotCommand, snapshotParser };
@@ -1,24 +1,28 @@
1
1
  import * as _optique_core from '@optique/core';
2
2
 
3
3
  declare const snapshotParser: _optique_core.Parser<"sync", {
4
- readonly transcriptions: string;
5
4
  readonly epub: string;
6
5
  readonly output: string;
7
- }, {
8
- readonly transcriptions: _optique_core.ValueParserResult<string> | undefined;
9
- readonly epub: _optique_core.ValueParserResult<string> | undefined;
10
- readonly output: _optique_core.ValueParserResult<string> | undefined;
11
- }>;
6
+ } & ({
7
+ readonly transcriptions: string;
8
+ } | {
9
+ readonly ctc: true;
10
+ readonly emissions: string;
11
+ }), Record<string | symbol, unknown>>;
12
12
  declare const snapshotCommand: _optique_core.Parser<"sync", {
13
13
  readonly action: "snapshot";
14
- } & {
15
- readonly transcriptions: string;
14
+ } & (({
16
15
  readonly epub: string;
17
16
  readonly output: string;
18
- } & {
17
+ } & ({
18
+ readonly transcriptions: string;
19
+ } | {
20
+ readonly ctc: true;
21
+ readonly emissions: string;
22
+ })) & {
19
23
  readonly noProgress: boolean;
20
24
  readonly logLevel: "silent" | "debug" | "info" | "warn" | "error";
21
25
  readonly time: boolean;
22
- }, ["matched", string] | ["parsing", Record<string | symbol, unknown>] | undefined>;
26
+ }), ["matched", string] | ["parsing", Record<string | symbol, unknown>] | undefined>;
23
27
 
24
28
  export { snapshotCommand, snapshotParser };
@@ -3,29 +3,44 @@ import {
3
3
  argument,
4
4
  command,
5
5
  constant,
6
+ flag,
6
7
  merge,
7
8
  message,
8
9
  object,
9
- option
10
+ option,
11
+ or
10
12
  } from "@optique/core";
11
13
  import { path } from "@optique/run";
12
14
  import { loggingParser } from "../common/parse.js";
13
- const snapshotParser = object("Snapshot", {
14
- transcriptions: option(
15
- "--transcriptions",
16
- path({ mustExist: true, type: "directory" })
17
- ),
18
- epub: option(
19
- "--epub",
20
- path({ mustExist: true, type: "file", extensions: [".epub"] }),
21
- {
22
- description: message`Path to an EPUB file to snapshot. This EPUB must have Media Overlays and audio files corresponding to the transcription files passed to --transcriptions.`
23
- }
24
- ),
25
- output: argument(path({ type: "file", metavar: "OUTPUT_PATH" }), {
26
- description: message`Path to save the snapshot.`
27
- })
28
- });
15
+ const snapshotParser = merge(
16
+ object("Snapshot", {
17
+ epub: option(
18
+ "--epub",
19
+ path({ mustExist: true, type: "file", extensions: [".epub"] }),
20
+ {
21
+ description: message`Path to an EPUB file to snapshot. This EPUB must have Media Overlays and audio files corresponding to the transcription files passed to --transcriptions.`
22
+ }
23
+ ),
24
+ output: argument(path({ type: "file", metavar: "OUTPUT_PATH" }), {
25
+ description: message`Path to save the snapshot.`
26
+ })
27
+ }),
28
+ or(
29
+ object({
30
+ transcriptions: option(
31
+ "--transcriptions",
32
+ path({ mustExist: true, type: "directory" })
33
+ )
34
+ }),
35
+ object({
36
+ ctc: flag("--ctc"),
37
+ emissions: option(
38
+ "--emissions",
39
+ path({ mustExist: true, type: "directory" })
40
+ )
41
+ })
42
+ )
43
+ );
29
44
  const snapshotCommand = command(
30
45
  "snapshot",
31
46
  merge(