music-metadata 11.10.5 → 11.11.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/README.md CHANGED
@@ -1,12 +1,11 @@
1
1
  [![CI](https://github.com/Borewit/music-metadata/actions/workflows/ci.yml/badge.svg)](https://github.com/Borewit/music-metadata/actions/workflows/ci.yml)
2
2
  [![NPM version](https://img.shields.io/npm/v/music-metadata.svg)](https://npmjs.org/package/music-metadata)
3
- [![npm downloads](http://img.shields.io/npm/dm/music-metadata.svg)](https://npmcharts.com/compare/music-metadata?start=600&interval=30)
3
+ [![npm downloads](http://img.shields.io/npm/dm/music-metadata.svg)](https://npmcharts.com/compare/music-metadata?start=600&interval=7)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/Borewit/music-metadata/badge.svg?branch=master)](https://coveralls.io/github/Borewit/music-metadata?branch=master)
5
5
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/57d731b05c9e41889a2a17cb4b0384d7)](https://app.codacy.com/app/Borewit/music-metadata?utm_source=github.com&utm_medium=referral&utm_content=Borewit/music-metadata&utm_campaign=Badge_Grade_Dashboard)
6
6
  [![CodeQL](https://github.com/Borewit/music-metadata/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Borewit/music-metadata/actions/workflows/codeql-analysis.yml)
7
7
  [![DeepScan grade](https://deepscan.io/api/teams/5165/projects/6938/branches/61821/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=5165&pid=6938&bid=61821)
8
8
  [![Known Vulnerabilities](https://snyk.io/test/github/Borewit/music-metadata/badge.svg?targetFile=package.json)](https://snyk.io/test/github/Borewit/music-metadata?targetFile=package.json)
9
- [![Discord](https://img.shields.io/discord/460524735235883049.svg)](https://discord.gg/KyBr6sb)
10
9
 
11
10
  # music-metadata
12
11
 
@@ -74,7 +73,7 @@ Following tag header formats are supported:
74
73
  - [APE](https://wikipedia.org/wiki/APE_tag)
75
74
  - [ASF](https://wikipedia.org/wiki/Advanced_Systems_Format)
76
75
  - EXIF 2.3
77
- - [ID3](https://wikipedia.org/wiki/ID3): ID3v1, ID3v1.1, ID3v2.2, [ID3v2.3](http://id3.org/id3v2.3.0) & [ID3v2.4](http://id3.org/id3v2.4.0-frames)
76
+ - [ID3](https://wikipedia.org/wiki/ID3): ID3v1, ID3v1.1, ID3v2.2, [ID3v2.3](http://id3.org/id3v2.3.0), [ID3v2.4](http://id3.org/id3v2.4.0-frames) and [ID3v2 Chapters 1.0](https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2-chapters-1.0.html)
78
77
  - [iTunes](https://github.com/sergiomb2/libmp4v2/wiki/iTunesMetadata)
79
78
  - [RIFF](https://wikipedia.org/wiki/Resource_Interchange_File_Format)/INFO
80
79
  - [Vorbis comment](https://wikipedia.org/wiki/Vorbis_comment)
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Ref:
3
+ * - https://tools.ietf.org/html/draft-fleischman-asf-01, Appendix A: ASF GUIDs
4
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
5
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
6
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
7
+ *
8
+ * ASF File Structure:
9
+ * - https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
10
+ *
11
+ * ASF GUIDs:
12
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
13
+ * - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
14
+ */
15
+ export default class AsfGuid {
16
+ static HeaderObject: AsfGuid;
17
+ static DataObject: AsfGuid;
18
+ static SimpleIndexObject: AsfGuid;
19
+ static IndexObject: AsfGuid;
20
+ static MediaObjectIndexObject: AsfGuid;
21
+ static TimecodeIndexObject: AsfGuid;
22
+ static FilePropertiesObject: AsfGuid;
23
+ static StreamPropertiesObject: AsfGuid;
24
+ static HeaderExtensionObject: AsfGuid;
25
+ static CodecListObject: AsfGuid;
26
+ static ScriptCommandObject: AsfGuid;
27
+ static MarkerObject: AsfGuid;
28
+ static BitrateMutualExclusionObject: AsfGuid;
29
+ static ErrorCorrectionObject: AsfGuid;
30
+ static ContentDescriptionObject: AsfGuid;
31
+ static ExtendedContentDescriptionObject: AsfGuid;
32
+ static ContentBrandingObject: AsfGuid;
33
+ static StreamBitratePropertiesObject: AsfGuid;
34
+ static ContentEncryptionObject: AsfGuid;
35
+ static ExtendedContentEncryptionObject: AsfGuid;
36
+ static DigitalSignatureObject: AsfGuid;
37
+ static PaddingObject: AsfGuid;
38
+ static ExtendedStreamPropertiesObject: AsfGuid;
39
+ static AdvancedMutualExclusionObject: AsfGuid;
40
+ static GroupMutualExclusionObject: AsfGuid;
41
+ static StreamPrioritizationObject: AsfGuid;
42
+ static BandwidthSharingObject: AsfGuid;
43
+ static LanguageListObject: AsfGuid;
44
+ static MetadataObject: AsfGuid;
45
+ static MetadataLibraryObject: AsfGuid;
46
+ static IndexParametersObject: AsfGuid;
47
+ static MediaObjectIndexParametersObject: AsfGuid;
48
+ static TimecodeIndexParametersObject: AsfGuid;
49
+ static CompatibilityObject: AsfGuid;
50
+ static AdvancedContentEncryptionObject: AsfGuid;
51
+ static AudioMedia: AsfGuid;
52
+ static VideoMedia: AsfGuid;
53
+ static CommandMedia: AsfGuid;
54
+ static JFIF_Media: AsfGuid;
55
+ static Degradable_JPEG_Media: AsfGuid;
56
+ static FileTransferMedia: AsfGuid;
57
+ static BinaryMedia: AsfGuid;
58
+ static ASF_Index_Placeholder_Object: AsfGuid;
59
+ static fromBin(bin: Uint8Array, offset?: number): AsfGuid;
60
+ /**
61
+ * Decode GUID in format like "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
62
+ * @param objectId Binary GUID
63
+ * @param offset Read offset in bytes, default 0
64
+ * @returns GUID as dashed hexadecimal representation
65
+ */
66
+ static decode(objectId: Uint8Array, offset?: number): string;
67
+ /**
68
+ * Decode stream type
69
+ * @param mediaType Media type GUID
70
+ * @returns Media type
71
+ */
72
+ static decodeMediaType(mediaType: AsfGuid): 'audio' | 'video' | 'command' | 'degradable-jpeg' | 'file-transfer' | 'binary' | undefined;
73
+ /**
74
+ * Encode GUID
75
+ * @param guid GUID like: "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
76
+ * @returns Encoded Binary GUID
77
+ */
78
+ static encode(guid: string): Uint8Array;
79
+ str: string;
80
+ constructor(str: string);
81
+ equals(guid: AsfGuid): boolean;
82
+ toBin(): Uint8Array;
83
+ }
@@ -0,0 +1,109 @@
1
+ import { parseWindowsGuid, Guid } from 'win-guid';
2
+ /**
3
+ * Ref:
4
+ * - https://tools.ietf.org/html/draft-fleischman-asf-01, Appendix A: ASF GUIDs
5
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
6
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
7
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
8
+ *
9
+ * ASF File Structure:
10
+ * - https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
11
+ *
12
+ * ASF GUIDs:
13
+ * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
14
+ * - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
15
+ */
16
+ class AsfGuid {
17
+ static fromBin(bin, offset = 0) {
18
+ return new AsfGuid(AsfGuid.decode(bin, offset));
19
+ }
20
+ /**
21
+ * Decode GUID in format like "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
22
+ * @param objectId Binary GUID
23
+ * @param offset Read offset in bytes, default 0
24
+ * @returns GUID as dashed hexadecimal representation
25
+ */
26
+ static decode(objectId, offset = 0) {
27
+ return new Guid(objectId.subarray(offset, offset + 16)).toString();
28
+ }
29
+ /**
30
+ * Decode stream type
31
+ * @param mediaType Media type GUID
32
+ * @returns Media type
33
+ */
34
+ static decodeMediaType(mediaType) {
35
+ switch (mediaType.str) {
36
+ case AsfGuid.AudioMedia.str: return 'audio';
37
+ case AsfGuid.VideoMedia.str: return 'video';
38
+ case AsfGuid.CommandMedia.str: return 'command';
39
+ case AsfGuid.Degradable_JPEG_Media.str: return 'degradable-jpeg';
40
+ case AsfGuid.FileTransferMedia.str: return 'file-transfer';
41
+ case AsfGuid.BinaryMedia.str: return 'binary';
42
+ }
43
+ }
44
+ /**
45
+ * Encode GUID
46
+ * @param guid GUID like: "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
47
+ * @returns Encoded Binary GUID
48
+ */
49
+ static encode(guid) {
50
+ return parseWindowsGuid(guid);
51
+ }
52
+ constructor(str) {
53
+ this.str = str;
54
+ }
55
+ equals(guid) {
56
+ return this.str === guid.str;
57
+ }
58
+ toBin() {
59
+ return AsfGuid.encode(this.str);
60
+ }
61
+ }
62
+ // 10.1 Top-level ASF object GUIDs
63
+ AsfGuid.HeaderObject = new AsfGuid("75B22630-668E-11CF-A6D9-00AA0062CE6C");
64
+ AsfGuid.DataObject = new AsfGuid("75B22636-668E-11CF-A6D9-00AA0062CE6C");
65
+ AsfGuid.SimpleIndexObject = new AsfGuid("33000890-E5B1-11CF-89F4-00A0C90349CB");
66
+ AsfGuid.IndexObject = new AsfGuid("D6E229D3-35DA-11D1-9034-00A0C90349BE");
67
+ AsfGuid.MediaObjectIndexObject = new AsfGuid("FEB103F8-12AD-4C64-840F-2A1D2F7AD48C");
68
+ AsfGuid.TimecodeIndexObject = new AsfGuid("3CB73FD0-0C4A-4803-953D-EDF7B6228F0C");
69
+ // 10.2 Header Object GUIDs
70
+ AsfGuid.FilePropertiesObject = new AsfGuid("8CABDCA1-A947-11CF-8EE4-00C00C205365");
71
+ AsfGuid.StreamPropertiesObject = new AsfGuid("B7DC0791-A9B7-11CF-8EE6-00C00C205365");
72
+ AsfGuid.HeaderExtensionObject = new AsfGuid("5FBF03B5-A92E-11CF-8EE3-00C00C205365");
73
+ AsfGuid.CodecListObject = new AsfGuid("86D15240-311D-11D0-A3A4-00A0C90348F6");
74
+ AsfGuid.ScriptCommandObject = new AsfGuid("1EFB1A30-0B62-11D0-A39B-00A0C90348F6");
75
+ AsfGuid.MarkerObject = new AsfGuid("F487CD01-A951-11CF-8EE6-00C00C205365");
76
+ AsfGuid.BitrateMutualExclusionObject = new AsfGuid("D6E229DC-35DA-11D1-9034-00A0C90349BE");
77
+ AsfGuid.ErrorCorrectionObject = new AsfGuid("75B22635-668E-11CF-A6D9-00AA0062CE6C");
78
+ AsfGuid.ContentDescriptionObject = new AsfGuid("75B22633-668E-11CF-A6D9-00AA0062CE6C");
79
+ AsfGuid.ExtendedContentDescriptionObject = new AsfGuid("D2D0A440-E307-11D2-97F0-00A0C95EA850");
80
+ AsfGuid.ContentBrandingObject = new AsfGuid("2211B3FA-BD23-11D2-B4B7-00A0C955FC6E");
81
+ AsfGuid.StreamBitratePropertiesObject = new AsfGuid("7BF875CE-468D-11D1-8D82-006097C9A2B2");
82
+ AsfGuid.ContentEncryptionObject = new AsfGuid("2211B3FB-BD23-11D2-B4B7-00A0C955FC6E");
83
+ AsfGuid.ExtendedContentEncryptionObject = new AsfGuid("298AE614-2622-4C17-B935-DAE07EE9289C");
84
+ AsfGuid.DigitalSignatureObject = new AsfGuid("2211B3FC-BD23-11D2-B4B7-00A0C955FC6E");
85
+ AsfGuid.PaddingObject = new AsfGuid("1806D474-CADF-4509-A4BA-9AABCB96AAE8");
86
+ // 10.3 Header Extension Object GUIDs
87
+ AsfGuid.ExtendedStreamPropertiesObject = new AsfGuid("14E6A5CB-C672-4332-8399-A96952065B5A");
88
+ AsfGuid.AdvancedMutualExclusionObject = new AsfGuid("A08649CF-4775-4670-8A16-6E35357566CD");
89
+ AsfGuid.GroupMutualExclusionObject = new AsfGuid("D1465A40-5A79-4338-B71B-E36B8FD6C249");
90
+ AsfGuid.StreamPrioritizationObject = new AsfGuid("D4FED15B-88D3-454F-81F0-ED5C45999E24");
91
+ AsfGuid.BandwidthSharingObject = new AsfGuid("A69609E6-517B-11D2-B6AF-00C04FD908E9");
92
+ AsfGuid.LanguageListObject = new AsfGuid("7C4346A9-EFE0-4BFC-B229-393EDE415C85");
93
+ AsfGuid.MetadataObject = new AsfGuid("C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA");
94
+ AsfGuid.MetadataLibraryObject = new AsfGuid("44231C94-9498-49D1-A141-1D134E457054");
95
+ AsfGuid.IndexParametersObject = new AsfGuid("D6E229DF-35DA-11D1-9034-00A0C90349BE");
96
+ AsfGuid.MediaObjectIndexParametersObject = new AsfGuid("6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7");
97
+ AsfGuid.TimecodeIndexParametersObject = new AsfGuid("F55E496D-9797-4B5D-8C8B-604DFE9BFB24");
98
+ AsfGuid.CompatibilityObject = new AsfGuid("26F18B5D-4584-47EC-9F5F-0E651F0452C9");
99
+ AsfGuid.AdvancedContentEncryptionObject = new AsfGuid("43058533-6981-49E6-9B74-AD12CB86D58C");
100
+ // 10.4 Stream Properties Object Stream Type GUIDs
101
+ AsfGuid.AudioMedia = new AsfGuid("F8699E40-5B4D-11CF-A8FD-00805F5C442B");
102
+ AsfGuid.VideoMedia = new AsfGuid("BC19EFC0-5B4D-11CF-A8FD-00805F5C442B");
103
+ AsfGuid.CommandMedia = new AsfGuid("59DACFC0-59E6-11D0-A3AC-00A0C90348F6");
104
+ AsfGuid.JFIF_Media = new AsfGuid("B61BE100-5B4E-11CF-A8FD-00805F5C442B");
105
+ AsfGuid.Degradable_JPEG_Media = new AsfGuid("35907DE0-E415-11CF-A917-00805F5C442B");
106
+ AsfGuid.FileTransferMedia = new AsfGuid("91BD222C-F21C-497A-8B6D-5AA86BFC0185");
107
+ AsfGuid.BinaryMedia = new AsfGuid("3AFB65E2-47EF-40F2-AC2C-70A90D71D343");
108
+ AsfGuid.ASF_Index_Placeholder_Object = new AsfGuid("D9AADE20-7C17-4F9C-BC28-8555DD98E2A2");
109
+ export default AsfGuid;
@@ -1,6 +1,6 @@
1
1
  import type { IGetToken, ITokenizer } from 'strtok3';
2
2
  import type { AnyTagValue, IPicture, ITag } from '../type.js';
3
- import GUID from './GUID.js';
3
+ import AsfGuid from './AsfGuid.js';
4
4
  declare const AsfContentParseError_base: {
5
5
  new (message: string): {
6
6
  readonly fileType: string;
@@ -52,7 +52,7 @@ export interface IAsfObjectHeader {
52
52
  /**
53
53
  * A GUID that identifies the object. 128 bits
54
54
  */
55
- objectId: GUID;
55
+ objectId: AsfGuid;
56
56
  /**
57
57
  * The size of the object (64-bits)
58
58
  */
@@ -95,7 +95,7 @@ export interface IFilePropertiesObject {
95
95
  * The value of this field shall be regenerated every time the file is modified in any way.
96
96
  * The value of this field shall be identical to the value of the File ID field of the Data Object.
97
97
  */
98
- fileId: GUID;
98
+ fileId: AsfGuid;
99
99
  /**
100
100
  * Specifies the size, in bytes, of the entire file.
101
101
  * The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
@@ -186,7 +186,7 @@ export interface IFilePropertiesObject {
186
186
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
187
187
  */
188
188
  export declare class FilePropertiesObject extends State<IFilePropertiesObject> {
189
- static guid: GUID;
189
+ static guid: AsfGuid;
190
190
  get(buf: Uint8Array, off: number): IFilePropertiesObject;
191
191
  }
192
192
  /**
@@ -200,18 +200,18 @@ export interface IStreamPropertiesObject {
200
200
  /**
201
201
  * Error Correction Type
202
202
  */
203
- errorCorrectionType: GUID;
203
+ errorCorrectionType: AsfGuid;
204
204
  }
205
205
  /**
206
206
  * Token for: 3.3 Stream Properties Object (mandatory, one per stream)
207
207
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
208
208
  */
209
209
  export declare class StreamPropertiesObject extends State<IStreamPropertiesObject> {
210
- static guid: GUID;
210
+ static guid: AsfGuid;
211
211
  get(buf: Uint8Array, off: number): IStreamPropertiesObject;
212
212
  }
213
213
  export interface IHeaderExtensionObject {
214
- reserved1: GUID;
214
+ reserved1: AsfGuid;
215
215
  reserved2: number;
216
216
  extensionDataSize: number;
217
217
  }
@@ -220,7 +220,7 @@ export interface IHeaderExtensionObject {
220
220
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
221
221
  */
222
222
  export declare class HeaderExtensionObject implements IGetToken<IHeaderExtensionObject> {
223
- static guid: GUID;
223
+ static guid: AsfGuid;
224
224
  len: number;
225
225
  constructor();
226
226
  get(buf: Uint8Array, off: number): IHeaderExtensionObject;
@@ -244,7 +244,7 @@ export declare function readCodecEntries(tokenizer: ITokenizer): Promise<ICodecE
244
244
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
245
245
  */
246
246
  export declare class ContentDescriptionObjectState extends State<ITag[]> {
247
- static guid: GUID;
247
+ static guid: AsfGuid;
248
248
  private static contentDescTags;
249
249
  get(buf: Uint8Array, off: number): ITag[];
250
250
  }
@@ -253,7 +253,7 @@ export declare class ContentDescriptionObjectState extends State<ITag[]> {
253
253
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_11
254
254
  */
255
255
  export declare class ExtendedContentDescriptionObjectState extends State<ITag[]> {
256
- static guid: GUID;
256
+ static guid: AsfGuid;
257
257
  get(buf: Uint8Array, off: number): ITag[];
258
258
  }
259
259
  export interface IStreamName {
@@ -292,7 +292,7 @@ export interface IExtendedStreamPropertiesObject {
292
292
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
293
293
  */
294
294
  export declare class ExtendedStreamPropertiesObjectState extends State<IExtendedStreamPropertiesObject> {
295
- static guid: GUID;
295
+ static guid: AsfGuid;
296
296
  get(buf: Uint8Array, off: number): IExtendedStreamPropertiesObject;
297
297
  }
298
298
  /**
@@ -300,11 +300,11 @@ export declare class ExtendedStreamPropertiesObjectState extends State<IExtended
300
300
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
301
301
  */
302
302
  export declare class MetadataObjectState extends State<ITag[]> {
303
- static guid: GUID;
303
+ static guid: AsfGuid;
304
304
  get(uint8Array: Uint8Array, off: number): ITag[];
305
305
  }
306
306
  export declare class MetadataLibraryObjectState extends MetadataObjectState {
307
- static guid: GUID;
307
+ static guid: AsfGuid;
308
308
  }
309
309
  export interface IWmPicture extends IPicture {
310
310
  type: string;
@@ -1,7 +1,7 @@
1
1
  // ASF Objects
2
2
  import * as Token from 'token-types';
3
3
  import * as util from '../common/Util.js';
4
- import GUID from './GUID.js';
4
+ import AsfGuid from './AsfGuid.js';
5
5
  import { getParserForAttr, parseUnicodeAttr } from './AsfUtil.js';
6
6
  import { AttachedPictureType } from '../id3v2/ID3v2Token.js';
7
7
  import { makeUnexpectedFileContentError } from '../ParseError.js';
@@ -44,7 +44,7 @@ export const TopLevelHeaderObjectToken = {
44
44
  len: 30,
45
45
  get: (buf, off) => {
46
46
  return {
47
- objectId: GUID.fromBin(buf, off),
47
+ objectId: AsfGuid.fromBin(buf, off),
48
48
  objectSize: Number(Token.UINT64_LE.get(buf, off + 16)),
49
49
  numberOfHeaderObjects: Token.UINT32_LE.get(buf, off + 24)
50
50
  // Reserved: 2 bytes
@@ -59,7 +59,7 @@ export const HeaderObjectToken = {
59
59
  len: 24,
60
60
  get: (buf, off) => {
61
61
  return {
62
- objectId: GUID.fromBin(buf, off),
62
+ objectId: AsfGuid.fromBin(buf, off),
63
63
  objectSize: Number(Token.UINT64_LE.get(buf, off + 16))
64
64
  };
65
65
  }
@@ -94,7 +94,7 @@ export class IgnoreObjectState extends State {
94
94
  export class FilePropertiesObject extends State {
95
95
  get(buf, off) {
96
96
  return {
97
- fileId: GUID.fromBin(buf, off),
97
+ fileId: AsfGuid.fromBin(buf, off),
98
98
  fileSize: Token.UINT64_LE.get(buf, off + 16),
99
99
  creationDate: Token.UINT64_LE.get(buf, off + 24),
100
100
  dataPacketsCount: Token.UINT64_LE.get(buf, off + 32),
@@ -112,7 +112,7 @@ export class FilePropertiesObject extends State {
112
112
  };
113
113
  }
114
114
  }
115
- FilePropertiesObject.guid = GUID.FilePropertiesObject;
115
+ FilePropertiesObject.guid = AsfGuid.FilePropertiesObject;
116
116
  /**
117
117
  * Token for: 3.3 Stream Properties Object (mandatory, one per stream)
118
118
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
@@ -120,13 +120,13 @@ FilePropertiesObject.guid = GUID.FilePropertiesObject;
120
120
  export class StreamPropertiesObject extends State {
121
121
  get(buf, off) {
122
122
  return {
123
- streamType: GUID.decodeMediaType(GUID.fromBin(buf, off)),
124
- errorCorrectionType: GUID.fromBin(buf, off + 8)
123
+ streamType: AsfGuid.decodeMediaType(AsfGuid.fromBin(buf, off)),
124
+ errorCorrectionType: AsfGuid.fromBin(buf, off + 8)
125
125
  // ToDo
126
126
  };
127
127
  }
128
128
  }
129
- StreamPropertiesObject.guid = GUID.StreamPropertiesObject;
129
+ StreamPropertiesObject.guid = AsfGuid.StreamPropertiesObject;
130
130
  /**
131
131
  * 3.4: Header Extension Object (mandatory, one only)
132
132
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
@@ -138,13 +138,13 @@ export class HeaderExtensionObject {
138
138
  get(buf, off) {
139
139
  const view = new DataView(buf.buffer, off);
140
140
  return {
141
- reserved1: GUID.fromBin(buf, off),
141
+ reserved1: AsfGuid.fromBin(buf, off),
142
142
  reserved2: view.getUint16(16, true),
143
143
  extensionDataSize: view.getUint16(18, true)
144
144
  };
145
145
  }
146
146
  }
147
- HeaderExtensionObject.guid = GUID.HeaderExtensionObject;
147
+ HeaderExtensionObject.guid = AsfGuid.HeaderExtensionObject;
148
148
  /**
149
149
  * 3.5: The Codec List Object provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
150
150
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
@@ -217,7 +217,7 @@ export class ContentDescriptionObjectState extends State {
217
217
  return tags;
218
218
  }
219
219
  }
220
- ContentDescriptionObjectState.guid = GUID.ContentDescriptionObject;
220
+ ContentDescriptionObjectState.guid = AsfGuid.ContentDescriptionObject;
221
221
  ContentDescriptionObjectState.contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
222
222
  /**
223
223
  * 3.11 Extended Content Description Object (optional, one only)
@@ -245,7 +245,7 @@ export class ExtendedContentDescriptionObjectState extends State {
245
245
  return tags;
246
246
  }
247
247
  }
248
- ExtendedContentDescriptionObjectState.guid = GUID.ExtendedContentDescriptionObject;
248
+ ExtendedContentDescriptionObjectState.guid = AsfGuid.ExtendedContentDescriptionObject;
249
249
  /**
250
250
  * 4.1 Extended Stream Properties Object (optional, 1 per media stream)
251
251
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
@@ -279,7 +279,7 @@ export class ExtendedStreamPropertiesObjectState extends State {
279
279
  };
280
280
  }
281
281
  }
282
- ExtendedStreamPropertiesObjectState.guid = GUID.ExtendedStreamPropertiesObject;
282
+ ExtendedStreamPropertiesObjectState.guid = AsfGuid.ExtendedStreamPropertiesObject;
283
283
  /**
284
284
  * 4.7 Metadata Object (optional, 0 or 1)
285
285
  * Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
@@ -307,11 +307,11 @@ export class MetadataObjectState extends State {
307
307
  return tags;
308
308
  }
309
309
  }
310
- MetadataObjectState.guid = GUID.MetadataObject;
310
+ MetadataObjectState.guid = AsfGuid.MetadataObject;
311
311
  // 4.8 Metadata Library Object (optional, 0 or 1)
312
312
  export class MetadataLibraryObjectState extends MetadataObjectState {
313
313
  }
314
- MetadataLibraryObjectState.guid = GUID.MetadataLibraryObject;
314
+ MetadataLibraryObjectState.guid = AsfGuid.MetadataLibraryObject;
315
315
  /**
316
316
  * Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
317
317
  */
@@ -1,6 +1,6 @@
1
1
  import initDebug from 'debug';
2
2
  import { TrackType } from '../type.js';
3
- import GUID from './GUID.js';
3
+ import AsfGuid from './AsfGuid.js';
4
4
  import * as AsfObject from './AsfObject.js';
5
5
  import { BasicParser } from '../common/BasicParser.js';
6
6
  import { AsfContentParseError } from './AsfObject.js';
@@ -19,7 +19,7 @@ const headerType = 'asf';
19
19
  export class AsfParser extends BasicParser {
20
20
  async parse() {
21
21
  const header = await this.tokenizer.readToken(AsfObject.TopLevelHeaderObjectToken);
22
- if (!header.objectId.equals(GUID.HeaderObject)) {
22
+ if (!header.objectId.equals(AsfGuid.HeaderObject)) {
23
23
  throw new AsfContentParseError(`expected asf header; but was not found; got: ${header.objectId.str}`);
24
24
  }
25
25
  try {
@@ -61,7 +61,7 @@ export class AsfParser extends BasicParser {
61
61
  tags = await this.tokenizer.readToken(new AsfObject.ExtendedContentDescriptionObjectState(header));
62
62
  await this.addTags(tags);
63
63
  break;
64
- case GUID.CodecListObject.str: {
64
+ case AsfGuid.CodecListObject.str: {
65
65
  const codecs = await AsfObject.readCodecEntries(this.tokenizer);
66
66
  codecs.forEach(codec => {
67
67
  this.metadata.addStreamInfo({
@@ -73,11 +73,11 @@ export class AsfParser extends BasicParser {
73
73
  this.metadata.setFormat('codec', audioCodecs);
74
74
  break;
75
75
  }
76
- case GUID.StreamBitratePropertiesObject.str:
76
+ case AsfGuid.StreamBitratePropertiesObject.str:
77
77
  // ToDo?
78
78
  await this.tokenizer.ignore(header.objectSize - AsfObject.HeaderObjectToken.len);
79
79
  break;
80
- case GUID.PaddingObject.str:
80
+ case AsfGuid.PaddingObject.str:
81
81
  // ToDo: register bytes pad
82
82
  debug('Padding: %s bytes', header.objectSize - AsfObject.HeaderObjectToken.len);
83
83
  await this.tokenizer.ignore(header.objectSize - AsfObject.HeaderObjectToken.len);
@@ -114,14 +114,14 @@ export class AsfParser extends BasicParser {
114
114
  await this.addTags(mlTags);
115
115
  break;
116
116
  }
117
- case GUID.PaddingObject.str:
117
+ case AsfGuid.PaddingObject.str:
118
118
  // ToDo: register bytes pad
119
119
  await this.tokenizer.ignore(remaining);
120
120
  break;
121
- case GUID.CompatibilityObject.str:
121
+ case AsfGuid.CompatibilityObject.str:
122
122
  await this.tokenizer.ignore(remaining);
123
123
  break;
124
- case GUID.ASF_Index_Placeholder_Object.str:
124
+ case AsfGuid.ASF_Index_Placeholder_Object.str:
125
125
  await this.tokenizer.ignore(remaining);
126
126
  break;
127
127
  default:
@@ -44,7 +44,6 @@ export type IElementListener = {
44
44
  * WEBM VP8 AUDIO FILE
45
45
  */
46
46
  export declare class EbmlIterator {
47
- private padding;
48
47
  private parserMap;
49
48
  private ebmlMaxIDLength;
50
49
  private ebmlMaxSizeLength;
@@ -27,7 +27,6 @@ export class EbmlIterator {
27
27
  * @param tokenizer
28
28
  */
29
29
  constructor(tokenizer) {
30
- this.padding = 0;
31
30
  this.parserMap = new Map();
32
31
  this.ebmlMaxIDLength = 4;
33
32
  this.ebmlMaxSizeLength = 8;
@@ -110,12 +109,10 @@ export class EbmlIterator {
110
109
  else {
111
110
  switch (element.id) {
112
111
  case 0xec: // void
113
- this.padding += element.len;
114
112
  await this.tokenizer.ignore(element.len);
115
113
  break;
116
114
  default:
117
115
  debug(`parseEbml: parent=${getElementPath(dtdElement)}, unknown child: id=${element.id.toString(16)} at position=${elementPosition}`);
118
- this.padding += element.len;
119
116
  await this.tokenizer.ignore(element.len);
120
117
  }
121
118
  }
@@ -203,7 +200,7 @@ function linkParents(element) {
203
200
  Object.keys(element.container)
204
201
  .map(id => {
205
202
  const child = element.container[id];
206
- child.id = Number.parseInt(id);
203
+ child.id = Number.parseInt(id, 10);
207
204
  return child;
208
205
  }).forEach(child => {
209
206
  child.parent = element;
@@ -3,7 +3,6 @@ import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
3
3
  import type { IBlockStreamInfo } from './FlacToken.js';
4
4
  export declare class FlacParser extends AbstractID3Parser {
5
5
  private vorbisParser;
6
- private padding;
7
6
  postId3v2Parse(): Promise<void>;
8
7
  private parseDataBlock;
9
8
  /**
@@ -14,7 +14,6 @@ export class FlacParser extends AbstractID3Parser {
14
14
  constructor() {
15
15
  super(...arguments);
16
16
  this.vorbisParser = new VorbisStream(this.metadata, this.options);
17
- this.padding = 0;
18
17
  }
19
18
  async postId3v2Parse() {
20
19
  const fourCC = await this.tokenizer.readToken(FourCcToken);
@@ -39,7 +38,6 @@ export class FlacParser extends AbstractID3Parser {
39
38
  case Flac.BlockType.STREAMINFO:
40
39
  return this.readBlockStreamInfo(blockHeader.length);
41
40
  case Flac.BlockType.PADDING:
42
- this.padding += blockHeader.length;
43
41
  break;
44
42
  case Flac.BlockType.APPLICATION:
45
43
  break;
@@ -1,5 +1,6 @@
1
1
  import { type ID3v2MajorVersion, type ITextEncoding } from './ID3v2Token.js';
2
2
  import type { IWarningCollector } from '../common/MetadataCollector.js';
3
+ import { type IChapterInfo } from './ID3v2ChapterToken.js';
3
4
  interface ICustomTag {
4
5
  owner_identifier: string;
5
6
  }
@@ -24,6 +25,22 @@ export interface IGeneralEncapsulatedObject {
24
25
  description: string;
25
26
  data: Uint8Array;
26
27
  }
28
+ export type Chapter = {
29
+ label: string;
30
+ info: IChapterInfo;
31
+ frames: Map<string, unknown>;
32
+ };
33
+ export type TableOfContents = {
34
+ label: string;
35
+ flags: {
36
+ /** If set, this is the top-level table of contents */
37
+ topLevel: boolean;
38
+ /** If set, the child element IDs are in a defined order */
39
+ ordered: boolean;
40
+ };
41
+ childElementIds: string[];
42
+ frames: Map<string, unknown>;
43
+ };
27
44
  export declare function parseGenre(origVal: string): string[];
28
45
  export declare class FrameParser {
29
46
  private major;
@@ -5,6 +5,8 @@ import { AttachedPictureType, SyncTextHeader, TextEncodingToken, TextHeader } fr
5
5
  import { Genres } from '../id3v1/ID3v1Parser.js';
6
6
  import { makeUnexpectedFileContentError } from '../ParseError.js';
7
7
  import { decodeUintBE } from '../common/Util.js';
8
+ import { ChapterInfo } from './ID3v2ChapterToken.js';
9
+ import { getFrameHeaderLength, readFrameHeader } from './FrameHeader.js';
8
10
  const debug = initDebug('music-metadata:id3v2:frame-parser');
9
11
  const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
10
12
  const urlEnc = { encoding: defaultEnc, bom: false };
@@ -109,6 +111,9 @@ export class FrameParser {
109
111
  case 'TRK':
110
112
  case 'TRCK':
111
113
  case 'TPOS':
114
+ case 'TIT1':
115
+ case 'TIT2':
116
+ case 'TIT3':
112
117
  output = text;
113
118
  break;
114
119
  case 'TCOM':
@@ -137,11 +142,10 @@ export class FrameParser {
137
142
  }
138
143
  case 'TXXX': {
139
144
  const idAndData = FrameParser.readIdentifierAndData(uint8Array.subarray(1), encoding);
140
- const textTag = {
145
+ output = {
141
146
  description: idAndData.id,
142
147
  text: this.splitValue(type, util.decodeString(idAndData.data, encoding).replace(/\x00+$/, ''))
143
148
  };
144
- output = textTag;
145
149
  break;
146
150
  }
147
151
  case 'PIC':
@@ -255,15 +259,15 @@ export class FrameParser {
255
259
  }
256
260
  case 'GEOB': { // General encapsulated object
257
261
  // [encoding] <MIME> 0x00 <filename> 0x00/0x00 0x00 <description> 0x00/0x00 0x00 <data>
258
- const { encoding: geobEncoding, bom: geobBom } = TextEncodingToken.get(uint8Array, 0);
262
+ const encoding = TextEncodingToken.get(uint8Array, 0);
259
263
  uint8Array = uint8Array.subarray(1);
260
264
  const mimeTypeStr = FrameParser.readNullTerminatedString(uint8Array, urlEnc);
261
265
  const mimeType = mimeTypeStr.text;
262
266
  uint8Array = uint8Array.subarray(mimeTypeStr.len);
263
- const filenameStr = FrameParser.readNullTerminatedString(uint8Array, { encoding: geobEncoding, bom: geobBom });
267
+ const filenameStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
264
268
  const filename = filenameStr.text;
265
269
  uint8Array = uint8Array.subarray(filenameStr.len);
266
- const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, { encoding: geobEncoding, bom: geobBom });
270
+ const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
267
271
  const description = descriptionStr.text;
268
272
  uint8Array = uint8Array.subarray(descriptionStr.len);
269
273
  const geob = {
@@ -289,9 +293,9 @@ export class FrameParser {
289
293
  break;
290
294
  case 'WXXX': {
291
295
  // [encoding] <description> 0x00/0x00 0x00 <url>
292
- const { encoding: wxxxEncoding, bom: wxxxBom } = TextEncodingToken.get(uint8Array, 0);
296
+ const encoding = TextEncodingToken.get(uint8Array, 0);
293
297
  uint8Array = uint8Array.subarray(1);
294
- const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, { encoding: wxxxEncoding, bom: wxxxBom });
298
+ const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
295
299
  const description = descriptionStr.text;
296
300
  uint8Array = uint8Array.subarray(descriptionStr.len);
297
301
  // URL is always ISO-8859-1
@@ -300,9 +304,9 @@ export class FrameParser {
300
304
  }
301
305
  case 'WFD':
302
306
  case 'WFED': {
303
- const { encoding: wfdEncoding, bom: wfdBom } = TextEncodingToken.get(uint8Array, 0);
307
+ const encoding = TextEncodingToken.get(uint8Array, 0);
304
308
  uint8Array = uint8Array.subarray(1);
305
- output = FrameParser.readNullTerminatedString(uint8Array, { encoding: wfdEncoding, bom: wfdBom }).text;
309
+ output = FrameParser.readNullTerminatedString(uint8Array, encoding).text;
306
310
  break;
307
311
  }
308
312
  case 'MCDI': {
@@ -310,6 +314,67 @@ export class FrameParser {
310
314
  output = uint8Array.subarray(0, length);
311
315
  break;
312
316
  }
317
+ // ID3v2 Chapters 1.0
318
+ // https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2-chapters-1.0.html#chapter-frame
319
+ case 'CHAP': { // // Chapter frame
320
+ debug("Reading CHAP");
321
+ fzero = util.findZero(uint8Array, defaultEnc);
322
+ const chapter = {
323
+ label: util.decodeString(uint8Array.subarray(0, fzero), defaultEnc),
324
+ info: ChapterInfo.get(uint8Array, fzero + 1),
325
+ frames: new Map()
326
+ };
327
+ offset += fzero + 1 + ChapterInfo.len;
328
+ while (offset < length) {
329
+ const subFrame = readFrameHeader(uint8Array.subarray(offset), this.major, this.warningCollector);
330
+ const headerSize = getFrameHeaderLength(this.major);
331
+ offset += headerSize;
332
+ const subOutput = this.readData(uint8Array.subarray(offset, offset + subFrame.length), subFrame.id, includeCovers);
333
+ offset += subFrame.length;
334
+ chapter.frames.set(subFrame.id, subOutput);
335
+ }
336
+ output = chapter;
337
+ break;
338
+ }
339
+ // ID3v2 Chapters 1.0
340
+ // https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2-chapters-1.0.html#table-of-contents-frame
341
+ case 'CTOC': { // Table of contents frame
342
+ debug('Reading CTOC');
343
+ // Element ID (null-terminated latin1)
344
+ const idEnd = util.findZero(uint8Array, defaultEnc);
345
+ const label = util.decodeString(uint8Array.subarray(0, idEnd), defaultEnc);
346
+ offset = idEnd + 1;
347
+ // Flags
348
+ const flags = uint8Array[offset++];
349
+ const topLevel = (flags & 0x02) !== 0;
350
+ const ordered = (flags & 0x01) !== 0;
351
+ // Child element IDs
352
+ const entryCount = uint8Array[offset++];
353
+ const childElementIds = [];
354
+ for (let i = 0; i < entryCount && offset < length; i++) {
355
+ const end = util.findZero(uint8Array.subarray(offset), defaultEnc);
356
+ const childId = util.decodeString(uint8Array.subarray(offset, offset + end), defaultEnc);
357
+ childElementIds.push(childId);
358
+ offset += end + 1;
359
+ }
360
+ const toc = {
361
+ label,
362
+ flags: { topLevel, ordered },
363
+ childElementIds,
364
+ frames: new Map()
365
+ };
366
+ // Optional embedded sub-frames (e.g. TIT2) follow after the child list
367
+ while (offset < length) {
368
+ const subFrame = readFrameHeader(uint8Array.subarray(offset), this.major, this.warningCollector);
369
+ const headerSize = getFrameHeaderLength(this.major);
370
+ offset += headerSize;
371
+ const subOutput = this.readData(uint8Array.subarray(offset, offset + subFrame.length), subFrame.id, includeCovers);
372
+ offset += subFrame.length;
373
+ toc.frames.set(subFrame.id, subOutput);
374
+ }
375
+ output = toc;
376
+ break;
377
+ }
313
378
  default:
314
379
  debug(`Warning: unsupported id3v2-tag-type: ${type}`);
315
380
  break;
@@ -0,0 +1,11 @@
1
+ import type { IGetToken } from 'strtok3';
2
+ export interface IChapterInfo {
3
+ startTime: number;
4
+ endTime: number;
5
+ startOffset?: number;
6
+ endOffset?: number;
7
+ }
8
+ /**
9
+ * Data portion of `CHAP` sub frame
10
+ */
11
+ export declare const ChapterInfo: IGetToken<IChapterInfo>;
@@ -0,0 +1,17 @@
1
+ import * as Token from 'token-types';
2
+ /**
3
+ * Data portion of `CHAP` sub frame
4
+ */
5
+ export const ChapterInfo = {
6
+ len: 16,
7
+ get: (buf, off) => {
8
+ const startOffset = Token.UINT32_BE.get(buf, off + 8);
9
+ const endOffset = Token.UINT32_BE.get(buf, off + 12);
10
+ return {
11
+ startTime: Token.UINT32_BE.get(buf, off),
12
+ endTime: Token.UINT32_BE.get(buf, off + 4),
13
+ startOffset: startOffset === 0xFFFFFFFF ? undefined : startOffset,
14
+ endOffset: endOffset === 0xFFFFFFFF ? undefined : endOffset,
15
+ };
16
+ }
17
+ };
@@ -23,4 +23,11 @@ export declare class ID3v2Parser {
23
23
  private handleTag;
24
24
  private addTag;
25
25
  private parseMetadata;
26
+ /**
27
+ * Convert parsed ID3v2 chapter frames (CHAP / CTOC) to generic `format.chapters`.
28
+ *
29
+ * This function expects the `native` tags already to contain parsed `CHAP` and `CTOC` frame values,
30
+ * as produced by `FrameParser.readData`.
31
+ */
32
+ private static mapId3v2Chapters;
26
33
  }
@@ -62,7 +62,10 @@ export class ID3v2Parser {
62
62
  }
63
63
  this.id3Header = id3Header;
64
64
  this.headerType = (`ID3v2.${id3Header.version.major}`);
65
- return id3Header.flags.isExtendedHeader ? this.parseExtendedHeader() : this.parseId3Data(id3Header.size);
65
+ await (id3Header.flags.isExtendedHeader ? this.parseExtendedHeader() : this.parseId3Data(id3Header.size));
66
+ // Post process
67
+ const chapters = ID3v2Parser.mapId3v2Chapters(this.metadata.native[this.headerType], this.metadata.format.sampleRate);
68
+ this.metadata.setFormat('chapters', chapters);
66
69
  }
67
70
  async parseExtendedHeader() {
68
71
  const extendedHeader = await this.tokenizer.readToken(ExtendedHeader);
@@ -116,6 +119,48 @@ export class ID3v2Parser {
116
119
  }
117
120
  return tags;
118
121
  }
122
+ /**
123
+ * Convert parsed ID3v2 chapter frames (CHAP / CTOC) to generic `format.chapters`.
124
+ *
125
+ * This function expects the `native` tags already to contain parsed `CHAP` and `CTOC` frame values,
126
+ * as produced by `FrameParser.readData`.
127
+ */
128
+ static mapId3v2Chapters(id3Tags, sampleRate) {
129
+ const chapFrames = id3Tags.filter(t => t.id === 'CHAP');
130
+ if (!chapFrames?.length)
131
+ return;
132
+ const tocFrames = id3Tags.filter(t => t.id === 'CTOC');
133
+ const topLevelToc = tocFrames?.find(t => t.value.flags?.topLevel);
134
+ const chapterById = new Map();
135
+ for (const chap of chapFrames) {
136
+ chapterById.set(chap.value.label, chap.value);
137
+ }
138
+ const orderedIds = topLevelToc?.value.childElementIds;
139
+ const chapters = [];
140
+ const source = orderedIds ?? [...chapterById.keys()];
141
+ for (const id of source) {
142
+ const chap = chapterById.get(id);
143
+ if (!chap)
144
+ continue;
145
+ const frames = chap.frames;
146
+ const title = frames.get('TIT2');
147
+ if (!title)
148
+ continue; // title is required
149
+ chapters.push({
150
+ id,
151
+ title,
152
+ url: frames.get('WXXX'),
153
+ start: chap.info.startTime / 1000,
154
+ end: chap.info.endTime / 1000,
155
+ image: frames.get('APIC')
156
+ });
157
+ }
158
+ // If no ordered CTOC, sort by time
159
+ if (!orderedIds) {
160
+ chapters.sort((a, b) => a.start - b.start);
161
+ }
162
+ return chapters.length ? chapters : undefined;
163
+ }
119
164
  }
120
165
  function makeUnexpectedMajorVersionError(majorVer) {
121
166
  throw new Id3v2ContentError(`Unexpected majorVer: ${majorVer}`);
@@ -57,10 +57,7 @@ export type TimestampFormat = typeof TimestampFormat[keyof typeof TimestampForma
57
57
  * 28 bits (representing up to 256MB) integer, the msb is 0 to avoid 'false syncsignals'.
58
58
  * 4 * %0xxxxxxx
59
59
  */
60
- export declare const UINT32SYNCSAFE: {
61
- get: (buf: Uint8Array, off: number) => number;
62
- len: number;
63
- };
60
+ export declare const UINT32SYNCSAFE: IGetToken<number>;
64
61
  /**
65
62
  * ID3v2 tag header
66
63
  */
@@ -16,13 +16,11 @@ export declare class MpegContentError extends MpegContentError_base {
16
16
  export declare class MpegParser extends AbstractID3Parser {
17
17
  private frameCount;
18
18
  private syncFrameCount;
19
- private countSkipFrameData;
20
19
  private totalDataLength;
21
20
  private audioFrameHeader?;
22
21
  private bitrates;
23
22
  private offset;
24
23
  private frame_size;
25
- private crc;
26
24
  private calculateEofDuration;
27
25
  private samplesPerFrame;
28
26
  private buf_frame_header;
@@ -233,12 +233,10 @@ export class MpegParser extends AbstractID3Parser {
233
233
  super(...arguments);
234
234
  this.frameCount = 0;
235
235
  this.syncFrameCount = -1;
236
- this.countSkipFrameData = 0;
237
236
  this.totalDataLength = 0;
238
237
  this.bitrates = [];
239
238
  this.offset = 0;
240
239
  this.frame_size = 0;
241
- this.crc = null;
242
240
  this.calculateEofDuration = false;
243
241
  this.samplesPerFrame = null;
244
242
  this.buf_frame_header = new Uint8Array(4);
@@ -467,8 +465,8 @@ export class MpegParser extends AbstractID3Parser {
467
465
  return false;
468
466
  }
469
467
  async parseCrc() {
470
- this.crc = await this.tokenizer.readNumber(Token.INT16_BE);
471
- this.offset += 2;
468
+ await this.tokenizer.ignore(Token.INT16_BE.len); // Ignore CRC
469
+ this.offset += Token.INT16_BE.len;
472
470
  return this.skipSideInformation();
473
471
  }
474
472
  async skipSideInformation() {
@@ -561,7 +559,6 @@ export class MpegParser extends AbstractID3Parser {
561
559
  if (frameDataLeft < 0)
562
560
  throw new MpegContentError('frame-data-left cannot be negative');
563
561
  await this.tokenizer.ignore(frameDataLeft);
564
- this.countSkipFrameData += frameDataLeft;
565
562
  }
566
563
  areAllSame(array) {
567
564
  const first = array[0];
@@ -37,7 +37,7 @@ export declare class VorbisStream implements IPageConsumer {
37
37
  flush(): Promise<void>;
38
38
  parseUserComment(pageData: Uint8Array, offset: number): Promise<number>;
39
39
  addTag(id: string, value: string | IVorbisPicture): Promise<void>;
40
- calculateDuration(enfOfStream: boolean): void;
40
+ calculateDuration(_enfOfStream: boolean): void;
41
41
  /**
42
42
  * Parse first Ogg/Vorbis page
43
43
  * @param _header
@@ -77,7 +77,7 @@ export class VorbisStream {
77
77
  }
78
78
  await this.metadata.addTag('vorbis', id, value);
79
79
  }
80
- calculateDuration(enfOfStream) {
80
+ calculateDuration(_enfOfStream) {
81
81
  if (this.lastPageHeader && this.metadata.format.sampleRate && this.lastPageHeader.absoluteGranulePosition >= 0) {
82
82
  // Calculate duration
83
83
  this.metadata.setFormat('numberOfSamples', this.lastPageHeader.absoluteGranulePosition);
package/lib/type.d.ts CHANGED
@@ -484,25 +484,46 @@ export interface ITag {
484
484
  id: string;
485
485
  value: AnyTagValue;
486
486
  }
487
+ export interface IUrl {
488
+ url: string;
489
+ description: string;
490
+ }
487
491
  export interface IChapter {
492
+ /**
493
+ * Internal chapter reference
494
+ */
495
+ id?: string;
488
496
  /**
489
497
  * Chapter title
490
498
  */
491
499
  title: string;
500
+ /**
501
+ * URL
502
+ */
503
+ url?: IUrl;
492
504
  /**
493
505
  * Audio offset in sample number, 0 is the first sample.
494
506
  * Duration offset is sampleOffset / format.sampleRate
495
507
  */
496
- sampleOffset: number;
508
+ sampleOffset?: number;
497
509
  /**
498
510
  * Timestamp where the chapter starts
499
511
  * Chapter timestamp is start/timeScale in seconds.
500
512
  */
501
513
  start: number;
502
514
  /**
503
- * Time value that indicates the time scale for chapter tracks, the number of time units that pass per second in its time coordinate system.
515
+ * Timestamp where the chapter end
516
+ * Chapter timestamp is start/timeScale in seconds.
517
+ */
518
+ end?: number;
519
+ /**
520
+ * Time value that indicates the timescale for chapter tracks, the number of time units that pass per second in its time coordinate system.
521
+ */
522
+ timeScale?: number;
523
+ /**
524
+ * Picture
504
525
  */
505
- timeScale: number;
526
+ image?: IPicture;
506
527
  }
507
528
  /**
508
529
  * Flat list of tags
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": "11.10.5",
4
+ "version": "11.11.0",
5
5
  "author": {
6
6
  "name": "Borewit",
7
7
  "url": "https://github.com/Borewit"
@@ -85,7 +85,9 @@
85
85
  "parser",
86
86
  "bwf",
87
87
  "slt",
88
- "lyrics"
88
+ "lyrics",
89
+ "Chapters",
90
+ "ID3v2 Chapters"
89
91
  ],
90
92
  "scripts": {
91
93
  "clean": "del-cli 'lib/**/*.js' 'lib/**/*.js.map' 'lib/**/*.d.ts' 'test/**/*.js' 'test/**/*.js.map' 'test/**/*.js' 'test/**/*.js.map' 'doc-gen/**/*.js' 'doc-gen/**/*.js.map'",
@@ -110,21 +112,22 @@
110
112
  "@tokenizer/token": "^0.3.0",
111
113
  "content-type": "^1.0.5",
112
114
  "debug": "^4.4.3",
113
- "file-type": "^21.2.0",
115
+ "file-type": "^21.3.0",
114
116
  "media-typer": "^1.1.0",
115
117
  "strtok3": "^10.3.4",
116
118
  "token-types": "^6.1.2",
117
- "uint8array-extras": "^1.5.0"
119
+ "uint8array-extras": "^1.5.0",
120
+ "win-guid": "^0.2.0"
118
121
  },
119
122
  "devDependencies": {
120
- "@biomejs/biome": "2.3.10",
123
+ "@biomejs/biome": "2.3.11",
121
124
  "@types/chai": "^5.2.3",
122
125
  "@types/chai-as-promised": "^8.0.2",
123
126
  "@types/content-type": "^1.1.9",
124
127
  "@types/debug": "^4.1.12",
125
128
  "@types/media-typer": "^1.1.3",
126
129
  "@types/mocha": "^10.0.10",
127
- "@types/node": "^25.0.3",
130
+ "@types/node": "^25.0.10",
128
131
  "c8": "^10.1.3",
129
132
  "chai": "^6.2.2",
130
133
  "chai-as-promised": "^8.0.2",
package/lib/asf/GUID.d.ts DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Ref:
3
- * - https://tools.ietf.org/html/draft-fleischman-asf-01, Appendix A: ASF GUIDs
4
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
5
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
6
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
7
- *
8
- * ASF File Structure:
9
- * - https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
10
- *
11
- * ASF GUIDs:
12
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
13
- * - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
14
- */
15
- export default class GUID {
16
- static HeaderObject: GUID;
17
- static DataObject: GUID;
18
- static SimpleIndexObject: GUID;
19
- static IndexObject: GUID;
20
- static MediaObjectIndexObject: GUID;
21
- static TimecodeIndexObject: GUID;
22
- static FilePropertiesObject: GUID;
23
- static StreamPropertiesObject: GUID;
24
- static HeaderExtensionObject: GUID;
25
- static CodecListObject: GUID;
26
- static ScriptCommandObject: GUID;
27
- static MarkerObject: GUID;
28
- static BitrateMutualExclusionObject: GUID;
29
- static ErrorCorrectionObject: GUID;
30
- static ContentDescriptionObject: GUID;
31
- static ExtendedContentDescriptionObject: GUID;
32
- static ContentBrandingObject: GUID;
33
- static StreamBitratePropertiesObject: GUID;
34
- static ContentEncryptionObject: GUID;
35
- static ExtendedContentEncryptionObject: GUID;
36
- static DigitalSignatureObject: GUID;
37
- static PaddingObject: GUID;
38
- static ExtendedStreamPropertiesObject: GUID;
39
- static AdvancedMutualExclusionObject: GUID;
40
- static GroupMutualExclusionObject: GUID;
41
- static StreamPrioritizationObject: GUID;
42
- static BandwidthSharingObject: GUID;
43
- static LanguageListObject: GUID;
44
- static MetadataObject: GUID;
45
- static MetadataLibraryObject: GUID;
46
- static IndexParametersObject: GUID;
47
- static MediaObjectIndexParametersObject: GUID;
48
- static TimecodeIndexParametersObject: GUID;
49
- static CompatibilityObject: GUID;
50
- static AdvancedContentEncryptionObject: GUID;
51
- static AudioMedia: GUID;
52
- static VideoMedia: GUID;
53
- static CommandMedia: GUID;
54
- static JFIF_Media: GUID;
55
- static Degradable_JPEG_Media: GUID;
56
- static FileTransferMedia: GUID;
57
- static BinaryMedia: GUID;
58
- static ASF_Index_Placeholder_Object: GUID;
59
- static fromBin(bin: Uint8Array, offset?: number): GUID;
60
- /**
61
- * Decode GUID in format like "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
62
- * @param objectId Binary GUID
63
- * @param offset Read offset in bytes, default 0
64
- * @returns GUID as dashed hexadecimal representation
65
- */
66
- static decode(objectId: Uint8Array, offset?: number): string;
67
- /**
68
- * Decode stream type
69
- * @param mediaType Media type GUID
70
- * @returns Media type
71
- */
72
- static decodeMediaType(mediaType: GUID): 'audio' | 'video' | 'command' | 'degradable-jpeg' | 'file-transfer' | 'binary' | undefined;
73
- /**
74
- * Encode GUID
75
- * @param guid GUID like: "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
76
- * @returns Encoded Binary GUID
77
- */
78
- static encode(str: string): Uint8Array;
79
- str: string;
80
- constructor(str: string);
81
- equals(guid: GUID): boolean;
82
- toBin(): Uint8Array;
83
- }
package/lib/asf/GUID.js DELETED
@@ -1,118 +0,0 @@
1
- import { hexToUint8Array, uint8ArrayToHex } from 'uint8array-extras';
2
- /**
3
- * Ref:
4
- * - https://tools.ietf.org/html/draft-fleischman-asf-01, Appendix A: ASF GUIDs
5
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
6
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
7
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
8
- *
9
- * ASF File Structure:
10
- * - https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
11
- *
12
- * ASF GUIDs:
13
- * - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
14
- * - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
15
- */
16
- class GUID {
17
- static fromBin(bin, offset = 0) {
18
- return new GUID(GUID.decode(bin, offset));
19
- }
20
- /**
21
- * Decode GUID in format like "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
22
- * @param objectId Binary GUID
23
- * @param offset Read offset in bytes, default 0
24
- * @returns GUID as dashed hexadecimal representation
25
- */
26
- static decode(objectId, offset = 0) {
27
- const view = new DataView(objectId.buffer, offset);
28
- const guid = `${view.getUint32(0, true).toString(16)}-${view.getUint16(4, true).toString(16)}-${view.getUint16(6, true).toString(16)}-${view.getUint16(8).toString(16)}-${uint8ArrayToHex(objectId.subarray(offset + 10, offset + 16))}`;
29
- return guid.toUpperCase();
30
- }
31
- /**
32
- * Decode stream type
33
- * @param mediaType Media type GUID
34
- * @returns Media type
35
- */
36
- static decodeMediaType(mediaType) {
37
- switch (mediaType.str) {
38
- case GUID.AudioMedia.str: return 'audio';
39
- case GUID.VideoMedia.str: return 'video';
40
- case GUID.CommandMedia.str: return 'command';
41
- case GUID.Degradable_JPEG_Media.str: return 'degradable-jpeg';
42
- case GUID.FileTransferMedia.str: return 'file-transfer';
43
- case GUID.BinaryMedia.str: return 'binary';
44
- }
45
- }
46
- /**
47
- * Encode GUID
48
- * @param guid GUID like: "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
49
- * @returns Encoded Binary GUID
50
- */
51
- static encode(str) {
52
- const bin = new Uint8Array(16);
53
- const view = new DataView(bin.buffer);
54
- view.setUint32(0, Number.parseInt(str.substring(0, 8), 16), true);
55
- view.setUint16(4, Number.parseInt(str.substring(9, 13), 16), true);
56
- view.setUint16(6, Number.parseInt(str.substring(14, 18), 16), true);
57
- bin.set(hexToUint8Array(str.substring(19, 23)), 8);
58
- bin.set(hexToUint8Array(str.substring(24)), 10);
59
- return bin;
60
- }
61
- constructor(str) {
62
- this.str = str;
63
- }
64
- equals(guid) {
65
- return this.str === guid.str;
66
- }
67
- toBin() {
68
- return GUID.encode(this.str);
69
- }
70
- }
71
- // 10.1 Top-level ASF object GUIDs
72
- GUID.HeaderObject = new GUID("75B22630-668E-11CF-A6D9-00AA0062CE6C");
73
- GUID.DataObject = new GUID("75B22636-668E-11CF-A6D9-00AA0062CE6C");
74
- GUID.SimpleIndexObject = new GUID("33000890-E5B1-11CF-89F4-00A0C90349CB");
75
- GUID.IndexObject = new GUID("D6E229D3-35DA-11D1-9034-00A0C90349BE");
76
- GUID.MediaObjectIndexObject = new GUID("FEB103F8-12AD-4C64-840F-2A1D2F7AD48C");
77
- GUID.TimecodeIndexObject = new GUID("3CB73FD0-0C4A-4803-953D-EDF7B6228F0C");
78
- // 10.2 Header Object GUIDs
79
- GUID.FilePropertiesObject = new GUID("8CABDCA1-A947-11CF-8EE4-00C00C205365");
80
- GUID.StreamPropertiesObject = new GUID("B7DC0791-A9B7-11CF-8EE6-00C00C205365");
81
- GUID.HeaderExtensionObject = new GUID("5FBF03B5-A92E-11CF-8EE3-00C00C205365");
82
- GUID.CodecListObject = new GUID("86D15240-311D-11D0-A3A4-00A0C90348F6");
83
- GUID.ScriptCommandObject = new GUID("1EFB1A30-0B62-11D0-A39B-00A0C90348F6");
84
- GUID.MarkerObject = new GUID("F487CD01-A951-11CF-8EE6-00C00C205365");
85
- GUID.BitrateMutualExclusionObject = new GUID("D6E229DC-35DA-11D1-9034-00A0C90349BE");
86
- GUID.ErrorCorrectionObject = new GUID("75B22635-668E-11CF-A6D9-00AA0062CE6C");
87
- GUID.ContentDescriptionObject = new GUID("75B22633-668E-11CF-A6D9-00AA0062CE6C");
88
- GUID.ExtendedContentDescriptionObject = new GUID("D2D0A440-E307-11D2-97F0-00A0C95EA850");
89
- GUID.ContentBrandingObject = new GUID("2211B3FA-BD23-11D2-B4B7-00A0C955FC6E");
90
- GUID.StreamBitratePropertiesObject = new GUID("7BF875CE-468D-11D1-8D82-006097C9A2B2");
91
- GUID.ContentEncryptionObject = new GUID("2211B3FB-BD23-11D2-B4B7-00A0C955FC6E");
92
- GUID.ExtendedContentEncryptionObject = new GUID("298AE614-2622-4C17-B935-DAE07EE9289C");
93
- GUID.DigitalSignatureObject = new GUID("2211B3FC-BD23-11D2-B4B7-00A0C955FC6E");
94
- GUID.PaddingObject = new GUID("1806D474-CADF-4509-A4BA-9AABCB96AAE8");
95
- // 10.3 Header Extension Object GUIDs
96
- GUID.ExtendedStreamPropertiesObject = new GUID("14E6A5CB-C672-4332-8399-A96952065B5A");
97
- GUID.AdvancedMutualExclusionObject = new GUID("A08649CF-4775-4670-8A16-6E35357566CD");
98
- GUID.GroupMutualExclusionObject = new GUID("D1465A40-5A79-4338-B71B-E36B8FD6C249");
99
- GUID.StreamPrioritizationObject = new GUID("D4FED15B-88D3-454F-81F0-ED5C45999E24");
100
- GUID.BandwidthSharingObject = new GUID("A69609E6-517B-11D2-B6AF-00C04FD908E9");
101
- GUID.LanguageListObject = new GUID("7C4346A9-EFE0-4BFC-B229-393EDE415C85");
102
- GUID.MetadataObject = new GUID("C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA");
103
- GUID.MetadataLibraryObject = new GUID("44231C94-9498-49D1-A141-1D134E457054");
104
- GUID.IndexParametersObject = new GUID("D6E229DF-35DA-11D1-9034-00A0C90349BE");
105
- GUID.MediaObjectIndexParametersObject = new GUID("6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7");
106
- GUID.TimecodeIndexParametersObject = new GUID("F55E496D-9797-4B5D-8C8B-604DFE9BFB24");
107
- GUID.CompatibilityObject = new GUID("26F18B5D-4584-47EC-9F5F-0E651F0452C9");
108
- GUID.AdvancedContentEncryptionObject = new GUID("43058533-6981-49E6-9B74-AD12CB86D58C");
109
- // 10.4 Stream Properties Object Stream Type GUIDs
110
- GUID.AudioMedia = new GUID("F8699E40-5B4D-11CF-A8FD-00805F5C442B");
111
- GUID.VideoMedia = new GUID("BC19EFC0-5B4D-11CF-A8FD-00805F5C442B");
112
- GUID.CommandMedia = new GUID("59DACFC0-59E6-11D0-A3AC-00A0C90348F6");
113
- GUID.JFIF_Media = new GUID("B61BE100-5B4E-11CF-A8FD-00805F5C442B");
114
- GUID.Degradable_JPEG_Media = new GUID("35907DE0-E415-11CF-A917-00805F5C442B");
115
- GUID.FileTransferMedia = new GUID("91BD222C-F21C-497A-8B6D-5AA86BFC0185");
116
- GUID.BinaryMedia = new GUID("3AFB65E2-47EF-40F2-AC2C-70A90D71D343");
117
- GUID.ASF_Index_Placeholder_Object = new GUID("D9AADE20-7C17-4F9C-BC28-8555DD98E2A2");
118
- export default GUID;