@storyteller-platform/audiobook 0.2.0 → 0.3.0
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/entry.cjs +56 -194
- package/dist/entry.d.cts +14 -15
- package/dist/entry.d.ts +14 -15
- package/dist/entry.js +58 -207
- package/dist/ffmpeg.cjs +194 -19
- package/dist/ffmpeg.d.cts +60 -4
- package/dist/ffmpeg.d.ts +60 -4
- package/dist/ffmpeg.js +191 -18
- package/dist/index.cjs +52 -56
- package/dist/index.d.cts +10 -15
- package/dist/index.d.ts +10 -15
- package/dist/index.js +54 -58
- package/dist/mime.cjs +112 -0
- package/dist/mime.d.cts +40 -0
- package/dist/mime.d.ts +40 -0
- package/dist/mime.js +75 -0
- package/dist/resources.d.cts +1 -1
- package/dist/resources.d.ts +1 -1
- package/package.json +1 -2
- package/dist/bufferEntry.cjs +0 -59
- package/dist/bufferEntry.d.cts +0 -16
- package/dist/bufferEntry.d.ts +0 -16
- package/dist/bufferEntry.js +0 -35
- package/dist/fileEntry.cjs +0 -58
- package/dist/fileEntry.d.cts +0 -16
- package/dist/fileEntry.d.ts +0 -16
- package/dist/fileEntry.js +0 -34
- package/dist/tagMaps.cjs +0 -120
- package/dist/tagMaps.d.cts +0 -24
- package/dist/tagMaps.d.ts +0 -24
- package/dist/tagMaps.js +0 -95
- package/dist/zipFsEntry.cjs +0 -65
- package/dist/zipFsEntry.d.cts +0 -19
- package/dist/zipFsEntry.d.ts +0 -19
- package/dist/zipFsEntry.js +0 -41
package/dist/entry.js
CHANGED
|
@@ -1,276 +1,127 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
MP3,
|
|
7
|
-
MP4,
|
|
8
|
-
Mp3OutputFormat,
|
|
9
|
-
Mp4OutputFormat,
|
|
10
|
-
OGG,
|
|
11
|
-
OggOutputFormat,
|
|
12
|
-
Output,
|
|
13
|
-
WAVE,
|
|
14
|
-
WavOutputFormat
|
|
15
|
-
} from "mediabunny";
|
|
16
|
-
import { getWriteTags, readTagMap } from "./tagMaps.js";
|
|
2
|
+
getTrackMetadata,
|
|
3
|
+
writeTrackMetadata
|
|
4
|
+
} from "./ffmpeg.js";
|
|
5
|
+
import { lookupAudioMime } from "./mime.js";
|
|
17
6
|
function splitNames(names) {
|
|
7
|
+
if (!names) return [];
|
|
18
8
|
return names.split(/[;,/]/).map((name) => name.trim());
|
|
19
9
|
}
|
|
20
10
|
class AudiobookEntry {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
return this.
|
|
11
|
+
constructor(filename) {
|
|
12
|
+
this.filename = filename;
|
|
13
|
+
}
|
|
14
|
+
info = null;
|
|
15
|
+
async getInfo() {
|
|
16
|
+
this.info ??= await getTrackMetadata(this.filename);
|
|
17
|
+
return this.info;
|
|
28
18
|
}
|
|
29
|
-
async
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
switch (inputFormat) {
|
|
33
|
-
case MP3: {
|
|
34
|
-
return new Mp3OutputFormat();
|
|
35
|
-
}
|
|
36
|
-
case MP4: {
|
|
37
|
-
return new Mp4OutputFormat();
|
|
38
|
-
}
|
|
39
|
-
case WAVE: {
|
|
40
|
-
return new WavOutputFormat();
|
|
41
|
-
}
|
|
42
|
-
case OGG: {
|
|
43
|
-
return new OggOutputFormat();
|
|
44
|
-
}
|
|
45
|
-
case FLAC: {
|
|
46
|
-
return new FlacOutputFormat();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
19
|
+
async getMetadataTags() {
|
|
20
|
+
const info = await this.getInfo();
|
|
21
|
+
return info.tags;
|
|
50
22
|
}
|
|
51
23
|
async getTitle() {
|
|
52
24
|
const tags = await this.getMetadataTags();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
25
|
+
return tags.album ?? tags.title ?? null;
|
|
26
|
+
}
|
|
27
|
+
async setTitle(title) {
|
|
28
|
+
const tags = await this.getMetadataTags();
|
|
29
|
+
tags.album = title;
|
|
60
30
|
}
|
|
61
31
|
async getTrackTitle() {
|
|
62
32
|
const tags = await this.getMetadataTags();
|
|
63
|
-
|
|
64
|
-
for (const tag of readTagMap.trackTitle) {
|
|
65
|
-
if (typeof tags.raw[tag] === "string") {
|
|
66
|
-
return tags.raw[tag];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
33
|
+
return tags.title ?? null;
|
|
70
34
|
}
|
|
71
|
-
async
|
|
35
|
+
async setTrackTitle(trackTitle) {
|
|
72
36
|
const tags = await this.getMetadataTags();
|
|
73
|
-
|
|
74
|
-
const inputFormat = await input.getFormat();
|
|
75
|
-
const writeTags = getWriteTags(inputFormat, "title");
|
|
76
|
-
tags.raw ??= {};
|
|
77
|
-
for (const tag of writeTags) {
|
|
78
|
-
tags.raw[tag] = title;
|
|
79
|
-
}
|
|
37
|
+
tags.title = trackTitle;
|
|
80
38
|
}
|
|
81
39
|
async getSubtitle() {
|
|
82
40
|
const tags = await this.getMetadataTags();
|
|
83
|
-
|
|
84
|
-
for (const subtitleTag of readTagMap.subtitle) {
|
|
85
|
-
if (typeof tags.raw[subtitleTag] === "string") {
|
|
86
|
-
return tags.raw[subtitleTag];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
41
|
+
return tags.subtitle ?? null;
|
|
90
42
|
}
|
|
91
43
|
async setSubtitle(subtitle) {
|
|
92
44
|
const tags = await this.getMetadataTags();
|
|
93
|
-
|
|
94
|
-
const inputFormat = await input.getFormat();
|
|
95
|
-
const writeTags = getWriteTags(inputFormat, "subtitle");
|
|
96
|
-
tags.raw ??= {};
|
|
97
|
-
for (const tag of writeTags) {
|
|
98
|
-
tags.raw[tag] = subtitle;
|
|
99
|
-
}
|
|
45
|
+
tags.subtitle = subtitle;
|
|
100
46
|
}
|
|
101
47
|
async getDescription() {
|
|
102
48
|
const tags = await this.getMetadataTags();
|
|
103
|
-
|
|
104
|
-
for (const tag of readTagMap.description) {
|
|
105
|
-
if (typeof tags.raw[tag] === "string") {
|
|
106
|
-
return tags.raw[tag];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
49
|
+
return tags.description ?? tags.comment ?? null;
|
|
110
50
|
}
|
|
111
51
|
async setDescription(description) {
|
|
112
52
|
const tags = await this.getMetadataTags();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const writeTags = getWriteTags(inputFormat, "description");
|
|
116
|
-
tags.raw ??= {};
|
|
117
|
-
for (const tag of writeTags) {
|
|
118
|
-
tags.raw[tag] = description;
|
|
119
|
-
}
|
|
53
|
+
tags.description = description;
|
|
54
|
+
tags.comment = description;
|
|
120
55
|
}
|
|
121
56
|
async getAuthors() {
|
|
122
57
|
const tags = await this.getMetadataTags();
|
|
123
|
-
|
|
124
|
-
for (const tag of readTagMap.authors) {
|
|
125
|
-
if (typeof tags.raw[tag] === "string") {
|
|
126
|
-
return splitNames(tags.raw[tag]);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return [];
|
|
58
|
+
return splitNames(tags.albumArtist ?? tags.artist);
|
|
130
59
|
}
|
|
131
60
|
async setAuthors(authors) {
|
|
132
61
|
const tags = await this.getMetadataTags();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const writeTags = getWriteTags(inputFormat, "authors");
|
|
136
|
-
tags.raw ??= {};
|
|
137
|
-
for (const tag of writeTags) {
|
|
138
|
-
tags.raw[tag] = authors.join(";");
|
|
139
|
-
}
|
|
62
|
+
tags.artist = authors.join(";");
|
|
63
|
+
tags.albumArtist = authors.join(";");
|
|
140
64
|
}
|
|
141
65
|
async getNarrators() {
|
|
142
66
|
const tags = await this.getMetadataTags();
|
|
143
|
-
|
|
144
|
-
for (const tag of readTagMap.narrators) {
|
|
145
|
-
if (typeof tags.raw[tag] === "string") {
|
|
146
|
-
return splitNames(tags.raw[tag]);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return [];
|
|
67
|
+
return splitNames(tags.composer ?? tags.performer);
|
|
150
68
|
}
|
|
151
69
|
async setNarrators(narrators) {
|
|
152
70
|
const tags = await this.getMetadataTags();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const writeTags = getWriteTags(inputFormat, "narrators");
|
|
156
|
-
tags.raw ??= {};
|
|
157
|
-
for (const tag of writeTags) {
|
|
158
|
-
tags.raw[tag] = narrators.join(";");
|
|
159
|
-
}
|
|
71
|
+
tags.composer = narrators.join(";");
|
|
72
|
+
tags.performer = narrators.join(";");
|
|
160
73
|
}
|
|
161
74
|
async getCoverArt() {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return ((_a = tags.images) == null ? void 0 : _a.find((image) => image.kind === "coverFront")) ?? null;
|
|
75
|
+
const info = await this.getInfo();
|
|
76
|
+
return info.attachedPic || null;
|
|
165
77
|
}
|
|
166
78
|
async setCoverArt(picture) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
const frontCover = images.find((image) => image.kind === "coverFront");
|
|
170
|
-
if (frontCover) {
|
|
171
|
-
Object.assign(frontCover, picture);
|
|
172
|
-
} else {
|
|
173
|
-
images.push(picture);
|
|
174
|
-
}
|
|
175
|
-
tags.images = images;
|
|
79
|
+
const info = await this.getInfo();
|
|
80
|
+
info.attachedPic = picture;
|
|
176
81
|
}
|
|
177
82
|
async getPublisher() {
|
|
178
83
|
const tags = await this.getMetadataTags();
|
|
179
|
-
|
|
180
|
-
for (const tag of readTagMap.publisher) {
|
|
181
|
-
if (typeof tags.raw[tag] === "string") {
|
|
182
|
-
return tags.raw[tag];
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return null;
|
|
84
|
+
return tags.publisher ?? null;
|
|
186
85
|
}
|
|
187
86
|
async setPublisher(publisher) {
|
|
188
87
|
const tags = await this.getMetadataTags();
|
|
189
|
-
|
|
190
|
-
const inputFormat = await input.getFormat();
|
|
191
|
-
const writeTags = getWriteTags(inputFormat, "publisher");
|
|
192
|
-
tags.raw ??= {};
|
|
193
|
-
for (const tag of writeTags) {
|
|
194
|
-
tags.raw[tag] = publisher;
|
|
195
|
-
}
|
|
88
|
+
tags.publisher = publisher;
|
|
196
89
|
}
|
|
197
90
|
async getReleaseDate() {
|
|
198
91
|
const tags = await this.getMetadataTags();
|
|
199
|
-
|
|
200
|
-
for (const tag of readTagMap.releaseDate) {
|
|
201
|
-
if (typeof tags.raw[tag] === "string") {
|
|
202
|
-
return new Date(tags.raw[tag]);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
92
|
+
return tags.date ? new Date(tags.date) : null;
|
|
206
93
|
}
|
|
207
94
|
async setReleaseDate(date) {
|
|
208
95
|
const tags = await this.getMetadataTags();
|
|
209
|
-
|
|
210
|
-
const inputFormat = await input.getFormat();
|
|
211
|
-
const writeTags = getWriteTags(inputFormat, "releaseDate");
|
|
212
|
-
tags.raw ??= {};
|
|
213
|
-
for (const tag of writeTags) {
|
|
214
|
-
tags.raw[tag] = `${date.getDate().toString().padStart(2, "0")}-${date.getMonth().toString().padStart(2, "0")}-${date.getFullYear()}`;
|
|
215
|
-
}
|
|
96
|
+
tags.date = `${date.getDate().toString().padStart(2, "0")}-${date.getMonth().toString().padStart(2, "0")}-${date.getFullYear()}`;
|
|
216
97
|
}
|
|
217
98
|
async getDuration() {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
return this.duration;
|
|
99
|
+
const info = await this.getInfo();
|
|
100
|
+
return info.duration;
|
|
221
101
|
}
|
|
222
102
|
async getResource() {
|
|
223
|
-
const
|
|
224
|
-
this.duration ??= await input.computeDuration();
|
|
103
|
+
const duration = await this.getDuration();
|
|
225
104
|
const title = await this.getTrackTitle() ?? null;
|
|
226
|
-
const type =
|
|
105
|
+
const type = lookupAudioMime(this.filename);
|
|
227
106
|
return {
|
|
228
107
|
filename: this.filename,
|
|
229
108
|
title,
|
|
230
109
|
type,
|
|
231
|
-
duration
|
|
110
|
+
duration,
|
|
232
111
|
bitrate: null
|
|
233
112
|
};
|
|
234
113
|
}
|
|
235
|
-
async
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
target: new BufferTarget()
|
|
247
|
-
});
|
|
248
|
-
const tags = await this.getMetadataTags();
|
|
249
|
-
const conversion = await Conversion.init({
|
|
250
|
-
input,
|
|
251
|
-
output,
|
|
252
|
-
tags: {
|
|
253
|
-
// Since we only write raw metadata tags, we
|
|
254
|
-
// only copy those into the output. Otherwise
|
|
255
|
-
// the unchanged parsed metadata tags will overwrite
|
|
256
|
-
// our changes to raw!
|
|
257
|
-
raw: tags.raw ?? {},
|
|
258
|
-
images: tags.images ?? []
|
|
259
|
-
},
|
|
260
|
-
showWarnings: false
|
|
261
|
-
});
|
|
262
|
-
if (!conversion.isValid) {
|
|
263
|
-
throw new Error(
|
|
264
|
-
conversion.discardedTracks.map((track) => track.reason).join(";")
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
await conversion.execute();
|
|
268
|
-
input.dispose();
|
|
269
|
-
return {
|
|
270
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
271
|
-
data: new Uint8Array(output.target.buffer),
|
|
272
|
-
filename: this.filename
|
|
273
|
-
};
|
|
114
|
+
async getChapters() {
|
|
115
|
+
const info = await this.getInfo();
|
|
116
|
+
return info.chapters.map((chapter) => ({
|
|
117
|
+
filename: this.filename,
|
|
118
|
+
title: chapter.title ?? null,
|
|
119
|
+
start: chapter.startTime
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
async saveAndClose() {
|
|
123
|
+
const info = await this.getInfo();
|
|
124
|
+
await writeTrackMetadata(this.filename, info.tags, info.attachedPic);
|
|
274
125
|
}
|
|
275
126
|
}
|
|
276
127
|
export {
|
package/dist/ffmpeg.cjs
CHANGED
|
@@ -18,9 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var ffmpeg_exports = {};
|
|
20
20
|
__export(ffmpeg_exports, {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
escapeQuotes: () => escapeQuotes,
|
|
22
|
+
getTrackMetadata: () => getTrackMetadata,
|
|
23
|
+
getTrackMetadataFromBuffer: () => getTrackMetadataFromBuffer,
|
|
24
|
+
quotePath: () => quotePath,
|
|
25
|
+
writeTrackMetadata: () => writeTrackMetadata
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(ffmpeg_exports);
|
|
26
28
|
var import_node_child_process = require("node:child_process");
|
|
@@ -30,7 +32,9 @@ var import_promises = require("node:fs/promises");
|
|
|
30
32
|
var import_node_os = require("node:os");
|
|
31
33
|
var import_node_path = require("node:path");
|
|
32
34
|
var import_node_util = require("node:util");
|
|
35
|
+
var import_path = require("@storyteller-platform/path");
|
|
33
36
|
const execPromise = (0, import_node_util.promisify)(import_node_child_process.exec);
|
|
37
|
+
const execFilePromise = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
34
38
|
async function execCmd(command) {
|
|
35
39
|
let stdout = "";
|
|
36
40
|
let stderr = "";
|
|
@@ -44,29 +48,198 @@ async function execCmd(command) {
|
|
|
44
48
|
throw new Error(stderr);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
async function execCmdBuffer(command, args) {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execFilePromise(command, args, {
|
|
55
|
+
encoding: "buffer",
|
|
56
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
57
|
+
cwd: process.cwd()
|
|
58
|
+
});
|
|
59
|
+
return stdout;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(error);
|
|
62
|
+
if (error && typeof error === "object" && "stdout" in error) {
|
|
63
|
+
console.warn((_a = error.stdout) == null ? void 0 : _a.toString());
|
|
64
|
+
throw new Error((_b = error.stdout) == null ? void 0 : _b.toString());
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function escapeQuotes(input) {
|
|
70
|
+
return input.replaceAll(/"/g, '\\"');
|
|
71
|
+
}
|
|
47
72
|
function quotePath(path) {
|
|
48
|
-
return `"${path
|
|
73
|
+
return `"${escapeQuotes(path)}"`;
|
|
74
|
+
}
|
|
75
|
+
function lookup(codecName) {
|
|
76
|
+
switch (codecName) {
|
|
77
|
+
case "mjpeg":
|
|
78
|
+
case "jpeg": {
|
|
79
|
+
return "image/jpeg";
|
|
80
|
+
}
|
|
81
|
+
case "bmp": {
|
|
82
|
+
return "image/bmp";
|
|
83
|
+
}
|
|
84
|
+
case "png": {
|
|
85
|
+
return "image/png";
|
|
86
|
+
}
|
|
87
|
+
case "gif": {
|
|
88
|
+
return "image/gif";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
49
91
|
}
|
|
50
|
-
function
|
|
92
|
+
async function getTrackMetadata(path) {
|
|
93
|
+
const stdout = await execCmd(
|
|
94
|
+
`ffprobe -i ${quotePath(path)} -v quiet -show_format -show_chapters -show_streams -output_format json`
|
|
95
|
+
);
|
|
96
|
+
const { chapters, streams, format, duration, bit_rate } = JSON.parse(
|
|
97
|
+
stdout
|
|
98
|
+
);
|
|
99
|
+
const attachedPicStream = streams.find(
|
|
100
|
+
(stream) => !!stream.disposition.attached_pic
|
|
101
|
+
);
|
|
102
|
+
const attachedPic = attachedPicStream && {
|
|
103
|
+
name: attachedPicStream.tags.title,
|
|
104
|
+
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,
|
|
107
|
+
data: await execCmdBuffer("ffmpeg", [
|
|
108
|
+
"-nostdin",
|
|
109
|
+
"-i",
|
|
110
|
+
path,
|
|
111
|
+
"-map",
|
|
112
|
+
"0:v",
|
|
113
|
+
"-c:v",
|
|
114
|
+
"copy",
|
|
115
|
+
"-vframes",
|
|
116
|
+
"1",
|
|
117
|
+
"-f",
|
|
118
|
+
"image2",
|
|
119
|
+
"-update",
|
|
120
|
+
"1",
|
|
121
|
+
"pipe:1"
|
|
122
|
+
])
|
|
123
|
+
};
|
|
51
124
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
125
|
+
duration: parseFloat(duration),
|
|
126
|
+
bitRate: bit_rate !== void 0 ? parseFloat(bit_rate) : bit_rate,
|
|
127
|
+
tags: {
|
|
128
|
+
title: format.tags.title ?? format.tags.Title,
|
|
129
|
+
subtitle: format.tags.subtitle ?? format.tags.Subtitle,
|
|
130
|
+
date: format.tags.date ?? format.tags.Date,
|
|
131
|
+
album: format.tags.album ?? format.tags.Album,
|
|
132
|
+
albumArtist: format.tags.album_artist ?? format.tags.Album_Artist,
|
|
133
|
+
artist: format.tags.artist ?? format.tags.Artist,
|
|
134
|
+
performer: format.tags.performer ?? format.tags.Performer,
|
|
135
|
+
composer: format.tags.composer ?? format.tags.Composer,
|
|
136
|
+
comment: format.tags.comment ?? format.tags.Comment,
|
|
137
|
+
description: format.tags.description ?? format.tags.Description,
|
|
138
|
+
publisher: format.tags.publisher ?? format.tags.Publisher
|
|
139
|
+
},
|
|
140
|
+
chapters: chapters.map(
|
|
141
|
+
(chapter) => ({
|
|
142
|
+
id: chapter.id,
|
|
143
|
+
startTime: parseFloat(chapter.start_time),
|
|
144
|
+
endTime: parseFloat(chapter.end_time),
|
|
145
|
+
title: chapter.tags.title
|
|
146
|
+
})
|
|
147
|
+
),
|
|
148
|
+
attachedPic
|
|
56
149
|
};
|
|
57
150
|
}
|
|
58
|
-
async function
|
|
59
|
-
const
|
|
60
|
-
|
|
151
|
+
async function writeTrackMetadata(path, metadata, attachedPic) {
|
|
152
|
+
const metadataArgs = [];
|
|
153
|
+
if (metadata.title) {
|
|
154
|
+
metadataArgs.push(`-metadata title="${escapeQuotes(metadata.title)}"`);
|
|
155
|
+
}
|
|
156
|
+
if (metadata.subtitle) {
|
|
157
|
+
metadataArgs.push(`-metadata subtitle="${escapeQuotes(metadata.subtitle)}"`);
|
|
158
|
+
}
|
|
159
|
+
if (metadata.date) {
|
|
160
|
+
metadataArgs.push(`-metadata date="${escapeQuotes(metadata.date)}"`);
|
|
161
|
+
}
|
|
162
|
+
if (metadata.album) {
|
|
163
|
+
metadataArgs.push(`-metadata album="${escapeQuotes(metadata.album)}"`);
|
|
164
|
+
}
|
|
165
|
+
if (metadata.albumArtist) {
|
|
166
|
+
metadataArgs.push(
|
|
167
|
+
`-metadata album_artist="${escapeQuotes(metadata.albumArtist)}"`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (metadata.artist) {
|
|
171
|
+
metadataArgs.push(`-metadata artist="${escapeQuotes(metadata.artist)}"`);
|
|
172
|
+
}
|
|
173
|
+
if (metadata.performer) {
|
|
174
|
+
metadataArgs.push(
|
|
175
|
+
`-metadata performer="${escapeQuotes(metadata.performer)}"`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (metadata.composer) {
|
|
179
|
+
metadataArgs.push(`-metadata composer="${escapeQuotes(metadata.composer)}"`);
|
|
180
|
+
}
|
|
181
|
+
if (metadata.comment) {
|
|
182
|
+
metadataArgs.push(`-metadata comment="${escapeQuotes(metadata.comment)}"`);
|
|
183
|
+
}
|
|
184
|
+
if (metadata.description) {
|
|
185
|
+
metadataArgs.push(
|
|
186
|
+
`-metadata description="${escapeQuotes(metadata.description)}"`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (metadata.publisher) {
|
|
190
|
+
metadataArgs.push(
|
|
191
|
+
`-metadata publisher="${escapeQuotes(metadata.publisher)}"`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const ext = (0, import_path.extname)(path);
|
|
195
|
+
const tmpPath = (0, import_node_path.join)(
|
|
196
|
+
(0, import_node_os.tmpdir)(),
|
|
197
|
+
`storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}${ext}`
|
|
61
198
|
);
|
|
62
|
-
|
|
63
|
-
|
|
199
|
+
let picPath = null;
|
|
200
|
+
try {
|
|
201
|
+
if (attachedPic) {
|
|
202
|
+
const imageExt = attachedPic.mimeType.split("/")[1];
|
|
203
|
+
picPath = (0, import_node_path.join)(
|
|
204
|
+
(0, import_node_os.tmpdir)(),
|
|
205
|
+
`storyteller-platform-audiobook-${(0, import_node_crypto.randomUUID)()}.${imageExt}`
|
|
206
|
+
);
|
|
207
|
+
await (0, import_promises.writeFile)(picPath, attachedPic.data);
|
|
208
|
+
metadataArgs.push(`-disposition:v:0 attached_pic`);
|
|
209
|
+
if (attachedPic.name) {
|
|
210
|
+
metadataArgs.push(
|
|
211
|
+
`-metadata:s:v title="${escapeQuotes(attachedPic.name)}"`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (attachedPic.kind !== "unknown") {
|
|
215
|
+
metadataArgs.push(
|
|
216
|
+
`-metadata:s:v comment="${attachedPic.kind === "coverFront" ? "Cover (front)" : "Cover (back)"}"`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const cmd = `ffmpeg -i "${path}" ${metadataArgs.join(" ")} -codec copy "${tmpPath}"`;
|
|
221
|
+
await execCmd(cmd);
|
|
222
|
+
await (0, import_promises.cp)(tmpPath, path, { force: true });
|
|
223
|
+
} finally {
|
|
224
|
+
try {
|
|
225
|
+
await (0, import_promises.rm)(tmpPath);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.warn(`Failed to clean up temporary file ${tmpPath}:`, error);
|
|
228
|
+
}
|
|
229
|
+
if (picPath) {
|
|
230
|
+
try {
|
|
231
|
+
await (0, import_promises.rm)(picPath);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(`Failed to clean up temporary file ${picPath}:`, error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
64
237
|
}
|
|
65
|
-
async function
|
|
238
|
+
async function getTrackMetadataFromBuffer(buffer) {
|
|
66
239
|
const tempFile = (0, import_node_path.join)((0, import_node_os.tmpdir)(), `ffprobe-${(0, import_node_crypto.randomUUID)()}`);
|
|
67
240
|
try {
|
|
68
241
|
(0, import_node_fs.writeFileSync)(tempFile, buffer);
|
|
69
|
-
const result = await
|
|
242
|
+
const result = await getTrackMetadata(tempFile);
|
|
70
243
|
return result;
|
|
71
244
|
} finally {
|
|
72
245
|
try {
|
|
@@ -78,7 +251,9 @@ async function getTrackChaptersFromBuffer(buffer) {
|
|
|
78
251
|
}
|
|
79
252
|
// Annotate the CommonJS export names for ESM import in node:
|
|
80
253
|
0 && (module.exports = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
254
|
+
escapeQuotes,
|
|
255
|
+
getTrackMetadata,
|
|
256
|
+
getTrackMetadataFromBuffer,
|
|
257
|
+
quotePath,
|
|
258
|
+
writeTrackMetadata
|
|
84
259
|
});
|
package/dist/ffmpeg.d.cts
CHANGED
|
@@ -1,11 +1,67 @@
|
|
|
1
|
+
declare function escapeQuotes(input: string): string;
|
|
1
2
|
declare function quotePath(path: string): string;
|
|
2
3
|
type ChapterInfo = {
|
|
3
4
|
id: number;
|
|
4
5
|
startTime: number;
|
|
5
6
|
endTime: number;
|
|
6
|
-
title
|
|
7
|
+
title?: string | undefined;
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
type TrackInfo = Awaited<ReturnType<typeof getTrackMetadata>>;
|
|
10
|
+
type AttachedPic = {
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
kind: "coverFront" | "coverBack" | "unknown";
|
|
14
|
+
name?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
};
|
|
17
|
+
declare function getTrackMetadata(path: string): Promise<{
|
|
18
|
+
duration: number;
|
|
19
|
+
bitRate: number | undefined;
|
|
20
|
+
tags: {
|
|
21
|
+
title: string | undefined;
|
|
22
|
+
subtitle: string | undefined;
|
|
23
|
+
date: string | undefined;
|
|
24
|
+
album: string | undefined;
|
|
25
|
+
albumArtist: string | undefined;
|
|
26
|
+
artist: string | undefined;
|
|
27
|
+
performer: string | undefined;
|
|
28
|
+
composer: string | undefined;
|
|
29
|
+
comment: string | undefined;
|
|
30
|
+
description: string | undefined;
|
|
31
|
+
publisher: string | undefined;
|
|
32
|
+
};
|
|
33
|
+
chapters: {
|
|
34
|
+
id: number;
|
|
35
|
+
startTime: number;
|
|
36
|
+
endTime: number;
|
|
37
|
+
title: string | undefined;
|
|
38
|
+
}[];
|
|
39
|
+
attachedPic: AttachedPic | undefined;
|
|
40
|
+
}>;
|
|
41
|
+
declare function writeTrackMetadata(path: string, metadata: TrackInfo["tags"], attachedPic: AttachedPic | undefined): Promise<void>;
|
|
42
|
+
declare function getTrackMetadataFromBuffer(buffer: Uint8Array): Promise<{
|
|
43
|
+
duration: number;
|
|
44
|
+
bitRate: number | undefined;
|
|
45
|
+
tags: {
|
|
46
|
+
title: string | undefined;
|
|
47
|
+
subtitle: string | undefined;
|
|
48
|
+
date: string | undefined;
|
|
49
|
+
album: string | undefined;
|
|
50
|
+
albumArtist: string | undefined;
|
|
51
|
+
artist: string | undefined;
|
|
52
|
+
performer: string | undefined;
|
|
53
|
+
composer: string | undefined;
|
|
54
|
+
comment: string | undefined;
|
|
55
|
+
description: string | undefined;
|
|
56
|
+
publisher: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
chapters: {
|
|
59
|
+
id: number;
|
|
60
|
+
startTime: number;
|
|
61
|
+
endTime: number;
|
|
62
|
+
title: string | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
attachedPic: AttachedPic | undefined;
|
|
65
|
+
}>;
|
|
10
66
|
|
|
11
|
-
export { type ChapterInfo,
|
|
67
|
+
export { type AttachedPic, type ChapterInfo, type TrackInfo, escapeQuotes, getTrackMetadata, getTrackMetadataFromBuffer, quotePath, writeTrackMetadata };
|