@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 CHANGED
@@ -21,265 +21,127 @@ __export(entry_exports, {
21
21
  AudiobookEntry: () => AudiobookEntry
22
22
  });
23
23
  module.exports = __toCommonJS(entry_exports);
24
- var import_mediabunny = require("mediabunny");
25
- var import_tagMaps = require("./tagMaps.cjs");
24
+ var import_ffmpeg = require("./ffmpeg.cjs");
25
+ var import_mime = require("./mime.cjs");
26
26
  function splitNames(names) {
27
+ if (!names) return [];
27
28
  return names.split(/[;,/]/).map((name) => name.trim());
28
29
  }
29
30
  class AudiobookEntry {
30
- metadataTags = null;
31
- duration = null;
32
- async getMetadataTags() {
33
- if (this.metadataTags) return this.metadataTags;
34
- const input = await this.getInput();
35
- this.metadataTags = await input.getMetadataTags();
36
- return this.metadataTags;
31
+ constructor(filename) {
32
+ this.filename = filename;
33
+ }
34
+ info = null;
35
+ async getInfo() {
36
+ this.info ??= await (0, import_ffmpeg.getTrackMetadata)(this.filename);
37
+ return this.info;
37
38
  }
38
- async getOutputFormat() {
39
- const input = await this.getInput();
40
- const inputFormat = await input.getFormat();
41
- switch (inputFormat) {
42
- case import_mediabunny.MP3: {
43
- return new import_mediabunny.Mp3OutputFormat();
44
- }
45
- case import_mediabunny.MP4: {
46
- return new import_mediabunny.Mp4OutputFormat();
47
- }
48
- case import_mediabunny.WAVE: {
49
- return new import_mediabunny.WavOutputFormat();
50
- }
51
- case import_mediabunny.OGG: {
52
- return new import_mediabunny.OggOutputFormat();
53
- }
54
- case import_mediabunny.FLAC: {
55
- return new import_mediabunny.FlacOutputFormat();
56
- }
57
- }
58
- return null;
39
+ async getMetadataTags() {
40
+ const info = await this.getInfo();
41
+ return info.tags;
59
42
  }
60
43
  async getTitle() {
61
44
  const tags = await this.getMetadataTags();
62
- if (!tags.raw) return null;
63
- for (const tag of import_tagMaps.readTagMap.title) {
64
- if (typeof tags.raw[tag] === "string") {
65
- return tags.raw[tag];
66
- }
67
- }
68
- return null;
45
+ return tags.album ?? tags.title ?? null;
46
+ }
47
+ async setTitle(title) {
48
+ const tags = await this.getMetadataTags();
49
+ tags.album = title;
69
50
  }
70
51
  async getTrackTitle() {
71
52
  const tags = await this.getMetadataTags();
72
- if (!tags.raw) return null;
73
- for (const tag of import_tagMaps.readTagMap.trackTitle) {
74
- if (typeof tags.raw[tag] === "string") {
75
- return tags.raw[tag];
76
- }
77
- }
78
- return null;
53
+ return tags.title ?? null;
79
54
  }
80
- async setTitle(title) {
55
+ async setTrackTitle(trackTitle) {
81
56
  const tags = await this.getMetadataTags();
82
- const input = await this.getInput();
83
- const inputFormat = await input.getFormat();
84
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "title");
85
- tags.raw ??= {};
86
- for (const tag of writeTags) {
87
- tags.raw[tag] = title;
88
- }
57
+ tags.title = trackTitle;
89
58
  }
90
59
  async getSubtitle() {
91
60
  const tags = await this.getMetadataTags();
92
- if (!tags.raw) return null;
93
- for (const subtitleTag of import_tagMaps.readTagMap.subtitle) {
94
- if (typeof tags.raw[subtitleTag] === "string") {
95
- return tags.raw[subtitleTag];
96
- }
97
- }
98
- return null;
61
+ return tags.subtitle ?? null;
99
62
  }
100
63
  async setSubtitle(subtitle) {
101
64
  const tags = await this.getMetadataTags();
102
- const input = await this.getInput();
103
- const inputFormat = await input.getFormat();
104
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "subtitle");
105
- tags.raw ??= {};
106
- for (const tag of writeTags) {
107
- tags.raw[tag] = subtitle;
108
- }
65
+ tags.subtitle = subtitle;
109
66
  }
110
67
  async getDescription() {
111
68
  const tags = await this.getMetadataTags();
112
- if (!tags.raw) return null;
113
- for (const tag of import_tagMaps.readTagMap.description) {
114
- if (typeof tags.raw[tag] === "string") {
115
- return tags.raw[tag];
116
- }
117
- }
118
- return null;
69
+ return tags.description ?? tags.comment ?? null;
119
70
  }
120
71
  async setDescription(description) {
121
72
  const tags = await this.getMetadataTags();
122
- const input = await this.getInput();
123
- const inputFormat = await input.getFormat();
124
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "description");
125
- tags.raw ??= {};
126
- for (const tag of writeTags) {
127
- tags.raw[tag] = description;
128
- }
73
+ tags.description = description;
74
+ tags.comment = description;
129
75
  }
130
76
  async getAuthors() {
131
77
  const tags = await this.getMetadataTags();
132
- if (!tags.raw) return [];
133
- for (const tag of import_tagMaps.readTagMap.authors) {
134
- if (typeof tags.raw[tag] === "string") {
135
- return splitNames(tags.raw[tag]);
136
- }
137
- }
138
- return [];
78
+ return splitNames(tags.albumArtist ?? tags.artist);
139
79
  }
140
80
  async setAuthors(authors) {
141
81
  const tags = await this.getMetadataTags();
142
- const input = await this.getInput();
143
- const inputFormat = await input.getFormat();
144
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "authors");
145
- tags.raw ??= {};
146
- for (const tag of writeTags) {
147
- tags.raw[tag] = authors.join(";");
148
- }
82
+ tags.artist = authors.join(";");
83
+ tags.albumArtist = authors.join(";");
149
84
  }
150
85
  async getNarrators() {
151
86
  const tags = await this.getMetadataTags();
152
- if (!tags.raw) return [];
153
- for (const tag of import_tagMaps.readTagMap.narrators) {
154
- if (typeof tags.raw[tag] === "string") {
155
- return splitNames(tags.raw[tag]);
156
- }
157
- }
158
- return [];
87
+ return splitNames(tags.composer ?? tags.performer);
159
88
  }
160
89
  async setNarrators(narrators) {
161
90
  const tags = await this.getMetadataTags();
162
- const input = await this.getInput();
163
- const inputFormat = await input.getFormat();
164
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "narrators");
165
- tags.raw ??= {};
166
- for (const tag of writeTags) {
167
- tags.raw[tag] = narrators.join(";");
168
- }
91
+ tags.composer = narrators.join(";");
92
+ tags.performer = narrators.join(";");
169
93
  }
170
94
  async getCoverArt() {
171
- var _a;
172
- const tags = await this.getMetadataTags();
173
- return ((_a = tags.images) == null ? void 0 : _a.find((image) => image.kind === "coverFront")) ?? null;
95
+ const info = await this.getInfo();
96
+ return info.attachedPic || null;
174
97
  }
175
98
  async setCoverArt(picture) {
176
- const tags = await this.getMetadataTags();
177
- const images = tags.images ?? [];
178
- const frontCover = images.find((image) => image.kind === "coverFront");
179
- if (frontCover) {
180
- Object.assign(frontCover, picture);
181
- } else {
182
- images.push(picture);
183
- }
184
- tags.images = images;
99
+ const info = await this.getInfo();
100
+ info.attachedPic = picture;
185
101
  }
186
102
  async getPublisher() {
187
103
  const tags = await this.getMetadataTags();
188
- if (!tags.raw) return null;
189
- for (const tag of import_tagMaps.readTagMap.publisher) {
190
- if (typeof tags.raw[tag] === "string") {
191
- return tags.raw[tag];
192
- }
193
- }
194
- return null;
104
+ return tags.publisher ?? null;
195
105
  }
196
106
  async setPublisher(publisher) {
197
107
  const tags = await this.getMetadataTags();
198
- const input = await this.getInput();
199
- const inputFormat = await input.getFormat();
200
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "publisher");
201
- tags.raw ??= {};
202
- for (const tag of writeTags) {
203
- tags.raw[tag] = publisher;
204
- }
108
+ tags.publisher = publisher;
205
109
  }
206
110
  async getReleaseDate() {
207
111
  const tags = await this.getMetadataTags();
208
- if (!tags.raw) return null;
209
- for (const tag of import_tagMaps.readTagMap.releaseDate) {
210
- if (typeof tags.raw[tag] === "string") {
211
- return new Date(tags.raw[tag]);
212
- }
213
- }
214
- return null;
112
+ return tags.date ? new Date(tags.date) : null;
215
113
  }
216
114
  async setReleaseDate(date) {
217
115
  const tags = await this.getMetadataTags();
218
- const input = await this.getInput();
219
- const inputFormat = await input.getFormat();
220
- const writeTags = (0, import_tagMaps.getWriteTags)(inputFormat, "releaseDate");
221
- tags.raw ??= {};
222
- for (const tag of writeTags) {
223
- tags.raw[tag] = `${date.getDate().toString().padStart(2, "0")}-${date.getMonth().toString().padStart(2, "0")}-${date.getFullYear()}`;
224
- }
116
+ tags.date = `${date.getDate().toString().padStart(2, "0")}-${date.getMonth().toString().padStart(2, "0")}-${date.getFullYear()}`;
225
117
  }
226
118
  async getDuration() {
227
- const input = await this.getInput();
228
- this.duration ??= await input.computeDuration();
229
- return this.duration;
119
+ const info = await this.getInfo();
120
+ return info.duration;
230
121
  }
231
122
  async getResource() {
232
- const input = await this.getInput();
233
- this.duration ??= await input.computeDuration();
123
+ const duration = await this.getDuration();
234
124
  const title = await this.getTrackTitle() ?? null;
235
- const type = await input.getMimeType();
125
+ const type = (0, import_mime.lookupAudioMime)(this.filename);
236
126
  return {
237
127
  filename: this.filename,
238
128
  title,
239
129
  type,
240
- duration: this.duration,
130
+ duration,
241
131
  bitrate: null
242
132
  };
243
133
  }
244
- async getArrayAndClose() {
245
- const input = await this.getInput();
246
- const outputFormat = await this.getOutputFormat();
247
- if (!outputFormat) {
248
- const inputFormat = await input.getFormat();
249
- throw new Error(
250
- `Failed to save Audiobook entry: could not find valid output format for input with format ${inputFormat.name}`
251
- );
252
- }
253
- const output = new import_mediabunny.Output({
254
- format: outputFormat,
255
- target: new import_mediabunny.BufferTarget()
256
- });
257
- const tags = await this.getMetadataTags();
258
- const conversion = await import_mediabunny.Conversion.init({
259
- input,
260
- output,
261
- tags: {
262
- // Since we only write raw metadata tags, we
263
- // only copy those into the output. Otherwise
264
- // the unchanged parsed metadata tags will overwrite
265
- // our changes to raw!
266
- raw: tags.raw ?? {},
267
- images: tags.images ?? []
268
- },
269
- showWarnings: false
270
- });
271
- if (!conversion.isValid) {
272
- throw new Error(
273
- conversion.discardedTracks.map((track) => track.reason).join(";")
274
- );
275
- }
276
- await conversion.execute();
277
- input.dispose();
278
- return {
279
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
280
- data: new Uint8Array(output.target.buffer),
281
- filename: this.filename
282
- };
134
+ async getChapters() {
135
+ const info = await this.getInfo();
136
+ return info.chapters.map((chapter) => ({
137
+ filename: this.filename,
138
+ title: chapter.title ?? null,
139
+ start: chapter.startTime
140
+ }));
141
+ }
142
+ async saveAndClose() {
143
+ const info = await this.getInfo();
144
+ await (0, import_ffmpeg.writeTrackMetadata)(this.filename, info.tags, info.attachedPic);
283
145
  }
284
146
  }
285
147
  // Annotate the CommonJS export names for ESM import in node:
package/dist/entry.d.cts CHANGED
@@ -1,22 +1,20 @@
1
- import { Input, MetadataTags, OutputFormat, AttachedImage } from 'mediabunny';
2
- import { AudiobookChapter, AudiobookResource } from './resources.cjs';
1
+ import { TrackInfo, AttachedPic } from './ffmpeg.cjs';
2
+ import { AudiobookResource, AudiobookChapter } from './resources.cjs';
3
3
 
4
4
  interface Uint8ArrayEntry {
5
5
  filename: string;
6
6
  data: Uint8Array;
7
7
  }
8
- declare abstract class AudiobookEntry {
9
- abstract filename: string;
10
- private metadataTags;
11
- private duration;
12
- abstract getChapters(): Promise<AudiobookChapter[]>;
13
- abstract getInput(): Promise<Input> | Input;
14
- abstract saveAndClose(): Promise<void>;
15
- getMetadataTags(): Promise<MetadataTags>;
16
- getOutputFormat(): Promise<OutputFormat | null>;
8
+ declare class AudiobookEntry {
9
+ filename: string;
10
+ private info;
11
+ constructor(filename: string);
12
+ getInfo(): Promise<TrackInfo>;
13
+ getMetadataTags(): Promise<TrackInfo["tags"]>;
17
14
  getTitle(): Promise<string | null>;
18
- getTrackTitle(): Promise<string | null>;
19
15
  setTitle(title: string): Promise<void>;
16
+ getTrackTitle(): Promise<string | null>;
17
+ setTrackTitle(trackTitle: string): Promise<void>;
20
18
  getSubtitle(): Promise<string | null>;
21
19
  setSubtitle(subtitle: string): Promise<void>;
22
20
  getDescription(): Promise<string | null>;
@@ -25,15 +23,16 @@ declare abstract class AudiobookEntry {
25
23
  setAuthors(authors: string[]): Promise<void>;
26
24
  getNarrators(): Promise<string[]>;
27
25
  setNarrators(narrators: string[]): Promise<void>;
28
- getCoverArt(): Promise<AttachedImage | null>;
29
- setCoverArt(picture: AttachedImage): Promise<void>;
26
+ getCoverArt(): Promise<AttachedPic | null>;
27
+ setCoverArt(picture: AttachedPic): Promise<void>;
30
28
  getPublisher(): Promise<string | null>;
31
29
  setPublisher(publisher: string): Promise<void>;
32
30
  getReleaseDate(): Promise<Date | null>;
33
31
  setReleaseDate(date: Date): Promise<void>;
34
32
  getDuration(): Promise<number>;
35
33
  getResource(): Promise<AudiobookResource>;
36
- getArrayAndClose(): Promise<Uint8ArrayEntry>;
34
+ getChapters(): Promise<AudiobookChapter[]>;
35
+ saveAndClose(): Promise<void>;
37
36
  }
38
37
 
39
38
  export { AudiobookEntry, type Uint8ArrayEntry };
package/dist/entry.d.ts CHANGED
@@ -1,22 +1,20 @@
1
- import { Input, MetadataTags, OutputFormat, AttachedImage } from 'mediabunny';
2
- import { AudiobookChapter, AudiobookResource } from './resources.js';
1
+ import { TrackInfo, AttachedPic } from './ffmpeg.js';
2
+ import { AudiobookResource, AudiobookChapter } from './resources.js';
3
3
 
4
4
  interface Uint8ArrayEntry {
5
5
  filename: string;
6
6
  data: Uint8Array;
7
7
  }
8
- declare abstract class AudiobookEntry {
9
- abstract filename: string;
10
- private metadataTags;
11
- private duration;
12
- abstract getChapters(): Promise<AudiobookChapter[]>;
13
- abstract getInput(): Promise<Input> | Input;
14
- abstract saveAndClose(): Promise<void>;
15
- getMetadataTags(): Promise<MetadataTags>;
16
- getOutputFormat(): Promise<OutputFormat | null>;
8
+ declare class AudiobookEntry {
9
+ filename: string;
10
+ private info;
11
+ constructor(filename: string);
12
+ getInfo(): Promise<TrackInfo>;
13
+ getMetadataTags(): Promise<TrackInfo["tags"]>;
17
14
  getTitle(): Promise<string | null>;
18
- getTrackTitle(): Promise<string | null>;
19
15
  setTitle(title: string): Promise<void>;
16
+ getTrackTitle(): Promise<string | null>;
17
+ setTrackTitle(trackTitle: string): Promise<void>;
20
18
  getSubtitle(): Promise<string | null>;
21
19
  setSubtitle(subtitle: string): Promise<void>;
22
20
  getDescription(): Promise<string | null>;
@@ -25,15 +23,16 @@ declare abstract class AudiobookEntry {
25
23
  setAuthors(authors: string[]): Promise<void>;
26
24
  getNarrators(): Promise<string[]>;
27
25
  setNarrators(narrators: string[]): Promise<void>;
28
- getCoverArt(): Promise<AttachedImage | null>;
29
- setCoverArt(picture: AttachedImage): Promise<void>;
26
+ getCoverArt(): Promise<AttachedPic | null>;
27
+ setCoverArt(picture: AttachedPic): Promise<void>;
30
28
  getPublisher(): Promise<string | null>;
31
29
  setPublisher(publisher: string): Promise<void>;
32
30
  getReleaseDate(): Promise<Date | null>;
33
31
  setReleaseDate(date: Date): Promise<void>;
34
32
  getDuration(): Promise<number>;
35
33
  getResource(): Promise<AudiobookResource>;
36
- getArrayAndClose(): Promise<Uint8ArrayEntry>;
34
+ getChapters(): Promise<AudiobookChapter[]>;
35
+ saveAndClose(): Promise<void>;
37
36
  }
38
37
 
39
38
  export { AudiobookEntry, type Uint8ArrayEntry };