music-metadata 8.0.1 → 8.1.1
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 +5 -4
- package/lib/aiff/AiffParser.d.ts +1 -0
- package/lib/aiff/AiffParser.js +13 -1
- package/lib/aiff/AiffTagMap.d.ts +4 -0
- package/lib/aiff/AiffTagMap.js +16 -0
- package/lib/asf/AsfObject.js +3 -3
- package/lib/asf/AsfUtil.d.ts +1 -1
- package/lib/asf/GUID.js +3 -3
- package/lib/common/CombinedTagMapper.js +3 -1
- package/lib/common/FourCC.js +2 -5
- package/lib/common/GenericTagMapper.js +4 -4
- package/lib/common/GenericTagTypes.d.ts +2 -2
- package/lib/common/MetadataCollector.js +2 -2
- package/lib/common/Util.d.ts +1 -1
- package/lib/id3v2/ID3v2Token.d.ts +1 -1
- package/lib/matroska/MatroskaParser.js +5 -3
- package/lib/mp4/Atom.d.ts +1 -1
- package/lib/mp4/Atom.js +7 -7
- package/lib/ogg/OggParser.js +3 -3
- package/lib/ogg/vorbis/Vorbis.js +3 -3
- package/lib/type.d.ts +3 -3
- package/package.json +17 -17
- package/lib/browser.js +0 -99
package/README.md
CHANGED
|
@@ -55,6 +55,7 @@ Following tag header formats are supported:
|
|
|
55
55
|
* [iTunes](https://github.com/sergiomb2/libmp4v2/wiki/iTunesMetadata)
|
|
56
56
|
* [RIFF](https://wikipedia.org/wiki/Resource_Interchange_File_Format)/INFO
|
|
57
57
|
* [Vorbis comment](https://wikipedia.org/wiki/Vorbis_comment)
|
|
58
|
+
* [AIFF](https://wikipedia.org/wiki/Audio_Interchange_File_Format)
|
|
58
59
|
|
|
59
60
|
It allows many tags to be accessed in audio format, and tag format independent way.
|
|
60
61
|
|
|
@@ -79,7 +80,7 @@ Support for encoding / format details:
|
|
|
79
80
|
|
|
80
81
|
Module: version 8 migrated from [CommonJS](https://en.wikipedia.org/wiki/CommonJS) to [pure ECMAScript Module (ESM)](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
|
|
81
82
|
JavaScript is compliant with [ECMAScript 2019 (ES10)](https://en.wikipedia.org/wiki/ECMAScript#10th_Edition_%E2%80%93_ECMAScript_2019).
|
|
82
|
-
Requires Node.js ≥
|
|
83
|
+
Requires Node.js ≥ 14.13.1 engine.
|
|
83
84
|
|
|
84
85
|
### Browser Support
|
|
85
86
|
|
|
@@ -91,8 +92,8 @@ import * as mm from 'music-metadata/lib/core';
|
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
| function | `music-metadata` | `music-metadata/lib/core` |
|
|
94
|
-
|
|
95
|
-
| [`parseBuffer`](#
|
|
95
|
+
|------------------------------------------------------| ---------------------------|----------------------------|
|
|
96
|
+
| [`parseBuffer`](#parsebuffer-function) | ✓ | ✓ |
|
|
96
97
|
| [`parseStream`](#parsestream-function) * | ✓ | ✓ |
|
|
97
98
|
| [`parseFromTokenizer`](#parsefromtokenizer-function) | ✓ | ✓ |
|
|
98
99
|
| [`parseFile`](#parsefile-function) | ✓ | |
|
|
@@ -191,7 +192,7 @@ import { parseBuffer } from 'music-metadata';
|
|
|
191
192
|
|
|
192
193
|
(async () => {
|
|
193
194
|
try {
|
|
194
|
-
const metadata = parseBuffer(someBuffer, 'audio/mpeg');
|
|
195
|
+
const metadata = await parseBuffer(someBuffer, 'audio/mpeg');
|
|
195
196
|
console.log(metadata);
|
|
196
197
|
} catch (error) {
|
|
197
198
|
console.error(error.message);
|
package/lib/aiff/AiffParser.d.ts
CHANGED
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -48,7 +48,6 @@ export class AIFFParser extends BasicParser {
|
|
|
48
48
|
while (!this.tokenizer.fileInfo.size || this.tokenizer.fileInfo.size - this.tokenizer.position >= iff.Header.len) {
|
|
49
49
|
debug('Reading AIFF chunk at offset=' + this.tokenizer.position);
|
|
50
50
|
const chunkHeader = await this.tokenizer.readToken(iff.Header);
|
|
51
|
-
debug(`Chunk id=${chunkHeader.chunkID}`);
|
|
52
51
|
const nextChunk = 2 * Math.round(chunkHeader.chunkSize / 2);
|
|
53
52
|
const bytesRead = await this.readData(chunkHeader);
|
|
54
53
|
await this.tokenizer.ignore(nextChunk - bytesRead);
|
|
@@ -85,8 +84,21 @@ export class AIFFParser extends BasicParser {
|
|
|
85
84
|
this.metadata.setFormat('bitrate', 8 * header.chunkSize / this.metadata.format.duration);
|
|
86
85
|
}
|
|
87
86
|
return 0;
|
|
87
|
+
case 'NAME': // Sample name chunk
|
|
88
|
+
case 'AUTH': // Author chunk
|
|
89
|
+
case '(c) ': // Copyright chunk
|
|
90
|
+
case 'ANNO': // Annotation chunk
|
|
91
|
+
return this.readTextChunk(header);
|
|
88
92
|
default:
|
|
93
|
+
debug(`Ignore chunk id=${header.chunkID}, size=${header.chunkSize}`);
|
|
89
94
|
return 0;
|
|
90
95
|
}
|
|
91
96
|
}
|
|
97
|
+
async readTextChunk(header) {
|
|
98
|
+
const value = await this.tokenizer.readToken(new Token.StringType(header.chunkSize, 'ascii'));
|
|
99
|
+
value.split('\0').map(v => v.trim()).filter(v => v && v.length > 0).forEach(v => {
|
|
100
|
+
this.metadata.addTag('AIFF', header.chunkID, v.trim());
|
|
101
|
+
});
|
|
102
|
+
return header.chunkSize;
|
|
103
|
+
}
|
|
92
104
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CommonTagMapper } from '../common/GenericTagMapper.js';
|
|
2
|
+
/**
|
|
3
|
+
* ID3v1 tag mappings
|
|
4
|
+
*/
|
|
5
|
+
const tagMap = {
|
|
6
|
+
NAME: 'title',
|
|
7
|
+
AUTH: 'artist',
|
|
8
|
+
'(c) ': 'copyright',
|
|
9
|
+
ANNO: 'comment'
|
|
10
|
+
};
|
|
11
|
+
export class AiffTagMapper extends CommonTagMapper {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(['AIFF'], tagMap);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=AiffTagMap.js.map
|
package/lib/asf/AsfObject.js
CHANGED
|
@@ -334,9 +334,6 @@ MetadataLibraryObjectState.guid = GUID.MetadataLibraryObject;
|
|
|
334
334
|
* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
|
|
335
335
|
*/
|
|
336
336
|
export class WmPictureToken {
|
|
337
|
-
constructor(len) {
|
|
338
|
-
this.len = len;
|
|
339
|
-
}
|
|
340
337
|
static fromBase64(base64str) {
|
|
341
338
|
return this.fromBuffer(Buffer.from(base64str, 'base64'));
|
|
342
339
|
}
|
|
@@ -344,6 +341,9 @@ export class WmPictureToken {
|
|
|
344
341
|
const pic = new WmPictureToken(buffer.length);
|
|
345
342
|
return pic.get(buffer, 0);
|
|
346
343
|
}
|
|
344
|
+
constructor(len) {
|
|
345
|
+
this.len = len;
|
|
346
|
+
}
|
|
347
347
|
get(buffer, offset) {
|
|
348
348
|
const typeId = buffer.readUInt8(offset++);
|
|
349
349
|
const size = buffer.readInt32LE(offset);
|
package/lib/asf/AsfUtil.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
3
|
import { DataType } from './AsfObject.js';
|
|
4
|
-
export
|
|
4
|
+
export type AttributeParser = (buf: Buffer) => boolean | string | number | bigint | Buffer;
|
|
5
5
|
export declare class AsfUtil {
|
|
6
6
|
static getParserForAttr(i: DataType): AttributeParser;
|
|
7
7
|
static parseUnicodeAttr(uint8Array: Uint8Array): string;
|
package/lib/asf/GUID.js
CHANGED
|
@@ -13,9 +13,6 @@
|
|
|
13
13
|
* - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
|
|
14
14
|
*/
|
|
15
15
|
export default class GUID {
|
|
16
|
-
constructor(str) {
|
|
17
|
-
this.str = str;
|
|
18
|
-
}
|
|
19
16
|
static fromBin(bin, offset = 0) {
|
|
20
17
|
return new GUID(this.decode(bin, offset));
|
|
21
18
|
}
|
|
@@ -62,6 +59,9 @@ export default class GUID {
|
|
|
62
59
|
Buffer.from(str.slice(24), "hex").copy(bin, 10);
|
|
63
60
|
return bin;
|
|
64
61
|
}
|
|
62
|
+
constructor(str) {
|
|
63
|
+
this.str = str;
|
|
64
|
+
}
|
|
65
65
|
equals(guid) {
|
|
66
66
|
return this.str === guid.str;
|
|
67
67
|
}
|
|
@@ -7,6 +7,7 @@ import { MP4TagMapper } from '../mp4/MP4TagMapper.js';
|
|
|
7
7
|
import { VorbisTagMapper } from '../ogg/vorbis/VorbisTagMapper.js';
|
|
8
8
|
import { RiffInfoTagMapper } from '../riff/RiffInfoTagMap.js';
|
|
9
9
|
import { MatroskaTagMapper } from '../matroska/MatroskaTagMapper.js';
|
|
10
|
+
import { AiffTagMapper } from '../aiff/AiffTagMap.js';
|
|
10
11
|
export class CombinedTagMapper {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.tagMappers = {};
|
|
@@ -20,7 +21,8 @@ export class CombinedTagMapper {
|
|
|
20
21
|
new APEv2TagMapper(),
|
|
21
22
|
new AsfTagMapper(),
|
|
22
23
|
new RiffInfoTagMapper(),
|
|
23
|
-
new MatroskaTagMapper()
|
|
24
|
+
new MatroskaTagMapper(),
|
|
25
|
+
new AiffTagMapper()
|
|
24
26
|
].forEach(mapper => {
|
|
25
27
|
this.registerTagMapper(mapper);
|
|
26
28
|
});
|
package/lib/common/FourCC.js
CHANGED
|
@@ -8,11 +8,8 @@ export const FourCcToken = {
|
|
|
8
8
|
len: 4,
|
|
9
9
|
get: (buf, off) => {
|
|
10
10
|
const id = buf.toString('binary', off, off + FourCcToken.len);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!id.match(validFourCC)) {
|
|
14
|
-
throw new Error(`FourCC contains invalid characters: ${util.a2hex(id)} "${id}"`);
|
|
15
|
-
}
|
|
11
|
+
if (!id.match(validFourCC)) {
|
|
12
|
+
throw new Error(`FourCC contains invalid characters: ${util.a2hex(id)} "${id}"`);
|
|
16
13
|
}
|
|
17
14
|
return id;
|
|
18
15
|
},
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
export class CommonTagMapper {
|
|
2
|
-
constructor(tagTypes, tagMap) {
|
|
3
|
-
this.tagTypes = tagTypes;
|
|
4
|
-
this.tagMap = tagMap;
|
|
5
|
-
}
|
|
6
2
|
static toIntOrNull(str) {
|
|
7
3
|
const cleaned = parseInt(str, 10);
|
|
8
4
|
return isNaN(cleaned) ? null : cleaned;
|
|
@@ -17,6 +13,10 @@ export class CommonTagMapper {
|
|
|
17
13
|
of: parseInt(split[1], 10) || null
|
|
18
14
|
};
|
|
19
15
|
}
|
|
16
|
+
constructor(tagTypes, tagMap) {
|
|
17
|
+
this.tagTypes = tagTypes;
|
|
18
|
+
this.tagMap = tagMap;
|
|
19
|
+
}
|
|
20
20
|
/**
|
|
21
21
|
* Process and set common tags
|
|
22
22
|
* write common tags to
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type TagType = 'vorbis' | 'ID3v1' | 'ID3v2.2' | 'ID3v2.3' | 'ID3v2.4' | 'APEv2' | 'asf' | 'iTunes' | 'exif' | 'matroska' | 'AIFF';
|
|
2
2
|
export interface IGenericTag {
|
|
3
3
|
id: GenericTagId;
|
|
4
4
|
value: any;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'comment' | 'genre' | 'picture' | 'composer' | 'lyrics' | 'albumsort' | 'titlesort' | 'work' | 'artistsort' | 'albumartistsort' | 'composersort' | 'lyricist' | 'writer' | 'conductor' | 'remixer' | 'arranger' | 'engineer' | 'technician' | 'producer' | 'djmixer' | 'mixer' | 'publisher' | 'label' | 'grouping' | 'subtitle' | 'discsubtitle' | 'totaltracks' | 'totaldiscs' | 'compilation' | 'rating' | 'bpm' | 'mood' | 'media' | 'catalognumber' | 'tvShow' | 'tvShowSort' | 'tvEpisode' | 'tvEpisodeId' | 'tvNetwork' | 'tvSeason' | 'podcast' | 'podcasturl' | 'releasestatus' | 'releasetype' | 'releasecountry' | 'script' | 'language' | 'copyright' | 'license' | 'encodedby' | 'encodersettings' | 'gapless' | 'barcode' | 'isrc' | 'asin' | 'musicbrainz_recordingid' | 'musicbrainz_trackid' | 'musicbrainz_albumid' | 'musicbrainz_artistid' | 'musicbrainz_albumartistid' | 'musicbrainz_releasegroupid' | 'musicbrainz_workid' | 'musicbrainz_trmid' | 'musicbrainz_discid' | 'acoustid_id' | 'acoustid_fingerprint' | 'musicip_puid' | 'musicip_fingerprint' | 'website' | 'performer:instrument' | 'peakLevel' | 'averageLevel' | 'notes' | 'key' | 'originalalbum' | 'originalartist' | 'discogs_artist_id' | 'discogs_label_id' | 'discogs_master_release_id' | 'discogs_rating' | 'discogs_release_id' | 'discogs_votes' | 'replaygain_track_gain' | 'replaygain_track_peak' | 'replaygain_album_gain' | 'replaygain_album_peak' | 'replaygain_track_minmax' | 'replaygain_album_minmax' | 'replaygain_undo' | 'description' | 'longDescription' | 'category' | 'hdVideo' | 'keywords' | 'movement' | 'movementIndex' | 'movementTotal' | 'podcastId' | 'showMovement' | 'stik';
|
|
7
7
|
export interface INativeTagMap {
|
|
8
8
|
[index: string]: GenericTagId;
|
|
9
9
|
}
|
|
@@ -6,7 +6,7 @@ import { CommonTagMapper } from './GenericTagMapper.js';
|
|
|
6
6
|
import { toRatio } from './Util.js';
|
|
7
7
|
import { fileTypeFromBuffer } from 'file-type';
|
|
8
8
|
const debug = initDebug('music-metadata:collector');
|
|
9
|
-
const TagPriority = ['matroska', 'APEv2', 'vorbis', 'ID3v2.4', 'ID3v2.3', 'ID3v2.2', 'exif', 'asf', 'iTunes', 'ID3v1'];
|
|
9
|
+
const TagPriority = ['matroska', 'APEv2', 'vorbis', 'ID3v2.4', 'ID3v2.3', 'ID3v2.2', 'exif', 'asf', 'iTunes', 'AIFF', 'ID3v1'];
|
|
10
10
|
/**
|
|
11
11
|
* Provided to the parser to uodate the metadata result.
|
|
12
12
|
* Responsible for triggering async updates
|
|
@@ -41,7 +41,7 @@ export class MetadataCollector {
|
|
|
41
41
|
this.originPriority[tagType] = priority++;
|
|
42
42
|
}
|
|
43
43
|
this.originPriority.artificial = 500; // Filled using alternative tags
|
|
44
|
-
this.originPriority.id3v1 = 600; // Consider worst
|
|
44
|
+
this.originPriority.id3v1 = 600; // Consider as the worst because of the field length limit
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* @returns {boolean} true if one or more tags have been found
|
package/lib/common/Util.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IRatio } from '../type.js';
|
|
2
|
-
export
|
|
2
|
+
export type StringEncoding = 'ascii' | 'utf8' | 'utf16le' | 'ucs2' | 'base64url' | 'latin1' | 'hex';
|
|
3
3
|
export interface ITextEncoding {
|
|
4
4
|
encoding: StringEncoding;
|
|
5
5
|
bom?: boolean;
|
|
@@ -27,7 +27,7 @@ export declare enum AttachedPictureType {
|
|
|
27
27
|
'Band/artist logotype' = 19,
|
|
28
28
|
'Publisher/Studio logotype' = 20
|
|
29
29
|
}
|
|
30
|
-
export
|
|
30
|
+
export type ID3v2MajorVersion = 2 | 3 | 4;
|
|
31
31
|
export interface IExtendedHeader {
|
|
32
32
|
size: number;
|
|
33
33
|
extendedFlags: number;
|
|
@@ -42,9 +42,11 @@ export class MatroskaParser extends BasicParser {
|
|
|
42
42
|
const info = matroska.segment.info;
|
|
43
43
|
if (info) {
|
|
44
44
|
const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
if (typeof info.duration === 'number') {
|
|
46
|
+
const duration = info.duration * timecodeScale / 1000000000;
|
|
47
|
+
this.addTag('segment:title', info.title);
|
|
48
|
+
this.metadata.setFormat('duration', duration);
|
|
49
|
+
}
|
|
48
50
|
}
|
|
49
51
|
const audioTracks = matroska.segment.tracks;
|
|
50
52
|
if (audioTracks && audioTracks.entries) {
|
package/lib/mp4/Atom.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as AtomToken from './AtomToken.js';
|
|
2
2
|
import { ITokenizer } from 'strtok3/core';
|
|
3
|
-
export
|
|
3
|
+
export type AtomDataHandler = (atom: Atom, remaining: number) => Promise<void>;
|
|
4
4
|
export declare class Atom {
|
|
5
5
|
readonly header: AtomToken.IAtomHeader;
|
|
6
6
|
extended: boolean;
|
package/lib/mp4/Atom.js
CHANGED
|
@@ -2,13 +2,6 @@ import initDebug from 'debug';
|
|
|
2
2
|
import * as AtomToken from './AtomToken.js';
|
|
3
3
|
const debug = initDebug('music-metadata:parser:MP4:Atom');
|
|
4
4
|
export class Atom {
|
|
5
|
-
constructor(header, extended, parent) {
|
|
6
|
-
this.header = header;
|
|
7
|
-
this.extended = extended;
|
|
8
|
-
this.parent = parent;
|
|
9
|
-
this.children = [];
|
|
10
|
-
this.atomPath = (this.parent ? this.parent.atomPath + '.' : '') + this.header.name;
|
|
11
|
-
}
|
|
12
5
|
static async readAtom(tokenizer, dataHandler, parent, remaining) {
|
|
13
6
|
// Parse atom header
|
|
14
7
|
const offset = tokenizer.position;
|
|
@@ -24,6 +17,13 @@ export class Atom {
|
|
|
24
17
|
await atomBean.readData(tokenizer, dataHandler, payloadLength);
|
|
25
18
|
return atomBean;
|
|
26
19
|
}
|
|
20
|
+
constructor(header, extended, parent) {
|
|
21
|
+
this.header = header;
|
|
22
|
+
this.extended = extended;
|
|
23
|
+
this.parent = parent;
|
|
24
|
+
this.children = [];
|
|
25
|
+
this.atomPath = (this.parent ? this.parent.atomPath + '.' : '') + this.header.name;
|
|
26
|
+
}
|
|
27
27
|
getHeaderLength() {
|
|
28
28
|
return this.extended ? 16 : 8;
|
|
29
29
|
}
|
package/lib/ogg/OggParser.js
CHANGED
|
@@ -10,9 +10,6 @@ import { SpeexParser } from './speex/SpeexParser.js';
|
|
|
10
10
|
import { TheoraParser } from './theora/TheoraParser.js';
|
|
11
11
|
const debug = initDebug('music-metadata:parser:ogg');
|
|
12
12
|
export class SegmentTable {
|
|
13
|
-
constructor(header) {
|
|
14
|
-
this.len = header.page_segments;
|
|
15
|
-
}
|
|
16
13
|
static sum(buf, off, len) {
|
|
17
14
|
let s = 0;
|
|
18
15
|
for (let i = off; i < off + len; ++i) {
|
|
@@ -20,6 +17,9 @@ export class SegmentTable {
|
|
|
20
17
|
}
|
|
21
18
|
return s;
|
|
22
19
|
}
|
|
20
|
+
constructor(header) {
|
|
21
|
+
this.len = header.page_segments;
|
|
22
|
+
}
|
|
23
23
|
get(buf, off) {
|
|
24
24
|
return {
|
|
25
25
|
totalPageSize: SegmentTable.sum(buf, off, this.len)
|
package/lib/ogg/vorbis/Vorbis.js
CHANGED
|
@@ -7,9 +7,6 @@ import { AttachedPictureType } from '../../id3v2/ID3v2Token.js';
|
|
|
7
7
|
* // ToDo: move to ID3 / APIC?
|
|
8
8
|
*/
|
|
9
9
|
export class VorbisPictureToken {
|
|
10
|
-
constructor(len) {
|
|
11
|
-
this.len = len;
|
|
12
|
-
}
|
|
13
10
|
static fromBase64(base64str) {
|
|
14
11
|
return this.fromBuffer(Buffer.from(base64str, 'base64'));
|
|
15
12
|
}
|
|
@@ -17,6 +14,9 @@ export class VorbisPictureToken {
|
|
|
17
14
|
const pic = new VorbisPictureToken(buffer.length);
|
|
18
15
|
return pic.get(buffer, 0);
|
|
19
16
|
}
|
|
17
|
+
constructor(len) {
|
|
18
|
+
this.len = len;
|
|
19
|
+
}
|
|
20
20
|
get(buffer, offset) {
|
|
21
21
|
const type = AttachedPictureType[Token.UINT32_BE.get(buffer, offset)];
|
|
22
22
|
const mimeLen = Token.UINT32_BE.get(buffer, offset += 4);
|
package/lib/type.d.ts
CHANGED
|
@@ -357,7 +357,7 @@ export interface IRatio {
|
|
|
357
357
|
*/
|
|
358
358
|
dB: number;
|
|
359
359
|
}
|
|
360
|
-
export
|
|
360
|
+
export type FormatId = 'container' | 'duration' | 'bitrate' | 'sampleRate' | 'bitsPerSample' | 'codec' | 'tool' | 'codecProfile' | 'lossless' | 'numberOfChannels' | 'numberOfSamples' | 'audioMD5' | 'chapters' | 'modificationTime' | 'creationTime' | 'trackPeakLevel' | 'trackGain' | 'albumGain';
|
|
361
361
|
export interface IAudioTrack {
|
|
362
362
|
samplingFrequency?: number;
|
|
363
363
|
outputSamplingFrequency?: number;
|
|
@@ -508,7 +508,7 @@ export interface IAudioMetadata extends INativeAudioMetadata {
|
|
|
508
508
|
/**
|
|
509
509
|
* Corresponds with parser module name
|
|
510
510
|
*/
|
|
511
|
-
export
|
|
511
|
+
export type ParserType = 'mpeg' | 'apev2' | 'mp4' | 'asf' | 'flac' | 'ogg' | 'aiff' | 'wavpack' | 'riff' | 'musepack' | 'dsf' | 'dsdiff' | 'adts' | 'matroska';
|
|
512
512
|
export interface IOptions {
|
|
513
513
|
/**
|
|
514
514
|
* default: `false`, if set to `true`, it will parse the whole media file if required to determine the duration.
|
|
@@ -571,7 +571,7 @@ export interface IMetadataEvent {
|
|
|
571
571
|
*/
|
|
572
572
|
metadata: IAudioMetadata;
|
|
573
573
|
}
|
|
574
|
-
export
|
|
574
|
+
export type Observer = (update: IMetadataEvent) => void;
|
|
575
575
|
/**
|
|
576
576
|
* Provides random data read access
|
|
577
577
|
* Used read operations on file of buffers
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "music-metadata",
|
|
3
3
|
"description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.1.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Borewit",
|
|
7
7
|
"url": "https://github.com/Borewit"
|
|
@@ -92,42 +92,42 @@
|
|
|
92
92
|
"@tokenizer/token": "^0.3.0",
|
|
93
93
|
"content-type": "^1.0.4",
|
|
94
94
|
"debug": "^4.3.4",
|
|
95
|
-
"file-type": "^
|
|
95
|
+
"file-type": "^18.0.0",
|
|
96
96
|
"media-typer": "^1.1.0",
|
|
97
97
|
"strtok3": "^7.0.0",
|
|
98
98
|
"token-types": "^5.0.1"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
|
-
"@types/chai": "^4.3.
|
|
101
|
+
"@types/chai": "^4.3.4",
|
|
102
102
|
"@types/chai-as-promised": "^7.1.5",
|
|
103
103
|
"@types/debug": "^4.1.7",
|
|
104
104
|
"@types/file-type": "^10.9.1",
|
|
105
|
-
"@types/mocha": "^
|
|
106
|
-
"@types/node": "^18.
|
|
107
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
108
|
-
"@typescript-eslint/parser": "^5.
|
|
105
|
+
"@types/mocha": "^10.0.0",
|
|
106
|
+
"@types/node": "^18.11.18",
|
|
107
|
+
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
|
108
|
+
"@typescript-eslint/parser": "^5.48.0",
|
|
109
109
|
"c8": "^7.12.0",
|
|
110
|
-
"chai": "^4.3.
|
|
110
|
+
"chai": "^4.3.7",
|
|
111
111
|
"chai-as-promised": "^7.1.1",
|
|
112
112
|
"del-cli": "5.0.0",
|
|
113
|
-
"eslint": "^8.
|
|
114
|
-
"eslint-config-prettier": "^8.
|
|
115
|
-
"eslint-import-resolver-typescript": "^3.
|
|
113
|
+
"eslint": "^8.31.0",
|
|
114
|
+
"eslint-config-prettier": "^8.6.0",
|
|
115
|
+
"eslint-import-resolver-typescript": "^3.5.2",
|
|
116
116
|
"eslint-plugin-import": "^2.26.0",
|
|
117
|
-
"eslint-plugin-jsdoc": "^39.
|
|
117
|
+
"eslint-plugin-jsdoc": "^39.6.4",
|
|
118
118
|
"eslint-plugin-node": "^11.1.0",
|
|
119
|
-
"eslint-plugin-unicorn": "^
|
|
119
|
+
"eslint-plugin-unicorn": "^45.0.2",
|
|
120
120
|
"mime": "^3.0.0",
|
|
121
|
-
"mocha": "^10.
|
|
121
|
+
"mocha": "^10.1.0",
|
|
122
122
|
"npm-run-all": "^4.1.5",
|
|
123
|
-
"prettier": "^2.
|
|
123
|
+
"prettier": "^2.8.1",
|
|
124
124
|
"remark-cli": "^11.0.0",
|
|
125
125
|
"remark-preset-lint-recommended": "^6.1.2",
|
|
126
126
|
"ts-node": "^10.9.1",
|
|
127
|
-
"typescript": "^4.
|
|
127
|
+
"typescript": "^4.9.4"
|
|
128
128
|
},
|
|
129
129
|
"engines": {
|
|
130
|
-
"node": "^
|
|
130
|
+
"node": "^14.13.1 || >=16.0.0"
|
|
131
131
|
},
|
|
132
132
|
"repository": {
|
|
133
133
|
"type": "git",
|
package/lib/browser.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import initDebug from 'debug';
|
|
2
|
-
import { ReadableWebToNodeStream } from 'readable-web-to-node-stream';
|
|
3
|
-
import * as mm from './core.js';
|
|
4
|
-
export { parseBuffer, parseFromTokenizer, orderTags, ratingToStars, selectCover } from './core.js';
|
|
5
|
-
const debug = initDebug('music-metadata-browser:main');
|
|
6
|
-
/**
|
|
7
|
-
* Parse audio Stream
|
|
8
|
-
* @param stream - ReadableStream
|
|
9
|
-
* @param contentType - MIME-Type
|
|
10
|
-
* @param options - Parsing options
|
|
11
|
-
* @returns Metadata
|
|
12
|
-
*/
|
|
13
|
-
export const parseNodeStream = mm.parseStream;
|
|
14
|
-
/**
|
|
15
|
-
* Parse Web API ReadableStream: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
|
|
16
|
-
* @param stream - ReadableStream (web stream according WTWG Streams Standard)
|
|
17
|
-
* @param fileInfo FileInfo object or MIME-Type
|
|
18
|
-
* @param options - Parsing options
|
|
19
|
-
* @returns Metadata
|
|
20
|
-
*/
|
|
21
|
-
export async function parseReadableStream(stream, fileInfo, options) {
|
|
22
|
-
const ns = new ReadableWebToNodeStream(stream);
|
|
23
|
-
const res = await parseNodeStream(ns, typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo, options);
|
|
24
|
-
await ns.close();
|
|
25
|
-
return res;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Parse Web API File
|
|
29
|
-
* @param blob - Blob to parse
|
|
30
|
-
* @param options - Parsing options
|
|
31
|
-
* @returns Metadata
|
|
32
|
-
*/
|
|
33
|
-
export async function parseBlob(blob, options) {
|
|
34
|
-
const fileInfo = { mimeType: blob.type, size: blob.size };
|
|
35
|
-
if (blob instanceof File) {
|
|
36
|
-
fileInfo.path = blob.name;
|
|
37
|
-
}
|
|
38
|
-
const stream = blob.stream ? blob.stream() : convertBlobToReadableStream(blob);
|
|
39
|
-
return parseReadableStream(stream, { mimeType: blob.type, size: blob.size }, options);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Convert Blob to ReadableStream
|
|
43
|
-
* Fallback for Safari versions < 14.1
|
|
44
|
-
* @param blob
|
|
45
|
-
*/
|
|
46
|
-
function convertBlobToReadableStream(blob) {
|
|
47
|
-
const fileReader = new FileReader();
|
|
48
|
-
return new ReadableStream({
|
|
49
|
-
start(controller) {
|
|
50
|
-
// The following function handles each data chunk
|
|
51
|
-
fileReader.onloadend = event => {
|
|
52
|
-
let data = event.target.result;
|
|
53
|
-
if (data instanceof ArrayBuffer) {
|
|
54
|
-
data = new Uint8Array(data);
|
|
55
|
-
}
|
|
56
|
-
controller.enqueue(data);
|
|
57
|
-
controller.close();
|
|
58
|
-
};
|
|
59
|
-
fileReader.onerror = error => {
|
|
60
|
-
controller.close();
|
|
61
|
-
};
|
|
62
|
-
fileReader.onabort = error => {
|
|
63
|
-
controller.close();
|
|
64
|
-
};
|
|
65
|
-
fileReader.readAsArrayBuffer(blob);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Parse fetched file, using the Web Fetch API
|
|
71
|
-
* @param audioTrackUrl - URL to download the audio track from
|
|
72
|
-
* @param options - Parsing options
|
|
73
|
-
* @returns Metadata
|
|
74
|
-
*/
|
|
75
|
-
export async function fetchFromUrl(audioTrackUrl, options) {
|
|
76
|
-
const response = await fetch(audioTrackUrl);
|
|
77
|
-
const fileInfo = {
|
|
78
|
-
size: parseInt(response.headers.get('Content-Length'), 10),
|
|
79
|
-
mimeType: response.headers.get('Content-Type')
|
|
80
|
-
};
|
|
81
|
-
if (response.ok) {
|
|
82
|
-
if (response.body) {
|
|
83
|
-
const res = await parseReadableStream(response.body, fileInfo, options);
|
|
84
|
-
debug('Closing HTTP-readable-stream...');
|
|
85
|
-
if (!response.body.locked) { // Prevent error in Firefox
|
|
86
|
-
await response.body.cancel();
|
|
87
|
-
}
|
|
88
|
-
debug('HTTP-readable-stream closed.');
|
|
89
|
-
return res;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
// Fall back on Blob
|
|
93
|
-
return parseBlob(await response.blob(), options);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
throw new Error(`HTTP error status=${response.status}: ${response.statusText}`);
|
|
98
|
-
}
|
|
99
|
-
}
|