@storyteller-platform/audiobook 0.3.3 → 0.3.5
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/chunk-BIEQXUOY.js +50 -0
- package/dist/entry.cjs +26 -2
- package/dist/entry.d.cts +2 -0
- package/dist/entry.d.ts +2 -0
- package/dist/entry.js +27 -2
- package/dist/ffmpeg.cjs +44 -9
- package/dist/ffmpeg.d.cts +3 -1
- package/dist/ffmpeg.d.ts +3 -1
- package/dist/ffmpeg.js +45 -9
- package/dist/index.cjs +176 -44
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +127 -46
- package/dist/mime.js +1 -0
- package/package.json +6 -4
|
@@ -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
|
|
136
|
+
bitrate
|
|
132
137
|
};
|
|
133
138
|
}
|
|
134
139
|
async getChapters() {
|
|
@@ -139,9 +144,28 @@ class AudiobookEntry {
|
|
|
139
144
|
start: chapter.startTime
|
|
140
145
|
}));
|
|
141
146
|
}
|
|
147
|
+
async setChapters(chapters) {
|
|
148
|
+
const info = await this.getInfo();
|
|
149
|
+
const duration = await this.getDuration();
|
|
150
|
+
const ffmpegChapters = [];
|
|
151
|
+
for (let i = 0; i < ffmpegChapters.length; i++) {
|
|
152
|
+
const chapter = chapters[i];
|
|
153
|
+
const prevFfmpegChapter = ffmpegChapters[i - 1];
|
|
154
|
+
ffmpegChapters.push({
|
|
155
|
+
id: i,
|
|
156
|
+
startTime: chapter.start ?? 0,
|
|
157
|
+
title: chapter.title ?? `ch${i}`,
|
|
158
|
+
endTime: duration
|
|
159
|
+
});
|
|
160
|
+
if (prevFfmpegChapter) {
|
|
161
|
+
prevFfmpegChapter.endTime = chapter.start ?? 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
info.chapters = ffmpegChapters;
|
|
165
|
+
}
|
|
142
166
|
async saveAndClose() {
|
|
143
167
|
const info = await this.getInfo();
|
|
144
|
-
await (0, import_ffmpeg.writeTrackMetadata)(this.filename, info
|
|
168
|
+
await (0, import_ffmpeg.writeTrackMetadata)(this.filename, info);
|
|
145
169
|
}
|
|
146
170
|
}
|
|
147
171
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/entry.d.cts
CHANGED
|
@@ -30,8 +30,10 @@ 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[]>;
|
|
36
|
+
setChapters(chapters: AudiobookChapter[]): Promise<void>;
|
|
35
37
|
saveAndClose(): Promise<void>;
|
|
36
38
|
}
|
|
37
39
|
|
package/dist/entry.d.ts
CHANGED
|
@@ -30,8 +30,10 @@ 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[]>;
|
|
36
|
+
setChapters(chapters: AudiobookChapter[]): Promise<void>;
|
|
35
37
|
saveAndClose(): Promise<void>;
|
|
36
38
|
}
|
|
37
39
|
|
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
|
|
117
|
+
bitrate
|
|
112
118
|
};
|
|
113
119
|
}
|
|
114
120
|
async getChapters() {
|
|
@@ -119,9 +125,28 @@ class AudiobookEntry {
|
|
|
119
125
|
start: chapter.startTime
|
|
120
126
|
}));
|
|
121
127
|
}
|
|
128
|
+
async setChapters(chapters) {
|
|
129
|
+
const info = await this.getInfo();
|
|
130
|
+
const duration = await this.getDuration();
|
|
131
|
+
const ffmpegChapters = [];
|
|
132
|
+
for (let i = 0; i < ffmpegChapters.length; i++) {
|
|
133
|
+
const chapter = chapters[i];
|
|
134
|
+
const prevFfmpegChapter = ffmpegChapters[i - 1];
|
|
135
|
+
ffmpegChapters.push({
|
|
136
|
+
id: i,
|
|
137
|
+
startTime: chapter.start ?? 0,
|
|
138
|
+
title: chapter.title ?? `ch${i}`,
|
|
139
|
+
endTime: duration
|
|
140
|
+
});
|
|
141
|
+
if (prevFfmpegChapter) {
|
|
142
|
+
prevFfmpegChapter.endTime = chapter.start ?? 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
info.chapters = ffmpegChapters;
|
|
146
|
+
}
|
|
122
147
|
async saveAndClose() {
|
|
123
148
|
const info = await this.getInfo();
|
|
124
|
-
await writeTrackMetadata(this.filename, info
|
|
149
|
+
await writeTrackMetadata(this.filename, info);
|
|
125
150
|
}
|
|
126
151
|
}
|
|
127
152
|
export {
|
package/dist/ffmpeg.cjs
CHANGED
|
@@ -94,9 +94,15 @@ async function getTrackMetadata(path) {
|
|
|
94
94
|
const stdout = await execCmd(
|
|
95
95
|
`ffprobe -i ${quotePath(path)} -v quiet -show_format -show_chapters -show_streams -output_format json`
|
|
96
96
|
);
|
|
97
|
-
const { chapters, streams, format
|
|
98
|
-
|
|
99
|
-
)
|
|
97
|
+
const { chapters, streams, format } = JSON.parse(stdout);
|
|
98
|
+
let id3v2Version = null;
|
|
99
|
+
if ((0, import_path.extname)(path).toLowerCase() === ".mp3") {
|
|
100
|
+
const { stderr: id3v2VersionOut } = await execPromise(
|
|
101
|
+
`ffprobe -i ${quotePath(path)} -v debug`
|
|
102
|
+
);
|
|
103
|
+
const id3v2VersionMatch = id3v2VersionOut.match(/id3v2 ver:(3|4)/);
|
|
104
|
+
id3v2Version = (id3v2VersionMatch == null ? void 0 : id3v2VersionMatch[1]) ?? null;
|
|
105
|
+
}
|
|
100
106
|
const attachedPicStream = streams.find(
|
|
101
107
|
(stream) => !!stream.disposition.attached_pic
|
|
102
108
|
);
|
|
@@ -123,8 +129,9 @@ async function getTrackMetadata(path) {
|
|
|
123
129
|
])
|
|
124
130
|
};
|
|
125
131
|
return {
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
id3v2Version,
|
|
133
|
+
duration: parseFloat(format.duration),
|
|
134
|
+
bitRate: format.bit_rate !== void 0 ? parseFloat(format.bit_rate) : format.bit_rate,
|
|
128
135
|
tags: {
|
|
129
136
|
title: format.tags.title ?? format.tags.Title,
|
|
130
137
|
subtitle: format.tags.subtitle ?? format.tags.Subtitle,
|
|
@@ -149,9 +156,16 @@ async function getTrackMetadata(path) {
|
|
|
149
156
|
attachedPic
|
|
150
157
|
};
|
|
151
158
|
}
|
|
152
|
-
|
|
159
|
+
function escapeFfmetadata(str) {
|
|
160
|
+
return str.replaceAll(/\\/g, "\\\\").replaceAll(/=/g, "\\=").replaceAll(/;/g, "\\;").replaceAll(/#/g, "\\#").replaceAll(/\n/g, "\\n");
|
|
161
|
+
}
|
|
162
|
+
async function writeTrackMetadata(path, trackInfo) {
|
|
163
|
+
const { tags: metadata, id3v2Version, chapters, attachedPic } = trackInfo;
|
|
153
164
|
const args = [];
|
|
154
165
|
const metadataArgs = [];
|
|
166
|
+
if (id3v2Version) {
|
|
167
|
+
metadataArgs.push(`-id3v2_version ${id3v2Version}`);
|
|
168
|
+
}
|
|
155
169
|
if (metadata.title) {
|
|
156
170
|
metadataArgs.push(`-metadata title="${escapeQuotes(metadata.title)}"`);
|
|
157
171
|
}
|
|
@@ -193,13 +207,34 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
|
|
|
193
207
|
`-metadata publisher="${escapeQuotes(metadata.publisher)}"`
|
|
194
208
|
);
|
|
195
209
|
}
|
|
210
|
+
metadataArgs.push("-map 0:a -map_metadata 0");
|
|
196
211
|
const ext = (0, import_path.extname)(path);
|
|
197
212
|
const tmpPath = (0, import_node_path.join)(
|
|
198
213
|
(0, import_node_os.tmpdir)(),
|
|
199
214
|
`storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}${ext}`
|
|
200
215
|
);
|
|
216
|
+
const chapterFilePath = (0, import_node_path.join)(
|
|
217
|
+
(0, import_node_os.tmpdir)(),
|
|
218
|
+
`storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}.txt`
|
|
219
|
+
);
|
|
201
220
|
let picPath = null;
|
|
202
221
|
try {
|
|
222
|
+
let chapterFileContents = ";FFMETADATA1\n\n";
|
|
223
|
+
for (const chapter of chapters) {
|
|
224
|
+
chapterFileContents += `[CHAPTER]
|
|
225
|
+
TIMEBASE=1/1000
|
|
226
|
+
START=${Math.floor(chapter.startTime * 1e3)}
|
|
227
|
+
END=${Math.floor(chapter.endTime * 1e3)}
|
|
228
|
+
`;
|
|
229
|
+
if (chapter.title) {
|
|
230
|
+
chapterFileContents += `TITLE=${escapeFfmetadata(chapter.title)}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
chapterFileContents += "\n";
|
|
234
|
+
}
|
|
235
|
+
await (0, import_promises.writeFile)(chapterFilePath, chapterFileContents);
|
|
236
|
+
args.push(`-i ${chapterFilePath}`);
|
|
237
|
+
metadataArgs.push(`-map_chapters 1`);
|
|
203
238
|
if (attachedPic) {
|
|
204
239
|
const imageExt = attachedPic.mimeType.split("/")[1];
|
|
205
240
|
picPath = (0, import_node_path.join)(
|
|
@@ -207,9 +242,9 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
|
|
|
207
242
|
`storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}.${imageExt}`
|
|
208
243
|
);
|
|
209
244
|
await (0, import_promises.writeFile)(picPath, attachedPic.data);
|
|
210
|
-
args.push(`-i ${
|
|
211
|
-
|
|
212
|
-
|
|
245
|
+
args.push(`-i ${picPath}`);
|
|
246
|
+
metadataArgs.push(`-map 2:v`);
|
|
247
|
+
metadataArgs.push(`-disposition:v:0 attached_pic`);
|
|
213
248
|
if (attachedPic.name) {
|
|
214
249
|
metadataArgs.push(
|
|
215
250
|
`-metadata:s:v title="${escapeQuotes(attachedPic.name)}"`
|
package/dist/ffmpeg.d.cts
CHANGED
|
@@ -15,6 +15,7 @@ type AttachedPic = {
|
|
|
15
15
|
description?: string | undefined;
|
|
16
16
|
};
|
|
17
17
|
declare function getTrackMetadata(path: string): Promise<{
|
|
18
|
+
id3v2Version: 3 | 4 | null;
|
|
18
19
|
duration: number;
|
|
19
20
|
bitRate: number | undefined;
|
|
20
21
|
tags: {
|
|
@@ -38,8 +39,9 @@ declare function getTrackMetadata(path: string): Promise<{
|
|
|
38
39
|
}[];
|
|
39
40
|
attachedPic: AttachedPic | undefined;
|
|
40
41
|
}>;
|
|
41
|
-
declare function writeTrackMetadata(path: string,
|
|
42
|
+
declare function writeTrackMetadata(path: string, trackInfo: TrackInfo): Promise<void>;
|
|
42
43
|
declare function getTrackMetadataFromBuffer(buffer: Uint8Array): Promise<{
|
|
44
|
+
id3v2Version: 3 | 4 | null;
|
|
43
45
|
duration: number;
|
|
44
46
|
bitRate: number | undefined;
|
|
45
47
|
tags: {
|
package/dist/ffmpeg.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ type AttachedPic = {
|
|
|
15
15
|
description?: string | undefined;
|
|
16
16
|
};
|
|
17
17
|
declare function getTrackMetadata(path: string): Promise<{
|
|
18
|
+
id3v2Version: 3 | 4 | null;
|
|
18
19
|
duration: number;
|
|
19
20
|
bitRate: number | undefined;
|
|
20
21
|
tags: {
|
|
@@ -38,8 +39,9 @@ declare function getTrackMetadata(path: string): Promise<{
|
|
|
38
39
|
}[];
|
|
39
40
|
attachedPic: AttachedPic | undefined;
|
|
40
41
|
}>;
|
|
41
|
-
declare function writeTrackMetadata(path: string,
|
|
42
|
+
declare function writeTrackMetadata(path: string, trackInfo: TrackInfo): Promise<void>;
|
|
42
43
|
declare function getTrackMetadataFromBuffer(buffer: Uint8Array): Promise<{
|
|
44
|
+
id3v2Version: 3 | 4 | null;
|
|
43
45
|
duration: number;
|
|
44
46
|
bitRate: number | undefined;
|
|
45
47
|
tags: {
|
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";
|
|
@@ -67,9 +68,15 @@ async function getTrackMetadata(path) {
|
|
|
67
68
|
const stdout = await execCmd(
|
|
68
69
|
`ffprobe -i ${quotePath(path)} -v quiet -show_format -show_chapters -show_streams -output_format json`
|
|
69
70
|
);
|
|
70
|
-
const { chapters, streams, format
|
|
71
|
-
|
|
72
|
-
)
|
|
71
|
+
const { chapters, streams, format } = JSON.parse(stdout);
|
|
72
|
+
let id3v2Version = null;
|
|
73
|
+
if (extname(path).toLowerCase() === ".mp3") {
|
|
74
|
+
const { stderr: id3v2VersionOut } = await execPromise(
|
|
75
|
+
`ffprobe -i ${quotePath(path)} -v debug`
|
|
76
|
+
);
|
|
77
|
+
const id3v2VersionMatch = id3v2VersionOut.match(/id3v2 ver:(3|4)/);
|
|
78
|
+
id3v2Version = (id3v2VersionMatch == null ? void 0 : id3v2VersionMatch[1]) ?? null;
|
|
79
|
+
}
|
|
73
80
|
const attachedPicStream = streams.find(
|
|
74
81
|
(stream) => !!stream.disposition.attached_pic
|
|
75
82
|
);
|
|
@@ -96,8 +103,9 @@ async function getTrackMetadata(path) {
|
|
|
96
103
|
])
|
|
97
104
|
};
|
|
98
105
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
id3v2Version,
|
|
107
|
+
duration: parseFloat(format.duration),
|
|
108
|
+
bitRate: format.bit_rate !== void 0 ? parseFloat(format.bit_rate) : format.bit_rate,
|
|
101
109
|
tags: {
|
|
102
110
|
title: format.tags.title ?? format.tags.Title,
|
|
103
111
|
subtitle: format.tags.subtitle ?? format.tags.Subtitle,
|
|
@@ -122,9 +130,16 @@ async function getTrackMetadata(path) {
|
|
|
122
130
|
attachedPic
|
|
123
131
|
};
|
|
124
132
|
}
|
|
125
|
-
|
|
133
|
+
function escapeFfmetadata(str) {
|
|
134
|
+
return str.replaceAll(/\\/g, "\\\\").replaceAll(/=/g, "\\=").replaceAll(/;/g, "\\;").replaceAll(/#/g, "\\#").replaceAll(/\n/g, "\\n");
|
|
135
|
+
}
|
|
136
|
+
async function writeTrackMetadata(path, trackInfo) {
|
|
137
|
+
const { tags: metadata, id3v2Version, chapters, attachedPic } = trackInfo;
|
|
126
138
|
const args = [];
|
|
127
139
|
const metadataArgs = [];
|
|
140
|
+
if (id3v2Version) {
|
|
141
|
+
metadataArgs.push(`-id3v2_version ${id3v2Version}`);
|
|
142
|
+
}
|
|
128
143
|
if (metadata.title) {
|
|
129
144
|
metadataArgs.push(`-metadata title="${escapeQuotes(metadata.title)}"`);
|
|
130
145
|
}
|
|
@@ -166,13 +181,34 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
|
|
|
166
181
|
`-metadata publisher="${escapeQuotes(metadata.publisher)}"`
|
|
167
182
|
);
|
|
168
183
|
}
|
|
184
|
+
metadataArgs.push("-map 0:a -map_metadata 0");
|
|
169
185
|
const ext = extname(path);
|
|
170
186
|
const tmpPath = join(
|
|
171
187
|
tmpdir(),
|
|
172
188
|
`storyteller-platform-audiobook-${randomUUID()}${ext}`
|
|
173
189
|
);
|
|
190
|
+
const chapterFilePath = join(
|
|
191
|
+
tmpdir(),
|
|
192
|
+
`storyteller-platform-audiobook-${randomUUID()}.txt`
|
|
193
|
+
);
|
|
174
194
|
let picPath = null;
|
|
175
195
|
try {
|
|
196
|
+
let chapterFileContents = ";FFMETADATA1\n\n";
|
|
197
|
+
for (const chapter of chapters) {
|
|
198
|
+
chapterFileContents += `[CHAPTER]
|
|
199
|
+
TIMEBASE=1/1000
|
|
200
|
+
START=${Math.floor(chapter.startTime * 1e3)}
|
|
201
|
+
END=${Math.floor(chapter.endTime * 1e3)}
|
|
202
|
+
`;
|
|
203
|
+
if (chapter.title) {
|
|
204
|
+
chapterFileContents += `TITLE=${escapeFfmetadata(chapter.title)}
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
chapterFileContents += "\n";
|
|
208
|
+
}
|
|
209
|
+
await writeFile(chapterFilePath, chapterFileContents);
|
|
210
|
+
args.push(`-i ${chapterFilePath}`);
|
|
211
|
+
metadataArgs.push(`-map_chapters 1`);
|
|
176
212
|
if (attachedPic) {
|
|
177
213
|
const imageExt = attachedPic.mimeType.split("/")[1];
|
|
178
214
|
picPath = join(
|
|
@@ -180,9 +216,9 @@ async function writeTrackMetadata(path, metadata, attachedPic) {
|
|
|
180
216
|
`storyteller-platform-audiobook-${randomUUID()}.${imageExt}`
|
|
181
217
|
);
|
|
182
218
|
await writeFile(picPath, attachedPic.data);
|
|
183
|
-
args.push(`-i ${
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
args.push(`-i ${picPath}`);
|
|
220
|
+
metadataArgs.push(`-map 2:v`);
|
|
221
|
+
metadataArgs.push(`-disposition:v:0 attached_pic`);
|
|
186
222
|
if (attachedPic.name) {
|
|
187
223
|
metadataArgs.push(
|
|
188
224
|
`-metadata:s:v title="${escapeQuotes(attachedPic.name)}"`
|
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
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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)()}`
|
|
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)
|
|
106
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) {
|
|
@@ -234,6 +331,13 @@ class Audiobook {
|
|
|
234
331
|
this.metadata.chapters = chapters;
|
|
235
332
|
return chapters;
|
|
236
333
|
}
|
|
334
|
+
async setChapters(chapters) {
|
|
335
|
+
if (this.entries.length > 1) {
|
|
336
|
+
throw new Error("Unable to set chapters for multi-file audiobook");
|
|
337
|
+
}
|
|
338
|
+
this.metadata.chapters = chapters;
|
|
339
|
+
await this.setValue((entry) => entry.setChapters(chapters));
|
|
340
|
+
}
|
|
237
341
|
async getResources() {
|
|
238
342
|
if (this.metadata.resources) return this.metadata.resources;
|
|
239
343
|
const resources = [];
|
|
@@ -252,30 +356,58 @@ class Audiobook {
|
|
|
252
356
|
for (const entry of this.entries) {
|
|
253
357
|
await entry.saveAndClose();
|
|
254
358
|
}
|
|
255
|
-
if (this.isZip) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
)
|
|
359
|
+
if (this.isZip && this.extractPath) {
|
|
360
|
+
var _stack = [];
|
|
361
|
+
try {
|
|
362
|
+
const tmpArchivePath = (0, import_path.join)(
|
|
363
|
+
(0, import_node_os.tmpdir)(),
|
|
364
|
+
`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
|
|
270
365
|
);
|
|
366
|
+
const [first] = this.inputs;
|
|
367
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
368
|
+
const zipfile = new import_yazl.ZipFile();
|
|
369
|
+
const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
|
|
370
|
+
writeStream.on("close", () => {
|
|
371
|
+
resolve();
|
|
372
|
+
});
|
|
373
|
+
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
374
|
+
stack.defer(async () => {
|
|
375
|
+
writeStream.close();
|
|
376
|
+
await (0, import_promises.rm)(tmpArchivePath, { force: true });
|
|
377
|
+
});
|
|
378
|
+
zipfile.outputStream.pipe(writeStream);
|
|
379
|
+
const entries = await (0, import_promises.readdir)(this.extractPath, {
|
|
380
|
+
recursive: true,
|
|
381
|
+
withFileTypes: true
|
|
382
|
+
});
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
if (entry.isDirectory()) continue;
|
|
385
|
+
zipfile.addFile(
|
|
386
|
+
(0, import_path.join)(entry.parentPath, entry.name),
|
|
387
|
+
(0, import_path.join)(entry.parentPath, entry.name).replace(
|
|
388
|
+
`${this.extractPath}/`,
|
|
389
|
+
""
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
zipfile.end();
|
|
394
|
+
await promise;
|
|
395
|
+
await (0, import_promises.cp)(tmpArchivePath, first);
|
|
396
|
+
return;
|
|
397
|
+
} catch (_) {
|
|
398
|
+
var _error = _, _hasError = true;
|
|
399
|
+
} finally {
|
|
400
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
401
|
+
_promise && await _promise;
|
|
271
402
|
}
|
|
272
|
-
zipFs.saveAndClose();
|
|
273
|
-
return;
|
|
274
403
|
}
|
|
275
404
|
}
|
|
276
405
|
discardAndClose() {
|
|
277
406
|
this.inputs = [""];
|
|
278
407
|
this.entries = [];
|
|
408
|
+
if (this.extractPath) {
|
|
409
|
+
(0, import_node_fs.rmSync)(this.extractPath, { recursive: true });
|
|
410
|
+
}
|
|
279
411
|
}
|
|
280
412
|
[Symbol.dispose]() {
|
|
281
413
|
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
|
|
33
|
+
private extractPath;
|
|
34
34
|
private isZip;
|
|
35
35
|
private constructor();
|
|
36
36
|
static from(...inputs: AudiobookInputs): Promise<Audiobook>;
|
|
@@ -55,6 +55,7 @@ declare class Audiobook {
|
|
|
55
55
|
setReleaseDate(date: Date): Promise<void>;
|
|
56
56
|
getDuration(): Promise<number>;
|
|
57
57
|
getChapters(): Promise<AudiobookChapter[]>;
|
|
58
|
+
setChapters(chapters: AudiobookChapter[]): Promise<void>;
|
|
58
59
|
getResources(): Promise<AudiobookResource[]>;
|
|
59
60
|
saveAndClose(): Promise<void>;
|
|
60
61
|
discardAndClose(): void;
|
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
|
|
33
|
+
private extractPath;
|
|
34
34
|
private isZip;
|
|
35
35
|
private constructor();
|
|
36
36
|
static from(...inputs: AudiobookInputs): Promise<Audiobook>;
|
|
@@ -55,6 +55,7 @@ declare class Audiobook {
|
|
|
55
55
|
setReleaseDate(date: Date): Promise<void>;
|
|
56
56
|
getDuration(): Promise<number>;
|
|
57
57
|
getChapters(): Promise<AudiobookChapter[]>;
|
|
58
|
+
setChapters(chapters: AudiobookChapter[]): Promise<void>;
|
|
58
59
|
getResources(): Promise<AudiobookResource[]>;
|
|
59
60
|
saveAndClose(): Promise<void>;
|
|
60
61
|
discardAndClose(): void;
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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()}`
|
|
71
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)
|
|
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) {
|
|
@@ -199,6 +245,13 @@ class Audiobook {
|
|
|
199
245
|
this.metadata.chapters = chapters;
|
|
200
246
|
return chapters;
|
|
201
247
|
}
|
|
248
|
+
async setChapters(chapters) {
|
|
249
|
+
if (this.entries.length > 1) {
|
|
250
|
+
throw new Error("Unable to set chapters for multi-file audiobook");
|
|
251
|
+
}
|
|
252
|
+
this.metadata.chapters = chapters;
|
|
253
|
+
await this.setValue((entry) => entry.setChapters(chapters));
|
|
254
|
+
}
|
|
202
255
|
async getResources() {
|
|
203
256
|
if (this.metadata.resources) return this.metadata.resources;
|
|
204
257
|
const resources = [];
|
|
@@ -217,30 +270,58 @@ class Audiobook {
|
|
|
217
270
|
for (const entry of this.entries) {
|
|
218
271
|
await entry.saveAndClose();
|
|
219
272
|
}
|
|
220
|
-
if (this.isZip) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
)
|
|
273
|
+
if (this.isZip && this.extractPath) {
|
|
274
|
+
var _stack = [];
|
|
275
|
+
try {
|
|
276
|
+
const tmpArchivePath = join(
|
|
277
|
+
tmpdir(),
|
|
278
|
+
`storyteller-platform-epub-${randomUUID()}`
|
|
235
279
|
);
|
|
280
|
+
const [first] = this.inputs;
|
|
281
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
282
|
+
const zipfile = new ZipFile();
|
|
283
|
+
const writeStream = createWriteStream(tmpArchivePath);
|
|
284
|
+
writeStream.on("close", () => {
|
|
285
|
+
resolve();
|
|
286
|
+
});
|
|
287
|
+
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
288
|
+
stack.defer(async () => {
|
|
289
|
+
writeStream.close();
|
|
290
|
+
await rm(tmpArchivePath, { force: true });
|
|
291
|
+
});
|
|
292
|
+
zipfile.outputStream.pipe(writeStream);
|
|
293
|
+
const entries = await readdir(this.extractPath, {
|
|
294
|
+
recursive: true,
|
|
295
|
+
withFileTypes: true
|
|
296
|
+
});
|
|
297
|
+
for (const entry of entries) {
|
|
298
|
+
if (entry.isDirectory()) continue;
|
|
299
|
+
zipfile.addFile(
|
|
300
|
+
join(entry.parentPath, entry.name),
|
|
301
|
+
join(entry.parentPath, entry.name).replace(
|
|
302
|
+
`${this.extractPath}/`,
|
|
303
|
+
""
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
zipfile.end();
|
|
308
|
+
await promise;
|
|
309
|
+
await cp(tmpArchivePath, first);
|
|
310
|
+
return;
|
|
311
|
+
} catch (_) {
|
|
312
|
+
var _error = _, _hasError = true;
|
|
313
|
+
} finally {
|
|
314
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
315
|
+
_promise && await _promise;
|
|
236
316
|
}
|
|
237
|
-
zipFs.saveAndClose();
|
|
238
|
-
return;
|
|
239
317
|
}
|
|
240
318
|
}
|
|
241
319
|
discardAndClose() {
|
|
242
320
|
this.inputs = [""];
|
|
243
321
|
this.entries = [];
|
|
322
|
+
if (this.extractPath) {
|
|
323
|
+
rmSync(this.extractPath, { recursive: true });
|
|
324
|
+
}
|
|
244
325
|
}
|
|
245
326
|
[Symbol.dispose]() {
|
|
246
327
|
this.discardAndClose();
|
package/dist/mime.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyteller-platform/audiobook",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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
|
-
"@
|
|
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",
|