music-metadata 10.0.1 → 10.2.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/LICENSE.txt +9 -9
- package/README.md +535 -520
- package/lib/ParserFactory.d.ts +20 -28
- package/lib/ParserFactory.js +204 -207
- package/lib/aiff/AiffParser.js +20 -19
- package/lib/aiff/AiffToken.d.ts +14 -2
- package/lib/aiff/AiffToken.js +12 -0
- package/lib/apev2/APEv2Parser.d.ts +4 -4
- package/lib/apev2/APEv2Parser.js +12 -10
- package/lib/apev2/APEv2Token.d.ts +2 -4
- package/lib/apev2/APEv2Token.js +0 -3
- package/lib/asf/AsfObject.d.ts +7 -16
- package/lib/asf/AsfObject.js +8 -34
- package/lib/asf/AsfParser.js +17 -10
- package/lib/asf/AsfTagMapper.d.ts +1 -1
- package/lib/asf/AsfTagMapper.js +3 -2
- package/lib/asf/AsfUtil.d.ts +3 -11
- package/lib/asf/AsfUtil.js +29 -30
- package/lib/asf/GUID.js +6 -9
- package/lib/common/BasicParser.d.ts +4 -4
- package/lib/common/BasicParser.js +6 -0
- package/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/lib/common/CombinedTagMapper.d.ts +5 -5
- package/lib/common/CombinedTagMapper.js +1 -1
- package/lib/common/FourCC.d.ts +1 -1
- package/lib/common/GenericTagMapper.d.ts +8 -8
- package/lib/common/GenericTagMapper.js +4 -4
- package/lib/common/GenericTagTypes.d.ts +5 -4
- package/lib/common/GenericTagTypes.js +2 -2
- package/lib/common/MetadataCollector.d.ts +9 -9
- package/lib/common/MetadataCollector.js +22 -17
- package/lib/common/RandomFileReader.d.ts +1 -1
- package/lib/common/RandomFileReader.js +1 -1
- package/lib/common/RandomUint8ArrayReader.d.ts +1 -1
- package/lib/common/Util.d.ts +2 -6
- package/lib/common/Util.js +9 -11
- package/lib/core.d.ts +7 -9
- package/lib/core.js +10 -7
- package/lib/dsdiff/DsdiffParser.js +26 -14
- package/lib/dsdiff/DsdiffToken.d.ts +2 -2
- package/lib/dsdiff/DsdiffToken.js +1 -0
- package/lib/dsf/DsfChunk.js +1 -0
- package/lib/dsf/DsfParser.js +4 -2
- package/lib/ebml/EbmlIterator.d.ts +52 -0
- package/lib/ebml/EbmlIterator.js +220 -0
- package/lib/ebml/types.d.ts +36 -0
- package/lib/ebml/types.js +10 -0
- package/lib/flac/FlacParser.d.ts +3 -3
- package/lib/flac/FlacParser.js +9 -12
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +7 -4
- package/lib/id3v2/AbstractID3Parser.d.ts +1 -1
- package/lib/id3v2/AbstractID3Parser.js +2 -1
- package/lib/id3v2/FrameParser.d.ts +28 -3
- package/lib/id3v2/FrameParser.js +50 -28
- package/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/lib/id3v2/ID3v24TagMapper.d.ts +2 -1
- package/lib/id3v2/ID3v24TagMapper.js +23 -16
- package/lib/id3v2/ID3v2Parser.d.ts +2 -2
- package/lib/id3v2/ID3v2Parser.js +18 -8
- package/lib/iff/index.js +1 -0
- package/lib/index.d.ts +5 -6
- package/lib/index.js +7 -8
- package/lib/lyrics3/Lyrics3.d.ts +1 -1
- package/lib/lyrics3/Lyrics3.js +1 -1
- package/lib/matroska/MatroskaDtd.d.ts +2 -2
- package/lib/matroska/MatroskaDtd.js +247 -239
- package/lib/matroska/MatroskaParser.d.ts +10 -24
- package/lib/matroska/MatroskaParser.js +120 -205
- package/lib/matroska/types.d.ts +12 -46
- package/lib/matroska/types.js +0 -9
- package/lib/mp4/Atom.d.ts +3 -3
- package/lib/mp4/Atom.js +4 -7
- package/lib/mp4/AtomToken.js +2 -1
- package/lib/mp4/MP4Parser.js +29 -20
- package/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/lib/mp4/MP4TagMapper.js +1 -1
- package/lib/mpeg/ExtendedLameHeader.d.ts +4 -4
- package/lib/mpeg/ExtendedLameHeader.js +2 -1
- package/lib/mpeg/MpegParser.d.ts +1 -1
- package/lib/mpeg/MpegParser.js +145 -96
- package/lib/mpeg/ReplayGainDataFormat.d.ts +1 -1
- package/lib/mpeg/ReplayGainDataFormat.js +1 -0
- package/lib/mpeg/XingTag.d.ts +4 -4
- package/lib/mpeg/XingTag.js +5 -4
- package/lib/musepack/index.js +1 -0
- package/lib/musepack/sv7/BitReader.js +14 -17
- package/lib/musepack/sv7/MpcSv7Parser.js +6 -1
- package/lib/musepack/sv7/StreamVersion7.js +1 -0
- package/lib/musepack/sv8/MpcSv8Parser.js +6 -2
- package/lib/musepack/sv8/StreamVersion8.d.ts +1 -1
- package/lib/musepack/sv8/StreamVersion8.js +1 -0
- package/lib/ogg/Ogg.d.ts +1 -1
- package/lib/ogg/Ogg.js +1 -0
- package/lib/ogg/OggParser.d.ts +3 -3
- package/lib/ogg/OggParser.js +25 -16
- package/lib/ogg/opus/Opus.d.ts +1 -1
- package/lib/ogg/opus/Opus.js +1 -0
- package/lib/ogg/opus/OpusParser.d.ts +4 -4
- package/lib/ogg/opus/OpusParser.js +2 -0
- package/lib/ogg/speex/Speex.js +1 -0
- package/lib/ogg/speex/SpeexParser.d.ts +4 -4
- package/lib/ogg/speex/SpeexParser.js +1 -0
- package/lib/ogg/theora/Theora.js +1 -0
- package/lib/ogg/theora/TheoraParser.d.ts +4 -4
- package/lib/ogg/theora/TheoraParser.js +1 -0
- package/lib/ogg/vorbis/Vorbis.d.ts +3 -3
- package/lib/ogg/vorbis/Vorbis.js +22 -11
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisDecoder.js +1 -0
- package/lib/ogg/vorbis/VorbisParser.d.ts +4 -4
- package/lib/ogg/vorbis/VorbisParser.js +3 -2
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +2 -2
- package/lib/ogg/vorbis/VorbisTagMapper.js +2 -2
- package/lib/riff/RiffChunk.d.ts +3 -3
- package/lib/riff/RiffChunk.js +1 -0
- package/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/lib/type.d.ts +44 -25
- package/lib/wav/BwfChunk.js +1 -0
- package/lib/wav/WaveChunk.d.ts +1 -1
- package/lib/wav/WaveChunk.js +1 -0
- package/lib/wav/WaveParser.js +27 -17
- package/lib/wavpack/WavPackParser.d.ts +1 -1
- package/lib/wavpack/WavPackParser.js +22 -13
- package/lib/wavpack/WavPackToken.d.ts +13 -17
- package/lib/wavpack/WavPackToken.js +22 -23
- package/package.json +139 -150
|
@@ -39,7 +39,7 @@ export class CombinedTagMapper {
|
|
|
39
39
|
if (tagMapper) {
|
|
40
40
|
return this.tagMappers[tagType].mapGenericTag(tag, warnings);
|
|
41
41
|
}
|
|
42
|
-
throw new Error(
|
|
42
|
+
throw new Error(`No generic tag mapper defined for tag-format: ${tagType}`);
|
|
43
43
|
}
|
|
44
44
|
registerTagMapper(genericTagMapper) {
|
|
45
45
|
for (const tagType of genericTagMapper.tagTypes) {
|
package/lib/common/FourCC.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as generic from './GenericTagTypes.js';
|
|
2
|
-
import { ITag } from '../type.js';
|
|
3
|
-
import { INativeMetadataCollector, IWarningCollector } from './MetadataCollector.js';
|
|
1
|
+
import type * as generic from './GenericTagTypes.js';
|
|
2
|
+
import type { ITag } from '../type.js';
|
|
3
|
+
import type { INativeMetadataCollector, IWarningCollector } from './MetadataCollector.js';
|
|
4
4
|
export interface IGenericTagMapper {
|
|
5
5
|
/**
|
|
6
6
|
* Which tagType is able to map to the generic mapping format
|
|
@@ -16,16 +16,16 @@ export interface IGenericTagMapper {
|
|
|
16
16
|
* @param warnings Register warnings
|
|
17
17
|
* @return Generic tag, if native tag could be mapped
|
|
18
18
|
*/
|
|
19
|
-
mapGenericTag(tag: ITag, warnings: INativeMetadataCollector): generic.IGenericTag;
|
|
19
|
+
mapGenericTag(tag: ITag, warnings: INativeMetadataCollector): generic.IGenericTag | null;
|
|
20
20
|
}
|
|
21
21
|
export declare class CommonTagMapper implements IGenericTagMapper {
|
|
22
22
|
tagTypes: generic.TagType[];
|
|
23
23
|
tagMap: generic.INativeTagMap;
|
|
24
24
|
static maxRatingScore: number;
|
|
25
|
-
static toIntOrNull(str: string): number;
|
|
25
|
+
static toIntOrNull(str: string): number | null;
|
|
26
26
|
static normalizeTrack(origVal: number | string): {
|
|
27
|
-
no: number;
|
|
28
|
-
of: number;
|
|
27
|
+
no: number | null;
|
|
28
|
+
of: number | null;
|
|
29
29
|
};
|
|
30
30
|
constructor(tagTypes: generic.TagType[], tagMap: generic.INativeTagMap);
|
|
31
31
|
/**
|
|
@@ -35,7 +35,7 @@ export declare class CommonTagMapper implements IGenericTagMapper {
|
|
|
35
35
|
* @param warnings Register warnings
|
|
36
36
|
* @return common name
|
|
37
37
|
*/
|
|
38
|
-
mapGenericTag(tag: ITag, warnings: IWarningCollector): generic.IGenericTag;
|
|
38
|
+
mapGenericTag(tag: ITag, warnings: IWarningCollector): generic.IGenericTag | null;
|
|
39
39
|
/**
|
|
40
40
|
* Convert native tag key to common tag key
|
|
41
41
|
* @param tag Native header tag
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class CommonTagMapper {
|
|
2
2
|
static toIntOrNull(str) {
|
|
3
|
-
const cleaned = parseInt(str, 10);
|
|
4
|
-
return isNaN(cleaned) ? null : cleaned;
|
|
3
|
+
const cleaned = Number.parseInt(str, 10);
|
|
4
|
+
return Number.isNaN(cleaned) ? null : cleaned;
|
|
5
5
|
}
|
|
6
6
|
// TODO: a string of 1of1 would fail to be converted
|
|
7
7
|
// converts 1/10 to no : 1, of : 10
|
|
@@ -9,8 +9,8 @@ export class CommonTagMapper {
|
|
|
9
9
|
static normalizeTrack(origVal) {
|
|
10
10
|
const split = origVal.toString().split('/');
|
|
11
11
|
return {
|
|
12
|
-
no: parseInt(split[0], 10) || null,
|
|
13
|
-
of: parseInt(split[1], 10) || null
|
|
12
|
+
no: Number.parseInt(split[0], 10) || null,
|
|
13
|
+
of: Number.parseInt(split[1], 10) || null
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
constructor(tagTypes, tagMap) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { AnyTagValue, ICommonTagsResult } from '../type.js';
|
|
1
2
|
export type TagType = 'vorbis' | 'ID3v1' | 'ID3v2.2' | 'ID3v2.3' | 'ID3v2.4' | 'APEv2' | 'asf' | 'iTunes' | 'exif' | 'matroska' | 'AIFF';
|
|
2
3
|
export interface IGenericTag {
|
|
3
|
-
id:
|
|
4
|
-
value:
|
|
4
|
+
id: keyof ICommonTagsResult;
|
|
5
|
+
value: AnyTagValue;
|
|
5
6
|
}
|
|
6
7
|
export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'releasedate' | '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
8
|
export interface INativeTagMap {
|
|
@@ -25,9 +26,9 @@ export declare const commonTags: ITagInfoMap;
|
|
|
25
26
|
* @param alias Name of common tag
|
|
26
27
|
* @returns {boolean|*} true if given alias is mapped as a singleton', otherwise false
|
|
27
28
|
*/
|
|
28
|
-
export declare function isSingleton(alias:
|
|
29
|
+
export declare function isSingleton(alias: keyof ICommonTagsResult): boolean;
|
|
29
30
|
/**
|
|
30
31
|
* @param alias Common (generic) tag
|
|
31
32
|
* @returns {boolean|*} true if given alias is a singleton or explicitly marked as unique
|
|
32
33
|
*/
|
|
33
|
-
export declare function isUnique(alias:
|
|
34
|
+
export declare function isUnique(alias: keyof ICommonTagsResult): boolean;
|
|
@@ -116,13 +116,13 @@ export const commonTags = {
|
|
|
116
116
|
* @returns {boolean|*} true if given alias is mapped as a singleton', otherwise false
|
|
117
117
|
*/
|
|
118
118
|
export function isSingleton(alias) {
|
|
119
|
-
return commonTags
|
|
119
|
+
return commonTags[alias] && !commonTags[alias].multiple;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* @param alias Common (generic) tag
|
|
123
123
|
* @returns {boolean|*} true if given alias is a singleton or explicitly marked as unique
|
|
124
124
|
*/
|
|
125
125
|
export function isUnique(alias) {
|
|
126
|
-
return !commonTags[alias].multiple || commonTags[alias].unique;
|
|
126
|
+
return !commonTags[alias].multiple || commonTags[alias].unique || false;
|
|
127
127
|
}
|
|
128
128
|
//# sourceMappingURL=GenericTagTypes.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormatId, IAudioMetadata, ICommonTagsResult, IFormat, INativeTags, IOptions, IQualityInformation, ITrackInfo } from '../type.js';
|
|
2
|
-
import { IGenericTag, TagType } from './GenericTagTypes.js';
|
|
1
|
+
import { type FormatId, type IAudioMetadata, type ICommonTagsResult, type IFormat, type INativeTags, type IOptions, type IQualityInformation, type ITrackInfo, type AnyTagValue } from '../type.js';
|
|
2
|
+
import { type IGenericTag, type TagType } from './GenericTagTypes.js';
|
|
3
3
|
/**
|
|
4
4
|
* Combines all generic-tag-mappers for each tag type
|
|
5
5
|
*/
|
|
@@ -8,7 +8,7 @@ export interface IWarningCollector {
|
|
|
8
8
|
* Register parser warning
|
|
9
9
|
* @param warning
|
|
10
10
|
*/
|
|
11
|
-
addWarning(warning: string):
|
|
11
|
+
addWarning(warning: string): void;
|
|
12
12
|
}
|
|
13
13
|
export interface INativeMetadataCollector extends IWarningCollector {
|
|
14
14
|
/**
|
|
@@ -21,8 +21,8 @@ export interface INativeMetadataCollector extends IWarningCollector {
|
|
|
21
21
|
* @returns {boolean} true if one or more tags have been found
|
|
22
22
|
*/
|
|
23
23
|
hasAny(): boolean;
|
|
24
|
-
setFormat(key: FormatId, value:
|
|
25
|
-
addTag(tagType: TagType, tagId: string, value:
|
|
24
|
+
setFormat(key: FormatId, value: AnyTagValue): void;
|
|
25
|
+
addTag(tagType: TagType, tagId: string, value: AnyTagValue): Promise<void>;
|
|
26
26
|
addStreamInfo(streamInfo: ITrackInfo): void;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -30,7 +30,7 @@ export interface INativeMetadataCollector extends IWarningCollector {
|
|
|
30
30
|
* Responsible for triggering async updates
|
|
31
31
|
*/
|
|
32
32
|
export declare class MetadataCollector implements INativeMetadataCollector {
|
|
33
|
-
private opts
|
|
33
|
+
private opts?;
|
|
34
34
|
readonly format: IFormat;
|
|
35
35
|
readonly native: INativeTags;
|
|
36
36
|
readonly common: ICommonTagsResult;
|
|
@@ -44,14 +44,14 @@ export declare class MetadataCollector implements INativeMetadataCollector {
|
|
|
44
44
|
*/
|
|
45
45
|
private readonly originPriority;
|
|
46
46
|
private tagMapper;
|
|
47
|
-
constructor(opts
|
|
47
|
+
constructor(opts?: IOptions | undefined);
|
|
48
48
|
/**
|
|
49
49
|
* @returns {boolean} true if one or more tags have been found
|
|
50
50
|
*/
|
|
51
51
|
hasAny(): boolean;
|
|
52
52
|
addStreamInfo(streamInfo: ITrackInfo): void;
|
|
53
|
-
setFormat(key: FormatId, value:
|
|
54
|
-
addTag(tagType: TagType, tagId: string, value:
|
|
53
|
+
setFormat(key: FormatId, value: AnyTagValue): void;
|
|
54
|
+
addTag(tagType: TagType, tagId: string, value: AnyTagValue): Promise<void>;
|
|
55
55
|
addWarning(warning: string): void;
|
|
56
56
|
postMap(tagType: TagType | 'artificial', tag: IGenericTag): Promise<void>;
|
|
57
57
|
/**
|
|
@@ -22,7 +22,7 @@ export class MetadataCollector {
|
|
|
22
22
|
this.common = {
|
|
23
23
|
track: { no: null, of: null },
|
|
24
24
|
disk: { no: null, of: null },
|
|
25
|
-
movementIndex: {}
|
|
25
|
+
movementIndex: { no: null, of: null }
|
|
26
26
|
};
|
|
27
27
|
this.quality = {
|
|
28
28
|
warnings: []
|
|
@@ -50,13 +50,13 @@ export class MetadataCollector {
|
|
|
50
50
|
return Object.keys(this.native).length > 0;
|
|
51
51
|
}
|
|
52
52
|
addStreamInfo(streamInfo) {
|
|
53
|
-
debug(`streamInfo: type=${TrackType[streamInfo.type]}, codec=${streamInfo.codecName}`);
|
|
53
|
+
debug(`streamInfo: type=${streamInfo.type ? TrackType[streamInfo.type] : '?'}, codec=${streamInfo.codecName}`);
|
|
54
54
|
this.format.trackInfo.push(streamInfo);
|
|
55
55
|
}
|
|
56
56
|
setFormat(key, value) {
|
|
57
57
|
debug(`format: ${key} = ${value}`);
|
|
58
58
|
this.format[key] = value; // as any to override readonly
|
|
59
|
-
if (this.opts
|
|
59
|
+
if (this.opts?.observer) {
|
|
60
60
|
this.opts.observer({ metadata: this, tag: { type: 'format', id: key, value } });
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -117,29 +117,31 @@ export class MetadataCollector {
|
|
|
117
117
|
return;
|
|
118
118
|
case 'track':
|
|
119
119
|
case 'disk':
|
|
120
|
-
case 'movementIndex':
|
|
120
|
+
case 'movementIndex': {
|
|
121
121
|
const of = this.common[tag.id].of; // store of value, maybe maybe overwritten
|
|
122
122
|
this.common[tag.id] = CommonTagMapper.normalizeTrack(tag.value);
|
|
123
123
|
this.common[tag.id].of = of != null ? of : this.common[tag.id].of;
|
|
124
124
|
return;
|
|
125
|
+
}
|
|
125
126
|
case 'bpm':
|
|
126
127
|
case 'year':
|
|
127
128
|
case 'originalyear':
|
|
128
|
-
tag.value = parseInt(tag.value, 10);
|
|
129
|
+
tag.value = Number.parseInt(tag.value, 10);
|
|
129
130
|
break;
|
|
130
|
-
case 'date':
|
|
131
|
+
case 'date': {
|
|
131
132
|
// ToDo: be more strict on 'YYYY...'
|
|
132
|
-
const year = parseInt(tag.value.substr(0, 4), 10);
|
|
133
|
-
if (!isNaN(year)) {
|
|
133
|
+
const year = Number.parseInt(tag.value.substr(0, 4), 10);
|
|
134
|
+
if (!Number.isNaN(year)) {
|
|
134
135
|
this.common.year = year;
|
|
135
136
|
}
|
|
136
137
|
break;
|
|
138
|
+
}
|
|
137
139
|
case 'discogs_label_id':
|
|
138
140
|
case 'discogs_release_id':
|
|
139
141
|
case 'discogs_master_release_id':
|
|
140
142
|
case 'discogs_artist_id':
|
|
141
143
|
case 'discogs_votes':
|
|
142
|
-
tag.value = typeof tag.value === 'string' ? parseInt(tag.value, 10) : tag.value;
|
|
144
|
+
tag.value = typeof tag.value === 'string' ? Number.parseInt(tag.value, 10) : tag.value;
|
|
143
145
|
break;
|
|
144
146
|
case 'replaygain_track_gain':
|
|
145
147
|
case 'replaygain_track_peak':
|
|
@@ -148,25 +150,28 @@ export class MetadataCollector {
|
|
|
148
150
|
tag.value = toRatio(tag.value);
|
|
149
151
|
break;
|
|
150
152
|
case 'replaygain_track_minmax':
|
|
151
|
-
tag.value = tag.value.split(',').map(v => parseInt(v, 10));
|
|
153
|
+
tag.value = tag.value.split(',').map(v => Number.parseInt(v, 10));
|
|
152
154
|
break;
|
|
153
|
-
case 'replaygain_undo':
|
|
154
|
-
const minMix = tag.value.split(',').map(v => parseInt(v, 10));
|
|
155
|
+
case 'replaygain_undo': {
|
|
156
|
+
const minMix = tag.value.split(',').map(v => Number.parseInt(v, 10));
|
|
155
157
|
tag.value = {
|
|
156
158
|
leftChannel: minMix[0],
|
|
157
159
|
rightChannel: minMix[1]
|
|
158
160
|
};
|
|
159
161
|
break;
|
|
162
|
+
}
|
|
160
163
|
case 'gapless': // iTunes gap-less flag
|
|
161
164
|
case 'compilation':
|
|
162
165
|
case 'podcast':
|
|
163
166
|
case 'showMovement':
|
|
164
167
|
tag.value = tag.value === '1' || tag.value === 1; // boolean
|
|
165
168
|
break;
|
|
166
|
-
case 'isrc': // Only keep unique values
|
|
167
|
-
|
|
169
|
+
case 'isrc': { // Only keep unique values
|
|
170
|
+
const commonTag = this.common[tag.id];
|
|
171
|
+
if (commonTag && commonTag.indexOf(tag.value) !== -1)
|
|
168
172
|
return;
|
|
169
173
|
break;
|
|
174
|
+
}
|
|
170
175
|
case 'comment':
|
|
171
176
|
if (typeof tag.value === 'string') {
|
|
172
177
|
tag.value = { text: tag.value };
|
|
@@ -216,7 +221,7 @@ export class MetadataCollector {
|
|
|
216
221
|
}
|
|
217
222
|
return picture;
|
|
218
223
|
}
|
|
219
|
-
this.addWarning(
|
|
224
|
+
this.addWarning("Empty picture tag found");
|
|
220
225
|
return null;
|
|
221
226
|
}
|
|
222
227
|
/**
|
|
@@ -263,7 +268,7 @@ export class MetadataCollector {
|
|
|
263
268
|
return debug(`Ignore native tag (list): ${tagType}.${tag.id} = ${tag.value}`);
|
|
264
269
|
}
|
|
265
270
|
}
|
|
266
|
-
if (this.opts
|
|
271
|
+
if (this.opts?.observer) {
|
|
267
272
|
this.opts.observer({ metadata: this, tag: { type: 'common', id: tag.id, value: tag.value } });
|
|
268
273
|
}
|
|
269
274
|
// ToDo: trigger metadata event
|
|
@@ -271,7 +276,7 @@ export class MetadataCollector {
|
|
|
271
276
|
}
|
|
272
277
|
export function joinArtists(artists) {
|
|
273
278
|
if (artists.length > 2) {
|
|
274
|
-
return artists.slice(0, artists.length - 1).join(', ')
|
|
279
|
+
return `${artists.slice(0, artists.length - 1).join(', ')} & ${artists[artists.length - 1]}`;
|
|
275
280
|
}
|
|
276
281
|
return artists.join(' & ');
|
|
277
282
|
}
|
package/lib/common/Util.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { IRatio } from '../type.js';
|
|
1
|
+
import type { IRatio } from '../type.js';
|
|
2
2
|
export type StringEncoding = 'ascii' | 'utf8' | 'utf-16le' | 'ucs2' | 'base64url' | 'latin1' | 'hex';
|
|
3
|
-
export interface ITextEncoding {
|
|
4
|
-
encoding: StringEncoding;
|
|
5
|
-
bom?: boolean;
|
|
6
|
-
}
|
|
7
3
|
export declare function getBit(buf: Uint8Array, off: number, bit: number): boolean;
|
|
8
4
|
/**
|
|
9
5
|
* Found delimiting zero in uint8Array
|
|
@@ -54,4 +50,4 @@ export declare function dbToRatio(dB: number): number;
|
|
|
54
50
|
* Convert replay gain to ratio and Decibel
|
|
55
51
|
* @param value string holding a ratio like '0.034' or '-7.54 dB'
|
|
56
52
|
*/
|
|
57
|
-
export declare function toRatio(value: string): IRatio;
|
|
53
|
+
export declare function toRatio(value: string): IRatio | undefined;
|
package/lib/common/Util.js
CHANGED
|
@@ -20,14 +20,12 @@ export function findZero(uint8Array, start, end, encoding) {
|
|
|
20
20
|
}
|
|
21
21
|
return i;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
i++;
|
|
28
|
-
}
|
|
29
|
-
return i;
|
|
23
|
+
while (uint8Array[i] !== 0) {
|
|
24
|
+
if (i >= end)
|
|
25
|
+
return end;
|
|
26
|
+
i++;
|
|
30
27
|
}
|
|
28
|
+
return i;
|
|
31
29
|
}
|
|
32
30
|
export function trimRightNull(x) {
|
|
33
31
|
const pos0 = x.indexOf('\0');
|
|
@@ -53,7 +51,7 @@ export function decodeString(uint8Array, encoding) {
|
|
|
53
51
|
if (uint8Array[0] === 0xFF && uint8Array[1] === 0xFE) { // little endian
|
|
54
52
|
return decodeString(uint8Array.subarray(2), encoding);
|
|
55
53
|
}
|
|
56
|
-
|
|
54
|
+
if (encoding === 'utf-16le' && uint8Array[0] === 0xFE && uint8Array[1] === 0xFF) {
|
|
57
55
|
// BOM, indicating big endian decoding
|
|
58
56
|
if ((uint8Array.length & 1) !== 0)
|
|
59
57
|
throw new Error('Expected even number of octets for 16-bit unicode string');
|
|
@@ -106,7 +104,7 @@ export function a2hex(str) {
|
|
|
106
104
|
const arr = [];
|
|
107
105
|
for (let i = 0, l = str.length; i < l; i++) {
|
|
108
106
|
const hex = Number(str.charCodeAt(i)).toString(16);
|
|
109
|
-
arr.push(hex.length === 1 ?
|
|
107
|
+
arr.push(hex.length === 1 ? `0${hex}` : hex);
|
|
110
108
|
}
|
|
111
109
|
return arr.join(' ');
|
|
112
110
|
}
|
|
@@ -122,7 +120,7 @@ export function ratioToDb(ratio) {
|
|
|
122
120
|
* db Decibels
|
|
123
121
|
*/
|
|
124
122
|
export function dbToRatio(dB) {
|
|
125
|
-
return
|
|
123
|
+
return 10 ** (dB / 10);
|
|
126
124
|
}
|
|
127
125
|
/**
|
|
128
126
|
* Convert replay gain to ratio and Decibel
|
|
@@ -132,7 +130,7 @@ export function toRatio(value) {
|
|
|
132
130
|
const ps = value.split(' ').map(p => p.trim().toLowerCase());
|
|
133
131
|
// @ts-ignore
|
|
134
132
|
if (ps.length >= 1) {
|
|
135
|
-
const v = parseFloat(ps[0]);
|
|
133
|
+
const v = Number.parseFloat(ps[0]);
|
|
136
134
|
return ps.length === 2 && ps[1] === 'db' ? {
|
|
137
135
|
dB: v,
|
|
138
136
|
ratio: dbToRatio(v)
|
package/lib/core.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Primary entry point, Node.js specific entry point is index.ts
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { type AnyWebByteStream, type IFileInfo, type ITokenizer } from 'strtok3';
|
|
5
5
|
import type { IAudioMetadata, INativeTagDict, IOptions, IPicture, IPrivateOptions, IRandomReader, ITag } from './type.js';
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
export { IAudioMetadata, IOptions, ITag, INativeTagDict, ICommonTagsResult, IFormat, IPicture, IRatio, IChapter, ILyricsTag, LyricsContentType, TimestampFormat } from './type.js';
|
|
9
|
-
export type AnyWebStream<G> = NodeReadableStream<G> | ReadableStream<G>;
|
|
6
|
+
export type { IFileInfo } from 'strtok3';
|
|
7
|
+
export { type IAudioMetadata, type IOptions, type ITag, type INativeTagDict, type ICommonTagsResult, type IFormat, type IPicture, type IRatio, type IChapter, type ILyricsTag, LyricsContentType, TimestampFormat, IMetadataEventTag, IMetadataEvent } from './type.js';
|
|
10
8
|
/**
|
|
11
9
|
* Parse Web API File
|
|
12
10
|
* Requires Blob to be able to stream using a ReadableStreamBYOBReader, only available since Node.js ≥ 20
|
|
@@ -22,7 +20,7 @@ export declare function parseBlob(blob: Blob, options?: IOptions): Promise<IAudi
|
|
|
22
20
|
* @param fileInfo - File information object or MIME-type string
|
|
23
21
|
* @returns Metadata
|
|
24
22
|
*/
|
|
25
|
-
export declare function parseWebStream(webStream:
|
|
23
|
+
export declare function parseWebStream(webStream: AnyWebByteStream, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
|
|
26
24
|
/**
|
|
27
25
|
* Parse audio from Node Buffer
|
|
28
26
|
* @param uint8Array - Uint8Array holding audio data
|
|
@@ -31,14 +29,14 @@ export declare function parseWebStream(webStream: AnyWebStream<Uint8Array>, file
|
|
|
31
29
|
* @returns Metadata
|
|
32
30
|
* Ref: https://github.com/Borewit/strtok3/blob/e6938c81ff685074d5eb3064a11c0b03ca934c1d/src/index.ts#L15
|
|
33
31
|
*/
|
|
34
|
-
export declare function parseBuffer(uint8Array: Uint8Array, fileInfo?:
|
|
32
|
+
export declare function parseBuffer(uint8Array: Uint8Array, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
|
|
35
33
|
/**
|
|
36
34
|
* Parse audio from ITokenizer source
|
|
37
35
|
* @param tokenizer - Audio source implementing the tokenizer interface
|
|
38
36
|
* @param options - Parsing options
|
|
39
37
|
* @returns Metadata
|
|
40
38
|
*/
|
|
41
|
-
export declare function parseFromTokenizer(tokenizer:
|
|
39
|
+
export declare function parseFromTokenizer(tokenizer: ITokenizer, options?: IOptions): Promise<IAudioMetadata>;
|
|
42
40
|
/**
|
|
43
41
|
* Create a dictionary ordered by their tag id (key)
|
|
44
42
|
* @param nativeTags list of tags
|
|
@@ -50,7 +48,7 @@ export declare function orderTags(nativeTags: ITag[]): INativeTagDict;
|
|
|
50
48
|
* @param rating Normalized rating [0..1] (common.rating[n].rating)
|
|
51
49
|
* @returns Number of stars: 1, 2, 3, 4 or 5 stars
|
|
52
50
|
*/
|
|
53
|
-
export declare function ratingToStars(rating: number): number;
|
|
51
|
+
export declare function ratingToStars(rating: number | undefined): number;
|
|
54
52
|
/**
|
|
55
53
|
* Select most likely cover image.
|
|
56
54
|
* @param pictures Usually metadata.common.picture
|
package/lib/core.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Primary entry point, Node.js specific entry point is index.ts
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import { fromWebStream, fromBuffer } from 'strtok3';
|
|
5
|
+
import { parseOnContentType } from './ParserFactory.js';
|
|
6
6
|
import { RandomUint8ArrayReader } from './common/RandomUint8ArrayReader.js';
|
|
7
7
|
import { APEv2Parser } from './apev2/APEv2Parser.js';
|
|
8
8
|
import { hasID3v1Header } from './id3v1/ID3v1Parser.js';
|
|
@@ -30,7 +30,7 @@ export async function parseBlob(blob, options = {}) {
|
|
|
30
30
|
* @returns Metadata
|
|
31
31
|
*/
|
|
32
32
|
export function parseWebStream(webStream, fileInfo, options = {}) {
|
|
33
|
-
return parseFromTokenizer(
|
|
33
|
+
return parseFromTokenizer(fromWebStream(webStream, { fileInfo: typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo }), options);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Parse audio from Node Buffer
|
|
@@ -43,7 +43,7 @@ export function parseWebStream(webStream, fileInfo, options = {}) {
|
|
|
43
43
|
export async function parseBuffer(uint8Array, fileInfo, options = {}) {
|
|
44
44
|
const bufferReader = new RandomUint8ArrayReader(uint8Array);
|
|
45
45
|
await scanAppendingHeaders(bufferReader, options);
|
|
46
|
-
const tokenizer =
|
|
46
|
+
const tokenizer = fromBuffer(uint8Array, { fileInfo: typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo });
|
|
47
47
|
return parseFromTokenizer(tokenizer, options);
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -53,7 +53,7 @@ export async function parseBuffer(uint8Array, fileInfo, options = {}) {
|
|
|
53
53
|
* @returns Metadata
|
|
54
54
|
*/
|
|
55
55
|
export function parseFromTokenizer(tokenizer, options) {
|
|
56
|
-
return
|
|
56
|
+
return parseOnContentType(tokenizer, options);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Create a dictionary ordered by their tag id (key)
|
|
@@ -62,8 +62,11 @@ export function parseFromTokenizer(tokenizer, options) {
|
|
|
62
62
|
*/
|
|
63
63
|
export function orderTags(nativeTags) {
|
|
64
64
|
const tags = {};
|
|
65
|
-
for (const
|
|
66
|
-
|
|
65
|
+
for (const { id, value } of nativeTags) {
|
|
66
|
+
if (!tags[id]) {
|
|
67
|
+
tags[id] = [];
|
|
68
|
+
}
|
|
69
|
+
tags[id].push(value);
|
|
67
70
|
}
|
|
68
71
|
return tags;
|
|
69
72
|
}
|
|
@@ -24,7 +24,7 @@ export class DsdiffParser extends BasicParser {
|
|
|
24
24
|
this.metadata.setFormat('lossless', true);
|
|
25
25
|
return this.readFmt8Chunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
26
26
|
default:
|
|
27
|
-
throw Error(`Unsupported DSDIFF type: ${type}`);
|
|
27
|
+
throw new Error(`Unsupported DSDIFF type: ${type}`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
async readFmt8Chunks(remainingSize) {
|
|
@@ -40,28 +40,35 @@ export class DsdiffParser extends BasicParser {
|
|
|
40
40
|
debug(`Reading data of chunk[ID=${header.chunkID}, size=${header.chunkSize}]`);
|
|
41
41
|
const p0 = this.tokenizer.position;
|
|
42
42
|
switch (header.chunkID.trim()) {
|
|
43
|
-
case 'FVER': // 3.1 FORMAT VERSION CHUNK
|
|
43
|
+
case 'FVER': { // 3.1 FORMAT VERSION CHUNK
|
|
44
44
|
const version = await this.tokenizer.readToken(Token.UINT32_LE);
|
|
45
45
|
debug(`DSDIFF version=${version}`);
|
|
46
46
|
break;
|
|
47
|
-
|
|
47
|
+
}
|
|
48
|
+
case 'PROP': { // 3.2 PROPERTY CHUNK
|
|
48
49
|
const propType = await this.tokenizer.readToken(FourCcToken);
|
|
49
50
|
if (propType !== 'SND ')
|
|
50
51
|
throw new Error('Unexpected PROP-chunk ID');
|
|
51
52
|
await this.handleSoundPropertyChunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
52
53
|
break;
|
|
53
|
-
|
|
54
|
+
}
|
|
55
|
+
case 'ID3': { // Unofficial ID3 tag support
|
|
54
56
|
const id3_data = await this.tokenizer.readToken(new Token.Uint8ArrayType(Number(header.chunkSize)));
|
|
55
57
|
const rst = strtok3.fromBuffer(id3_data);
|
|
56
58
|
await new ID3v2Parser().parse(this.metadata, rst, this.options);
|
|
57
59
|
break;
|
|
60
|
+
}
|
|
61
|
+
case 'DSD':
|
|
62
|
+
if (this.metadata.format.numberOfChannels) {
|
|
63
|
+
this.metadata.setFormat('numberOfSamples', Number(header.chunkSize * BigInt(8) / BigInt(this.metadata.format.numberOfChannels)));
|
|
64
|
+
}
|
|
65
|
+
if (this.metadata.format.numberOfSamples && this.metadata.format.sampleRate) {
|
|
66
|
+
this.metadata.setFormat('duration', this.metadata.format.numberOfSamples / this.metadata.format.sampleRate);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
58
69
|
default:
|
|
59
70
|
debug(`Ignore chunk[ID=${header.chunkID}, size=${header.chunkSize}]`);
|
|
60
71
|
break;
|
|
61
|
-
case 'DSD':
|
|
62
|
-
this.metadata.setFormat('numberOfSamples', Number(header.chunkSize * BigInt(8) / BigInt(this.metadata.format.numberOfChannels)));
|
|
63
|
-
this.metadata.setFormat('duration', this.metadata.format.numberOfSamples / this.metadata.format.sampleRate);
|
|
64
|
-
break;
|
|
65
72
|
}
|
|
66
73
|
const remaining = header.chunkSize - BigInt(this.tokenizer.position - p0);
|
|
67
74
|
if (remaining > 0) {
|
|
@@ -76,16 +83,18 @@ export class DsdiffParser extends BasicParser {
|
|
|
76
83
|
debug(`Sound-property-chunk[ID=${sndPropHeader.chunkID}, size=${sndPropHeader.chunkSize}]`);
|
|
77
84
|
const p0 = this.tokenizer.position;
|
|
78
85
|
switch (sndPropHeader.chunkID.trim()) {
|
|
79
|
-
case 'FS': // 3.2.1 Sample Rate Chunk
|
|
86
|
+
case 'FS': { // 3.2.1 Sample Rate Chunk
|
|
80
87
|
const sampleRate = await this.tokenizer.readToken(Token.UINT32_BE);
|
|
81
88
|
this.metadata.setFormat('sampleRate', sampleRate);
|
|
82
89
|
break;
|
|
83
|
-
|
|
90
|
+
}
|
|
91
|
+
case 'CHNL': { // 3.2.2 Channels Chunk
|
|
84
92
|
const numChannels = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
85
93
|
this.metadata.setFormat('numberOfChannels', numChannels);
|
|
86
94
|
await this.handleChannelChunks(sndPropHeader.chunkSize - BigInt(Token.UINT16_BE.len));
|
|
87
95
|
break;
|
|
88
|
-
|
|
96
|
+
}
|
|
97
|
+
case 'CMPR': { // 3.2.3 Compression Type Chunk
|
|
89
98
|
const compressionIdCode = (await this.tokenizer.readToken(FourCcToken)).trim();
|
|
90
99
|
const count = await this.tokenizer.readToken(Token.UINT8);
|
|
91
100
|
const compressionName = await this.tokenizer.readToken(new Token.StringType(count, 'ascii'));
|
|
@@ -95,18 +104,20 @@ export class DsdiffParser extends BasicParser {
|
|
|
95
104
|
}
|
|
96
105
|
this.metadata.setFormat('codec', `${compressionIdCode} (${compressionName})`);
|
|
97
106
|
break;
|
|
98
|
-
|
|
107
|
+
}
|
|
108
|
+
case 'ABSS': { // 3.2.4 Absolute Start Time Chunk
|
|
99
109
|
const hours = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
100
110
|
const minutes = await this.tokenizer.readToken(Token.UINT8);
|
|
101
111
|
const seconds = await this.tokenizer.readToken(Token.UINT8);
|
|
102
112
|
const samples = await this.tokenizer.readToken(Token.UINT32_BE);
|
|
103
113
|
debug(`ABSS ${hours}:${minutes}:${seconds}.${samples}`);
|
|
104
114
|
break;
|
|
105
|
-
|
|
115
|
+
}
|
|
116
|
+
case 'LSCO': { // 3.2.5 Loudspeaker Configuration Chunk
|
|
106
117
|
const lsConfig = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
107
118
|
debug(`LSCO lsConfig=${lsConfig}`);
|
|
108
119
|
break;
|
|
109
|
-
|
|
120
|
+
}
|
|
110
121
|
default:
|
|
111
122
|
debug(`Unknown sound-property-chunk[ID=${sndPropHeader.chunkID}, size=${sndPropHeader.chunkSize}]`);
|
|
112
123
|
await this.tokenizer.ignore(Number(sndPropHeader.chunkSize));
|
|
@@ -137,3 +148,4 @@ export class DsdiffParser extends BasicParser {
|
|
|
137
148
|
return channels;
|
|
138
149
|
}
|
|
139
150
|
}
|
|
151
|
+
//# sourceMappingURL=DsdiffParser.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IGetToken } from 'strtok3';
|
|
2
|
-
import { IChunkHeader64 } from '../iff/index.js';
|
|
3
|
-
export { IChunkHeader64 } from '../iff/index.js';
|
|
2
|
+
import type { IChunkHeader64 } from '../iff/index.js';
|
|
3
|
+
export { type IChunkHeader64 } from '../iff/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* DSDIFF chunk header
|
|
6
6
|
* The data-size encoding is deviating from EA-IFF 85
|
package/lib/dsf/DsfChunk.js
CHANGED