taglib-wasm 2.2.0 → 2.2.2

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.
Files changed (61) hide show
  1. package/dist/disc-folder.d.ts +3 -1
  2. package/dist/disc-folder.d.ts.map +1 -1
  3. package/dist/disc-folder.js +187 -152
  4. package/dist/index.browser.js +7 -4
  5. package/dist/simple.browser.js +22 -9
  6. package/dist/src/folder-api/album-corroborate.d.ts +13 -0
  7. package/dist/src/folder-api/album-corroborate.d.ts.map +1 -0
  8. package/dist/src/folder-api/album-corroborate.js +36 -0
  9. package/dist/src/folder-api/album-discs.d.ts +13 -0
  10. package/dist/src/folder-api/album-discs.d.ts.map +1 -0
  11. package/dist/src/folder-api/album-discs.js +122 -0
  12. package/dist/src/folder-api/album-grouping.d.ts +24 -0
  13. package/dist/src/folder-api/album-grouping.d.ts.map +1 -0
  14. package/dist/src/folder-api/album-grouping.js +118 -0
  15. package/dist/src/folder-api/album-identity.d.ts +27 -0
  16. package/dist/src/folder-api/album-identity.d.ts.map +1 -0
  17. package/dist/src/folder-api/album-identity.js +151 -0
  18. package/dist/src/folder-api/{group-albums.d.ts → album-types.d.ts} +55 -26
  19. package/dist/src/folder-api/album-types.d.ts.map +1 -0
  20. package/dist/src/folder-api/album-types.js +0 -0
  21. package/dist/src/folder-api/folder-disc.d.ts +24 -0
  22. package/dist/src/folder-api/folder-disc.d.ts.map +1 -0
  23. package/dist/src/folder-api/folder-disc.js +169 -0
  24. package/dist/src/folder-api/index.d.ts +3 -1
  25. package/dist/src/folder-api/index.d.ts.map +1 -1
  26. package/dist/src/folder-api/index.js +2 -4
  27. package/dist/src/folder-api/scan-for-albums.d.ts +1 -1
  28. package/dist/src/folder-api/scan-for-albums.d.ts.map +1 -1
  29. package/dist/src/folder-api/scan-for-albums.js +1 -3
  30. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +20 -0
  31. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -0
  32. package/dist/src/runtime/wasi-adapter/audio-properties.js +84 -0
  33. package/dist/src/runtime/wasi-adapter/file-handle.d.ts +5 -19
  34. package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
  35. package/dist/src/runtime/wasi-adapter/file-handle.js +67 -389
  36. package/dist/src/runtime/wasi-adapter/handle-state.d.ts +28 -0
  37. package/dist/src/runtime/wasi-adapter/handle-state.d.ts.map +1 -0
  38. package/dist/src/runtime/wasi-adapter/handle-state.js +58 -0
  39. package/dist/src/runtime/wasi-adapter/mp4-items.d.ts +34 -0
  40. package/dist/src/runtime/wasi-adapter/mp4-items.d.ts.map +1 -0
  41. package/dist/src/runtime/wasi-adapter/mp4-items.js +80 -0
  42. package/dist/src/runtime/wasi-adapter/property-surface.d.ts +25 -0
  43. package/dist/src/runtime/wasi-adapter/property-surface.d.ts.map +1 -0
  44. package/dist/src/runtime/wasi-adapter/property-surface.js +104 -0
  45. package/dist/src/runtime/wasi-adapter/structured-fields.d.ts +44 -0
  46. package/dist/src/runtime/wasi-adapter/structured-fields.d.ts.map +1 -0
  47. package/dist/src/runtime/wasi-adapter/structured-fields.js +139 -0
  48. package/dist/src/runtime/wasi-adapter/tag-keys.d.ts +28 -0
  49. package/dist/src/runtime/wasi-adapter/tag-keys.d.ts.map +1 -0
  50. package/dist/src/runtime/wasi-adapter/tag-keys.js +46 -0
  51. package/dist/src/simple/batch-operations.d.ts +28 -11
  52. package/dist/src/simple/batch-operations.d.ts.map +1 -1
  53. package/dist/src/simple/batch-operations.js +17 -7
  54. package/dist/src/utils/tag-mapping.d.ts +8 -0
  55. package/dist/src/utils/tag-mapping.d.ts.map +1 -1
  56. package/dist/src/utils/tag-mapping.js +6 -3
  57. package/dist/src/version.d.ts +1 -1
  58. package/dist/src/version.js +1 -1
  59. package/package.json +2 -1
  60. package/dist/src/folder-api/group-albums.d.ts.map +0 -1
  61. package/dist/src/folder-api/group-albums.js +0 -544
@@ -2,515 +2,193 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { WasmerExecutionError } from "../wasmer-sdk-loader/types.js";
5
- import { decodeTagData } from "../../msgpack/decoder.js";
6
5
  import {
7
- fromTagLibKey,
8
- mp4AtomWireKey,
9
- toTagLibKey
10
- } from "../../constants/properties.js";
11
- import {
12
- DATE_MIRROR,
13
- firstValueString,
14
- isShadowedNumericMirror,
15
- mirrorForRawKey,
16
- readNumericMirror,
17
- stageNumericWrite,
18
- stageRawWrite,
19
- stringifyScalar,
20
- TRACK_MIRROR
21
- } from "../../utils/mirror-fields.js";
22
- import {
23
- readId3v2FramesFromWasm,
24
- readTagsFromWasm,
25
- readTagsFromWasmPath,
26
- writeTagsToWasm,
27
- writeTagsToWasmPath
28
- } from "./wasm-io.js";
29
- const AUDIO_KEYS = /* @__PURE__ */ new Set([
30
- "bitrate",
31
- "bitrateMode",
32
- "bitsPerSample",
33
- "channels",
34
- "codec",
35
- "containerFormat",
36
- "formatVersion",
37
- "isEncrypted",
38
- "isLossless",
39
- "duration",
40
- "length",
41
- "lengthMs",
42
- "mpegLayer",
43
- "mpegVersion",
44
- "outputGainDb",
45
- "sampleRate"
46
- ]);
47
- const INTERNAL_KEYS = /* @__PURE__ */ new Set([
48
- "pictures",
49
- "ratings",
50
- "lyrics",
51
- "chapters",
52
- "_mp4ChapterStyle",
53
- "bextData",
54
- "ixml",
55
- // Exact MP4 atom names for the write path, not a readable property.
56
- "_mp4ItemNames",
57
- // Foreign-mean MP4 atom names to delete at save (taglib-65nm): a write-time
58
- // directive, never a property.
59
- "_mp4ItemRemovals"
60
- ]);
61
- function preserveEmptyValues(data) {
62
- for (const [key, value] of Object.entries(data)) {
63
- if (value !== "") continue;
64
- if (AUDIO_KEYS.has(key) || INTERNAL_KEYS.has(key)) continue;
65
- if (key.startsWith("----:")) continue;
66
- data[key] = [""];
67
- }
68
- return data;
69
- }
70
- const CONTAINER_TO_FORMAT = {
71
- MP3: "MP3",
72
- MP4: "MP4",
73
- FLAC: "FLAC",
74
- OGG: "OGG",
75
- WAV: "WAV",
76
- AIFF: "AIFF",
77
- WavPack: "WV",
78
- TTA: "TTA",
79
- ASF: "ASF",
80
- Matroska: "MATROSKA"
81
- };
82
- function mp4ItemPropertyKey(key) {
83
- if (key.startsWith("----:")) {
84
- return key.slice(key.lastIndexOf(":") + 1).toUpperCase();
85
- }
86
- return mp4AtomWireKey(key) ?? key;
87
- }
6
+ createHandleState,
7
+ destroy,
8
+ getBuffer,
9
+ isValid,
10
+ loadBuffer,
11
+ loadPath,
12
+ save
13
+ } from "./handle-state.js";
14
+ import * as audio from "./audio-properties.js";
15
+ import * as props from "./property-surface.js";
16
+ import * as mp4 from "./mp4-items.js";
17
+ import * as fields from "./structured-fields.js";
88
18
  class WasiFileHandle {
89
19
  constructor(wasiModule) {
90
- __publicField(this, "wasi");
91
- __publicField(this, "fileData", null);
92
- __publicField(this, "filePath", null);
93
- __publicField(this, "tagData", null);
20
+ __publicField(this, "state");
94
21
  __publicField(this, "destroyed", false);
95
- this.wasi = wasiModule;
22
+ this.state = createHandleState(wasiModule);
96
23
  }
97
24
  checkNotDestroyed() {
98
25
  if (this.destroyed) {
99
- throw new WasmerExecutionError(
100
- "FileHandle has been destroyed"
101
- );
26
+ throw new WasmerExecutionError("FileHandle has been destroyed");
102
27
  }
103
28
  }
104
29
  loadFromBuffer(buffer) {
105
30
  this.checkNotDestroyed();
106
- this.fileData = buffer;
107
- const msgpackData = readTagsFromWasm(this.wasi, buffer);
108
- this.tagData = preserveEmptyValues(
109
- decodeTagData(msgpackData)
110
- );
31
+ loadBuffer(this.state, buffer);
111
32
  return true;
112
33
  }
113
34
  loadFromPath(path) {
114
35
  this.checkNotDestroyed();
115
- this.filePath = path;
116
- const msgpackData = readTagsFromWasmPath(this.wasi, path);
117
- this.tagData = preserveEmptyValues(
118
- decodeTagData(msgpackData)
119
- );
36
+ loadPath(this.state, path);
120
37
  return true;
121
38
  }
122
39
  isValid() {
123
40
  this.checkNotDestroyed();
124
- return this.fileData !== null && this.fileData.length > 0 || this.filePath !== null && this.tagData !== null;
41
+ return isValid(this.state);
125
42
  }
126
43
  save() {
127
44
  this.checkNotDestroyed();
128
- if (!this.tagData) return false;
129
- if (this.filePath) {
130
- return writeTagsToWasmPath(
131
- this.wasi,
132
- this.filePath,
133
- this.tagData
134
- );
135
- }
136
- if (!this.fileData) return false;
137
- const result = writeTagsToWasm(this.wasi, this.fileData, this.tagData);
138
- if (result) {
139
- this.fileData = result;
140
- return true;
141
- }
142
- return false;
45
+ return save(this.state);
46
+ }
47
+ getBuffer() {
48
+ this.checkNotDestroyed();
49
+ return getBuffer(this.state);
143
50
  }
144
51
  getTagData() {
145
52
  this.checkNotDestroyed();
146
- const d = this.tagData ?? {};
147
- return {
148
- title: firstValueString(d.title),
149
- artist: firstValueString(d.artist),
150
- album: firstValueString(d.album),
151
- comment: firstValueString(d.comment),
152
- genre: firstValueString(d.genre),
153
- year: readNumericMirror(d, DATE_MIRROR),
154
- track: readNumericMirror(d, TRACK_MIRROR)
155
- };
53
+ return props.getBasicTagData(this.state.tagData);
156
54
  }
157
55
  setTagData(data) {
158
56
  this.checkNotDestroyed();
159
- const merged = { ...this.tagData, ...data };
160
- if (data.year !== void 0) {
161
- stageNumericWrite(merged, DATE_MIRROR, data.year);
162
- }
163
- if (data.track !== void 0) {
164
- stageNumericWrite(merged, TRACK_MIRROR, data.track);
165
- }
166
- this.tagData = merged;
57
+ this.state.tagData = props.setBasicTagData(this.state.tagData, data);
167
58
  }
168
59
  getAudioProperties() {
169
60
  this.checkNotDestroyed();
170
- if (!this.tagData || !("sampleRate" in this.tagData)) return null;
171
- const d = this.tagData;
172
- const containerFormat = d.containerFormat || "unknown";
173
- const mpegVersion = d.mpegVersion ?? 0;
174
- const formatVersion = d.formatVersion ?? 0;
175
- return {
176
- duration: d.length ?? 0,
177
- durationMs: d.lengthMs ?? 0,
178
- bitrate: d.bitrate ?? 0,
179
- sampleRate: d.sampleRate ?? 0,
180
- channels: d.channels ?? 0,
181
- bitsPerSample: d.bitsPerSample ?? 0,
182
- codec: d.codec || "unknown",
183
- containerFormat,
184
- isLossless: d.isLossless ?? false,
185
- ...mpegVersion > 0 ? { mpegVersion, mpegLayer: d.mpegLayer ?? 0 } : {},
186
- ...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: d.isEncrypted ?? false } : {},
187
- ...formatVersion > 0 ? { formatVersion } : {},
188
- ...d.outputGainDb !== void 0 ? { outputGainDb: d.outputGainDb } : {}
189
- };
61
+ return audio.getAudioProperties(this.state.tagData);
190
62
  }
191
63
  getFormat() {
192
64
  this.checkNotDestroyed();
193
- const container = this.tagData?.containerFormat;
194
- if (container) {
195
- const codec = this.tagData?.codec;
196
- if (container === "OGG" && codec === "Opus") return "OPUS";
197
- if (CONTAINER_TO_FORMAT[container]) return CONTAINER_TO_FORMAT[container];
198
- }
199
- if (!this.fileData || this.fileData.length < 8) return "unknown";
200
- const magic = this.fileData.slice(0, 4);
201
- if (magic[0] === 255 && (magic[1] & 224) === 224) return "MP3";
202
- if (magic[0] === 73 && magic[1] === 68 && magic[2] === 51) {
203
- return "MP3";
204
- }
205
- if (magic[0] === 102 && magic[1] === 76 && magic[2] === 97 && magic[3] === 67) return "FLAC";
206
- if (magic[0] === 79 && magic[1] === 103 && magic[2] === 103 && magic[3] === 83) return this.detectOggCodec();
207
- if (magic[0] === 82 && magic[1] === 73 && magic[2] === 70 && magic[3] === 70) return "WAV";
208
- if (magic[0] === 119 && magic[1] === 118 && magic[2] === 112 && magic[3] === 107) return "WV";
209
- if (magic[0] === 84 && magic[1] === 84 && magic[2] === 65 && magic[3] === 49) return "TTA";
210
- if (this.fileData.length >= 16 && magic[0] === 48 && magic[1] === 38 && magic[2] === 178 && magic[3] === 117) return "ASF";
211
- if (magic[0] === 26 && magic[1] === 69 && magic[2] === 223 && magic[3] === 163) return "MATROSKA";
212
- const ftyp = this.fileData.slice(4, 8);
213
- if (ftyp[0] === 102 && ftyp[1] === 116 && ftyp[2] === 121 && ftyp[3] === 112) return "MP4";
214
- return "unknown";
215
- }
216
- detectOggCodec() {
217
- if (!this.fileData || this.fileData.length < 37) return "OGG";
218
- const segCount = this.fileData[26];
219
- if (segCount === void 0) return "OGG";
220
- const payloadStart = 27 + segCount;
221
- if (this.fileData.length < payloadStart + 8) return "OGG";
222
- const sig = String.fromCharCode(
223
- ...this.fileData.slice(payloadStart, payloadStart + 8)
224
- );
225
- if (sig === "OpusHead") return "OPUS";
226
- return "OGG";
65
+ return audio.getFormat(this.state.tagData, this.state.fileData);
227
66
  }
228
- getBuffer() {
67
+ isMP4() {
229
68
  this.checkNotDestroyed();
230
- return this.fileData ?? new Uint8Array(0);
69
+ return audio.isMP4(this.state.tagData, this.state.fileData);
231
70
  }
232
71
  getProperties() {
233
72
  this.checkNotDestroyed();
234
- const result = {};
235
- const data = this.tagData ?? {};
236
- for (const [key, value] of Object.entries(data)) {
237
- if (AUDIO_KEYS.has(key) || INTERNAL_KEYS.has(key)) continue;
238
- if (key.startsWith("----:")) continue;
239
- if (isShadowedNumericMirror(data, key)) continue;
240
- if (value === void 0 || value === null) continue;
241
- if (value === 0 || value === "") continue;
242
- const propKey = toTagLibKey(key);
243
- if (Array.isArray(value)) {
244
- if (value.length === 0) continue;
245
- result[propKey] = value.map(String);
246
- } else if (typeof value === "object") {
247
- continue;
248
- } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") {
249
- result[propKey] = [String(value)];
250
- }
251
- }
252
- return result;
253
- }
254
- setProperties(props) {
255
- this.checkNotDestroyed();
256
- const next = { ...this.tagData };
257
- for (const [key, values] of Object.entries(props)) {
258
- const camelKey = fromTagLibKey(key);
259
- if (camelKey === "_mp4ItemNames") {
260
- const existing = next._mp4ItemNames ?? [];
261
- next._mp4ItemNames = [.../* @__PURE__ */ new Set([...existing, ...values])];
262
- continue;
263
- }
264
- const mirror = mirrorForRawKey(camelKey);
265
- if (mirror !== void 0) {
266
- stageRawWrite(next, mirror, values);
267
- } else if (values.length === 0) {
268
- if (camelKey === "itunesAdvisory") next[camelKey] = [];
269
- else delete next[camelKey];
270
- } else {
271
- next[camelKey] = values;
272
- }
273
- }
274
- this.tagData = next;
73
+ return props.getProperties(this.state.tagData);
275
74
  }
276
- getProperty(key) {
75
+ setProperties(properties) {
277
76
  this.checkNotDestroyed();
278
- const mappedKey = fromTagLibKey(key);
279
- const stored = this.tagData?.[mappedKey];
280
- if (Array.isArray(stored)) {
281
- const first = stored[0];
282
- return stringifyScalar(first);
283
- }
284
- return stringifyScalar(stored);
77
+ this.state.tagData = props.setProperties(this.state.tagData, properties);
285
78
  }
286
- setProperty(key, value) {
79
+ getProperty(key) {
287
80
  this.checkNotDestroyed();
288
- const mappedKey = fromTagLibKey(key);
289
- const mirror = mirrorForRawKey(mappedKey);
290
- if (mirror !== void 0) {
291
- const next = { ...this.tagData };
292
- stageRawWrite(next, mirror, value === "" ? [] : [value]);
293
- this.tagData = next;
294
- } else {
295
- this.tagData = { ...this.tagData, [mappedKey]: value };
296
- }
81
+ return props.getProperty(this.state.tagData, key);
297
82
  }
298
- isMP4() {
83
+ setProperty(key, value) {
299
84
  this.checkNotDestroyed();
300
- if (!this.fileData) {
301
- return this.tagData?.containerFormat === "MP4";
302
- }
303
- if (this.fileData.length < 8) return false;
304
- const magic = this.fileData.slice(4, 8);
305
- return magic[0] === 102 && magic[1] === 116 && magic[2] === 121 && magic[3] === 112;
85
+ this.state.tagData = props.setProperty(this.state.tagData, key, value);
306
86
  }
307
87
  getMP4Item(key) {
308
88
  this.checkNotDestroyed();
309
- if (key.startsWith("----:")) {
310
- const foreign = this.tagData?.[key];
311
- if (foreign !== void 0) {
312
- if (Array.isArray(foreign)) {
313
- const first = foreign[0];
314
- return stringifyScalar(first);
315
- }
316
- return stringifyScalar(foreign);
317
- }
318
- }
319
- return this.getProperty(mp4ItemPropertyKey(key));
89
+ return mp4.getMP4Item(this.state.tagData, key);
320
90
  }
321
91
  setMP4Item(key, value) {
322
92
  this.checkNotDestroyed();
323
- this.setProperty(mp4ItemPropertyKey(key), value);
324
- if (key.startsWith("----:")) {
325
- this.registerMp4ItemName(key);
326
- if (this.tagData?.[key] !== void 0) {
327
- this.tagData = { ...this.tagData, [key]: value };
328
- }
329
- }
330
- }
331
- /** Record an exact atom name to be repaired after the PropertyMap write. */
332
- registerMp4ItemName(name) {
333
- const existing = this.tagData?._mp4ItemNames ?? [];
334
- if (existing.includes(name)) return;
335
- this.tagData = {
336
- ...this.tagData,
337
- _mp4ItemNames: [...existing, name]
338
- };
339
- }
340
- /**
341
- * Record a foreign-mean freeform atom for deletion at save (taglib-65nm).
342
- * The PropertyMap erase pass cannot express foreign atoms, so the C++ shim
343
- * consumes this list and calls removeItem on each exact atom name.
344
- */
345
- registerMp4ItemRemoval(name) {
346
- const existing = this.tagData?._mp4ItemRemovals ?? [];
347
- if (existing.includes(name)) return;
348
- this.tagData = {
349
- ...this.tagData,
350
- _mp4ItemRemovals: [...existing, name]
351
- };
93
+ this.state.tagData = mp4.setMP4Item(this.state.tagData, key, value);
352
94
  }
353
95
  getMp4ItemRemovals() {
354
96
  this.checkNotDestroyed();
355
- return this.tagData?._mp4ItemRemovals;
97
+ return mp4.getMp4ItemRemovals(this.state.tagData);
356
98
  }
357
99
  setMp4ItemRemovals(removals) {
358
100
  this.checkNotDestroyed();
359
- this.tagData = {
360
- ...this.tagData,
361
- _mp4ItemRemovals: [...new Set(removals)]
362
- };
101
+ this.state.tagData = mp4.setMp4ItemRemovals(this.state.tagData, removals);
363
102
  }
364
103
  removeMP4Item(key) {
365
104
  this.checkNotDestroyed();
366
- if (this.tagData) {
367
- if (key.startsWith("----:")) {
368
- delete this.tagData[key];
369
- this.registerMp4ItemRemoval(key);
370
- }
371
- const mappedKey = fromTagLibKey(mp4ItemPropertyKey(key));
372
- delete this.tagData[mappedKey];
373
- const mirror = mirrorForRawKey(mappedKey);
374
- if (mirror !== void 0) delete this.tagData[mirror.numeric];
375
- }
105
+ this.state.tagData = mp4.removeMP4Item(this.state.tagData, key);
376
106
  }
377
107
  getPictures() {
378
108
  this.checkNotDestroyed();
379
- return this.tagData?.pictures ?? [];
109
+ return fields.getPictures(this.state.tagData);
380
110
  }
381
111
  setPictures(pictures) {
382
112
  this.checkNotDestroyed();
383
- this.tagData = { ...this.tagData, pictures };
113
+ this.state.tagData = fields.setPictures(this.state.tagData, pictures);
384
114
  }
385
115
  addPicture(picture) {
386
116
  this.checkNotDestroyed();
387
- const pictures = this.getPictures();
388
- pictures.push(picture);
389
- this.setPictures(pictures);
117
+ this.state.tagData = fields.addPicture(this.state.tagData, picture);
390
118
  }
391
119
  removePictures() {
392
120
  this.checkNotDestroyed();
393
- this.tagData = { ...this.tagData, pictures: [] };
121
+ this.state.tagData = fields.removePictures(this.state.tagData);
394
122
  }
395
123
  getChapters() {
396
124
  this.checkNotDestroyed();
397
- return this.tagData?.chapters ?? [];
125
+ return fields.getChapters(this.state.tagData);
398
126
  }
399
127
  setChapters(chapters, mp4ChapterStyle) {
400
128
  this.checkNotDestroyed();
401
- this.tagData = {
402
- ...this.tagData,
403
- _mp4ChapterStyle: mp4ChapterStyle,
404
- chapters
405
- };
129
+ this.state.tagData = fields.setChapters(
130
+ this.state.tagData,
131
+ chapters,
132
+ mp4ChapterStyle
133
+ );
406
134
  }
407
135
  getBextData() {
408
136
  this.checkNotDestroyed();
409
- return this.tagData?.bextData ?? void 0;
137
+ return fields.getBextData(this.state.tagData);
410
138
  }
411
139
  setBextData(data) {
412
140
  this.checkNotDestroyed();
413
- this.tagData = { ...this.tagData, bextData: data };
141
+ this.state.tagData = fields.setBextData(this.state.tagData, data);
414
142
  }
415
143
  getIxml() {
416
144
  this.checkNotDestroyed();
417
- const v = this.tagData?.ixml;
418
- return typeof v === "string" && v.length > 0 ? v : void 0;
145
+ return fields.getIxml(this.state.tagData);
419
146
  }
420
147
  setIxml(data) {
421
148
  this.checkNotDestroyed();
422
- this.tagData = { ...this.tagData, ixml: data };
149
+ this.state.tagData = fields.setIxml(this.state.tagData, data);
423
150
  }
424
151
  hasId3Tags() {
425
152
  this.checkNotDestroyed();
426
- const t = this.tagData?.id3Tags;
427
- return { v1: t?.v1 ?? false, v2: t?.v2 ?? false };
153
+ return fields.hasId3Tags(this.state.tagData);
428
154
  }
429
155
  stripId3Tags(opts) {
430
156
  this.checkNotDestroyed();
431
- const current = this.tagData?.id3Tags;
432
- if (!current) return;
433
- const prior = this.tagData?._stripId3;
434
- const stripV1 = (prior?.v1 ?? false) || opts.v1;
435
- const stripV2 = (prior?.v2 ?? false) || opts.v2;
436
- this.tagData = {
437
- ...this.tagData,
438
- _stripId3: { v1: stripV1, v2: stripV2 },
439
- id3Tags: {
440
- v1: (current.v1 ?? false) && !stripV1,
441
- v2: (current.v2 ?? false) && !stripV2
442
- }
443
- };
157
+ this.state.tagData = fields.stripId3Tags(this.state.tagData, opts);
444
158
  }
445
159
  getRatings() {
446
160
  this.checkNotDestroyed();
447
- return this.tagData?.ratings ?? [];
161
+ return fields.getRatings(this.state.tagData);
448
162
  }
449
163
  setRatings(ratings) {
450
164
  this.checkNotDestroyed();
451
- const normalizedRatings = ratings.map((r) => ({
452
- rating: r.rating,
453
- email: r.email ?? "",
454
- counter: r.counter ?? 0
455
- }));
456
- this.tagData = {
457
- ...this.tagData,
458
- ratings: normalizedRatings
459
- };
165
+ this.state.tagData = fields.setRatings(this.state.tagData, ratings);
460
166
  }
461
167
  getLyrics() {
462
168
  this.checkNotDestroyed();
463
- const value = this.tagData?.lyrics;
464
- if (value === void 0 || value === null) return [];
465
- const entries = Array.isArray(value) ? value : [value];
466
- return entries.map(
467
- (entry) => typeof entry === "string" ? { text: entry, description: "", language: "" } : {
468
- text: entry?.text ?? "",
469
- description: entry?.description ?? "",
470
- language: entry?.language ?? ""
471
- }
472
- );
169
+ return fields.getLyrics(this.state.tagData);
473
170
  }
474
171
  setLyrics(lyrics) {
475
172
  this.checkNotDestroyed();
476
- this.tagData = { ...this.tagData, lyrics };
173
+ this.state.tagData = fields.setLyrics(this.state.tagData, lyrics);
477
174
  }
478
175
  getId3v2Frames(id) {
479
176
  this.checkNotDestroyed();
480
- const filter = id === "" ? void 0 : id;
481
- const source = this.filePath ?? this.fileData;
482
- let frames = [];
483
- if (source) {
484
- frames = readId3v2FramesFromWasm(this.wasi, source, filter);
485
- }
486
- const staged = this.getStagedId3v2Frames();
487
- if (!staged) return frames;
488
- const stagedIds = new Set(Object.keys(staged));
489
- frames = frames.filter((f) => !stagedIds.has(f.id));
490
- for (const [sid, bodies] of Object.entries(staged)) {
491
- if (filter && sid !== filter) continue;
492
- for (const data of bodies) frames.push({ id: sid, data: data.slice() });
493
- }
494
- return frames;
177
+ return fields.getId3v2Frames(this.state, id);
495
178
  }
496
179
  setId3v2Frames(id, data) {
497
180
  this.checkNotDestroyed();
498
- const staged = { ...this.getStagedId3v2Frames() ?? {} };
499
- staged[id] = data.map((d) => new Uint8Array(d));
500
- this.tagData = {
501
- ...this.tagData,
502
- id3v2Frames: staged
503
- };
181
+ this.state.tagData = fields.setId3v2Frames(this.state.tagData, id, data);
504
182
  }
505
183
  removeId3v2Frames(id) {
506
- this.setId3v2Frames(id, []);
184
+ this.checkNotDestroyed();
185
+ this.state.tagData = fields.removeId3v2Frames(this.state.tagData, id);
507
186
  }
508
187
  getStagedId3v2Frames() {
509
- return this.tagData?.id3v2Frames;
188
+ return fields.getStagedId3v2Frames(this.state.tagData);
510
189
  }
511
190
  destroy() {
512
- this.fileData = null;
513
- this.tagData = null;
191
+ destroy(this.state);
514
192
  this.destroyed = true;
515
193
  }
516
194
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Handle state and lifecycle for the WASI FileHandle.
3
+ *
4
+ * The WasiFileHandle class owns destruction guarding and delegates here;
5
+ * this module's branching logic is testable without a Wasm module (pass a
6
+ * stub state/wasi). Extracted from file-handle.ts in the taglib-1dfc split.
7
+ */
8
+ import type { WasiModule } from "../wasmer-sdk-loader/types.js";
9
+ /** The mutable handle state: source bytes/path + the tag-data snapshot. */
10
+ export interface HandleState {
11
+ wasi: WasiModule;
12
+ fileData: Uint8Array | null;
13
+ filePath: string | null;
14
+ tagData: Record<string, unknown> | null;
15
+ }
16
+ export declare function createHandleState(wasi: WasiModule): HandleState;
17
+ /** Read + decode + normalize a buffer source into the snapshot. */
18
+ export declare function loadBuffer(state: HandleState, buffer: Uint8Array): void;
19
+ /** Read + decode + normalize a path source into the snapshot. */
20
+ export declare function loadPath(state: HandleState, path: string): void;
21
+ export declare function isValid(state: HandleState): boolean;
22
+ /** Write the snapshot back; path mode writes in place, buffer mode returns
23
+ * the new bytes (stored back into state.fileData on success). */
24
+ export declare function save(state: HandleState): boolean;
25
+ export declare function getBuffer(state: HandleState): Uint8Array;
26
+ /** Null the source and snapshot; the class tracks the destroyed flag. */
27
+ export declare function destroy(state: HandleState): void;
28
+ //# sourceMappingURL=handle-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-state.d.ts","sourceRoot":"","sources":["../../../../src/runtime/wasi-adapter/handle-state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAKhE,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAE/D;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAKvE;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAK/D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAGnD;AAED;iEACiE;AACjE,wBAAgB,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAsBhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,CAExD;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAGhD"}
@@ -0,0 +1,58 @@
1
+ import { decodeTagData } from "../../msgpack/decoder.js";
2
+ import { preserveEmptyValues } from "./tag-keys.js";
3
+ import * as wasmIo from "./wasm-io.js";
4
+ function createHandleState(wasi) {
5
+ return { wasi, fileData: null, filePath: null, tagData: null };
6
+ }
7
+ function loadBuffer(state, buffer) {
8
+ state.fileData = buffer;
9
+ state.tagData = preserveEmptyValues(
10
+ decodeTagData(wasmIo.readTagsFromWasm(state.wasi, buffer))
11
+ );
12
+ }
13
+ function loadPath(state, path) {
14
+ state.filePath = path;
15
+ state.tagData = preserveEmptyValues(
16
+ decodeTagData(wasmIo.readTagsFromWasmPath(state.wasi, path))
17
+ );
18
+ }
19
+ function isValid(state) {
20
+ return state.fileData !== null && state.fileData.length > 0 || state.filePath !== null && state.tagData !== null;
21
+ }
22
+ function save(state) {
23
+ if (!state.tagData) return false;
24
+ if (state.filePath) {
25
+ return wasmIo.writeTagsToWasmPath(
26
+ state.wasi,
27
+ state.filePath,
28
+ state.tagData
29
+ );
30
+ }
31
+ if (!state.fileData) return false;
32
+ const result = wasmIo.writeTagsToWasm(
33
+ state.wasi,
34
+ state.fileData,
35
+ state.tagData
36
+ );
37
+ if (result) {
38
+ state.fileData = result;
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ function getBuffer(state) {
44
+ return state.fileData ?? new Uint8Array(0);
45
+ }
46
+ function destroy(state) {
47
+ state.fileData = null;
48
+ state.tagData = null;
49
+ }
50
+ export {
51
+ createHandleState,
52
+ destroy,
53
+ getBuffer,
54
+ isValid,
55
+ loadBuffer,
56
+ loadPath,
57
+ save
58
+ };