taglib-wasm 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -1,25 +1,48 @@
1
- # taglib-wasm
1
+ # TagLib-Wasm
2
2
 
3
- This is the Wasm version of [**TagLib**](https://taglib.org/), the most robust, de-facto standard for reading and editing metadata tags (Title, Album, Artist, etc.) in all popular audio formats. `taglib-wasm` exists because the JavaScipt/TypeScipt ecosystem had no battle-tested audio tagging library that supports reading and writing music metadata to all popular audio formats — until now!
3
+ > TagLib for TypeScript platforms: Deno, Node.js, Bun, browsers, and Cloudflare
4
+ > Workers
4
5
 
5
- `taglib-wasm` stands on the shoulders of giants, including [TagLib](https://taglib.org/) itself, [Emscripten](https://emscripten.org/), and [Wasm](https://webassembly.org/) ([WebAssembly](https://webassembly.org/)).
6
+ This is the Wasm version of [**TagLib**](https://taglib.org/), the most robust,
7
+ de-facto standard for reading and editing metadata tags (Title, Album, Artist,
8
+ etc.) in all popular audio formats. `taglib-wasm` exists because the
9
+ JavaScipt/TypeScipt ecosystem had no battle-tested audio tagging library that
10
+ supports reading and writing music metadata to all popular audio formats — until
11
+ now!
6
12
 
7
- `taglib-wasm` aspires to be a universal solution for **JavaScript/TypeScript** platforms — Deno, Node.js, Bun, web browsers, and Cloudflare Workers. Note: This project is a baby, and you’re likely to experience some surprises at this stage of its development. I’m extremely motivated to help address them, since I’ll also be depending on this project.
13
+ `taglib-wasm` stands on the shoulders of giants, including
14
+ [TagLib](https://taglib.org/) itself, [Emscripten](https://emscripten.org/), and
15
+ [Wasm](https://webassembly.org/) ([WebAssembly](https://webassembly.org/)).
16
+
17
+ `taglib-wasm` aspires to be a universal solution for **JavaScript/TypeScript**
18
+ platforms — Deno, Node.js, Bun, web browsers, and Cloudflare Workers. Note: This
19
+ project is a baby, and you’re likely to experience some surprises at this stage
20
+ of its development. I’m extremely motivated to help address them, since I’ll
21
+ also be depending on this project.
8
22
 
9
23
  ## 🤔 Why?
10
24
 
11
- Because there’s nothing like it. [`mp3tag.js`](https://mp3tag.js.org/) is mature and active, but only supports MP3 files and ID3 tags. TagLib was an ideal choice from a maturity and capabilities point of view, but wrappers like `node-taglib` appeared to be dormant, and I wanted to avoid making users install platform-specific dependencies whenever possible.
25
+ Because there’s nothing like it. [`mp3tag.js`](https://mp3tag.js.org/) is mature
26
+ and active, but only supports MP3 files and ID3 tags. TagLib was an ideal choice
27
+ from a maturity and capabilities point of view, but wrappers like `node-taglib`
28
+ appeared to be dormant, and I wanted to avoid making users install
29
+ platform-specific dependencies whenever possible.
12
30
 
13
31
  ## 🎯 Features
14
32
 
15
- - **✅ Universal compatibility** – Works with Deno, Node.js, Bun, web browsers, and Cloudflare Workers
33
+ - **✅ Universal compatibility** – Works with Deno, Node.js, Bun, web browsers,
34
+ and Cloudflare Workers
16
35
  - **✅ TypeScript first** – Complete type definitions and modern API
17
- - **✅ Full audio format support** – Supports all audio formats supported by TagLib
18
- - **✅ Format abstraction** – `taglib-wasm` deals with how tags are read from/written to in different file formats
19
- - **✅ Zero dependencies** – Self-contained WASM bundle
36
+ - **✅ Full audio format support** – Supports all audio formats supported by
37
+ TagLib
38
+ - **✅ Format abstraction** – `taglib-wasm` deals with how tags are read
39
+ from/written to in different file formats
40
+ - **✅ Zero dependencies** – Self-contained Wasm bundle
20
41
  - **✅ Memory efficient** – In-memory processing without filesystem access
21
- - **✅ Production ready** – Growing test suite helps ensure safety and reliability
22
- - **🆕 Two API styles** – Choose between Simple (3 functions) or Core (full control) APIs
42
+ - **✅ Production ready** – Growing test suite helps ensure safety and
43
+ reliability
44
+ - **🆕 Two API styles** – Choose between Simple (3 functions) or Core (full
45
+ control) APIs
23
46
 
24
47
  ## 📦 Installation
25
48
 
@@ -35,7 +58,19 @@ import { TagLib } from "npm:taglib-wasm";
35
58
  npm install taglib-wasm
36
59
  ```
37
60
 
38
- **Note:** The NPM package ships TypeScript source files. Use a TypeScript loader like [`tsx`](https://github.com/privatenumber/tsx):
61
+ The package uses TypeScript. You have two options:
62
+
63
+ #### Option 1: Use Node’s native TypeScript support
64
+
65
+ ```bash
66
+ # Node.js 22.6.0+ with experimental flag
67
+ node --experimental-strip-types your-script.ts
68
+
69
+ # Node.js 23.6.0+ (no flag needed)
70
+ node your-script.ts
71
+ ```
72
+
73
+ #### Option 2: TypeScript loader (recommended for production)
39
74
 
40
75
  ```bash
41
76
  npm install --save-dev tsx
@@ -52,16 +87,17 @@ bun add taglib-wasm
52
87
 
53
88
  ### Simple API
54
89
 
55
- Inspired by [go-taglib](https://github.com/sentriz/go-taglib)’s excellent developer experience:
90
+ This was inspired by [go-taglib](https://github.com/sentriz/go-taglib), a
91
+ similar project for Go created at about the same time.
56
92
 
57
93
  ```typescript
58
94
  import { readProperties, readTags, writeTags } from "taglib-wasm/simple";
59
95
 
60
- // Read tags - just one function call!
96
+ // Read tags
61
97
  const tags = await readTags("song.mp3");
62
98
  console.log(tags.title, tags.artist, tags.album);
63
99
 
64
- // Write tags - simple as can be
100
+ // Write tags
65
101
  await writeTags("song.mp3", {
66
102
  title: "New Title",
67
103
  artist: "New Artist",
@@ -75,7 +111,7 @@ console.log(`Duration: ${props.length}s, Bitrate: ${props.bitrate} kbps`);
75
111
 
76
112
  ### Core API
77
113
 
78
- Full control when you need it:
114
+ The Core API provides full control for more advanced applications.
79
115
 
80
116
  ```typescript
81
117
  import { TagLib } from "taglib-wasm";
@@ -111,19 +147,51 @@ console.log("Updated tags:", file.tag());
111
147
  file.dispose();
112
148
  ```
113
149
 
150
+ ### Tag Constants
151
+
152
+ taglib-wasm provides type-safe tag constants with IDE autocomplete:
153
+
154
+ ```typescript
155
+ import { Tags } from "taglib-wasm";
156
+
157
+ // Use constants for better type safety and autocomplete
158
+ const properties = file.properties();
159
+
160
+ // Read properties
161
+ const title = properties[Tags.Title]?.[0];
162
+ const albumArtist = properties[Tags.AlbumArtist]?.[0];
163
+ const musicBrainzId = properties[Tags.MusicBrainzArtistId]?.[0];
164
+
165
+ // Write properties
166
+ file.setProperties({
167
+ [Tags.Title]: ["My Song"],
168
+ [Tags.AlbumArtist]: ["Various Artists"],
169
+ [Tags.Bpm]: ["128"],
170
+ });
171
+
172
+ // All constants provide IDE autocomplete
173
+ Tags.Title; // → "TITLE"
174
+ Tags.Artist; // → "ARTIST"
175
+ Tags.AlbumArtist; // → "ALBUMARTIST"
176
+ Tags.TrackGain; // → "REPLAYGAIN_TRACK_GAIN"
177
+ // ... and many more
178
+ ```
179
+
180
+ See [Tag Name Constants](docs/Tag-Name-Constants.md) for the complete list of
181
+ available tags and format-specific mappings.
182
+
114
183
  ## Platform examples
115
184
 
116
- ### Node.js
185
+ ### Deno
117
186
 
118
187
  ```typescript
119
- import { TagLib } from "taglib-wasm";
120
- import { readFile } from "fs/promises";
188
+ import { TagLib } from "npm:taglib-wasm";
121
189
 
122
190
  // Initialize taglib-wasm
123
191
  const taglib = await TagLib.initialize();
124
192
 
125
193
  // Load audio file from filesystem
126
- const audioData = await readFile("song.mp3");
194
+ const audioData = await Deno.readFile("song.mp3");
127
195
  const file = taglib.openFile(audioData);
128
196
 
129
197
  // Read metadata
@@ -150,17 +218,18 @@ console.log("Updated tags:", file.tag());
150
218
  file.dispose();
151
219
  ```
152
220
 
153
- ### Bun
221
+ ### Node.js
154
222
 
155
223
  ```typescript
156
224
  import { TagLib } from "taglib-wasm";
225
+ import { readFile } from "fs/promises";
157
226
 
158
227
  // Initialize taglib-wasm
159
228
  const taglib = await TagLib.initialize();
160
229
 
161
- // Load from file system (Bun's native file API)
162
- const audioData = await Bun.file("song.mp3").arrayBuffer();
163
- const file = taglib.openFile(new Uint8Array(audioData));
230
+ // Load audio file from filesystem
231
+ const audioData = await readFile("song.mp3");
232
+ const file = taglib.openFile(audioData);
164
233
 
165
234
  // Read metadata
166
235
  const tags = file.tag();
@@ -186,7 +255,7 @@ console.log("Updated tags:", file.tag());
186
255
  file.dispose();
187
256
  ```
188
257
 
189
- ### Browser
258
+ ### Bun
190
259
 
191
260
  ```typescript
192
261
  import { TagLib } from "taglib-wasm";
@@ -194,11 +263,9 @@ import { TagLib } from "taglib-wasm";
194
263
  // Initialize taglib-wasm
195
264
  const taglib = await TagLib.initialize();
196
265
 
197
- // Load from file input or fetch
198
- const fileInput = document.querySelector('input[type="file"]');
199
- const audioFile = fileInput.files[0];
200
- const audioData = new Uint8Array(await audioFile.arrayBuffer());
201
- const file = taglib.openFile(audioData);
266
+ // Load from file system (Bun's native file API)
267
+ const audioData = await Bun.file("song.mp3").arrayBuffer();
268
+ const file = taglib.openFile(new Uint8Array(audioData));
202
269
 
203
270
  // Read metadata
204
271
  const tags = file.tag();
@@ -224,16 +291,18 @@ console.log("Updated tags:", file.tag());
224
291
  file.dispose();
225
292
  ```
226
293
 
227
- ### Deno
294
+ ### Browser
228
295
 
229
296
  ```typescript
230
- import { TagLib } from "npm:taglib-wasm";
297
+ import { TagLib } from "taglib-wasm";
231
298
 
232
299
  // Initialize taglib-wasm
233
300
  const taglib = await TagLib.initialize();
234
301
 
235
- // Load audio file from filesystem
236
- const audioData = await Deno.readFile("song.mp3");
302
+ // Load from file input or fetch
303
+ const fileInput = document.querySelector('input[type="file"]');
304
+ const audioFile = fileInput.files[0];
305
+ const audioData = new Uint8Array(await audioFile.arrayBuffer());
237
306
  const file = taglib.openFile(audioData);
238
307
 
239
308
  // Read metadata
@@ -269,7 +338,8 @@ export default {
269
338
  async fetch(request: Request): Promise<Response> {
270
339
  if (request.method === "POST") {
271
340
  try {
272
- // Initialize taglib-wasm
341
+ // Initialize taglib-wasm with Workers-specific configuration
342
+ // See docs/Cloudflare-Workers.md for memory configuration details
273
343
  const taglib = await TagLib.initialize({
274
344
  memory: { initial: 8 * 1024 * 1024 }, // 8MB for Workers
275
345
  });
@@ -319,22 +389,33 @@ export default {
319
389
 
320
390
  `tag-wasm` is designed to support all formats supported by TagLib:
321
391
 
322
- - ✅ **.m4a (.mp4)** – Standard MPEG-4/AAC metadata for AAC and Apple Lossless audio
392
+ - ✅ **.m4a (.mp4)** – Standard MPEG-4/AAC metadata for AAC and Apple Lossless
393
+ audio
323
394
  - ✅ **.mp3** – ID3v2 and ID3v1 tags
324
395
  - ✅ **.flac** – Vorbis comments and audio properties
325
396
  - ✅ **.wav** – INFO chunk metadata
326
- - ✅ **Legacy formats**: Opus, APE, MPC, WavPack, TrueAudio, and more
397
+ - ✅ **Legacy formats** Opus, APE, MPC, WavPack, TrueAudio, and more
327
398
 
328
399
  ## 🎯 Extended Metadata with PropertyMap
329
400
 
330
- `taglib-wasm` provides a **PropertyMap API** for accessing extended metadata beyond the basic tags. This allows you to read and write format-specific fields and custom metadata.
401
+ `taglib-wasm` provides a **PropertyMap API** for accessing extended metadata
402
+ beyond the basic tags. This allows you to read and write format-specific fields
403
+ and custom metadata.
331
404
 
332
405
  ### AcoustID example
333
406
 
334
407
  ```typescript
335
- // Using PropertyMap API to set extended metadata
408
+ import { Tags } from "taglib-wasm";
409
+
410
+ // Using PropertyMap API to set extended metadata with tag constants
411
+ file.setProperty(
412
+ Tags.AcoustidFingerprint,
413
+ "AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...",
414
+ );
415
+ file.setProperty(Tags.AcoustidId, "e7359e88-f1f7-41ed-b9f6-16e58e906997");
416
+
417
+ // Or using string property names
336
418
  file.setProperty("ACOUSTID_FINGERPRINT", "AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
337
- file.setProperty("ACOUSTID_ID", "e7359e88-f1f7-41ed-b9f6-16e58e906997");
338
419
 
339
420
  // Note: Property keys may vary by format
340
421
  // Use file.properties() to see all available properties
@@ -344,20 +425,29 @@ file.save(); // Don't forget to save!
344
425
  ### MusicBrainz example
345
426
 
346
427
  ```typescript
347
- // MusicBrainz metadata using PropertyMap
348
- file.setProperty("MUSICBRAINZ_TRACKID", "f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
349
- file.setProperty("MUSICBRAINZ_ALBUMID", "a1b2c3d4-e5f6-7890-abcd-ef1234567890");
350
- file.setProperty("MUSICBRAINZ_ARTISTID", "12345678-90ab-cdef-1234-567890abcdef");
428
+ // MusicBrainz metadata using PropertyMap with tag constants
429
+ file.setProperty(
430
+ Tags.MusicBrainzTrackId,
431
+ "f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab",
432
+ );
433
+ file.setProperty(
434
+ Tags.MusicBrainzAlbumId,
435
+ "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
436
+ );
437
+ file.setProperty(
438
+ Tags.MusicBrainzArtistId,
439
+ "12345678-90ab-cdef-1234-567890abcdef",
440
+ );
351
441
  ```
352
442
 
353
443
  ### Volume example
354
444
 
355
445
  ```typescript
356
- // ReplayGain volume normalization
357
- file.setProperty("REPLAYGAIN_TRACK_GAIN", "-6.54 dB");
358
- file.setProperty("REPLAYGAIN_TRACK_PEAK", "0.987654");
359
- file.setProperty("REPLAYGAIN_ALBUM_GAIN", "-8.12 dB");
360
- file.setProperty("REPLAYGAIN_ALBUM_PEAK", "0.995432");
446
+ // ReplayGain volume normalization with tag constants
447
+ file.setProperty(Tags.TrackGain, "-6.54 dB");
448
+ file.setProperty(Tags.TrackPeak, "0.987654");
449
+ file.setProperty(Tags.AlbumGain, "-8.12 dB");
450
+ file.setProperty(Tags.AlbumPeak, "0.995432");
361
451
  ```
362
452
 
363
453
  ### Extended fields
@@ -366,20 +456,20 @@ file.setProperty("REPLAYGAIN_ALBUM_PEAK", "0.995432");
366
456
  // Using PropertyMap to set multiple properties at once
367
457
  const properties = file.properties(); // Get current properties
368
458
 
369
- // Set extended metadata
459
+ // Set extended metadata with tag constants
370
460
  file.setProperties({
371
- ALBUMARTIST: ["Various Artists"],
372
- COMPOSER: ["Composer Name"],
373
- BPM: ["120"],
374
- COMPILATION: ["1"],
375
- DISCNUMBER: ["1"],
376
- TRACKTOTAL: ["12"],
461
+ [Tags.AlbumArtist]: ["Various Artists"],
462
+ [Tags.Composer]: ["Composer Name"],
463
+ [Tags.Bpm]: ["120"],
464
+ [Tags.Compilation]: ["1"],
465
+ [Tags.DiscNumber]: ["1"],
466
+ [Tags.TrackTotal]: ["12"],
377
467
  // Note: Property keys vary by format
378
468
  });
379
469
 
380
470
  // Or set individual properties
381
- file.setProperty("ALBUMARTIST", "Various Artists");
382
- file.setProperty("COMPOSER", "Composer Name");
471
+ file.setProperty(Tags.AlbumArtist, "Various Artists");
472
+ file.setProperty(Tags.Composer, "Composer Name");
383
473
  ```
384
474
 
385
475
  ## 🏗️ Development
@@ -394,7 +484,7 @@ file.setProperty("COMPOSER", "Composer Name");
394
484
  git clone <repository>
395
485
  cd taglib-wasm
396
486
 
397
- # Build WASM module
487
+ # Build Wasm module
398
488
  deno task build:wasm
399
489
 
400
490
  # Run tests
@@ -408,7 +498,7 @@ src/
408
498
  ├── mod.ts # Main module exports
409
499
  ├── taglib.ts # Core TagLib and AudioFile classes
410
500
  ├── types.ts # TypeScript type definitions
411
- └── wasm.ts # WASM module interface and utilities
501
+ └── wasm.ts # Wasm module interface and utilities
412
502
 
413
503
  build/
414
504
  ├── build-wasm.sh # Complete build script with C++ wrapper
@@ -442,23 +532,29 @@ npm test
442
532
  # ✅ MP3 - ID3v1/v2 tag support
443
533
  # ✅ FLAC - Vorbis comments and properties
444
534
  # ✅ OGG - Vorbis comments
445
- # ✅ M4A - iTunes-compatible metadata atoms
535
+ # ✅ M4A - MPEG-4 (AAC and Apple Lossless) metadata
446
536
  ```
447
537
 
448
538
  ## 🔧 Technical Implementation
449
539
 
450
540
  ### Key architecture decisions
451
541
 
452
- 1. **Memory Management**: Uses Emscripten's `allocate()` for reliable JS↔WASM data transfer
453
- 2. **Buffer-Based Processing**: `TagLib::ByteVectorStream` enables in-memory file processing
454
- 3. **C++ Wrapper**: Custom C functions bridge TagLib's C++ API to WASM exports
455
- 4. **Type Safety**: Complete TypeScript definitions for all audio formats and metadata
542
+ 1. **Memory Management**: Uses Emscripten's `allocate()` for reliable JS↔Wasm
543
+ data transfer
544
+ 2. **Buffer-Based Processing**: `TagLib::ByteVectorStream` enables in-memory
545
+ file processing
546
+ 3. **C++ Wrapper**: Custom C functions bridge TagLib's C++ API to Wasm exports
547
+ 4. **Type Safety**: Complete TypeScript definitions for all audio formats and
548
+ metadata
456
549
 
457
550
  ### Critical implementation details
458
551
 
459
- - **ByteVectorStream**: Enables processing audio files from memory buffers without filesystem
460
- - **ID-based Object Management**: C++ objects managed via integer IDs for memory safety
461
- - **Emscripten allocate()**: Ensures proper memory synchronization between JS and WASM
552
+ - **ByteVectorStream**: Enables processing audio files from memory buffers
553
+ without filesystem
554
+ - **ID-based Object Management**: C++ objects managed via integer IDs for memory
555
+ safety
556
+ - **Emscripten allocate()**: Ensures proper memory synchronization between JS
557
+ and Wasm
462
558
  - **UTF-8 String Handling**: Proper encoding for international metadata
463
559
 
464
560
  ## 📚 API Reference
@@ -536,51 +632,48 @@ interface Tag {
536
632
  type PropertyMap = { [key: string]: string[] };
537
633
  ```
538
634
 
539
- ## 🎛️ Configuration
635
+ ## 📖 Additional Documentation
540
636
 
541
- ```typescript
542
- interface TagLibConfig {
543
- memory?: {
544
- initial?: number; // Initial memory size (default: 16MB)
545
- maximum?: number; // Maximum memory size (default: 256MB)
546
- };
547
- debug?: boolean; // Enable debug output
548
- }
549
- ```
637
+ - [**Tag Name Constants**](docs/Tag-Name-Constants.md) - Comprehensive reference
638
+ for standard tag names and cross-format mapping
639
+ - [**Automatic Tag Mapping**](docs/Automatic-Tag-Mapping.md) - How taglib-wasm
640
+ handles format-specific tag differences
641
+ - [**Implementation Details**](docs/Implementation.md) - Technical details about
642
+ the Wasm implementation
643
+ - [**Runtime Compatibility**](docs/Runtime-Compatibility.md) - Platform-specific
644
+ setup and considerations
550
645
 
551
646
  ## 🌐 Runtime Compatibility
552
647
 
553
- `taglib-wasm` works seamlessly across all major JavaScript runtimes:
648
+ `taglib-wasm` works across all major JavaScript runtimes:
554
649
 
555
- | Runtime | Status | Installation | Performance | TypeScript |
556
- | ----------- | ------- | --------------------------------- | ----------- | ---------- |
557
- | **Deno** | ✅ Full | `npm:taglib-wasm` | Excellent | Native |
558
- | **Bun** | ✅ Full | `bun add taglib-wasm` | Excellent | Native |
559
- | **Node.js** | ✅ Full | `npm install taglib-wasm` | Good | Via loader |
560
- | **Browser** | ✅ Full | CDN/bundler | Good | Via build |
650
+ | Runtime | Status | Installation | Performance | TypeScript |
651
+ | ----------- | ------- | ------------------------- | ----------- | ---------- |
652
+ | **Deno** | ✅ Full | `npm:taglib-wasm` | Excellent | Native |
653
+ | **Bun** | ✅ Full | `bun add taglib-wasm` | Excellent | Native |
654
+ | **Node.js** | ✅ Full | `npm install taglib-wasm` | Good | Native/tsx |
655
+ | **Browser** | ✅ Full | CDN/bundler | Good | Via build |
561
656
 
562
- **📖 See [docs/Runtime-Compatibility.md](docs/Runtime-Compatibility.md) for detailed runtime information**
657
+ **📖 See [docs/Runtime-Compatibility.md](docs/Runtime-Compatibility.md) for
658
+ detailed runtime information**
563
659
 
564
660
  ## 🚧 Known Limitations
565
661
 
566
- - **File Writing**: Saves only affect in-memory representation (no filesystem persistence)
567
- - **Large Files**: Memory usage scales with file size (entire file loaded into memory)
568
- - **Concurrent Access**: Not thread-safe (JavaScript single-threaded nature)
662
+ - **File Writing** Saves only affect in-memory representation (no filesystem
663
+ persistence)
664
+ - **Large Files** Memory usage scales with file size (entire file loaded into
665
+ memory)
666
+ - **Concurrent Access** – Not thread-safe (JavaScript single-threaded nature)
569
667
 
570
668
  ## 🤝 Contributing
571
669
 
572
- Contributions welcome! Areas of interest:
573
-
574
- - Additional format support (DSF, DSDIFF, etc.)
575
- - Advanced metadata implementation (PropertyMap integration)
576
- - Performance optimizations
577
- - Runtime-specific optimizations
578
- - Documentation improvements
670
+ Contributions welcome!
579
671
 
580
672
  ## 📄 License
581
673
 
582
674
  - **This project**: MIT License (see [LICENSE](LICENSE))
583
- - **TagLib library**: LGPL/MPL dual license (see [lib/taglib/COPYING.LGPL](lib/taglib/COPYING.LGPL))
675
+ - **TagLib library**: LGPL/MPL dual license (see
676
+ [lib/taglib/COPYING.LGPL](lib/taglib/COPYING.LGPL))
584
677
 
585
678
  ## 🙏 Acknowledgments
586
679