@rafaelsilvadeveloper/fast-media-tags 1.0.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.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @rafaelsilvadeveloper/fast-media-tags
2
+
3
+ An ultra-lightweight, zero-dependency, and extremely fast binary ID3v2 tag parser for Node.js, Bun, Edge, and browser environments.
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/@rafaelsilvadeveloper/fast-media-tags.svg?style=flat-square)](https://www.npmjs.com/package/@rafaelsilvadeveloper/fast-media-tags)
6
+ [![Discord Support](https://img.shields.io/discord/1111111111?color=7289da&label=Discord&logo=discord&style=flat-square)](https://discord.gg/7Fw7snafYS)
7
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-zero-blueviolet.svg?style=flat-square)](https://www.npmjs.com/package/@rafaelsilvadeveloper/fast-media-tags)
8
+
9
+ ## Features
10
+
11
+ * 🚀 **High Performance**: Parses ID3 tags by reading only the first few Kilobytes of the audio file, saving memory, bandwidth, and CPU cycles.
12
+ * 📦 **Zero Dependencies**: Pure TypeScript/JavaScript binary operations. Works out-of-the-box in Node.js, Bun, Edge/Serverless functions, and modern browsers.
13
+ * 🖼️ **Album Art (APIC)**: Decodes cover photos (MIME type, format, type, description) and automatically exposes base64 and binary array data.
14
+ * 🛡️ **TypeScript Definitions**: Strongly typed return signatures.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @rafaelsilvadeveloper/fast-media-tags
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Reading tags from an ArrayBuffer
25
+
26
+ ```typescript
27
+ import { readAudioTags } from '@rafaelsilvadeveloper/fast-media-tags';
28
+
29
+ // In browser or bun
30
+ const buffer = await file.arrayBuffer();
31
+ const tags = await readAudioTags(buffer);
32
+
33
+ console.log(tags.title); // 'Synthwave Dreams'
34
+ console.log(tags.artist); // 'CyberRunner'
35
+ console.log(tags.album); // 'Grid Horizons'
36
+ ```
37
+
38
+ ### Reading tags from a Blob / File (Browser or Bun)
39
+
40
+ ```typescript
41
+ import { readAudioTags } from '@rafaelsilvadeveloper/fast-media-tags';
42
+
43
+ // Directly pass a Blob or HTML5 File object
44
+ const tags = await readAudioTags(fileBlob);
45
+ if (tags.picture) {
46
+ console.log(`Album Art format: ${tags.picture.format}`);
47
+ // base64 ready for <img src="...">
48
+ const imageSrc = tags.picture.base64;
49
+ }
50
+ ```
51
+
52
+ ### Reading tags from a local File Path (Node.js & Bun)
53
+
54
+ ```typescript
55
+ import { readAudioTags } from '@rafaelsilvadeveloper/fast-media-tags';
56
+
57
+ const tags = await readAudioTags('./music/track.mp3');
58
+ console.log(`Title: ${tags.title}, Year: ${tags.year}`);
59
+ ```
60
+
61
+ ## Supported Tags
62
+
63
+ * `title` (`TIT2`)
64
+ * `artist` (`TPE1`)
65
+ * `album` (`TALB`)
66
+ * `year` (`TYER` or `TDRC`)
67
+ * `genre` (`TCON`)
68
+ * `picture` (`APIC`)
69
+
70
+ ## Support
71
+
72
+ For support, questions, or discussions, join our Discord server:
73
+
74
+ [![Discord Server](https://img.shields.io/discord/1111111111?color=7289da&label=Discord&logo=discord&style=for-the-badge)](https://discord.gg/7Fw7snafYS)
75
+
76
+ ## License
77
+ MIT
@@ -0,0 +1,5 @@
1
+ import type { AudioTags } from './types';
2
+ /**
3
+ * Parses ID3v2 tags from audio bytes or files.
4
+ */
5
+ export declare function readAudioTags(file: Blob | ArrayBuffer | string): Promise<AudioTags>;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readAudioTags = readAudioTags;
37
+ function readString(bytes, start, end, encoding) {
38
+ const slice = bytes.subarray(start, end);
39
+ if (encoding === 1 || encoding === 2) {
40
+ let offset = 0;
41
+ if (slice[0] === 0xff && slice[1] === 0xfe)
42
+ offset = 2; // UTF-16LE BOM
43
+ if (slice[0] === 0xfe && slice[1] === 0xff)
44
+ offset = 2; // UTF-16BE BOM
45
+ let str = '';
46
+ for (let i = offset; i < slice.length - 1; i += 2) {
47
+ const code = slice[i] | (slice[i + 1] << 8);
48
+ if (code === 0)
49
+ break;
50
+ str += String.fromCharCode(code);
51
+ }
52
+ return str;
53
+ }
54
+ else {
55
+ let str = '';
56
+ for (let i = 0; i < slice.length; i++) {
57
+ if (slice[i] === 0)
58
+ break;
59
+ str += String.fromCharCode(slice[i]);
60
+ }
61
+ try {
62
+ return decodeURIComponent(escape(str));
63
+ }
64
+ catch (_) {
65
+ return str;
66
+ }
67
+ }
68
+ }
69
+ function parseID3v2(buffer) {
70
+ const bytes = new Uint8Array(buffer);
71
+ if (bytes[0] !== 0x49 || bytes[1] !== 0x44 || bytes[2] !== 0x33) {
72
+ return {};
73
+ }
74
+ const version = bytes[3];
75
+ if (version < 3 || version > 4) {
76
+ return {};
77
+ }
78
+ const tagSize = ((bytes[6] & 0x7f) << 21) |
79
+ ((bytes[7] & 0x7f) << 14) |
80
+ ((bytes[8] & 0x7f) << 7) |
81
+ (bytes[9] & 0x7f);
82
+ const tags = {};
83
+ let offset = 10;
84
+ const tagEnd = Math.min(offset + tagSize, bytes.length);
85
+ while (offset < tagEnd - 10) {
86
+ const frameId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
87
+ if (frameId.charCodeAt(0) === 0 || !/^[A-Z0-9]{4}$/.test(frameId)) {
88
+ break;
89
+ }
90
+ let frameSize = 0;
91
+ if (version === 3) {
92
+ frameSize =
93
+ (bytes[offset + 4] << 24) |
94
+ (bytes[offset + 5] << 16) |
95
+ (bytes[offset + 6] << 8) |
96
+ bytes[offset + 7];
97
+ }
98
+ else {
99
+ frameSize =
100
+ ((bytes[offset + 4] & 0x7f) << 21) |
101
+ ((bytes[offset + 5] & 0x7f) << 14) |
102
+ ((bytes[offset + 6] & 0x7f) << 7) |
103
+ (bytes[offset + 7] & 0x7f);
104
+ }
105
+ if (frameSize <= 0 || offset + 10 + frameSize > tagEnd) {
106
+ break;
107
+ }
108
+ const frameStart = offset + 10;
109
+ const frameEnd = frameStart + frameSize;
110
+ if (frameId.startsWith('T') && frameId !== 'TXXX') {
111
+ const encoding = bytes[frameStart];
112
+ const text = readString(bytes, frameStart + 1, frameEnd, encoding);
113
+ if (frameId === 'TIT2')
114
+ tags.title = text;
115
+ else if (frameId === 'TPE1')
116
+ tags.artist = text;
117
+ else if (frameId === 'TALB')
118
+ tags.album = text;
119
+ else if (frameId === 'TYER' || frameId === 'TDRC')
120
+ tags.year = text.substring(0, 4);
121
+ else if (frameId === 'TCON')
122
+ tags.genre = text;
123
+ }
124
+ else if (frameId === 'APIC') {
125
+ const encoding = bytes[frameStart];
126
+ let mimeEnd = frameStart + 1;
127
+ while (bytes[mimeEnd] !== 0 && mimeEnd < frameEnd)
128
+ mimeEnd++;
129
+ const format = String.fromCharCode(...bytes.subarray(frameStart + 1, mimeEnd));
130
+ const typeCode = bytes[mimeEnd + 1];
131
+ const type = getPictureType(typeCode);
132
+ let descEnd = mimeEnd + 2;
133
+ if (encoding === 1 || encoding === 2) {
134
+ while ((bytes[descEnd] !== 0 || bytes[descEnd + 1] !== 0) && descEnd < frameEnd - 1)
135
+ descEnd += 2;
136
+ descEnd += 2;
137
+ }
138
+ else {
139
+ while (bytes[descEnd] !== 0 && descEnd < frameEnd)
140
+ descEnd++;
141
+ descEnd += 1;
142
+ }
143
+ const description = readString(bytes, mimeEnd + 2, descEnd, encoding);
144
+ const imgData = bytes.subarray(descEnd, frameEnd);
145
+ let binary = '';
146
+ for (let i = 0; i < imgData.length; i++) {
147
+ binary += String.fromCharCode(imgData[i]);
148
+ }
149
+ const base64 = btoa(binary);
150
+ tags.picture = {
151
+ format,
152
+ type,
153
+ description,
154
+ data: new Uint8Array(imgData),
155
+ base64: `data:${format};base64,${base64}`,
156
+ };
157
+ }
158
+ offset = frameEnd;
159
+ }
160
+ return tags;
161
+ }
162
+ function getPictureType(code) {
163
+ const types = [
164
+ 'Other',
165
+ '32x32 pixels file icon',
166
+ 'Other file icon',
167
+ 'Cover (front)',
168
+ 'Cover (back)',
169
+ 'Leaflet page',
170
+ 'Media (e.g. label side of CD)',
171
+ 'Lead artist/lead performer/soloist',
172
+ 'Artist/performer',
173
+ 'Conductor',
174
+ 'Band/Orchestra',
175
+ 'Composer',
176
+ 'Lyricist/text writer',
177
+ 'Recording Location',
178
+ 'During recording',
179
+ 'During performance',
180
+ 'Movie/video screen capture',
181
+ 'A bright coloured fish',
182
+ 'Illustration',
183
+ 'Band/artist logotype',
184
+ 'Publisher/Studio logotype',
185
+ ];
186
+ return types[code] || 'Unknown';
187
+ }
188
+ /**
189
+ * Parses ID3v2 tags from audio bytes or files.
190
+ */
191
+ async function readAudioTags(file) {
192
+ if (file instanceof ArrayBuffer) {
193
+ return parseID3v2(file);
194
+ }
195
+ if (typeof Blob !== 'undefined' && file instanceof Blob) {
196
+ const buffer = await file.slice(0, 131072).arrayBuffer();
197
+ return parseID3v2(buffer);
198
+ }
199
+ if (typeof file === 'string') {
200
+ try {
201
+ const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
202
+ const handle = await fs.open(file, 'r');
203
+ const buffer = Buffer.alloc(131072);
204
+ await handle.read(buffer, 0, 131072, 0);
205
+ await handle.close();
206
+ return parseID3v2(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
207
+ }
208
+ catch (err) {
209
+ throw new Error(`Failed to read file path: ${err.message}`);
210
+ }
211
+ }
212
+ throw new Error('Unsupported file type. Expected Blob, ArrayBuffer, or file path string.');
213
+ }
@@ -0,0 +1,2 @@
1
+ export { readAudioTags } from './fastMediaTags';
2
+ export type * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readAudioTags = void 0;
4
+ var fastMediaTags_1 = require("./fastMediaTags");
5
+ Object.defineProperty(exports, "readAudioTags", { enumerable: true, get: function () { return fastMediaTags_1.readAudioTags; } });
@@ -0,0 +1,15 @@
1
+ export interface AudioPicture {
2
+ format: string;
3
+ type: string;
4
+ description: string;
5
+ data: Uint8Array;
6
+ base64?: string;
7
+ }
8
+ export interface AudioTags {
9
+ title?: string;
10
+ artist?: string;
11
+ album?: string;
12
+ year?: string;
13
+ genre?: string;
14
+ picture?: AudioPicture;
15
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@rafaelsilvadeveloper/fast-media-tags",
3
+ "version": "1.0.2",
4
+ "description": "An ultra-lightweight, zero-dependency binary ID3v2 tag parser for Node/Bun/Edge, optimized for high speed and low memory.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/rafael-packages/fast-media-tags.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/rafael-packages/fast-media-tags/issues"
11
+ },
12
+ "homepage": "https://github.com/rafael-packages/fast-media-tags#readme",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "test": "bun test",
22
+ "prepare": "bun run build",
23
+ "lint": "eslint src/**/*.ts",
24
+ "format": "prettier --write src/**/*.ts tests/**/*.ts"
25
+ },
26
+ "keywords": [
27
+ "id3",
28
+ "mp3",
29
+ "audio",
30
+ "tags",
31
+ "metadata",
32
+ "binary-parser",
33
+ "typescript"
34
+ ],
35
+ "author": "realkalashnikov",
36
+ "license": "MIT",
37
+ "peerDependencies": {
38
+ "typescript": "^5.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.9.1",
42
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
43
+ "@typescript-eslint/parser": "^7.18.0",
44
+ "eslint": "^8.57.0",
45
+ "prettier": "^3.3.3"
46
+ }
47
+ }