music-metadata 12.2.0 → 12.2.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 CHANGED
@@ -177,6 +177,8 @@ parseFile(filePath: string, options?: IOptions): Promise<IAudioMetadata>
177
177
 
178
178
  - This function is **Node.js-only** and relies on Node.js-specific APIs to access the filesystem.
179
179
 
180
+ - The path must resolve to a regular file. Symlinks to regular files are supported. Directories, FIFOs (named pipes), and other non-regular filesystem objects are rejected with `NotRegularFileError` from `strtok3`. FIFOs are rejected without waiting for a writer. To parse streamed audio, use [`parseStream()`](#parsestream-function).
181
+
180
182
  - For browser environments, consider using the [parseBlob](#parseblob-function) to parse [File object](https://developer.mozilla.org/en-US/docs/Web/API/File) objects.
181
183
 
182
184
  ##### Example:
@@ -631,6 +633,10 @@ Returns a list of supported MIME-types. This may include some MIME-types which a
631
633
  - Metadata not listed in the SeekHead may be skipped.
632
634
  - If the SeekHead is missing, this option has no effect.
633
635
 
636
+ For large MKV files on network shares, use `parseFile(filePath, { mkvUseIndex: true })`
637
+ when the file's SeekHead index is reliable. The default traversal reads every cluster header,
638
+ so network latency can dominate even though the audio/video payloads are skipped.
639
+
634
640
  - `observer`: `(update: IMetadataEvent) => void`:
635
641
 
636
642
  Callback function triggered when common tags or format properties are updated during parsing.
@@ -1,7 +1,7 @@
1
1
  export const dsdiffParserLoader = {
2
2
  parserType: 'dsdiff',
3
3
  extensions: ['.dff'],
4
- mimeTypes: ['audio/dsf', 'audio/dsd'],
4
+ mimeTypes: ['audio/dsf', 'audio/dsd', 'audio/dff'],
5
5
  async load() {
6
6
  return (await import('./DsdiffParser.js')).DsdiffParser;
7
7
  }
@@ -51,6 +51,7 @@ export declare class EbmlIterator {
51
51
  private ebmlMaxIDLength;
52
52
  private ebmlMaxSizeLength;
53
53
  private tokenizer;
54
+ private readonly elementHeader;
54
55
  /**
55
56
  * @param {ITokenizer} tokenizer Input
56
57
  * @param tokenizer
@@ -2,7 +2,7 @@ import initDebug from 'debug';
2
2
  import { EndOfStreamError } from 'strtok3';
3
3
  import * as Token from 'token-types';
4
4
  import { Float32_BE, Float64_BE, StringType, UINT8 } from 'token-types';
5
- import { readBuffer } from '../common/Allocation.js';
5
+ import { getMaxMemoryAllocation, readBuffer } from '../common/Allocation.js';
6
6
  import { makeUnexpectedFileContentError } from '../ParseError.js';
7
7
  import { DataType } from './types.js';
8
8
  const debug = initDebug('music-metadata:parser:ebml');
@@ -28,6 +28,7 @@ export class EbmlIterator {
28
28
  ebmlMaxIDLength = 4;
29
29
  ebmlMaxSizeLength = 8;
30
30
  tokenizer;
31
+ elementHeader;
31
32
  /**
32
33
  * @param {ITokenizer} tokenizer Input
33
34
  * @param tokenizer
@@ -35,6 +36,7 @@ export class EbmlIterator {
35
36
  constructor(tokenizer, options = {}) {
36
37
  this.options = options;
37
38
  this.tokenizer = tokenizer;
39
+ this.elementHeader = new Uint8Array(Math.min(12, getMaxMemoryAllocation(options)));
38
40
  this.parserMap.set(DataType.uint, e => this.readUint(e));
39
41
  this.parserMap.set(DataType.string, e => this.readString(e));
40
42
  this.parserMap.set(DataType.binary, e => this.readBuffer(e));
@@ -158,6 +160,33 @@ export class EbmlIterator {
158
160
  return id;
159
161
  }
160
162
  async readElement() {
163
+ if (this.tokenizer.supportsRandomAccess() && this.elementHeader.length === 12) {
164
+ // RFC 8794 (July 2020), sections 4-6: an ID and size occupy at most 4 + 8 bytes.
165
+ // Peek both VINTs together to avoid four separate file/network reads per element.
166
+ // https://www.rfc-editor.org/rfc/rfc8794.html#section-4
167
+ const available = await this.tokenizer.peekBuffer(this.elementHeader, { mayBeLess: true });
168
+ const width = (offset, maximum) => {
169
+ if (offset >= available) {
170
+ throw new EndOfStreamError();
171
+ }
172
+ for (let length = 1; length <= maximum; ++length) {
173
+ if ((this.elementHeader[offset] & (0x80 >> (length - 1))) !== 0) {
174
+ if (offset + length > available) {
175
+ throw new EndOfStreamError();
176
+ }
177
+ return length;
178
+ }
179
+ }
180
+ throw new EbmlContentError('VINT value exceeding maximum size');
181
+ };
182
+ const idLength = width(0, this.ebmlMaxIDLength);
183
+ const sizeLength = width(idLength, this.ebmlMaxSizeLength);
184
+ const id = readUIntBE(this.elementHeader.subarray(0, idLength), idLength);
185
+ const lenField = this.elementHeader.subarray(idLength, idLength + sizeLength);
186
+ lenField[0] ^= 0x80 >> (sizeLength - 1);
187
+ this.tokenizer.setPosition(this.tokenizer.position + idLength + sizeLength);
188
+ return { id, len: readUIntBE(lenField, sizeLength) };
189
+ }
161
190
  const id = await this.readVintData(this.ebmlMaxIDLength);
162
191
  const lenField = await this.readVintData(this.ebmlMaxSizeLength);
163
192
  lenField[0] ^= 0x80 >> (lenField.length - 1);
package/lib/index.d.ts CHANGED
@@ -15,8 +15,10 @@ export * from './core.js';
15
15
  export declare function parseStream(stream: Readable, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
16
16
  /**
17
17
  * Parse audio from Node file
18
+ * Requires a regular file; symlinks to regular files are supported.
18
19
  * @param filePath - Media file to read meta-data from
19
20
  * @param options - Parsing options
20
21
  * @returns Metadata
22
+ * @throws {NotRegularFileError} If the opened filesystem object is not a regular file.
21
23
  */
22
24
  export declare function parseFile(filePath: string, options?: IOptions): Promise<IAudioMetadata>;
package/lib/index.js CHANGED
@@ -26,13 +26,18 @@ export async function parseStream(stream, fileInfo, options = {}) {
26
26
  }
27
27
  /**
28
28
  * Parse audio from Node file
29
+ * Requires a regular file; symlinks to regular files are supported.
29
30
  * @param filePath - Media file to read meta-data from
30
31
  * @param options - Parsing options
31
32
  * @returns Metadata
33
+ * @throws {NotRegularFileError} If the opened filesystem object is not a regular file.
32
34
  */
33
35
  export async function parseFile(filePath, options = {}) {
34
36
  debug(`parseFile: ${filePath}`);
35
- const fileTokenizer = await fromFile(filePath, getTokenizerOptions(options));
37
+ const fileTokenizer = await fromFile(filePath, {
38
+ ...getTokenizerOptions(options),
39
+ regularFileOnly: true
40
+ });
36
41
  const parserFactory = new ParserFactory();
37
42
  try {
38
43
  const parserLoader = parserFactory.findLoaderForExtension(filePath);
@@ -626,7 +626,13 @@ export class MP4Parser extends BasicParser {
626
626
  case 'tkhd': // TrackHeaderBox
627
627
  track.header = await this.readToken(new AtomToken.TrackHeaderAtom(payLoadLength));
628
628
  break;
629
- case 'hdlr': // TrackHeaderBox
629
+ case 'hdlr': // Media handler; metadata and data handlers do not identify the track type.
630
+ // QuickTime File Format, Media atom: Handler reference atom ('hdlr').
631
+ // https://developer.apple.com/documentation/quicktime-file-format/media_atom/handler_reference_atom
632
+ if (child.parent?.header.name !== 'mdia' || child.parent.parent !== trakBox) {
633
+ await this.tokenizer.ignore(payLoadLength);
634
+ break;
635
+ }
630
636
  track.handler = await this.readToken(new AtomToken.HandlerBox(payLoadLength));
631
637
  if (track.handler.handlerType === 'audi' || track.handler.handlerType === 'soun') {
632
638
  this.hasAudioTrack = true;
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": "12.2.0",
4
+ "version": "12.2.1",
5
5
  "author": {
6
6
  "name": "Borewit",
7
7
  "url": "https://github.com/Borewit"
@@ -122,9 +122,9 @@
122
122
  "@tokenizer/token": "^0.3.0",
123
123
  "content-type": "^3.1.1",
124
124
  "debug": "^4.4.3",
125
- "file-type": "^22.1.1",
125
+ "file-type": "^22.2.0",
126
126
  "media-typer": "^2.0.0",
127
- "strtok3": "^10.4.1",
127
+ "strtok3": "^10.5.0",
128
128
  "token-types": "^6.1.2",
129
129
  "uint8array-extras": "^1.6.0",
130
130
  "win-guid": "^0.2.1"
@@ -136,7 +136,7 @@
136
136
  "@types/debug": "^4.1.13",
137
137
  "@types/media-typer": "^1.1.3",
138
138
  "@types/mocha": "^10.0.10",
139
- "@types/node": "^26.6.4",
139
+ "@types/node": "^26.6.5",
140
140
  "c8": "^12.0.0",
141
141
  "chai": "^6.3.0",
142
142
  "chai-as-promised": "^8.0.2",