@storyteller-platform/audiobook 0.3.0 → 0.3.4

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.
@@ -0,0 +1,50 @@
1
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __using = (stack, value, async) => {
6
+ if (value != null) {
7
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
8
+ var dispose, inner;
9
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
10
+ if (dispose === void 0) {
11
+ dispose = value[__knownSymbol("dispose")];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") __typeError("Object not disposable");
15
+ if (inner) dispose = function() {
16
+ try {
17
+ inner.call(this);
18
+ } catch (e) {
19
+ return Promise.reject(e);
20
+ }
21
+ };
22
+ stack.push([async, dispose, value]);
23
+ } else if (async) {
24
+ stack.push([async]);
25
+ }
26
+ return value;
27
+ };
28
+ var __callDispose = (stack, error, hasError) => {
29
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
30
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
31
+ };
32
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
33
+ var next = (it) => {
34
+ while (it = stack.pop()) {
35
+ try {
36
+ var result = it[1] && it[1].call(it[2]);
37
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
38
+ } catch (e) {
39
+ fail(e);
40
+ }
41
+ }
42
+ if (hasError) throw error;
43
+ };
44
+ return next();
45
+ };
46
+
47
+ export {
48
+ __using,
49
+ __callDispose
50
+ };
package/dist/entry.cjs CHANGED
@@ -119,8 +119,13 @@ class AudiobookEntry {
119
119
  const info = await this.getInfo();
120
120
  return info.duration;
121
121
  }
122
+ async getBitRate() {
123
+ const info = await this.getInfo();
124
+ return info.bitRate;
125
+ }
122
126
  async getResource() {
123
127
  const duration = await this.getDuration();
128
+ const bitrate = await this.getBitRate() ?? null;
124
129
  const title = await this.getTrackTitle() ?? null;
125
130
  const type = (0, import_mime.lookupAudioMime)(this.filename);
126
131
  return {
@@ -128,7 +133,7 @@ class AudiobookEntry {
128
133
  title,
129
134
  type,
130
135
  duration,
131
- bitrate: null
136
+ bitrate
132
137
  };
133
138
  }
134
139
  async getChapters() {
package/dist/entry.d.cts CHANGED
@@ -30,6 +30,7 @@ declare class AudiobookEntry {
30
30
  getReleaseDate(): Promise<Date | null>;
31
31
  setReleaseDate(date: Date): Promise<void>;
32
32
  getDuration(): Promise<number>;
33
+ getBitRate(): Promise<number | undefined>;
33
34
  getResource(): Promise<AudiobookResource>;
34
35
  getChapters(): Promise<AudiobookChapter[]>;
35
36
  saveAndClose(): Promise<void>;
package/dist/entry.d.ts CHANGED
@@ -30,6 +30,7 @@ declare class AudiobookEntry {
30
30
  getReleaseDate(): Promise<Date | null>;
31
31
  setReleaseDate(date: Date): Promise<void>;
32
32
  getDuration(): Promise<number>;
33
+ getBitRate(): Promise<number | undefined>;
33
34
  getResource(): Promise<AudiobookResource>;
34
35
  getChapters(): Promise<AudiobookChapter[]>;
35
36
  saveAndClose(): Promise<void>;
package/dist/entry.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./chunk-BIEQXUOY.js";
1
2
  import {
2
3
  getTrackMetadata,
3
4
  writeTrackMetadata
@@ -99,8 +100,13 @@ class AudiobookEntry {
99
100
  const info = await this.getInfo();
100
101
  return info.duration;
101
102
  }
103
+ async getBitRate() {
104
+ const info = await this.getInfo();
105
+ return info.bitRate;
106
+ }
102
107
  async getResource() {
103
108
  const duration = await this.getDuration();
109
+ const bitrate = await this.getBitRate() ?? null;
104
110
  const title = await this.getTrackTitle() ?? null;
105
111
  const type = lookupAudioMime(this.filename);
106
112
  return {
@@ -108,7 +114,7 @@ class AudiobookEntry {
108
114
  title,
109
115
  type,
110
116
  duration,
111
- bitrate: null
117
+ bitrate
112
118
  };
113
119
  }
114
120
  async getChapters() {
package/dist/ffmpeg.cjs CHANGED
@@ -90,20 +90,19 @@ function lookup(codecName) {
90
90
  }
91
91
  }
92
92
  async function getTrackMetadata(path) {
93
+ var _a, _b;
93
94
  const stdout = await execCmd(
94
95
  `ffprobe -i ${quotePath(path)} -v quiet -show_format -show_chapters -show_streams -output_format json`
95
96
  );
96
- const { chapters, streams, format, duration, bit_rate } = JSON.parse(
97
- stdout
98
- );
97
+ const { chapters, streams, format } = JSON.parse(stdout);
99
98
  const attachedPicStream = streams.find(
100
99
  (stream) => !!stream.disposition.attached_pic
101
100
  );
102
101
  const attachedPic = attachedPicStream && {
103
- name: attachedPicStream.tags.title,
102
+ name: (_a = attachedPicStream.tags) == null ? void 0 : _a.title,
104
103
  mimeType: lookup(attachedPicStream.codec_name),
105
- kind: attachedPicStream.tags.comment === "Cover (front)" ? "coverFront" : attachedPicStream.tags.comment === "Cover (back)" ? "coverBack" : "unknown",
106
- description: attachedPicStream.tags.comment,
104
+ kind: "coverFront",
105
+ description: (_b = attachedPicStream.tags) == null ? void 0 : _b.comment,
107
106
  data: await execCmdBuffer("ffmpeg", [
108
107
  "-nostdin",
109
108
  "-i",
@@ -122,8 +121,8 @@ async function getTrackMetadata(path) {
122
121
  ])
123
122
  };
124
123
  return {
125
- duration: parseFloat(duration),
126
- bitRate: bit_rate !== void 0 ? parseFloat(bit_rate) : bit_rate,
124
+ duration: parseFloat(format.duration),
125
+ bitRate: format.bit_rate !== void 0 ? parseFloat(format.bit_rate) : format.bit_rate,
127
126
  tags: {
128
127
  title: format.tags.title ?? format.tags.Title,
129
128
  subtitle: format.tags.subtitle ?? format.tags.Subtitle,
@@ -149,6 +148,7 @@ async function getTrackMetadata(path) {
149
148
  };
150
149
  }
151
150
  async function writeTrackMetadata(path, metadata, attachedPic) {
151
+ const args = [];
152
152
  const metadataArgs = [];
153
153
  if (metadata.title) {
154
154
  metadataArgs.push(`-metadata title="${escapeQuotes(metadata.title)}"`);
@@ -205,7 +205,9 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
205
205
  `storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}.${imageExt}`
206
206
  );
207
207
  await (0, import_promises.writeFile)(picPath, attachedPic.data);
208
- metadataArgs.push(`-disposition:v:0 attached_pic`);
208
+ args.push(`-i ${quotePath(picPath)}`);
209
+ args.push(`-map 0:a -map 1:v`);
210
+ args.push(`-disposition:v:0 attached_pic`);
209
211
  if (attachedPic.name) {
210
212
  metadataArgs.push(
211
213
  `-metadata:s:v title="${escapeQuotes(attachedPic.name)}"`
@@ -217,7 +219,8 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
217
219
  );
218
220
  }
219
221
  }
220
- const cmd = `ffmpeg -i "${path}" ${metadataArgs.join(" ")} -codec copy "${tmpPath}"`;
222
+ args.push(...metadataArgs);
223
+ const cmd = `ffmpeg -i ${quotePath(path)} ${args.join(" ")} -codec copy "${tmpPath}"`;
221
224
  await execCmd(cmd);
222
225
  await (0, import_promises.cp)(tmpPath, path, { force: true });
223
226
  } finally {
package/dist/ffmpeg.d.cts CHANGED
@@ -11,8 +11,8 @@ type AttachedPic = {
11
11
  data: Uint8Array;
12
12
  mimeType: string;
13
13
  kind: "coverFront" | "coverBack" | "unknown";
14
- name?: string;
15
- description?: string;
14
+ name?: string | undefined;
15
+ description?: string | undefined;
16
16
  };
17
17
  declare function getTrackMetadata(path: string): Promise<{
18
18
  duration: number;
package/dist/ffmpeg.d.ts CHANGED
@@ -11,8 +11,8 @@ type AttachedPic = {
11
11
  data: Uint8Array;
12
12
  mimeType: string;
13
13
  kind: "coverFront" | "coverBack" | "unknown";
14
- name?: string;
15
- description?: string;
14
+ name?: string | undefined;
15
+ description?: string | undefined;
16
16
  };
17
17
  declare function getTrackMetadata(path: string): Promise<{
18
18
  duration: number;
package/dist/ffmpeg.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./chunk-BIEQXUOY.js";
1
2
  import { exec, execFile } from "node:child_process";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { writeFileSync } from "node:fs";
@@ -63,20 +64,19 @@ function lookup(codecName) {
63
64
  }
64
65
  }
65
66
  async function getTrackMetadata(path) {
67
+ var _a, _b;
66
68
  const stdout = await execCmd(
67
69
  `ffprobe -i ${quotePath(path)} -v quiet -show_format -show_chapters -show_streams -output_format json`
68
70
  );
69
- const { chapters, streams, format, duration, bit_rate } = JSON.parse(
70
- stdout
71
- );
71
+ const { chapters, streams, format } = JSON.parse(stdout);
72
72
  const attachedPicStream = streams.find(
73
73
  (stream) => !!stream.disposition.attached_pic
74
74
  );
75
75
  const attachedPic = attachedPicStream && {
76
- name: attachedPicStream.tags.title,
76
+ name: (_a = attachedPicStream.tags) == null ? void 0 : _a.title,
77
77
  mimeType: lookup(attachedPicStream.codec_name),
78
- kind: attachedPicStream.tags.comment === "Cover (front)" ? "coverFront" : attachedPicStream.tags.comment === "Cover (back)" ? "coverBack" : "unknown",
79
- description: attachedPicStream.tags.comment,
78
+ kind: "coverFront",
79
+ description: (_b = attachedPicStream.tags) == null ? void 0 : _b.comment,
80
80
  data: await execCmdBuffer("ffmpeg", [
81
81
  "-nostdin",
82
82
  "-i",
@@ -95,8 +95,8 @@ async function getTrackMetadata(path) {
95
95
  ])
96
96
  };
97
97
  return {
98
- duration: parseFloat(duration),
99
- bitRate: bit_rate !== void 0 ? parseFloat(bit_rate) : bit_rate,
98
+ duration: parseFloat(format.duration),
99
+ bitRate: format.bit_rate !== void 0 ? parseFloat(format.bit_rate) : format.bit_rate,
100
100
  tags: {
101
101
  title: format.tags.title ?? format.tags.Title,
102
102
  subtitle: format.tags.subtitle ?? format.tags.Subtitle,
@@ -122,6 +122,7 @@ async function getTrackMetadata(path) {
122
122
  };
123
123
  }
124
124
  async function writeTrackMetadata(path, metadata, attachedPic) {
125
+ const args = [];
125
126
  const metadataArgs = [];
126
127
  if (metadata.title) {
127
128
  metadataArgs.push(`-metadata title="${escapeQuotes(metadata.title)}"`);
@@ -178,7 +179,9 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
178
179
  `storyteller-platform-audiobook-${randomUUID()}.${imageExt}`
179
180
  );
180
181
  await writeFile(picPath, attachedPic.data);
181
- metadataArgs.push(`-disposition:v:0 attached_pic`);
182
+ args.push(`-i ${quotePath(picPath)}`);
183
+ args.push(`-map 0:a -map 1:v`);
184
+ args.push(`-disposition:v:0 attached_pic`);
182
185
  if (attachedPic.name) {
183
186
  metadataArgs.push(
184
187
  `-metadata:s:v title="${escapeQuotes(attachedPic.name)}"`
@@ -190,7 +193,8 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
190
193
  );
191
194
  }
192
195
  }
193
- const cmd = `ffmpeg -i "${path}" ${metadataArgs.join(" ")} -codec copy "${tmpPath}"`;
196
+ args.push(...metadataArgs);
197
+ const cmd = `ffmpeg -i ${quotePath(path)} ${args.join(" ")} -codec copy "${tmpPath}"`;
194
198
  await execCmd(cmd);
195
199
  await cp(tmpPath, path, { force: true });
196
200
  } finally {
package/dist/index.cjs CHANGED
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
9
+ var __typeError = (msg) => {
10
+ throw TypeError(msg);
11
+ };
6
12
  var __export = (target, all) => {
7
13
  for (var name in all)
8
14
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,7 +21,56 @@ var __copyProps = (to, from, except, desc) => {
15
21
  }
16
22
  return to;
17
23
  };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
18
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+ var __using = (stack, value, async) => {
34
+ if (value != null) {
35
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
36
+ var dispose, inner;
37
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
38
+ if (dispose === void 0) {
39
+ dispose = value[__knownSymbol("dispose")];
40
+ if (async) inner = dispose;
41
+ }
42
+ if (typeof dispose !== "function") __typeError("Object not disposable");
43
+ if (inner) dispose = function() {
44
+ try {
45
+ inner.call(this);
46
+ } catch (e) {
47
+ return Promise.reject(e);
48
+ }
49
+ };
50
+ stack.push([async, dispose, value]);
51
+ } else if (async) {
52
+ stack.push([async]);
53
+ }
54
+ return value;
55
+ };
56
+ var __callDispose = (stack, error, hasError) => {
57
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
58
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
59
+ };
60
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
61
+ var next = (it) => {
62
+ while (it = stack.pop()) {
63
+ try {
64
+ var result = it[1] && it[1].call(it[2]);
65
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
66
+ } catch (e) {
67
+ fail(e);
68
+ }
69
+ }
70
+ if (hasError) throw error;
71
+ };
72
+ return next();
73
+ };
19
74
  var index_exports = {};
20
75
  __export(index_exports, {
21
76
  AAC_FILE_EXTENSIONS: () => AAC_FILE_EXTENSIONS,
@@ -37,12 +92,30 @@ var import_node_crypto = require("node:crypto");
37
92
  var import_node_fs = require("node:fs");
38
93
  var import_promises = require("node:fs/promises");
39
94
  var import_node_os = require("node:os");
40
- var import_promises2 = require("node:stream/promises");
41
- var import_fslib = require("@yarnpkg/fslib");
42
- var import_libzip = require("@yarnpkg/libzip");
95
+ var util = __toESM(require("node:util"), 1);
43
96
  var import_mime_types = require("mime-types");
97
+ var import_yauzl = __toESM(require("yauzl"), 1);
98
+ var import_yazl = require("yazl");
44
99
  var import_path = require("@storyteller-platform/path");
45
100
  var import_entry = require("./entry.cjs");
101
+ function promisify(api) {
102
+ return function(arg, options) {
103
+ return new Promise(function(resolve, reject) {
104
+ api(arg, options, function(err, response) {
105
+ if (err) {
106
+ reject(err);
107
+ return;
108
+ }
109
+ resolve(response);
110
+ });
111
+ });
112
+ };
113
+ }
114
+ const unzipFromPath = promisify(
115
+ (arg, options, callback) => {
116
+ import_yauzl.default.open(arg, options, callback);
117
+ }
118
+ );
46
119
  const COVER_IMAGE_FILE_EXTENSIONS = [
47
120
  ".jpeg",
48
121
  ".jpg",
@@ -75,7 +148,7 @@ class Audiobook {
75
148
  metadata = {};
76
149
  inputs;
77
150
  entries = [];
78
- tmpDir = void 0;
151
+ extractPath = void 0;
79
152
  isZip;
80
153
  constructor(...inputs) {
81
154
  this.inputs = inputs;
@@ -89,32 +162,56 @@ class Audiobook {
89
162
  }
90
163
  async getEntries() {
91
164
  if (this.isZip) {
92
- const [first] = this.inputs;
93
- const tmpDir = (0, import_path.join)(
94
- (0, import_node_os.tmpdir)(),
95
- `storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}`
96
- );
97
- this.tmpDir = tmpDir;
98
- await (0, import_promises.mkdir)(this.tmpDir, { recursive: true });
99
- const zipFs = new import_libzip.ZipFS(first);
100
- const entries = zipFs.getAllFiles();
101
- for (const entry of entries) {
102
- await (0, import_promises.mkdir)((0, import_path.join)(this.tmpDir, (0, import_path.dirname)(entry)), { recursive: true });
103
- await (0, import_promises2.pipeline)(
104
- zipFs.createReadStream(entry),
105
- (0, import_node_fs.createWriteStream)((0, import_path.join)(this.tmpDir, entry))
165
+ var _stack = [];
166
+ try {
167
+ const [first] = this.inputs;
168
+ const extractPath = (0, import_path.join)(
169
+ (0, import_node_os.tmpdir)(),
170
+ `storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}`
106
171
  );
172
+ this.extractPath = extractPath;
173
+ await (0, import_promises.mkdir)(this.extractPath, { recursive: true });
174
+ const zipfile = await unzipFromPath(first, { lazyEntries: true });
175
+ const stack = __using(_stack, new DisposableStack());
176
+ stack.defer(() => {
177
+ zipfile.close();
178
+ });
179
+ const entries = [];
180
+ const { promise, resolve } = Promise.withResolvers();
181
+ zipfile.on("end", () => {
182
+ resolve();
183
+ });
184
+ const openReadStream = util.promisify(
185
+ zipfile.openReadStream.bind(zipfile)
186
+ );
187
+ zipfile.readEntry();
188
+ zipfile.on("entry", async (entry) => {
189
+ if (entry.fileName.endsWith("/")) {
190
+ zipfile.readEntry();
191
+ } else {
192
+ entries.push(entry.fileName);
193
+ const readStream = await openReadStream(entry);
194
+ readStream.on("end", function() {
195
+ zipfile.readEntry();
196
+ });
197
+ const writePath = (0, import_path.join)(extractPath, entry.fileName);
198
+ await (0, import_promises.mkdir)((0, import_path.dirname)(writePath), { recursive: true });
199
+ readStream.pipe((0, import_node_fs.createWriteStream)(writePath));
200
+ }
201
+ });
202
+ await promise;
203
+ return entries.filter(
204
+ (entry) => AUDIO_FILE_EXTENSIONS.includes(
205
+ (0, import_path.extname)(entry)
206
+ )
207
+ ).map((entry) => new import_entry.AudiobookEntry((0, import_path.join)(extractPath, entry)));
208
+ } catch (_) {
209
+ var _error = _, _hasError = true;
210
+ } finally {
211
+ __callDispose(_stack, _error, _hasError);
107
212
  }
108
- zipFs.discardAndClose();
109
- return entries.filter(
110
- (entry) => AUDIO_FILE_EXTENSIONS.includes(
111
- (0, import_path.extname)(entry)
112
- )
113
- ).map((entry) => new import_entry.AudiobookEntry((0, import_path.join)(tmpDir, entry)));
114
213
  } else {
115
- return this.inputs.map(
116
- (input) => new import_entry.AudiobookEntry(input)
117
- );
214
+ return this.inputs.map((input) => new import_entry.AudiobookEntry(input));
118
215
  }
119
216
  }
120
217
  async getFirstValue(getter) {
@@ -252,30 +349,58 @@ class Audiobook {
252
349
  for (const entry of this.entries) {
253
350
  await entry.saveAndClose();
254
351
  }
255
- if (this.isZip) {
256
- const [first] = this.inputs;
257
- const zipFs = new import_libzip.ZipFS(first);
258
- const tmpDir = this.tmpDir;
259
- const entries = await (0, import_promises.readdir)(tmpDir, { recursive: true });
260
- for (const entry of entries) {
261
- await zipFs.mkdirPromise(
262
- import_fslib.ppath.join(import_fslib.PortablePath.root, (0, import_path.dirname)(entry)),
263
- { recursive: true }
264
- );
265
- await (0, import_promises2.pipeline)(
266
- (0, import_node_fs.createReadStream)((0, import_path.join)(tmpDir, entry)),
267
- zipFs.createWriteStream(
268
- import_fslib.ppath.join(import_fslib.PortablePath.root, entry)
269
- )
352
+ if (this.isZip && this.extractPath) {
353
+ var _stack = [];
354
+ try {
355
+ const tmpArchivePath = (0, import_path.join)(
356
+ (0, import_node_os.tmpdir)(),
357
+ `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
270
358
  );
359
+ const [first] = this.inputs;
360
+ const { promise, resolve } = Promise.withResolvers();
361
+ const zipfile = new import_yazl.ZipFile();
362
+ const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
363
+ writeStream.on("close", () => {
364
+ resolve();
365
+ });
366
+ const stack = __using(_stack, new AsyncDisposableStack(), true);
367
+ stack.defer(async () => {
368
+ writeStream.close();
369
+ await (0, import_promises.rm)(tmpArchivePath, { force: true });
370
+ });
371
+ zipfile.outputStream.pipe(writeStream);
372
+ const entries = await (0, import_promises.readdir)(this.extractPath, {
373
+ recursive: true,
374
+ withFileTypes: true
375
+ });
376
+ for (const entry of entries) {
377
+ if (entry.isDirectory()) continue;
378
+ zipfile.addFile(
379
+ (0, import_path.join)(entry.parentPath, entry.name),
380
+ (0, import_path.join)(entry.parentPath, entry.name).replace(
381
+ `${this.extractPath}/`,
382
+ ""
383
+ )
384
+ );
385
+ }
386
+ zipfile.end();
387
+ await promise;
388
+ await (0, import_promises.cp)(tmpArchivePath, first);
389
+ return;
390
+ } catch (_) {
391
+ var _error = _, _hasError = true;
392
+ } finally {
393
+ var _promise = __callDispose(_stack, _error, _hasError);
394
+ _promise && await _promise;
271
395
  }
272
- zipFs.saveAndClose();
273
- return;
274
396
  }
275
397
  }
276
398
  discardAndClose() {
277
399
  this.inputs = [""];
278
400
  this.entries = [];
401
+ if (this.extractPath) {
402
+ (0, import_node_fs.rmSync)(this.extractPath, { recursive: true });
403
+ }
279
404
  }
280
405
  [Symbol.dispose]() {
281
406
  this.discardAndClose();
package/dist/index.d.cts CHANGED
@@ -30,7 +30,7 @@ declare class Audiobook {
30
30
  protected metadata: AudiobookMetadata;
31
31
  private inputs;
32
32
  private entries;
33
- private tmpDir;
33
+ private extractPath;
34
34
  private isZip;
35
35
  private constructor();
36
36
  static from(...inputs: AudiobookInputs): Promise<Audiobook>;
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@ declare class Audiobook {
30
30
  protected metadata: AudiobookMetadata;
31
31
  private inputs;
32
32
  private entries;
33
- private tmpDir;
33
+ private extractPath;
34
34
  private isZip;
35
35
  private constructor();
36
36
  static from(...inputs: AudiobookInputs): Promise<Audiobook>;
package/dist/index.js CHANGED
@@ -1,13 +1,35 @@
1
+ import {
2
+ __callDispose,
3
+ __using
4
+ } from "./chunk-BIEQXUOY.js";
1
5
  import { randomUUID } from "node:crypto";
2
- import { createReadStream, createWriteStream } from "node:fs";
3
- import { mkdir, readFile, readdir } from "node:fs/promises";
6
+ import { createWriteStream, rmSync } from "node:fs";
7
+ import { cp, mkdir, readFile, readdir, rm } from "node:fs/promises";
4
8
  import { tmpdir } from "node:os";
5
- import { pipeline } from "node:stream/promises";
6
- import { PortablePath, ppath } from "@yarnpkg/fslib";
7
- import { ZipFS } from "@yarnpkg/libzip";
9
+ import * as util from "node:util";
8
10
  import { lookup } from "mime-types";
11
+ import yauzl from "yauzl";
12
+ import { ZipFile } from "yazl";
9
13
  import { basename, dirname, extname, join } from "@storyteller-platform/path";
10
14
  import { AudiobookEntry } from "./entry.js";
15
+ function promisify(api) {
16
+ return function(arg, options) {
17
+ return new Promise(function(resolve, reject) {
18
+ api(arg, options, function(err, response) {
19
+ if (err) {
20
+ reject(err);
21
+ return;
22
+ }
23
+ resolve(response);
24
+ });
25
+ });
26
+ };
27
+ }
28
+ const unzipFromPath = promisify(
29
+ (arg, options, callback) => {
30
+ yauzl.open(arg, options, callback);
31
+ }
32
+ );
11
33
  const COVER_IMAGE_FILE_EXTENSIONS = [
12
34
  ".jpeg",
13
35
  ".jpg",
@@ -40,7 +62,7 @@ class Audiobook {
40
62
  metadata = {};
41
63
  inputs;
42
64
  entries = [];
43
- tmpDir = void 0;
65
+ extractPath = void 0;
44
66
  isZip;
45
67
  constructor(...inputs) {
46
68
  this.inputs = inputs;
@@ -54,32 +76,56 @@ class Audiobook {
54
76
  }
55
77
  async getEntries() {
56
78
  if (this.isZip) {
57
- const [first] = this.inputs;
58
- const tmpDir = join(
59
- tmpdir(),
60
- `storyteller-platform-audiobook-${randomUUID()}`
61
- );
62
- this.tmpDir = tmpDir;
63
- await mkdir(this.tmpDir, { recursive: true });
64
- const zipFs = new ZipFS(first);
65
- const entries = zipFs.getAllFiles();
66
- for (const entry of entries) {
67
- await mkdir(join(this.tmpDir, dirname(entry)), { recursive: true });
68
- await pipeline(
69
- zipFs.createReadStream(entry),
70
- createWriteStream(join(this.tmpDir, entry))
79
+ var _stack = [];
80
+ try {
81
+ const [first] = this.inputs;
82
+ const extractPath = join(
83
+ tmpdir(),
84
+ `storyteller-platform-audiobook-${randomUUID()}`
85
+ );
86
+ this.extractPath = extractPath;
87
+ await mkdir(this.extractPath, { recursive: true });
88
+ const zipfile = await unzipFromPath(first, { lazyEntries: true });
89
+ const stack = __using(_stack, new DisposableStack());
90
+ stack.defer(() => {
91
+ zipfile.close();
92
+ });
93
+ const entries = [];
94
+ const { promise, resolve } = Promise.withResolvers();
95
+ zipfile.on("end", () => {
96
+ resolve();
97
+ });
98
+ const openReadStream = util.promisify(
99
+ zipfile.openReadStream.bind(zipfile)
71
100
  );
101
+ zipfile.readEntry();
102
+ zipfile.on("entry", async (entry) => {
103
+ if (entry.fileName.endsWith("/")) {
104
+ zipfile.readEntry();
105
+ } else {
106
+ entries.push(entry.fileName);
107
+ const readStream = await openReadStream(entry);
108
+ readStream.on("end", function() {
109
+ zipfile.readEntry();
110
+ });
111
+ const writePath = join(extractPath, entry.fileName);
112
+ await mkdir(dirname(writePath), { recursive: true });
113
+ readStream.pipe(createWriteStream(writePath));
114
+ }
115
+ });
116
+ await promise;
117
+ return entries.filter(
118
+ (entry) => AUDIO_FILE_EXTENSIONS.includes(
119
+ extname(entry)
120
+ )
121
+ ).map((entry) => new AudiobookEntry(join(extractPath, entry)));
122
+ } catch (_) {
123
+ var _error = _, _hasError = true;
124
+ } finally {
125
+ __callDispose(_stack, _error, _hasError);
72
126
  }
73
- zipFs.discardAndClose();
74
- return entries.filter(
75
- (entry) => AUDIO_FILE_EXTENSIONS.includes(
76
- extname(entry)
77
- )
78
- ).map((entry) => new AudiobookEntry(join(tmpDir, entry)));
79
127
  } else {
80
- return this.inputs.map(
81
- (input) => new AudiobookEntry(input)
82
- );
128
+ return this.inputs.map((input) => new AudiobookEntry(input));
83
129
  }
84
130
  }
85
131
  async getFirstValue(getter) {
@@ -217,30 +263,58 @@ class Audiobook {
217
263
  for (const entry of this.entries) {
218
264
  await entry.saveAndClose();
219
265
  }
220
- if (this.isZip) {
221
- const [first] = this.inputs;
222
- const zipFs = new ZipFS(first);
223
- const tmpDir = this.tmpDir;
224
- const entries = await readdir(tmpDir, { recursive: true });
225
- for (const entry of entries) {
226
- await zipFs.mkdirPromise(
227
- ppath.join(PortablePath.root, dirname(entry)),
228
- { recursive: true }
229
- );
230
- await pipeline(
231
- createReadStream(join(tmpDir, entry)),
232
- zipFs.createWriteStream(
233
- ppath.join(PortablePath.root, entry)
234
- )
266
+ if (this.isZip && this.extractPath) {
267
+ var _stack = [];
268
+ try {
269
+ const tmpArchivePath = join(
270
+ tmpdir(),
271
+ `storyteller-platform-epub-${randomUUID()}`
235
272
  );
273
+ const [first] = this.inputs;
274
+ const { promise, resolve } = Promise.withResolvers();
275
+ const zipfile = new ZipFile();
276
+ const writeStream = createWriteStream(tmpArchivePath);
277
+ writeStream.on("close", () => {
278
+ resolve();
279
+ });
280
+ const stack = __using(_stack, new AsyncDisposableStack(), true);
281
+ stack.defer(async () => {
282
+ writeStream.close();
283
+ await rm(tmpArchivePath, { force: true });
284
+ });
285
+ zipfile.outputStream.pipe(writeStream);
286
+ const entries = await readdir(this.extractPath, {
287
+ recursive: true,
288
+ withFileTypes: true
289
+ });
290
+ for (const entry of entries) {
291
+ if (entry.isDirectory()) continue;
292
+ zipfile.addFile(
293
+ join(entry.parentPath, entry.name),
294
+ join(entry.parentPath, entry.name).replace(
295
+ `${this.extractPath}/`,
296
+ ""
297
+ )
298
+ );
299
+ }
300
+ zipfile.end();
301
+ await promise;
302
+ await cp(tmpArchivePath, first);
303
+ return;
304
+ } catch (_) {
305
+ var _error = _, _hasError = true;
306
+ } finally {
307
+ var _promise = __callDispose(_stack, _error, _hasError);
308
+ _promise && await _promise;
236
309
  }
237
- zipFs.saveAndClose();
238
- return;
239
310
  }
240
311
  }
241
312
  discardAndClose() {
242
313
  this.inputs = [""];
243
314
  this.entries = [];
315
+ if (this.extractPath) {
316
+ rmSync(this.extractPath, { recursive: true });
317
+ }
244
318
  }
245
319
  [Symbol.dispose]() {
246
320
  this.discardAndClose();
package/dist/mime.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./chunk-BIEQXUOY.js";
1
2
  const COVER_IMAGE_FILE_EXTENSIONS = [".jpeg", ".jpg", ".png", ".svg"];
2
3
  const MP3_FILE_EXTENSIONS = [".mp3"];
3
4
  const MPEG4_FILE_EXTENSIONS = [".mp4", ".m4a", ".m4b"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyteller-platform/audiobook",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -32,6 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@storyteller-platform/tsup": "^0.1.0",
34
34
  "@tsconfig/strictest": "^2.0.5",
35
+ "@types/yazl": "^3",
35
36
  "markdown-toc": "^1.2.0",
36
37
  "remark-toc": "^9.0.0",
37
38
  "tsup": "^8.5.0",
@@ -46,10 +47,11 @@
46
47
  "@storyteller-platform/path": "^0.1.1",
47
48
  "@types/mime-types": "^2",
48
49
  "@types/node": "^24.0.0",
49
- "@yarnpkg/fslib": "^3.1.3",
50
- "@yarnpkg/libzip": "^3.2.2",
50
+ "@types/yauzl": "^2.10.3",
51
51
  "mime-types": "^3.0.1",
52
- "music-metadata": "^11.9.0"
52
+ "music-metadata": "^11.9.0",
53
+ "yauzl": "^3.2.0",
54
+ "yazl": "^3.3.1"
53
55
  },
54
56
  "publishConfig": {
55
57
  "access": "public",