@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/README.md +529 -1075
- package/dist/adapters/interface.d.cts +25 -19
- package/dist/adapters/interface.d.ts +25 -19
- package/dist/adapters/memory.cjs +19 -29
- package/dist/adapters/memory.d.cts +4 -5
- package/dist/adapters/memory.d.ts +4 -5
- package/dist/adapters/memory.js +19 -32
- package/dist/adapters/tmpfs.cjs +50 -45
- package/dist/adapters/tmpfs.d.cts +9 -7
- package/dist/adapters/tmpfs.d.ts +9 -7
- package/dist/adapters/tmpfs.js +50 -45
- package/dist/epubcheck.cjs +2 -2
- package/dist/epubcheck.js +2 -2
- package/dist/index.cjs +585 -94
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +586 -100
- package/dist/upgrade.cjs +12 -44
- package/dist/upgrade.d.cts +175 -11
- package/dist/upgrade.d.ts +175 -11
- package/dist/upgrade.js +12 -44
- package/dist/zip.cjs +142 -0
- package/dist/zip.d.cts +5 -0
- package/dist/zip.d.ts +5 -0
- package/dist/zip.js +124 -0
- package/package.json +4 -5
|
@@ -13,30 +13,40 @@ interface EpubStorageCapabilities {
|
|
|
13
13
|
readonly writable: boolean;
|
|
14
14
|
}
|
|
15
15
|
interface EpubListEntry {
|
|
16
|
-
/**
|
|
16
|
+
/** location the adapter can read the entry back from, in whatever form it stores */
|
|
17
17
|
absolutePath: string;
|
|
18
|
-
/**
|
|
18
|
+
/** the entry name, as it appears in the archive */
|
|
19
19
|
relativePath: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Adapters only receive entry names, which is:
|
|
23
|
+
* - the name a file has in the archive itself
|
|
24
|
+
* - relative to the container root, slash-separated,
|
|
25
|
+
* - already percent-decoded (eg `OEBPS/a b.xhtml`, not `OEBPS/a%20b.xhtml`)
|
|
26
|
+
*
|
|
27
|
+
* Adapter decides where to store files under what name
|
|
28
|
+
* In previous versions of the EPUB lib, this was a little bit more muddled, leading to
|
|
29
|
+
* eg the TmpFsAdapter trying to read `OEBPS/a%20b.xhtml`, whereas it was stored as `OEBPS/a b.xhtml`
|
|
30
|
+
*/
|
|
21
31
|
interface EpubStorageAdapter {
|
|
32
|
+
read(entry: string): Promise<Uint8Array>;
|
|
33
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
22
34
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
35
|
+
* Whether the archive holds an entry under exactly this name.
|
|
36
|
+
* Used to choose between different canditates for an href
|
|
37
|
+
* so should be exact
|
|
26
38
|
*/
|
|
27
|
-
|
|
28
|
-
read(path: string): Promise<Uint8Array>;
|
|
29
|
-
read(path: string, encoding: "utf-8"): Promise<string>;
|
|
39
|
+
exists(entry: string): Promise<boolean>;
|
|
30
40
|
/**
|
|
31
41
|
* Length of an entry in bytes, for the readium page-count heuristic
|
|
32
42
|
* which expects compressed size when available
|
|
33
43
|
*/
|
|
34
|
-
archiveLength(
|
|
44
|
+
archiveLength(entry: string): Promise<number>;
|
|
35
45
|
/** Required for any mutation method on Epub */
|
|
36
|
-
write?(
|
|
37
|
-
write?(
|
|
46
|
+
write?(entry: string, data: Uint8Array): Promise<void>;
|
|
47
|
+
write?(entry: string, data: string, encoding: "utf-8"): Promise<void>;
|
|
38
48
|
/** Required for removeManifestItem / setCoverImage replacement */
|
|
39
|
-
remove?(
|
|
49
|
+
remove?(entry: string): Promise<void>;
|
|
40
50
|
/** Required for Epub.saveAndClose to walk the contents */
|
|
41
51
|
list?(): AsyncIterable<EpubListEntry>;
|
|
42
52
|
/** Required for Epub.copy */
|
|
@@ -50,17 +60,13 @@ interface EpubStorageAdapterClass<Opts extends object = object> {
|
|
|
50
60
|
readonly kind: EpubStorageKind;
|
|
51
61
|
readonly capabilities: EpubStorageCapabilities;
|
|
52
62
|
/**
|
|
53
|
-
* Open an archive from a path or buffer.
|
|
54
|
-
*
|
|
55
|
-
* afterwards.
|
|
63
|
+
* Open an archive from a path or buffer.
|
|
64
|
+
* Will throw if the archive is not a valid EPUB 3
|
|
56
65
|
*/
|
|
57
66
|
init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
|
|
58
67
|
/**
|
|
59
68
|
* Initialize an empty extract root for {@link Epub.create}.
|
|
60
|
-
*
|
|
61
|
-
* Optional — adapters that can't be written to from scratch
|
|
62
|
-
* (e.g. read-only zip handles) should leave this off, and
|
|
63
|
-
* `Epub.using(...).create(...)` will throw.
|
|
69
|
+
* Only necessary for adapters that can write
|
|
64
70
|
*/
|
|
65
71
|
initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
|
|
66
72
|
}
|
|
@@ -13,30 +13,40 @@ interface EpubStorageCapabilities {
|
|
|
13
13
|
readonly writable: boolean;
|
|
14
14
|
}
|
|
15
15
|
interface EpubListEntry {
|
|
16
|
-
/**
|
|
16
|
+
/** location the adapter can read the entry back from, in whatever form it stores */
|
|
17
17
|
absolutePath: string;
|
|
18
|
-
/**
|
|
18
|
+
/** the entry name, as it appears in the archive */
|
|
19
19
|
relativePath: string;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Adapters only receive entry names, which is:
|
|
23
|
+
* - the name a file has in the archive itself
|
|
24
|
+
* - relative to the container root, slash-separated,
|
|
25
|
+
* - already percent-decoded (eg `OEBPS/a b.xhtml`, not `OEBPS/a%20b.xhtml`)
|
|
26
|
+
*
|
|
27
|
+
* Adapter decides where to store files under what name
|
|
28
|
+
* In previous versions of the EPUB lib, this was a little bit more muddled, leading to
|
|
29
|
+
* eg the TmpFsAdapter trying to read `OEBPS/a%20b.xhtml`, whereas it was stored as `OEBPS/a b.xhtml`
|
|
30
|
+
*/
|
|
21
31
|
interface EpubStorageAdapter {
|
|
32
|
+
read(entry: string): Promise<Uint8Array>;
|
|
33
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
22
34
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
35
|
+
* Whether the archive holds an entry under exactly this name.
|
|
36
|
+
* Used to choose between different canditates for an href
|
|
37
|
+
* so should be exact
|
|
26
38
|
*/
|
|
27
|
-
|
|
28
|
-
read(path: string): Promise<Uint8Array>;
|
|
29
|
-
read(path: string, encoding: "utf-8"): Promise<string>;
|
|
39
|
+
exists(entry: string): Promise<boolean>;
|
|
30
40
|
/**
|
|
31
41
|
* Length of an entry in bytes, for the readium page-count heuristic
|
|
32
42
|
* which expects compressed size when available
|
|
33
43
|
*/
|
|
34
|
-
archiveLength(
|
|
44
|
+
archiveLength(entry: string): Promise<number>;
|
|
35
45
|
/** Required for any mutation method on Epub */
|
|
36
|
-
write?(
|
|
37
|
-
write?(
|
|
46
|
+
write?(entry: string, data: Uint8Array): Promise<void>;
|
|
47
|
+
write?(entry: string, data: string, encoding: "utf-8"): Promise<void>;
|
|
38
48
|
/** Required for removeManifestItem / setCoverImage replacement */
|
|
39
|
-
remove?(
|
|
49
|
+
remove?(entry: string): Promise<void>;
|
|
40
50
|
/** Required for Epub.saveAndClose to walk the contents */
|
|
41
51
|
list?(): AsyncIterable<EpubListEntry>;
|
|
42
52
|
/** Required for Epub.copy */
|
|
@@ -50,17 +60,13 @@ interface EpubStorageAdapterClass<Opts extends object = object> {
|
|
|
50
60
|
readonly kind: EpubStorageKind;
|
|
51
61
|
readonly capabilities: EpubStorageCapabilities;
|
|
52
62
|
/**
|
|
53
|
-
* Open an archive from a path or buffer.
|
|
54
|
-
*
|
|
55
|
-
* afterwards.
|
|
63
|
+
* Open an archive from a path or buffer.
|
|
64
|
+
* Will throw if the archive is not a valid EPUB 3
|
|
56
65
|
*/
|
|
57
66
|
init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
|
|
58
67
|
/**
|
|
59
68
|
* Initialize an empty extract root for {@link Epub.create}.
|
|
60
|
-
*
|
|
61
|
-
* Optional — adapters that can't be written to from scratch
|
|
62
|
-
* (e.g. read-only zip handles) should leave this off, and
|
|
63
|
-
* `Epub.using(...).create(...)` will throw.
|
|
69
|
+
* Only necessary for adapters that can write
|
|
64
70
|
*/
|
|
65
71
|
initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
|
|
66
72
|
}
|
package/dist/adapters/memory.cjs
CHANGED
|
@@ -21,13 +21,9 @@ __export(memory_exports, {
|
|
|
21
21
|
MemoryAdapter: () => MemoryAdapter
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(memory_exports);
|
|
24
|
-
var
|
|
25
|
-
var import_node_os = require("node:os");
|
|
26
|
-
var import_yauzl_promise = require("yauzl-promise");
|
|
27
|
-
var import_path = require("@storyteller-platform/path");
|
|
24
|
+
var import_zip = require("../zip.cjs");
|
|
28
25
|
class MemoryAdapter {
|
|
29
|
-
constructor(
|
|
30
|
-
this.rootPath = rootPath;
|
|
26
|
+
constructor(zip, entries, cache) {
|
|
31
27
|
this.zip = zip;
|
|
32
28
|
this.entries = entries;
|
|
33
29
|
this.cache = cache;
|
|
@@ -37,7 +33,7 @@ class MemoryAdapter {
|
|
|
37
33
|
static async init(source, opts = {}) {
|
|
38
34
|
const { cache = true, signal } = opts;
|
|
39
35
|
signal == null ? void 0 : signal.throwIfAborted();
|
|
40
|
-
const zip =
|
|
36
|
+
const zip = await (0, import_zip.openZip)(source);
|
|
41
37
|
const entries = /* @__PURE__ */ new Map();
|
|
42
38
|
try {
|
|
43
39
|
for await (const entry of zip) {
|
|
@@ -49,46 +45,40 @@ class MemoryAdapter {
|
|
|
49
45
|
await zip.close();
|
|
50
46
|
throw error;
|
|
51
47
|
}
|
|
52
|
-
|
|
53
|
-
(0, import_node_os.tmpdir)(),
|
|
54
|
-
`storyteller-platform-epub-zip-${(0, import_node_crypto.randomUUID)()}.epub`
|
|
55
|
-
);
|
|
56
|
-
return new MemoryAdapter(rootPath, zip, entries, cache ? /* @__PURE__ */ new Map() : null);
|
|
48
|
+
return new MemoryAdapter(zip, entries, cache ? /* @__PURE__ */ new Map() : null);
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
const prefix = this.rootPath.endsWith(import_path.sep) ? this.rootPath : this.rootPath + import_path.sep;
|
|
60
|
-
const rel = path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
61
|
-
return import_path.sep === "/" ? rel : rel.split(import_path.sep).join("/");
|
|
62
|
-
}
|
|
63
|
-
async read(path, encoding) {
|
|
50
|
+
async read(entry, encoding) {
|
|
64
51
|
var _a, _b;
|
|
65
|
-
const
|
|
66
|
-
const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
|
|
52
|
+
const cached = (_a = this.cache) == null ? void 0 : _a.get(entry);
|
|
67
53
|
if (cached) {
|
|
68
54
|
return encoding ? cached.toString(encoding) : new Uint8Array(cached);
|
|
69
55
|
}
|
|
70
|
-
const
|
|
71
|
-
if (!
|
|
56
|
+
const zipEntry = this.entries.get(entry);
|
|
57
|
+
if (!zipEntry) {
|
|
72
58
|
const err = new Error(
|
|
73
|
-
`ENOENT: zip entry not found in EPUB archive: ${
|
|
59
|
+
`ENOENT: zip entry not found in EPUB archive: ${entry}`
|
|
74
60
|
);
|
|
75
61
|
err.code = "ENOENT";
|
|
76
62
|
throw err;
|
|
77
63
|
}
|
|
78
|
-
const stream = await
|
|
64
|
+
const stream = await zipEntry.openReadStream();
|
|
79
65
|
const chunks = [];
|
|
80
66
|
for await (const chunk of stream) {
|
|
81
67
|
chunks.push(chunk);
|
|
82
68
|
}
|
|
83
69
|
const buf = Buffer.concat(chunks);
|
|
84
|
-
(_b = this.cache) == null ? void 0 : _b.set(
|
|
70
|
+
(_b = this.cache) == null ? void 0 : _b.set(entry, buf);
|
|
85
71
|
return encoding ? buf.toString(encoding) : new Uint8Array(buf);
|
|
86
72
|
}
|
|
87
73
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
88
|
-
async
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
async exists(entry) {
|
|
75
|
+
return this.entries.has(entry);
|
|
76
|
+
}
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
78
|
+
async archiveLength(entry) {
|
|
79
|
+
const zipEntry = this.entries.get(entry);
|
|
80
|
+
if (!zipEntry) return 0;
|
|
81
|
+
return zipEntry.compressedSize || zipEntry.uncompressedSize;
|
|
92
82
|
}
|
|
93
83
|
dispose() {
|
|
94
84
|
var _a;
|
|
@@ -17,7 +17,6 @@ interface MemoryAdapterOptions {
|
|
|
17
17
|
*
|
|
18
18
|
*/
|
|
19
19
|
declare class MemoryAdapter implements EpubStorageAdapter {
|
|
20
|
-
readonly rootPath: string;
|
|
21
20
|
private zip;
|
|
22
21
|
private entries;
|
|
23
22
|
private cache;
|
|
@@ -27,10 +26,10 @@ declare class MemoryAdapter implements EpubStorageAdapter {
|
|
|
27
26
|
};
|
|
28
27
|
static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
|
|
29
28
|
private constructor();
|
|
30
|
-
|
|
31
|
-
read(
|
|
32
|
-
|
|
33
|
-
archiveLength(
|
|
29
|
+
read(entry: string): Promise<Uint8Array>;
|
|
30
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
31
|
+
exists(entry: string): Promise<boolean>;
|
|
32
|
+
archiveLength(entry: string): Promise<number>;
|
|
34
33
|
dispose(): void;
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -17,7 +17,6 @@ interface MemoryAdapterOptions {
|
|
|
17
17
|
*
|
|
18
18
|
*/
|
|
19
19
|
declare class MemoryAdapter implements EpubStorageAdapter {
|
|
20
|
-
readonly rootPath: string;
|
|
21
20
|
private zip;
|
|
22
21
|
private entries;
|
|
23
22
|
private cache;
|
|
@@ -27,10 +26,10 @@ declare class MemoryAdapter implements EpubStorageAdapter {
|
|
|
27
26
|
};
|
|
28
27
|
static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
|
|
29
28
|
private constructor();
|
|
30
|
-
|
|
31
|
-
read(
|
|
32
|
-
|
|
33
|
-
archiveLength(
|
|
29
|
+
read(entry: string): Promise<Uint8Array>;
|
|
30
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
31
|
+
exists(entry: string): Promise<boolean>;
|
|
32
|
+
archiveLength(entry: string): Promise<number>;
|
|
34
33
|
dispose(): void;
|
|
35
34
|
}
|
|
36
35
|
|
package/dist/adapters/memory.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import "../chunk-BIEQXUOY.js";
|
|
2
|
-
import {
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
-
fromBuffer,
|
|
6
|
-
open
|
|
7
|
-
} from "yauzl-promise";
|
|
8
|
-
import { join, sep } from "@storyteller-platform/path";
|
|
2
|
+
import { openZip } from "../zip.js";
|
|
9
3
|
class MemoryAdapter {
|
|
10
|
-
constructor(
|
|
11
|
-
this.rootPath = rootPath;
|
|
4
|
+
constructor(zip, entries, cache) {
|
|
12
5
|
this.zip = zip;
|
|
13
6
|
this.entries = entries;
|
|
14
7
|
this.cache = cache;
|
|
@@ -18,7 +11,7 @@ class MemoryAdapter {
|
|
|
18
11
|
static async init(source, opts = {}) {
|
|
19
12
|
const { cache = true, signal } = opts;
|
|
20
13
|
signal == null ? void 0 : signal.throwIfAborted();
|
|
21
|
-
const zip =
|
|
14
|
+
const zip = await openZip(source);
|
|
22
15
|
const entries = /* @__PURE__ */ new Map();
|
|
23
16
|
try {
|
|
24
17
|
for await (const entry of zip) {
|
|
@@ -30,46 +23,40 @@ class MemoryAdapter {
|
|
|
30
23
|
await zip.close();
|
|
31
24
|
throw error;
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
tmpdir(),
|
|
35
|
-
`storyteller-platform-epub-zip-${randomUUID()}.epub`
|
|
36
|
-
);
|
|
37
|
-
return new MemoryAdapter(rootPath, zip, entries, cache ? /* @__PURE__ */ new Map() : null);
|
|
26
|
+
return new MemoryAdapter(zip, entries, cache ? /* @__PURE__ */ new Map() : null);
|
|
38
27
|
}
|
|
39
|
-
|
|
40
|
-
const prefix = this.rootPath.endsWith(sep) ? this.rootPath : this.rootPath + sep;
|
|
41
|
-
const rel = path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
42
|
-
return sep === "/" ? rel : rel.split(sep).join("/");
|
|
43
|
-
}
|
|
44
|
-
async read(path, encoding) {
|
|
28
|
+
async read(entry, encoding) {
|
|
45
29
|
var _a, _b;
|
|
46
|
-
const
|
|
47
|
-
const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
|
|
30
|
+
const cached = (_a = this.cache) == null ? void 0 : _a.get(entry);
|
|
48
31
|
if (cached) {
|
|
49
32
|
return encoding ? cached.toString(encoding) : new Uint8Array(cached);
|
|
50
33
|
}
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
34
|
+
const zipEntry = this.entries.get(entry);
|
|
35
|
+
if (!zipEntry) {
|
|
53
36
|
const err = new Error(
|
|
54
|
-
`ENOENT: zip entry not found in EPUB archive: ${
|
|
37
|
+
`ENOENT: zip entry not found in EPUB archive: ${entry}`
|
|
55
38
|
);
|
|
56
39
|
err.code = "ENOENT";
|
|
57
40
|
throw err;
|
|
58
41
|
}
|
|
59
|
-
const stream = await
|
|
42
|
+
const stream = await zipEntry.openReadStream();
|
|
60
43
|
const chunks = [];
|
|
61
44
|
for await (const chunk of stream) {
|
|
62
45
|
chunks.push(chunk);
|
|
63
46
|
}
|
|
64
47
|
const buf = Buffer.concat(chunks);
|
|
65
|
-
(_b = this.cache) == null ? void 0 : _b.set(
|
|
48
|
+
(_b = this.cache) == null ? void 0 : _b.set(entry, buf);
|
|
66
49
|
return encoding ? buf.toString(encoding) : new Uint8Array(buf);
|
|
67
50
|
}
|
|
68
51
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
69
|
-
async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
async exists(entry) {
|
|
53
|
+
return this.entries.has(entry);
|
|
54
|
+
}
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
56
|
+
async archiveLength(entry) {
|
|
57
|
+
const zipEntry = this.entries.get(entry);
|
|
58
|
+
if (!zipEntry) return 0;
|
|
59
|
+
return zipEntry.compressedSize || zipEntry.uncompressedSize;
|
|
73
60
|
}
|
|
74
61
|
dispose() {
|
|
75
62
|
var _a;
|
package/dist/adapters/tmpfs.cjs
CHANGED
|
@@ -69,38 +69,23 @@ module.exports = __toCommonJS(tmpfs_exports);
|
|
|
69
69
|
var import_node_crypto = require("node:crypto");
|
|
70
70
|
var import_node_fs = require("node:fs");
|
|
71
71
|
var import_promises = require("node:fs/promises");
|
|
72
|
-
var import_node_os = require("node:os");
|
|
73
72
|
var import_promises2 = require("node:stream/promises");
|
|
74
|
-
var import_yauzl_promise = require("yauzl-promise");
|
|
75
73
|
var import_yazl = require("yazl");
|
|
74
|
+
var import_fs = require("@storyteller-platform/fs");
|
|
75
|
+
var import_media_types = require("@storyteller-platform/media-types");
|
|
76
76
|
var import_path = require("@storyteller-platform/path");
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const FLAC_FILE_EXTENSIONS = [".flac"];
|
|
85
|
-
const ALAC_FILE_EXTENSIONS = [".alac"];
|
|
86
|
-
const WEBM_FILE_EXTENSIONS = [".weba"];
|
|
87
|
-
const AUDIO_FILE_EXTENSIONS = [
|
|
88
|
-
...MP3_FILE_EXTENSIONS,
|
|
89
|
-
...AAC_FILE_EXTENSIONS,
|
|
90
|
-
...MPEG4_FILE_EXTENSIONS,
|
|
91
|
-
...OPUS_FILE_EXTENSIONS,
|
|
92
|
-
...OGG_FILE_EXTENSIONS,
|
|
93
|
-
...WAVE_FILE_EXTENSIONS,
|
|
94
|
-
...AIFF_FILE_EXTENSIONS,
|
|
95
|
-
...FLAC_FILE_EXTENSIONS,
|
|
96
|
-
...ALAC_FILE_EXTENSIONS,
|
|
97
|
-
...WEBM_FILE_EXTENSIONS
|
|
98
|
-
];
|
|
99
|
-
function isAudioFile(filenameOrExt) {
|
|
100
|
-
return AUDIO_FILE_EXTENSIONS.some((ext) => filenameOrExt.endsWith(ext));
|
|
101
|
-
}
|
|
77
|
+
var import_zip = require("../zip.cjs");
|
|
78
|
+
const PRECOMPRESSED_KINDS = /* @__PURE__ */ new Set([
|
|
79
|
+
"audio",
|
|
80
|
+
"image",
|
|
81
|
+
"archive",
|
|
82
|
+
"package"
|
|
83
|
+
]);
|
|
102
84
|
function mintRootPath() {
|
|
103
|
-
return (0,
|
|
85
|
+
return (0, import_fs.scratchPath)(`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`);
|
|
86
|
+
}
|
|
87
|
+
function isStorableEntryName(entry) {
|
|
88
|
+
return !entry.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
|
|
104
89
|
}
|
|
105
90
|
class TmpFsAdapter {
|
|
106
91
|
constructor(rootPath) {
|
|
@@ -113,7 +98,7 @@ class TmpFsAdapter {
|
|
|
113
98
|
try {
|
|
114
99
|
var _stack = [];
|
|
115
100
|
try {
|
|
116
|
-
const zipfile =
|
|
101
|
+
const zipfile = await (0, import_zip.openZip)(source);
|
|
117
102
|
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
118
103
|
stack.defer(async () => {
|
|
119
104
|
await zipfile.close();
|
|
@@ -122,7 +107,8 @@ class TmpFsAdapter {
|
|
|
122
107
|
if (entry.filename.endsWith("/")) {
|
|
123
108
|
continue;
|
|
124
109
|
}
|
|
125
|
-
|
|
110
|
+
if (!isStorableEntryName(entry.filename)) continue;
|
|
111
|
+
const writePath = (0, import_path.join)(rootPath, ...entry.filename.split("/"));
|
|
126
112
|
const readStream = await entry.openReadStream();
|
|
127
113
|
await (0, import_promises.mkdir)((0, import_path.dirname)(writePath), { recursive: true });
|
|
128
114
|
const writeStream = (0, import_node_fs.createWriteStream)(writePath);
|
|
@@ -145,19 +131,36 @@ class TmpFsAdapter {
|
|
|
145
131
|
await (0, import_promises.mkdir)(rootPath, { recursive: true });
|
|
146
132
|
return new TmpFsAdapter(rootPath);
|
|
147
133
|
}
|
|
148
|
-
|
|
149
|
-
|
|
134
|
+
diskPath(entry) {
|
|
135
|
+
if (!isStorableEntryName(entry)) {
|
|
136
|
+
const err = new Error(`ENOENT: no such entry in EPUB archive: ${entry}`);
|
|
137
|
+
err.code = "ENOENT";
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
return (0, import_path.join)(this.rootPath, ...entry.split("/"));
|
|
141
|
+
}
|
|
142
|
+
async read(entry, encoding) {
|
|
143
|
+
return await (0, import_promises.readFile)(this.diskPath(entry), encoding);
|
|
150
144
|
}
|
|
151
|
-
async write(
|
|
145
|
+
async write(entry, data, encoding) {
|
|
146
|
+
const path = this.diskPath(entry);
|
|
152
147
|
await (0, import_promises.mkdir)((0, import_path.dirname)(path), { recursive: true });
|
|
153
148
|
await (0, import_promises.writeFile)(path, data, encoding);
|
|
154
149
|
}
|
|
155
|
-
async remove(
|
|
156
|
-
await (0, import_promises.rm)(
|
|
150
|
+
async remove(entry) {
|
|
151
|
+
await (0, import_promises.rm)(this.diskPath(entry));
|
|
152
|
+
}
|
|
153
|
+
async exists(entry) {
|
|
154
|
+
try {
|
|
155
|
+
await (0, import_promises.stat)(this.diskPath(entry));
|
|
156
|
+
return true;
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
157
160
|
}
|
|
158
|
-
async archiveLength(
|
|
161
|
+
async archiveLength(entry) {
|
|
159
162
|
try {
|
|
160
|
-
const st = await (0, import_promises.stat)(
|
|
163
|
+
const st = await (0, import_promises.stat)(this.diskPath(entry));
|
|
161
164
|
return st.size;
|
|
162
165
|
} catch {
|
|
163
166
|
return 0;
|
|
@@ -173,7 +176,7 @@ class TmpFsAdapter {
|
|
|
173
176
|
const absolutePath = (0, import_path.join)(entry.parentPath, entry.name);
|
|
174
177
|
yield {
|
|
175
178
|
absolutePath,
|
|
176
|
-
relativePath: absolutePath.
|
|
179
|
+
relativePath: absolutePath.slice(this.rootPath.length + 1).split(import_path.sep).join("/")
|
|
177
180
|
};
|
|
178
181
|
}
|
|
179
182
|
}
|
|
@@ -188,31 +191,33 @@ class TmpFsAdapter {
|
|
|
188
191
|
return new TmpFsAdapter(newRoot);
|
|
189
192
|
}
|
|
190
193
|
async serialize(targetPath) {
|
|
194
|
+
var _a;
|
|
191
195
|
var _stack = [];
|
|
192
196
|
try {
|
|
193
|
-
const tmpArchivePath = (
|
|
194
|
-
|
|
195
|
-
`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
|
|
196
|
-
);
|
|
197
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
197
|
+
const tmpArchivePath = mintRootPath();
|
|
198
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
198
199
|
const zipfile = new import_yazl.ZipFile();
|
|
199
200
|
const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
|
|
200
201
|
writeStream.on("close", () => {
|
|
201
202
|
resolve();
|
|
202
203
|
});
|
|
204
|
+
writeStream.on("error", reject);
|
|
205
|
+
zipfile.outputStream.on("error", reject);
|
|
203
206
|
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
204
207
|
stack.defer(async () => {
|
|
205
208
|
writeStream.close();
|
|
206
209
|
await (0, import_promises.rm)(tmpArchivePath, { force: true });
|
|
207
210
|
});
|
|
208
211
|
zipfile.outputStream.pipe(writeStream);
|
|
209
|
-
zipfile.addBuffer(Buffer.from(
|
|
212
|
+
zipfile.addBuffer(Buffer.from(import_media_types.MediaType.EPUB.mime), "mimetype", {
|
|
210
213
|
compress: false
|
|
211
214
|
});
|
|
212
215
|
for await (const entry of this.list()) {
|
|
213
216
|
if (entry.relativePath === "mimetype") continue;
|
|
214
217
|
zipfile.addFile(entry.absolutePath, entry.relativePath, {
|
|
215
|
-
compress: !
|
|
218
|
+
compress: !PRECOMPRESSED_KINDS.has(
|
|
219
|
+
(_a = import_media_types.MediaType.fromPath(entry.absolutePath)) == null ? void 0 : _a.kind
|
|
220
|
+
)
|
|
216
221
|
});
|
|
217
222
|
}
|
|
218
223
|
zipfile.end();
|
|
@@ -6,7 +6,7 @@ import { EpubStorageAdapter, EpubListEntry } from './interface.cjs';
|
|
|
6
6
|
* `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
|
|
7
7
|
*/
|
|
8
8
|
declare class TmpFsAdapter implements EpubStorageAdapter {
|
|
9
|
-
readonly rootPath
|
|
9
|
+
private readonly rootPath;
|
|
10
10
|
static readonly kind: "extracted-dir";
|
|
11
11
|
static readonly capabilities: {
|
|
12
12
|
readonly writable: true;
|
|
@@ -14,12 +14,14 @@ declare class TmpFsAdapter implements EpubStorageAdapter {
|
|
|
14
14
|
static init(source: string | Uint8Array): Promise<TmpFsAdapter>;
|
|
15
15
|
static initEmpty(): Promise<TmpFsAdapter>;
|
|
16
16
|
private constructor();
|
|
17
|
-
|
|
18
|
-
read(
|
|
19
|
-
|
|
20
|
-
write(
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
private diskPath;
|
|
18
|
+
read(entry: string): Promise<Uint8Array>;
|
|
19
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
20
|
+
write(entry: string, data: Uint8Array): Promise<void>;
|
|
21
|
+
write(entry: string, data: string, encoding: "utf-8"): Promise<void>;
|
|
22
|
+
remove(entry: string): Promise<void>;
|
|
23
|
+
exists(entry: string): Promise<boolean>;
|
|
24
|
+
archiveLength(entry: string): Promise<number>;
|
|
23
25
|
list(): AsyncIterable<EpubListEntry>;
|
|
24
26
|
duplicate(): Promise<TmpFsAdapter>;
|
|
25
27
|
serialize(targetPath: string): Promise<void>;
|
package/dist/adapters/tmpfs.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { EpubStorageAdapter, EpubListEntry } from './interface.js';
|
|
|
6
6
|
* `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
|
|
7
7
|
*/
|
|
8
8
|
declare class TmpFsAdapter implements EpubStorageAdapter {
|
|
9
|
-
readonly rootPath
|
|
9
|
+
private readonly rootPath;
|
|
10
10
|
static readonly kind: "extracted-dir";
|
|
11
11
|
static readonly capabilities: {
|
|
12
12
|
readonly writable: true;
|
|
@@ -14,12 +14,14 @@ declare class TmpFsAdapter implements EpubStorageAdapter {
|
|
|
14
14
|
static init(source: string | Uint8Array): Promise<TmpFsAdapter>;
|
|
15
15
|
static initEmpty(): Promise<TmpFsAdapter>;
|
|
16
16
|
private constructor();
|
|
17
|
-
|
|
18
|
-
read(
|
|
19
|
-
|
|
20
|
-
write(
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
private diskPath;
|
|
18
|
+
read(entry: string): Promise<Uint8Array>;
|
|
19
|
+
read(entry: string, encoding: "utf-8"): Promise<string>;
|
|
20
|
+
write(entry: string, data: Uint8Array): Promise<void>;
|
|
21
|
+
write(entry: string, data: string, encoding: "utf-8"): Promise<void>;
|
|
22
|
+
remove(entry: string): Promise<void>;
|
|
23
|
+
exists(entry: string): Promise<boolean>;
|
|
24
|
+
archiveLength(entry: string): Promise<number>;
|
|
23
25
|
list(): AsyncIterable<EpubListEntry>;
|
|
24
26
|
duplicate(): Promise<TmpFsAdapter>;
|
|
25
27
|
serialize(targetPath: string): Promise<void>;
|