@storyteller-platform/epub 0.6.3 → 0.7.1

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/upgrade.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import "./chunk-BIEQXUOY.js";
2
2
  import { nanoid } from "nanoid";
3
+ import { MediaType } from "@storyteller-platform/media-types";
3
4
  import {
4
5
  Epub
5
6
  } from "./index.js";
@@ -49,31 +50,7 @@ const GUIDE_TO_EPUBTYPE = {
49
50
  "other.volume": "volume",
50
51
  "other.warning": "warning"
51
52
  };
52
- const XHTML_MEDIA_TYPES = /* @__PURE__ */ new Set([
53
- "application/xhtml+xml",
54
- "application/vnd.adobe-page-template+xml",
55
- "text/html"
56
- ]);
57
- const OEB_FONTS = /* @__PURE__ */ new Set([
58
- "application/vnd.ms-opentype",
59
- "application/x-font-ttf",
60
- "application/x-font-otf",
61
- "application/x-font-truetype",
62
- "application/font-sfnt",
63
- "application/font-woff",
64
- "application/font-woff2",
65
- "font/woff",
66
- "font/woff2",
67
- "font/otf",
68
- "font/ttf",
69
- "font/sfnt"
70
- ]);
71
- const FONT_MIME_BY_EXT = {
72
- ".ttf": "application/font-sfnt",
73
- ".otf": "application/font-sfnt",
74
- ".woff": "application/font-woff",
75
- ".woff2": "font/woff2"
76
- };
53
+ const XHTML_MEDIA_TYPES = /* @__PURE__ */ new Set([MediaType.XHTML, MediaType.HTML]);
77
54
  function getMetadataElement(pkg) {
78
55
  return Epub.findXmlChildByName("metadata", Epub.getXmlChildren(pkg));
79
56
  }
@@ -350,11 +327,10 @@ function removeSpineTocRef(pkg) {
350
327
  delete spine[":@"]["@_toc"];
351
328
  }
352
329
  async function collectManifestProperties(epub) {
353
- var _a;
354
330
  const manifest = await epub.getManifest();
355
331
  for (const item of Object.values(manifest)) {
356
- const mediaType = ((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) ?? "";
357
- if (!XHTML_MEDIA_TYPES.has(mediaType)) continue;
332
+ const mediaType = MediaType.fromMime(item.mediaType ?? "");
333
+ if (!mediaType || !XHTML_MEDIA_TYPES.has(mediaType)) continue;
358
334
  let xml;
359
335
  try {
360
336
  xml = await epub.readXhtmlItemContents(item.id);
@@ -375,21 +351,16 @@ async function collectManifestProperties(epub) {
375
351
  }
376
352
  }
377
353
  function fixFontMimeTypes(pkg) {
378
- var _a, _b;
354
+ var _a, _b, _c;
379
355
  const manifest = getManifestElement(pkg);
380
356
  if (!manifest) return;
381
357
  for (const item of findAllByName("item", manifest.manifest)) {
382
- const mt = (((_a = item[":@"]) == null ? void 0 : _a["@_media-type"]) ?? "").toLowerCase();
383
- if (!OEB_FONTS.has(mt)) continue;
384
- const href = ((_b = item[":@"]) == null ? void 0 : _b["@_href"]) ?? "";
385
- const dotIdx = href.lastIndexOf(".");
386
- if (dotIdx === -1) continue;
387
- const ext = href.slice(dotIdx).toLowerCase();
388
- const corrected = FONT_MIME_BY_EXT[ext];
389
- if (corrected && corrected !== mt) {
390
- item[":@"] ??= {};
391
- item[":@"]["@_media-type"] = corrected;
392
- }
358
+ const declared = ((_a = item[":@"]) == null ? void 0 : _a["@_media-type"]) ?? "";
359
+ if (!((_b = MediaType.fromMime(declared)) == null ? void 0 : _b.is("font"))) continue;
360
+ const corrected = MediaType.fromPath(((_c = item[":@"]) == null ? void 0 : _c["@_href"]) ?? "");
361
+ if (!(corrected == null ? void 0 : corrected.is("font")) || corrected.mime === declared) continue;
362
+ item[":@"] ??= {};
363
+ item[":@"]["@_media-type"] = corrected.mime;
393
364
  }
394
365
  }
395
366
  function buildTocOl(entries) {
@@ -482,10 +453,7 @@ async function chooseNavHref(epub) {
482
453
  async function removeNcx(epub) {
483
454
  const manifest = await epub.getManifest();
484
455
  const ncxItem = Object.values(manifest).find(
485
- (item) => {
486
- var _a;
487
- return ((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) === "application/x-dtbncx+xml";
488
- }
456
+ (item) => MediaType.fromMime(item.mediaType ?? "") === MediaType.NCX
489
457
  );
490
458
  if (ncxItem) {
491
459
  await epub.removeManifestItem(ncxItem.id);
package/dist/zip.cjs ADDED
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var zip_exports = {};
20
+ __export(zip_exports, {
21
+ openZip: () => openZip
22
+ });
23
+ module.exports = __toCommonJS(zip_exports);
24
+ var import_node_fs = require("node:fs");
25
+ var import_promises = require("node:fs/promises");
26
+ var import_yauzl_promise = require("yauzl-promise");
27
+ const EOCDR_SIGNATURE = Buffer.from([80, 75, 5, 6]);
28
+ const EOCDR_SIZE = 22;
29
+ const MAX_TAIL_SCAN = 1024 * 1024;
30
+ function toBuffer(source) {
31
+ return Buffer.isBuffer(source) ? source : Buffer.from(source.buffer, source.byteOffset, source.byteLength);
32
+ }
33
+ function findArchiveEnd(tail, tailStart, fileSize) {
34
+ let searchFrom = tail.length - EOCDR_SIZE;
35
+ while (searchFrom >= 0) {
36
+ const index = tail.lastIndexOf(EOCDR_SIGNATURE, searchFrom);
37
+ if (index === -1) return null;
38
+ const commentLength = tail.readUInt16LE(index + 20);
39
+ const end = tailStart + index + EOCDR_SIZE + commentLength;
40
+ if (end < fileSize) return end;
41
+ searchFrom = index - 1;
42
+ }
43
+ return null;
44
+ }
45
+ async function readExactly(handle, length, position) {
46
+ const buffer = Buffer.allocUnsafe(length);
47
+ let offset = 0;
48
+ while (offset < length) {
49
+ const { bytesRead } = await handle.read(
50
+ buffer,
51
+ offset,
52
+ length - offset,
53
+ position + offset
54
+ );
55
+ if (bytesRead === 0) return null;
56
+ offset += bytesRead;
57
+ }
58
+ return buffer;
59
+ }
60
+ async function findFileArchiveEnd(path) {
61
+ let handle;
62
+ try {
63
+ handle = await (0, import_promises.open)(path, "r");
64
+ } catch {
65
+ return null;
66
+ }
67
+ try {
68
+ const { size } = await handle.stat();
69
+ const tailLength = Math.min(size, MAX_TAIL_SCAN);
70
+ if (tailLength < EOCDR_SIZE) return null;
71
+ const tailStart = size - tailLength;
72
+ const tail = await readExactly(handle, tailLength, tailStart);
73
+ if (!tail) return null;
74
+ return findArchiveEnd(tail, tailStart, size);
75
+ } finally {
76
+ await handle.close();
77
+ }
78
+ }
79
+ class TruncatedFileReader extends import_yauzl_promise.Reader {
80
+ constructor(path) {
81
+ super();
82
+ this.path = path;
83
+ }
84
+ handle = null;
85
+ async _open() {
86
+ this.handle = await (0, import_promises.open)(this.path, "r");
87
+ return {};
88
+ }
89
+ async _close() {
90
+ const handle = this.handle;
91
+ this.handle = null;
92
+ if (handle) await handle.close();
93
+ return {};
94
+ }
95
+ async _read(start, length) {
96
+ if (!this.handle) throw new Error(`reader for ${this.path} is not open`);
97
+ const buffer = await readExactly(this.handle, length, start);
98
+ if (!buffer) {
99
+ throw new Error(`unexpected end of file reading ${this.path}`);
100
+ }
101
+ return buffer;
102
+ }
103
+ _createReadStream(start, length) {
104
+ return (0, import_node_fs.createReadStream)(this.path, { start, end: start + length - 1 });
105
+ }
106
+ }
107
+ async function openBufferZip(data) {
108
+ try {
109
+ return await (0, import_yauzl_promise.fromBuffer)(data);
110
+ } catch (error) {
111
+ const tailStart = Math.max(0, data.length - MAX_TAIL_SCAN);
112
+ const end = findArchiveEnd(data.subarray(tailStart), tailStart, data.length);
113
+ if (end === null) throw error;
114
+ try {
115
+ return await (0, import_yauzl_promise.fromBuffer)(data.subarray(0, end));
116
+ } catch {
117
+ throw error;
118
+ }
119
+ }
120
+ }
121
+ async function openFileZip(path) {
122
+ try {
123
+ return await (0, import_yauzl_promise.open)(path);
124
+ } catch (error) {
125
+ const end = await findFileArchiveEnd(path);
126
+ if (end === null) throw error;
127
+ const reader = new TruncatedFileReader(path);
128
+ try {
129
+ return await (0, import_yauzl_promise.fromReader)(reader, end);
130
+ } catch {
131
+ await reader._close().catch(() => void 0);
132
+ throw error;
133
+ }
134
+ }
135
+ }
136
+ async function openZip(source) {
137
+ return typeof source === "string" ? openFileZip(source) : openBufferZip(toBuffer(source));
138
+ }
139
+ // Annotate the CommonJS export names for ESM import in node:
140
+ 0 && (module.exports = {
141
+ openZip
142
+ });
package/dist/zip.d.cts ADDED
@@ -0,0 +1,5 @@
1
+ import { ZipFile } from 'yauzl-promise';
2
+
3
+ declare function openZip(source: string | Uint8Array): Promise<ZipFile>;
4
+
5
+ export { openZip };
package/dist/zip.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { ZipFile } from 'yauzl-promise';
2
+
3
+ declare function openZip(source: string | Uint8Array): Promise<ZipFile>;
4
+
5
+ export { openZip };
package/dist/zip.js ADDED
@@ -0,0 +1,124 @@
1
+ import "./chunk-BIEQXUOY.js";
2
+ import { createReadStream } from "node:fs";
3
+ import { open as openFile } from "node:fs/promises";
4
+ import {
5
+ Reader,
6
+ fromBuffer,
7
+ fromReader,
8
+ open
9
+ } from "yauzl-promise";
10
+ const EOCDR_SIGNATURE = Buffer.from([80, 75, 5, 6]);
11
+ const EOCDR_SIZE = 22;
12
+ const MAX_TAIL_SCAN = 1024 * 1024;
13
+ function toBuffer(source) {
14
+ return Buffer.isBuffer(source) ? source : Buffer.from(source.buffer, source.byteOffset, source.byteLength);
15
+ }
16
+ function findArchiveEnd(tail, tailStart, fileSize) {
17
+ let searchFrom = tail.length - EOCDR_SIZE;
18
+ while (searchFrom >= 0) {
19
+ const index = tail.lastIndexOf(EOCDR_SIGNATURE, searchFrom);
20
+ if (index === -1) return null;
21
+ const commentLength = tail.readUInt16LE(index + 20);
22
+ const end = tailStart + index + EOCDR_SIZE + commentLength;
23
+ if (end < fileSize) return end;
24
+ searchFrom = index - 1;
25
+ }
26
+ return null;
27
+ }
28
+ async function readExactly(handle, length, position) {
29
+ const buffer = Buffer.allocUnsafe(length);
30
+ let offset = 0;
31
+ while (offset < length) {
32
+ const { bytesRead } = await handle.read(
33
+ buffer,
34
+ offset,
35
+ length - offset,
36
+ position + offset
37
+ );
38
+ if (bytesRead === 0) return null;
39
+ offset += bytesRead;
40
+ }
41
+ return buffer;
42
+ }
43
+ async function findFileArchiveEnd(path) {
44
+ let handle;
45
+ try {
46
+ handle = await openFile(path, "r");
47
+ } catch {
48
+ return null;
49
+ }
50
+ try {
51
+ const { size } = await handle.stat();
52
+ const tailLength = Math.min(size, MAX_TAIL_SCAN);
53
+ if (tailLength < EOCDR_SIZE) return null;
54
+ const tailStart = size - tailLength;
55
+ const tail = await readExactly(handle, tailLength, tailStart);
56
+ if (!tail) return null;
57
+ return findArchiveEnd(tail, tailStart, size);
58
+ } finally {
59
+ await handle.close();
60
+ }
61
+ }
62
+ class TruncatedFileReader extends Reader {
63
+ constructor(path) {
64
+ super();
65
+ this.path = path;
66
+ }
67
+ handle = null;
68
+ async _open() {
69
+ this.handle = await openFile(this.path, "r");
70
+ return {};
71
+ }
72
+ async _close() {
73
+ const handle = this.handle;
74
+ this.handle = null;
75
+ if (handle) await handle.close();
76
+ return {};
77
+ }
78
+ async _read(start, length) {
79
+ if (!this.handle) throw new Error(`reader for ${this.path} is not open`);
80
+ const buffer = await readExactly(this.handle, length, start);
81
+ if (!buffer) {
82
+ throw new Error(`unexpected end of file reading ${this.path}`);
83
+ }
84
+ return buffer;
85
+ }
86
+ _createReadStream(start, length) {
87
+ return createReadStream(this.path, { start, end: start + length - 1 });
88
+ }
89
+ }
90
+ async function openBufferZip(data) {
91
+ try {
92
+ return await fromBuffer(data);
93
+ } catch (error) {
94
+ const tailStart = Math.max(0, data.length - MAX_TAIL_SCAN);
95
+ const end = findArchiveEnd(data.subarray(tailStart), tailStart, data.length);
96
+ if (end === null) throw error;
97
+ try {
98
+ return await fromBuffer(data.subarray(0, end));
99
+ } catch {
100
+ throw error;
101
+ }
102
+ }
103
+ }
104
+ async function openFileZip(path) {
105
+ try {
106
+ return await open(path);
107
+ } catch (error) {
108
+ const end = await findFileArchiveEnd(path);
109
+ if (end === null) throw error;
110
+ const reader = new TruncatedFileReader(path);
111
+ try {
112
+ return await fromReader(reader, end);
113
+ } catch {
114
+ await reader._close().catch(() => void 0);
115
+ throw error;
116
+ }
117
+ }
118
+ }
119
+ async function openZip(source) {
120
+ return typeof source === "string" ? openFileZip(source) : openBufferZip(toBuffer(source));
121
+ }
122
+ export {
123
+ openZip
124
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyteller-platform/epub",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,7 +32,6 @@
32
32
  "@storyteller-platform/eslint": "0.1.0",
33
33
  "@storyteller-platform/tsup": "^0.1.0",
34
34
  "@tsconfig/strictest": "^2.0.5",
35
- "@types/mime-types": "^2",
36
35
  "@types/node": "^24.0.0",
37
36
  "@types/yauzl-promise": "^4",
38
37
  "@types/yazl": "^3",
@@ -48,13 +47,13 @@
48
47
  },
49
48
  "dependencies": {
50
49
  "@readium/shared": "^2.2.0",
51
- "@storyteller-platform/fs": "^0.1.4",
52
- "@storyteller-platform/path": "^0.1.3",
50
+ "@storyteller-platform/fs": "^0.2.0",
51
+ "@storyteller-platform/media-types": "^0.1.0",
52
+ "@storyteller-platform/path": "^0.2.0",
53
53
  "@zip.js/zip.js": "^2.0.0",
54
54
  "async-mutex": "^0.5.0",
55
55
  "fast-xml-parser": "^4.0.1",
56
56
  "mem": "^8.0.0",
57
- "mime-types": "^3.0.1",
58
57
  "nanoid": "^5.1.5",
59
58
  "yauzl-promise": "^4.0.0",
60
59
  "yazl": "^3.3.1"