@storyteller-platform/align 0.1.41 → 0.1.47

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/cli/bin.cjs CHANGED
@@ -359,13 +359,12 @@ async function main() {
359
359
  if (parsed.textRef === "id-fragment") {
360
360
  logger.info("Marking up EPUB...");
361
361
  startProgressBar();
362
- const markedup2 = parsed.markedup ?? (0, import_node_path.join)(os.tmpdir(), `stalign-markedup-${(0, import_node_crypto.randomUUID)()}.epub`);
363
362
  if (!parsed.markedup) {
364
363
  stack.defer(() => {
365
- (0, import_node_fs.rmSync)(markedup2, { recursive: true, force: true });
364
+ (0, import_node_fs.rmSync)(markedup, { recursive: true, force: true });
366
365
  });
367
366
  }
368
- const markupTiming = await (0, import_markup.markup)(input, markedup2, {
367
+ const markupTiming = await (0, import_markup.markup)(input, markedup, {
369
368
  granularity: parsed.granularity,
370
369
  primaryLocale,
371
370
  logger,
@@ -376,12 +375,12 @@ async function main() {
376
375
  }
377
376
  });
378
377
  resetProgressBar();
379
- logger.info(`Markup complete, marked up EPUB saved to ${markedup2}.`);
378
+ logger.info(`Markup complete, marked up EPUB saved to ${markedup}.`);
380
379
  if (parsed.time) {
381
380
  markupTiming.print();
382
381
  }
383
382
  } else {
384
- logger.info("Skipping markup, text-range-type set to text-fragment");
383
+ logger.info("Skipping markup, text-ref set to text-fragment");
385
384
  }
386
385
  logger.info("Aligning EPUB with audiobook...");
387
386
  startProgressBar();
package/dist/cli/bin.js CHANGED
@@ -312,13 +312,12 @@ async function main() {
312
312
  if (parsed.textRef === "id-fragment") {
313
313
  logger.info("Marking up EPUB...");
314
314
  startProgressBar();
315
- const markedup2 = parsed.markedup ?? join(os.tmpdir(), `stalign-markedup-${randomUUID()}.epub`);
316
315
  if (!parsed.markedup) {
317
316
  stack.defer(() => {
318
- rmSync(markedup2, { recursive: true, force: true });
317
+ rmSync(markedup, { recursive: true, force: true });
319
318
  });
320
319
  }
321
- const markupTiming = await markup(input, markedup2, {
320
+ const markupTiming = await markup(input, markedup, {
322
321
  granularity: parsed.granularity,
323
322
  primaryLocale,
324
323
  logger,
@@ -329,12 +328,12 @@ async function main() {
329
328
  }
330
329
  });
331
330
  resetProgressBar();
332
- logger.info(`Markup complete, marked up EPUB saved to ${markedup2}.`);
331
+ logger.info(`Markup complete, marked up EPUB saved to ${markedup}.`);
333
332
  if (parsed.time) {
334
333
  markupTiming.print();
335
334
  }
336
335
  } else {
337
- logger.info("Skipping markup, text-range-type set to text-fragment");
336
+ logger.info("Skipping markup, text-ref set to text-fragment");
338
337
  }
339
338
  logger.info("Aligning EPUB with audiobook...");
340
339
  startProgressBar();
@@ -43,14 +43,11 @@ var import_mime = require("../process/mime.cjs");
43
43
  var import_shell = require("./shell.cjs");
44
44
  const execPromise = (0, import_node_util.promisify)(import_node_child_process.exec);
45
45
  async function execCmd(command, logger, signal) {
46
- let stdout = "";
47
- let stderr = "";
48
46
  try {
49
- ;
50
- ({ stdout, stderr } = await execPromise(command, {
47
+ const { stdout } = await execPromise(command, {
51
48
  maxBuffer: 50 * 1024 * 1024,
52
49
  signal: signal ?? void 0
53
- }));
50
+ });
54
51
  return stdout;
55
52
  } catch (error) {
56
53
  if (error instanceof RangeError && error.message.includes("stdout maxBuffer length exceeded")) {
@@ -58,14 +55,16 @@ async function execCmd(command, logger, signal) {
58
55
  "stdout maxBuffer length exceeded. This likely means that youre trying to process a very large file, and the ffmpeg process is running out of memory. Maybe check the image size of your cover art."
59
56
  );
60
57
  }
58
+ const execErr = error;
61
59
  logger?.error(error);
62
- logger?.info(stdout);
63
- throw new Error(stderr);
60
+ if (execErr.stdout) logger?.info(execErr.stdout);
61
+ const errorDetail = execErr.stderr || execErr.stdout || `Command failed: ${command}`;
62
+ throw new Error(errorDetail);
64
63
  }
65
64
  }
66
65
  const getTrackInfo = (0, import_memoize.default)(async function getTrackInfo2(path, logger) {
67
66
  const stdout = await execCmd(
68
- `ffprobe -i ${(0, import_shell.quotePath)(path)} -show_format -of json`,
67
+ `ffprobe -v error -i ${(0, import_shell.quotePath)(path)} -show_format -of json`,
69
68
  logger
70
69
  );
71
70
  const info = JSON.parse(stdout);
@@ -8,14 +8,11 @@ import { areSameType } from "../process/mime.js";
8
8
  import { quotePath } from "./shell.js";
9
9
  const execPromise = promisify(exec);
10
10
  async function execCmd(command, logger, signal) {
11
- let stdout = "";
12
- let stderr = "";
13
11
  try {
14
- ;
15
- ({ stdout, stderr } = await execPromise(command, {
12
+ const { stdout } = await execPromise(command, {
16
13
  maxBuffer: 50 * 1024 * 1024,
17
14
  signal: signal ?? void 0
18
- }));
15
+ });
19
16
  return stdout;
20
17
  } catch (error) {
21
18
  if (error instanceof RangeError && error.message.includes("stdout maxBuffer length exceeded")) {
@@ -23,14 +20,16 @@ async function execCmd(command, logger, signal) {
23
20
  "stdout maxBuffer length exceeded. This likely means that youre trying to process a very large file, and the ffmpeg process is running out of memory. Maybe check the image size of your cover art."
24
21
  );
25
22
  }
23
+ const execErr = error;
26
24
  logger?.error(error);
27
- logger?.info(stdout);
28
- throw new Error(stderr);
25
+ if (execErr.stdout) logger?.info(execErr.stdout);
26
+ const errorDetail = execErr.stderr || execErr.stdout || `Command failed: ${command}`;
27
+ throw new Error(errorDetail);
29
28
  }
30
29
  }
31
30
  const getTrackInfo = memoize(async function getTrackInfo2(path, logger) {
32
31
  const stdout = await execCmd(
33
- `ffprobe -i ${quotePath(path)} -show_format -of json`,
32
+ `ffprobe -v error -i ${quotePath(path)} -show_format -of json`,
34
33
  logger
35
34
  );
36
35
  const info = JSON.parse(stdout);
@@ -88,10 +88,19 @@ async function generateReadiumManifest(epub, options = {}) {
88
88
  const dir = await epub.getBaseDirection();
89
89
  const epubMetadata = await epub.getMetadata();
90
90
  const vocab = await epub.getPackageVocabularyPrefixes();
91
- const duration = epubMetadata.find(
92
- ({ properties }) => properties["property"] === "media:duration"
93
- )?.value;
94
- const durationMs = duration !== void 0 ? (0, import_smil_clockvalue.default)(duration) : void 0;
91
+ let duration = void 0;
92
+ const refinesDurationMap = /* @__PURE__ */ new Map();
93
+ for (const dur of epubMetadata) {
94
+ if (dur.properties["property"] !== "media:duration") continue;
95
+ if (!dur.properties["refines"]) {
96
+ duration = dur.value ? (0, import_smil_clockvalue.default)(dur.value) / 1e3 : void 0;
97
+ continue;
98
+ }
99
+ const value = dur.value ? (0, import_smil_clockvalue.default)(dur.value) / 1e3 : void 0;
100
+ if (value) {
101
+ refinesDurationMap.set(dur.properties["refines"], value);
102
+ }
103
+ }
95
104
  const otherMetadata = epubMetadata.filter(
96
105
  (meta) => (meta.properties["property"]?.split(":")[0] ?? "") in vocab
97
106
  ).map((meta) => {
@@ -130,8 +139,8 @@ async function generateReadiumManifest(epub, options = {}) {
130
139
  ...dir !== "auto" && {
131
140
  readingProgression: dir === "ltr" ? import_shared.ReadingProgression.ltr : import_shared.ReadingProgression.rtl
132
141
  },
133
- // TODO: is this meant to be in milliseconds (as here) or seconds?
134
- ...durationMs !== void 0 && { duration: durationMs },
142
+ // it's seconds
143
+ ...duration !== void 0 && { duration },
135
144
  ...numberOfPages !== void 0 && { numberOfPages },
136
145
  otherMetadata: Object.fromEntries(otherMetadata)
137
146
  });
@@ -184,16 +193,13 @@ async function generateReadiumManifest(epub, options = {}) {
184
193
  if (!item.mediaOverlay) return link;
185
194
  const mediaOverlayItem = epubManifest[item.id];
186
195
  if (!mediaOverlayItem) return link;
187
- const refinedBy = epubMetadata.find(
188
- ({ properties }) => properties["property"] === "media:duration" && properties["refines"] === `#${mediaOverlayItem.id}`
189
- );
190
- if (!refinedBy?.value) return link;
191
- const itemDuration = (0, import_smil_clockvalue.default)(refinedBy.value);
196
+ const duration2 = refinesDurationMap.get(`#${item.mediaOverlay}`) || refinesDurationMap.get(`#${mediaOverlayItem.id}`);
197
+ if (!duration2) return link;
192
198
  return new import_shared.Link({
193
199
  href: link.href,
194
200
  type: link.mediaType.string,
195
201
  ...link.properties && { properties: link.properties },
196
- duration: itemDuration
202
+ duration: duration2
197
203
  });
198
204
  })
199
205
  );
@@ -71,10 +71,19 @@ async function generateReadiumManifest(epub, options = {}) {
71
71
  const dir = await epub.getBaseDirection();
72
72
  const epubMetadata = await epub.getMetadata();
73
73
  const vocab = await epub.getPackageVocabularyPrefixes();
74
- const duration = epubMetadata.find(
75
- ({ properties }) => properties["property"] === "media:duration"
76
- )?.value;
77
- const durationMs = duration !== void 0 ? clockvalue(duration) : void 0;
74
+ let duration = void 0;
75
+ const refinesDurationMap = /* @__PURE__ */ new Map();
76
+ for (const dur of epubMetadata) {
77
+ if (dur.properties["property"] !== "media:duration") continue;
78
+ if (!dur.properties["refines"]) {
79
+ duration = dur.value ? clockvalue(dur.value) / 1e3 : void 0;
80
+ continue;
81
+ }
82
+ const value = dur.value ? clockvalue(dur.value) / 1e3 : void 0;
83
+ if (value) {
84
+ refinesDurationMap.set(dur.properties["refines"], value);
85
+ }
86
+ }
78
87
  const otherMetadata = epubMetadata.filter(
79
88
  (meta) => (meta.properties["property"]?.split(":")[0] ?? "") in vocab
80
89
  ).map((meta) => {
@@ -113,8 +122,8 @@ async function generateReadiumManifest(epub, options = {}) {
113
122
  ...dir !== "auto" && {
114
123
  readingProgression: dir === "ltr" ? ReadingProgression.ltr : ReadingProgression.rtl
115
124
  },
116
- // TODO: is this meant to be in milliseconds (as here) or seconds?
117
- ...durationMs !== void 0 && { duration: durationMs },
125
+ // it's seconds
126
+ ...duration !== void 0 && { duration },
118
127
  ...numberOfPages !== void 0 && { numberOfPages },
119
128
  otherMetadata: Object.fromEntries(otherMetadata)
120
129
  });
@@ -167,16 +176,13 @@ async function generateReadiumManifest(epub, options = {}) {
167
176
  if (!item.mediaOverlay) return link;
168
177
  const mediaOverlayItem = epubManifest[item.id];
169
178
  if (!mediaOverlayItem) return link;
170
- const refinedBy = epubMetadata.find(
171
- ({ properties }) => properties["property"] === "media:duration" && properties["refines"] === `#${mediaOverlayItem.id}`
172
- );
173
- if (!refinedBy?.value) return link;
174
- const itemDuration = clockvalue(refinedBy.value);
179
+ const duration2 = refinesDurationMap.get(`#${item.mediaOverlay}`) || refinesDurationMap.get(`#${mediaOverlayItem.id}`);
180
+ if (!duration2) return link;
175
181
  return new Link({
176
182
  href: link.href,
177
183
  type: link.mediaType.string,
178
184
  ...link.properties && { properties: link.properties },
179
- duration: itemDuration
185
+ duration: duration2
180
186
  });
181
187
  })
182
188
  );
@@ -101,8 +101,9 @@ async function transcribe(input, output, locale, options) {
101
101
  const engine = options.engine ?? "whisper.cpp";
102
102
  const model = options.model ?? "tiny.en";
103
103
  if (engine === "whisper.cpp") {
104
+ const resolvedModel = getWhisperCppModelId(locale.language, model);
104
105
  await (0, import_ghost_story.ensureWhisperInstalled)({
105
- model,
106
+ model: resolvedModel,
106
107
  printOutput: ["debug", "info"].includes(
107
108
  options.logger?.level ?? "silent"
108
109
  ),
@@ -205,8 +206,12 @@ async function transcribeFile(input, locale, options) {
205
206
  const fallbackVariant = getCpuOverrideVariant(
206
207
  options.whisperCpuOverride ?? null
207
208
  );
209
+ const resolvedModel = getWhisperCppModelId(
210
+ sharedOptions.language,
211
+ options.model
212
+ );
208
213
  const whisperOptions = await (0, import_ghost_story.ensureWhisperInstalled)({
209
- model: options.model,
214
+ model: resolvedModel,
210
215
  variant: fallbackVariant,
211
216
  printOutput: ["debug", "info"].includes(
212
217
  options.logger?.level ?? "silent"
@@ -218,7 +223,7 @@ async function transcribeFile(input, locale, options) {
218
223
  engine: options.engine,
219
224
  options: {
220
225
  flashAttention: true,
221
- model: getWhisperCppModelId(sharedOptions.language, options.model),
226
+ model: resolvedModel,
222
227
  processors: options.processors,
223
228
  threads: options.threads,
224
229
  onProgress: (progress) => {
@@ -32,8 +32,9 @@ async function transcribe(input, output, locale, options) {
32
32
  const engine = options.engine ?? "whisper.cpp";
33
33
  const model = options.model ?? "tiny.en";
34
34
  if (engine === "whisper.cpp") {
35
+ const resolvedModel = getWhisperCppModelId(locale.language, model);
35
36
  await ensureWhisperInstalled({
36
- model,
37
+ model: resolvedModel,
37
38
  printOutput: ["debug", "info"].includes(
38
39
  options.logger?.level ?? "silent"
39
40
  ),
@@ -136,8 +137,12 @@ async function transcribeFile(input, locale, options) {
136
137
  const fallbackVariant = getCpuOverrideVariant(
137
138
  options.whisperCpuOverride ?? null
138
139
  );
140
+ const resolvedModel = getWhisperCppModelId(
141
+ sharedOptions.language,
142
+ options.model
143
+ );
139
144
  const whisperOptions = await ensureWhisperInstalled({
140
- model: options.model,
145
+ model: resolvedModel,
141
146
  variant: fallbackVariant,
142
147
  printOutput: ["debug", "info"].includes(
143
148
  options.logger?.level ?? "silent"
@@ -149,7 +154,7 @@ async function transcribeFile(input, locale, options) {
149
154
  engine: options.engine,
150
155
  options: {
151
156
  flashAttention: true,
152
- model: getWhisperCppModelId(sharedOptions.language, options.model),
157
+ model: resolvedModel,
153
158
  processors: options.processors,
154
159
  threads: options.threads,
155
160
  onProgress: (progress) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyteller-platform/align",
3
- "version": "0.1.41",
3
+ "version": "0.1.47",
4
4
  "description": "A library and CLI for automatically aligning audiobooks and EPUBs to produce Media Overlays",
5
5
  "author": "Shane Friedman",
6
6
  "license": "MIT",
@@ -70,8 +70,8 @@
70
70
  "@optique/core": "^0.10.7",
71
71
  "@optique/run": "^0.10.7",
72
72
  "@readium/shared": "^2.2.0",
73
- "@storyteller-platform/audiobook": "^0.4.0",
74
- "@storyteller-platform/epub": "^0.6.0",
73
+ "@storyteller-platform/audiobook": "^0.4.1",
74
+ "@storyteller-platform/epub": "^0.6.1",
75
75
  "@storyteller-platform/ghost-story": "^0.1.11",
76
76
  "@storyteller-platform/transliteration": "^3.1.2",
77
77
  "chalk": "^5.4.1",