@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.js CHANGED
@@ -1,276 +1,127 @@
1
1
  import {
2
- BufferTarget,
3
- Conversion,
4
- FLAC,
5
- FlacOutputFormat,
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
- metadataTags = null;
22
- duration = null;
23
- async getMetadataTags() {
24
- if (this.metadataTags) return this.metadataTags;
25
- const input = await this.getInput();
26
- this.metadataTags = await input.getMetadataTags();
27
- return this.metadataTags;
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 getOutputFormat() {
30
- const input = await this.getInput();
31
- const inputFormat = await input.getFormat();
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
- if (!tags.raw) return null;
54
- for (const tag of readTagMap.title) {
55
- if (typeof tags.raw[tag] === "string") {
56
- return tags.raw[tag];
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
- if (!tags.raw) return null;
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 setTitle(title) {
35
+ async setTrackTitle(trackTitle) {
72
36
  const tags = await this.getMetadataTags();
73
- const input = await this.getInput();
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
- if (!tags.raw) return null;
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
- const input = await this.getInput();
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
- if (!tags.raw) return null;
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
- const input = await this.getInput();
114
- const inputFormat = await input.getFormat();
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
- if (!tags.raw) return [];
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
- const input = await this.getInput();
134
- const inputFormat = await input.getFormat();
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
- if (!tags.raw) return [];
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
- const input = await this.getInput();
154
- const inputFormat = await input.getFormat();
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
- var _a;
163
- const tags = await this.getMetadataTags();
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 tags = await this.getMetadataTags();
168
- const images = tags.images ?? [];
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
- if (!tags.raw) return null;
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
- const input = await this.getInput();
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
- if (!tags.raw) return null;
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
- const input = await this.getInput();
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 input = await this.getInput();
219
- this.duration ??= await input.computeDuration();
220
- return this.duration;
99
+ const info = await this.getInfo();
100
+ return info.duration;
221
101
  }
222
102
  async getResource() {
223
- const input = await this.getInput();
224
- this.duration ??= await input.computeDuration();
103
+ const duration = await this.getDuration();
225
104
  const title = await this.getTrackTitle() ?? null;
226
- const type = await input.getMimeType();
105
+ const type = lookupAudioMime(this.filename);
227
106
  return {
228
107
  filename: this.filename,
229
108
  title,
230
109
  type,
231
- duration: this.duration,
110
+ duration,
232
111
  bitrate: null
233
112
  };
234
113
  }
235
- async getArrayAndClose() {
236
- const input = await this.getInput();
237
- const outputFormat = await this.getOutputFormat();
238
- if (!outputFormat) {
239
- const inputFormat = await input.getFormat();
240
- throw new Error(
241
- `Failed to save Audiobook entry: could not find valid output format for input with format ${inputFormat.name}`
242
- );
243
- }
244
- const output = new Output({
245
- format: outputFormat,
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
- getTrackChapters: () => getTrackChapters,
22
- getTrackChaptersFromBuffer: () => getTrackChaptersFromBuffer,
23
- quotePath: () => quotePath
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.replaceAll(/"/g, '\\"')}"`;
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 parseChapterInfo(ffmpegChapterInfo) {
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
- id: ffmpegChapterInfo.id,
53
- startTime: parseFloat(ffmpegChapterInfo.start_time),
54
- endTime: parseFloat(ffmpegChapterInfo.end_time),
55
- title: ffmpegChapterInfo.tags.title
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 getTrackChapters(path) {
59
- const stdout = await execCmd(
60
- `ffprobe -i ${quotePath(path)} -show_chapters -of json`
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
- const { chapters } = JSON.parse(stdout);
63
- return chapters.map((chapter) => parseChapterInfo(chapter));
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 getTrackChaptersFromBuffer(buffer) {
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 getTrackChapters(tempFile);
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
- getTrackChapters,
82
- getTrackChaptersFromBuffer,
83
- quotePath
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: string;
7
+ title?: string | undefined;
7
8
  };
8
- declare function getTrackChapters(path: string): Promise<ChapterInfo[]>;
9
- declare function getTrackChaptersFromBuffer(buffer: Uint8Array): Promise<ChapterInfo[]>;
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, getTrackChapters, getTrackChaptersFromBuffer, quotePath };
67
+ export { type AttachedPic, type ChapterInfo, type TrackInfo, escapeQuotes, getTrackMetadata, getTrackMetadataFromBuffer, quotePath, writeTrackMetadata };