file-type 20.5.0 → 21.0.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/core.d.ts CHANGED
@@ -24,16 +24,17 @@ export type FileTypeResult = {
24
24
  };
25
25
 
26
26
  /**
27
- Detect the file type of a `Uint8Array`, or `ArrayBuffer`.
27
+ Detect the file type of a `Uint8Array` or `ArrayBuffer`.
28
28
 
29
29
  The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
30
30
 
31
31
  If file access is available, it is recommended to use `.fromFile()` instead.
32
32
 
33
33
  @param buffer - An Uint8Array or ArrayBuffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well.
34
+ @param options - Options to override default behavior.
34
35
  @returns The detected file type, or `undefined` when there is no match.
35
36
  */
36
- export function fileTypeFromBuffer(buffer: Uint8Array | ArrayBuffer): Promise<FileTypeResult | undefined>;
37
+ export function fileTypeFromBuffer(buffer: Uint8Array | ArrayBuffer, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
37
38
 
38
39
  /**
39
40
  Detect the file type of a [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
@@ -41,9 +42,10 @@ Detect the file type of a [web `ReadableStream`](https://developer.mozilla.org/e
41
42
  The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
42
43
 
43
44
  @param stream - A [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) streaming a file to examine.
45
+ @param options - Options to override default behavior.
44
46
  @returns A `Promise` for an object with the detected file type, or `undefined` when there is no match.
45
47
  */
46
- export function fileTypeFromStream(stream: AnyWebByteStream): Promise<FileTypeResult | undefined>;
48
+ export function fileTypeFromStream(stream: AnyWebByteStream, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
47
49
 
48
50
  /**
49
51
  Detect the file type from an [`ITokenizer`](https://github.com/Borewit/strtok3#tokenizer) source.
@@ -53,6 +55,7 @@ This method is used internally, but can also be used for a special "tokenizer" r
53
55
  A tokenizer propagates the internal read functions, allowing alternative transport mechanisms, to access files, to be implemented and used.
54
56
 
55
57
  @param tokenizer - File source implementing the tokenizer interface.
58
+ @param options - Options to override default behavior.
56
59
  @returns The detected file type, or `undefined` when there is no match.
57
60
 
58
61
  An example is [`@tokenizer/http`](https://github.com/Borewit/tokenizer-http), which requests data using [HTTP-range-requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests). A difference with a conventional stream and the [*tokenizer*](https://github.com/Borewit/strtok3#tokenizer), is that it is able to *ignore* (seek, fast-forward) in the stream. For example, you may only need and read the first 6 bytes, and the last 128 bytes, which may be an advantage in case reading the entire file would take longer.
@@ -71,7 +74,7 @@ console.log(fileType);
71
74
  //=> {ext: 'mp3', mime: 'audio/mpeg'}
72
75
  ```
73
76
  */
74
- export function fileTypeFromTokenizer(tokenizer: ITokenizer): Promise<FileTypeResult | undefined>;
77
+ export function fileTypeFromTokenizer(tokenizer: ITokenizer, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
75
78
 
76
79
  /**
77
80
  Supported file extensions.
@@ -96,6 +99,7 @@ export type StreamOptions = {
96
99
  Detect the file type of a [`Blob`](https://nodejs.org/api/buffer.html#class-blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
97
100
 
98
101
  @param blob - The [`Blob`](https://nodejs.org/api/buffer.html#class-blob) used for file detection.
102
+ @param options - Options to override default behavior.
99
103
  @returns The detected file type, or `undefined` when there is no match.
100
104
 
101
105
  @example
@@ -111,7 +115,7 @@ console.log(await fileTypeFromBlob(blob));
111
115
  //=> {ext: 'txt', mime: 'text/plain'}
112
116
  ```
113
117
  */
114
- export declare function fileTypeFromBlob(blob: Blob): Promise<FileTypeResult | undefined>;
118
+ export declare function fileTypeFromBlob(blob: Blob, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
115
119
 
116
120
  /**
117
121
  A custom file type detector.
@@ -184,6 +188,17 @@ export type Detector = {
184
188
 
185
189
  export type FileTypeOptions = {
186
190
  customDetectors?: Iterable<Detector>;
191
+
192
+ /**
193
+ Specifies the byte tolerance for locating the first MPEG audio frame (e.g. `.mp1`, `.mp2`, `.mp3`, `.aac`).
194
+
195
+ Allows detection to handle slight sync offsets between the expected and actual frame start. Common in malformed or incorrectly muxed files, which, while technically invalid, do occur in the wild.
196
+
197
+ A tolerance of 10 bytes covers most cases.
198
+
199
+ @default 0
200
+ */
201
+ mpegOffsetTolerance?: number;
187
202
  };
188
203
 
189
204
  export declare class TokenizerPositionError extends Error {
package/core.js CHANGED
@@ -5,7 +5,7 @@ Primary entry point, Node.js specific entry point is index.js
5
5
  import * as Token from 'token-types';
6
6
  import * as strtok3 from 'strtok3/core';
7
7
  import {ZipHandler} from '@tokenizer/inflate';
8
- import {includes, getUintBE} from 'uint8array-extras';
8
+ import {getUintBE} from 'uint8array-extras';
9
9
  import {
10
10
  stringToBytes,
11
11
  tarHeaderChecksumMatches,
@@ -15,74 +15,75 @@ import {extensions, mimeTypes} from './supported.js';
15
15
 
16
16
  export const reasonableDetectionSizeInBytes = 4100; // A fair amount of file-types are detectable within this range.
17
17
 
18
- export async function fileTypeFromStream(stream) {
19
- return new FileTypeParser().fromStream(stream);
18
+ export async function fileTypeFromStream(stream, options) {
19
+ return new FileTypeParser(options).fromStream(stream);
20
20
  }
21
21
 
22
- export async function fileTypeFromBuffer(input) {
23
- return new FileTypeParser().fromBuffer(input);
22
+ export async function fileTypeFromBuffer(input, options) {
23
+ return new FileTypeParser(options).fromBuffer(input);
24
24
  }
25
25
 
26
- export async function fileTypeFromBlob(blob) {
27
- return new FileTypeParser().fromBlob(blob);
26
+ export async function fileTypeFromBlob(blob, options) {
27
+ return new FileTypeParser(options).fromBlob(blob);
28
28
  }
29
29
 
30
30
  function getFileTypeFromMimeType(mimeType) {
31
- switch (mimeType.toLowerCase()) {
31
+ mimeType = mimeType.toLowerCase();
32
+ switch (mimeType) {
32
33
  case 'application/epub+zip':
33
34
  return {
34
35
  ext: 'epub',
35
- mime: 'application/epub+zip',
36
+ mime: mimeType,
36
37
  };
37
38
  case 'application/vnd.oasis.opendocument.text':
38
39
  return {
39
40
  ext: 'odt',
40
- mime: 'application/vnd.oasis.opendocument.text',
41
+ mime: mimeType,
41
42
  };
42
43
  case 'application/vnd.oasis.opendocument.text-template':
43
44
  return {
44
45
  ext: 'ott',
45
- mime: 'application/vnd.oasis.opendocument.text-template',
46
+ mime: mimeType,
46
47
  };
47
48
  case 'application/vnd.oasis.opendocument.spreadsheet':
48
49
  return {
49
50
  ext: 'ods',
50
- mime: 'application/vnd.oasis.opendocument.spreadsheet',
51
+ mime: mimeType,
51
52
  };
52
53
  case 'application/vnd.oasis.opendocument.spreadsheet-template':
53
54
  return {
54
55
  ext: 'ots',
55
- mime: 'application/vnd.oasis.opendocument.spreadsheet-template',
56
+ mime: mimeType,
56
57
  };
57
58
  case 'application/vnd.oasis.opendocument.presentation':
58
59
  return {
59
60
  ext: 'odp',
60
- mime: 'application/vnd.oasis.opendocument.presentation',
61
+ mime: mimeType,
61
62
  };
62
63
  case 'application/vnd.oasis.opendocument.presentation-template':
63
64
  return {
64
65
  ext: 'otp',
65
- mime: 'application/vnd.oasis.opendocument.presentation-template',
66
+ mime: mimeType,
66
67
  };
67
68
  case 'application/vnd.oasis.opendocument.graphics':
68
69
  return {
69
70
  ext: 'odg',
70
- mime: 'application/vnd.oasis.opendocument.graphics',
71
+ mime: mimeType,
71
72
  };
72
73
  case 'application/vnd.oasis.opendocument.graphics-template':
73
74
  return {
74
75
  ext: 'otg',
75
- mime: 'application/vnd.oasis.opendocument.graphics-template',
76
+ mime: mimeType,
76
77
  };
77
78
  case 'application/vnd.openxmlformats-officedocument.presentationml.slideshow':
78
79
  return {
79
80
  ext: 'ppsx',
80
- mime: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
81
+ mime: mimeType,
81
82
  };
82
83
  case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
83
84
  return {
84
85
  ext: 'xlsx',
85
- mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
86
+ mime: mimeType,
86
87
  };
87
88
  case 'application/vnd.ms-excel.sheet.macroenabled':
88
89
  return {
@@ -92,7 +93,7 @@ function getFileTypeFromMimeType(mimeType) {
92
93
  case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
93
94
  return {
94
95
  ext: 'xltx',
95
- mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
96
+ mime: mimeType,
96
97
  };
97
98
  case 'application/vnd.ms-excel.template.macroenabled':
98
99
  return {
@@ -107,7 +108,7 @@ function getFileTypeFromMimeType(mimeType) {
107
108
  case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
108
109
  return {
109
110
  ext: 'docx',
110
- mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
111
+ mime: mimeType,
111
112
  };
112
113
  case 'application/vnd.ms-word.document.macroenabled':
113
114
  return {
@@ -117,7 +118,7 @@ function getFileTypeFromMimeType(mimeType) {
117
118
  case 'application/vnd.openxmlformats-officedocument.wordprocessingml.template':
118
119
  return {
119
120
  ext: 'dotx',
120
- mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
121
+ mime: mimeType,
121
122
  };
122
123
  case 'application/vnd.ms-word.template.macroenabledtemplate':
123
124
  return {
@@ -127,7 +128,7 @@ function getFileTypeFromMimeType(mimeType) {
127
128
  case 'application/vnd.openxmlformats-officedocument.presentationml.template':
128
129
  return {
129
130
  ext: 'potx',
130
- mime: 'application/vnd.openxmlformats-officedocument.presentationml.template',
131
+ mime: mimeType,
131
132
  };
132
133
  case 'application/vnd.ms-powerpoint.template.macroenabled':
133
134
  return {
@@ -137,7 +138,7 @@ function getFileTypeFromMimeType(mimeType) {
137
138
  case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
138
139
  return {
139
140
  ext: 'pptx',
140
- mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
141
+ mime: mimeType,
141
142
  };
142
143
  case 'application/vnd.ms-powerpoint.presentation.macroenabled':
143
144
  return {
@@ -179,8 +180,8 @@ function _check(buffer, headers, options) {
179
180
  return true;
180
181
  }
181
182
 
182
- export async function fileTypeFromTokenizer(tokenizer) {
183
- return new FileTypeParser().fromTokenizer(tokenizer);
183
+ export async function fileTypeFromTokenizer(tokenizer, options) {
184
+ return new FileTypeParser(options).fromTokenizer(tokenizer);
184
185
  }
185
186
 
186
187
  export async function fileTypeStream(webStream, options) {
@@ -189,6 +190,11 @@ export async function fileTypeStream(webStream, options) {
189
190
 
190
191
  export class FileTypeParser {
191
192
  constructor(options) {
193
+ this.options = {
194
+ mpegOffsetTolerance: 0,
195
+ ...options,
196
+ };
197
+
192
198
  this.detectors = [...(options?.customDetectors ?? []),
193
199
  {id: 'core', detect: this.detectConfident},
194
200
  {id: 'core.imprecise', detect: this.detectImprecise}];
@@ -253,7 +259,7 @@ export class FileTypeParser {
253
259
  if (!done && chunk) {
254
260
  try {
255
261
  // Attempt to detect the file type from the chunk
256
- detectedFileType = await this.fromBuffer(chunk.slice(0, sampleSize));
262
+ detectedFileType = await this.fromBuffer(chunk.subarray(0, sampleSize));
257
263
  } catch (error) {
258
264
  if (!(error instanceof strtok3.EndOfStreamError)) {
259
265
  throw error; // Re-throw non-EndOfStreamError
@@ -709,7 +715,7 @@ export class FileTypeParser {
709
715
  if (this.checkString('fLaC')) {
710
716
  return {
711
717
  ext: 'flac',
712
- mime: 'audio/x-flac',
718
+ mime: 'audio/flac',
713
719
  };
714
720
  }
715
721
 
@@ -728,28 +734,6 @@ export class FileTypeParser {
728
734
  }
729
735
 
730
736
  if (this.checkString('%PDF')) {
731
- try {
732
- const skipBytes = 1350;
733
- if (skipBytes === await tokenizer.ignore(skipBytes)) {
734
- const maxBufferSize = 10 * 1024 * 1024;
735
- const buffer = new Uint8Array(Math.min(maxBufferSize, tokenizer.fileInfo.size - skipBytes));
736
- await tokenizer.readBuffer(buffer, {mayBeLess: true});
737
-
738
- // Check if this is an Adobe Illustrator file
739
- if (includes(buffer, new TextEncoder().encode('AIPrivateData'))) {
740
- return {
741
- ext: 'ai',
742
- mime: 'application/postscript',
743
- };
744
- }
745
- }
746
- } catch (error) {
747
- // Swallow end of stream error if file is too small for the Adobe AI check
748
- if (!(error instanceof strtok3.EndOfStreamError)) {
749
- throw error;
750
- }
751
- }
752
-
753
737
  // Assume this is just a normal PDF
754
738
  return {
755
739
  ext: 'pdf',
@@ -846,7 +830,7 @@ export class FileTypeParser {
846
830
  case 'matroska':
847
831
  return {
848
832
  ext: 'mkv',
849
- mime: 'video/x-matroska',
833
+ mime: 'video/matroska',
850
834
  };
851
835
 
852
836
  default:
@@ -920,10 +904,10 @@ export class FileTypeParser {
920
904
  };
921
905
  }
922
906
 
923
- if (this.checkString('PAR1')) {
907
+ if (this.checkString('PAR1') || this.checkString('PARE')) {
924
908
  return {
925
909
  ext: 'parquet',
926
- mime: 'application/x-parquet',
910
+ mime: 'application/vnd.apache.parquet',
927
911
  };
928
912
  }
929
913
 
@@ -1199,7 +1183,7 @@ export class FileTypeParser {
1199
1183
  if (this.check([0x41, 0x52, 0x52, 0x4F, 0x57, 0x31, 0x00, 0x00])) {
1200
1184
  return {
1201
1185
  ext: 'arrow',
1202
- mime: 'application/x-apache-arrow',
1186
+ mime: 'application/vnd.apache.arrow.file',
1203
1187
  };
1204
1188
  }
1205
1189
 
@@ -1541,7 +1525,7 @@ export class FileTypeParser {
1541
1525
 
1542
1526
  if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) {
1543
1527
  try {
1544
- const header = new TextDecoder().decode(this.buffer.slice(16, jsonSize + 16));
1528
+ const header = new TextDecoder().decode(this.buffer.subarray(16, jsonSize + 16));
1545
1529
  const json = JSON.parse(header);
1546
1530
  // Check if Pickle is ASAR
1547
1531
  if (json.files) { // Final check, assuring Pickle/ASAR format
@@ -1644,7 +1628,8 @@ export class FileTypeParser {
1644
1628
  await tokenizer.peekBuffer(this.buffer, {length: Math.min(512, tokenizer.fileInfo.size), mayBeLess: true});
1645
1629
 
1646
1630
  // Requires a buffer size of 512 bytes
1647
- if (tarHeaderChecksumMatches(this.buffer)) {
1631
+ if ((this.checkString('ustar', {offset: 257}) && (this.checkString('\0', {offset: 262}) || this.checkString(' ', {offset: 262})))
1632
+ || (this.check([0, 0, 0, 0, 0, 0], {offset: 257}) && tarHeaderChecksumMatches(this.buffer))) {
1648
1633
  return {
1649
1634
  ext: 'tar',
1650
1635
  mime: 'application/x-tar',
@@ -1714,47 +1699,16 @@ export class FileTypeParser {
1714
1699
  };
1715
1700
  }
1716
1701
 
1702
+ // Adjust buffer to `mpegOffsetTolerance`
1703
+ await tokenizer.peekBuffer(this.buffer, {length: Math.min(2 + this.options.mpegOffsetTolerance, tokenizer.fileInfo.size), mayBeLess: true});
1704
+
1717
1705
  // Check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE)
1718
- if (this.buffer.length >= 2 && this.check([0xFF, 0xE0], {offset: 0, mask: [0xFF, 0xE0]})) {
1719
- if (this.check([0x10], {offset: 1, mask: [0x16]})) {
1720
- // Check for (ADTS) MPEG-2
1721
- if (this.check([0x08], {offset: 1, mask: [0x08]})) {
1722
- return {
1723
- ext: 'aac',
1724
- mime: 'audio/aac',
1725
- };
1706
+ if (this.buffer.length >= (2 + this.options.mpegOffsetTolerance)) {
1707
+ for (let depth = 0; depth <= this.options.mpegOffsetTolerance; ++depth) {
1708
+ const type = this.scanMpeg(depth);
1709
+ if (type) {
1710
+ return type;
1726
1711
  }
1727
-
1728
- // Must be (ADTS) MPEG-4
1729
- return {
1730
- ext: 'aac',
1731
- mime: 'audio/aac',
1732
- };
1733
- }
1734
-
1735
- // MPEG 1 or 2 Layer 3 header
1736
- // Check for MPEG layer 3
1737
- if (this.check([0x02], {offset: 1, mask: [0x06]})) {
1738
- return {
1739
- ext: 'mp3',
1740
- mime: 'audio/mpeg',
1741
- };
1742
- }
1743
-
1744
- // Check for MPEG layer 2
1745
- if (this.check([0x04], {offset: 1, mask: [0x06]})) {
1746
- return {
1747
- ext: 'mp2',
1748
- mime: 'audio/mpeg',
1749
- };
1750
- }
1751
-
1752
- // Check for MPEG layer 1
1753
- if (this.check([0x06], {offset: 1, mask: [0x06]})) {
1754
- return {
1755
- ext: 'mp1',
1756
- mime: 'audio/mpeg',
1757
- };
1758
1712
  }
1759
1713
  }
1760
1714
  };
@@ -1831,6 +1785,57 @@ export class FileTypeParser {
1831
1785
  };
1832
1786
  }
1833
1787
  }
1788
+
1789
+ /**
1790
+ Scan check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE).
1791
+
1792
+ @param offset - Offset to scan for sync-preamble.
1793
+ @returns {{ext: string, mime: string}}
1794
+ */
1795
+ scanMpeg(offset) {
1796
+ if (this.check([0xFF, 0xE0], {offset, mask: [0xFF, 0xE0]})) {
1797
+ if (this.check([0x10], {offset: offset + 1, mask: [0x16]})) {
1798
+ // Check for (ADTS) MPEG-2
1799
+ if (this.check([0x08], {offset: offset + 1, mask: [0x08]})) {
1800
+ return {
1801
+ ext: 'aac',
1802
+ mime: 'audio/aac',
1803
+ };
1804
+ }
1805
+
1806
+ // Must be (ADTS) MPEG-4
1807
+ return {
1808
+ ext: 'aac',
1809
+ mime: 'audio/aac',
1810
+ };
1811
+ }
1812
+
1813
+ // MPEG 1 or 2 Layer 3 header
1814
+ // Check for MPEG layer 3
1815
+ if (this.check([0x02], {offset: offset + 1, mask: [0x06]})) {
1816
+ return {
1817
+ ext: 'mp3',
1818
+ mime: 'audio/mpeg',
1819
+ };
1820
+ }
1821
+
1822
+ // Check for MPEG layer 2
1823
+ if (this.check([0x04], {offset: offset + 1, mask: [0x06]})) {
1824
+ return {
1825
+ ext: 'mp2',
1826
+ mime: 'audio/mpeg',
1827
+ };
1828
+ }
1829
+
1830
+ // Check for MPEG layer 1
1831
+ if (this.check([0x06], {offset: offset + 1, mask: [0x06]})) {
1832
+ return {
1833
+ ext: 'mp1',
1834
+ mime: 'audio/mpeg',
1835
+ };
1836
+ }
1837
+ }
1838
+ }
1834
1839
  }
1835
1840
 
1836
1841
  export const supportedExtensions = new Set(extensions);
package/index.d.ts CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  type FileTypeResult,
9
9
  type StreamOptions,
10
10
  type AnyWebReadableStream,
11
- type Detector,
12
11
  type AnyWebReadableByteStreamWithFileType,
12
+ type FileTypeOptions,
13
13
  FileTypeParser as DefaultFileTypeParser,
14
14
  } from './core.js';
15
15
 
@@ -31,8 +31,8 @@ export declare class FileTypeParser extends DefaultFileTypeParser {
31
31
  /**
32
32
  Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor).
33
33
  */
34
- toDetectionStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
35
- toDetectionStream(webStream: AnyWebReadableStream<Uint8Array>, options?: StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
34
+ toDetectionStream(readableStream: NodeReadableStream, options?: FileTypeOptions & StreamOptions): Promise<ReadableStreamWithFileType>;
35
+ toDetectionStream(webStream: AnyWebReadableStream<Uint8Array>, options?: FileTypeOptions & StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
36
36
  }
37
37
 
38
38
  /**
@@ -48,7 +48,7 @@ The file type is detected by checking the [magic number](https://en.wikipedia.or
48
48
 
49
49
  @returns The detected file type and MIME type or `undefined` when there is no match.
50
50
  */
51
- export function fileTypeFromFile(filePath: string, options?: {customDetectors?: Iterable<Detector>}): Promise<FileTypeResult | undefined>;
51
+ export function fileTypeFromFile(filePath: string, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
52
52
 
53
53
  /**
54
54
  Detect the file type of a [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
@@ -60,9 +60,10 @@ Direct support for Node.js streams will be dropped in the future, when Node.js s
60
60
  The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
61
61
 
62
62
  @param stream - A [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [Node.js `stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable) streaming a file to examine.
63
- @returns A `Promise` for an object with the detected file type, or `undefined` when there is no match.
63
+ @param options - Options to override default behaviour.
64
+ @returns A `Promise` for an object with the detected file type, or `undefined` when there is no match.
64
65
  */
65
- export function fileTypeFromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream): Promise<FileTypeResult | undefined>;
66
+ export function fileTypeFromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
66
67
 
67
68
  /**
68
69
  Returns a `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileTypeFromFile()`.
@@ -91,7 +92,7 @@ if (stream2.fileType?.mime === 'image/jpeg') {
91
92
  }
92
93
  ```
93
94
  */
94
- export function fileTypeStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
95
- export function fileTypeStream(webStream: AnyWebByteStream, options?: StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
95
+ export function fileTypeStream(readableStream: NodeReadableStream, options?: FileTypeOptions & StreamOptions): Promise<ReadableStreamWithFileType>;
96
+ export function fileTypeStream(webStream: AnyWebByteStream, options?: FileTypeOptions & StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
96
97
 
97
98
  export * from './core.js';
package/index.js CHANGED
@@ -65,12 +65,12 @@ export class FileTypeParser extends DefaultFileTypeParser {
65
65
  }
66
66
  }
67
67
 
68
- export async function fileTypeFromFile(path, fileTypeOptions) {
69
- return (new FileTypeParser(fileTypeOptions)).fromFile(path, fileTypeOptions);
68
+ export async function fileTypeFromFile(path, options) {
69
+ return (new FileTypeParser(options)).fromFile(path, options);
70
70
  }
71
71
 
72
- export async function fileTypeFromStream(stream, fileTypeOptions) {
73
- return (new FileTypeParser(fileTypeOptions)).fromStream(stream);
72
+ export async function fileTypeFromStream(stream, options) {
73
+ return (new FileTypeParser(options)).fromStream(stream);
74
74
  }
75
75
 
76
76
  export async function fileTypeStream(readableStream, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-type",
3
- "version": "20.5.0",
3
+ "version": "21.0.0",
4
4
  "description": "Detect the file type of a file, stream, or data",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/file-type",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "sideEffects": false,
37
37
  "engines": {
38
- "node": ">=18"
38
+ "node": ">=20"
39
39
  },
40
40
  "scripts": {
41
41
  "test": "xo && ava && tsd"
@@ -198,7 +198,6 @@
198
198
  "it",
199
199
  "s3m",
200
200
  "xm",
201
- "ai",
202
201
  "skp",
203
202
  "avif",
204
203
  "eps",
@@ -247,19 +246,19 @@
247
246
  "ppsx"
248
247
  ],
249
248
  "dependencies": {
250
- "@tokenizer/inflate": "^0.2.6",
251
- "strtok3": "^10.2.0",
249
+ "@tokenizer/inflate": "^0.2.7",
250
+ "strtok3": "^10.2.2",
252
251
  "token-types": "^6.0.0",
253
252
  "uint8array-extras": "^1.4.0"
254
253
  },
255
254
  "devDependencies": {
256
255
  "@tokenizer/token": "^0.3.0",
257
- "@types/node": "^22.10.5",
258
- "ava": "^6.0.1",
256
+ "@types/node": "^22.15.21",
257
+ "ava": "^6.3.0",
259
258
  "commonmark": "^0.31.2",
260
259
  "get-stream": "^9.0.1",
261
260
  "noop-stream": "^1.0.0",
262
- "tsd": "^0.31.2",
261
+ "tsd": "^0.32.0",
263
262
  "xo": "^0.60.0"
264
263
  },
265
264
  "xo": {
package/readme.md CHANGED
@@ -109,7 +109,7 @@ console.log(fileType);
109
109
 
110
110
  ## API
111
111
 
112
- ### fileTypeFromBuffer(buffer)
112
+ ### fileTypeFromBuffer(buffer, options)
113
113
 
114
114
  Detect the file type of a `Uint8Array`, or `ArrayBuffer`.
115
115
 
@@ -130,13 +130,13 @@ Type: `Uint8Array | ArrayBuffer`
130
130
 
131
131
  A buffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well.
132
132
 
133
- ### fileTypeFromFile(filePath)
133
+ ### fileTypeFromFile(filePath, options)
134
134
 
135
135
  Detect the file type of a file path.
136
136
 
137
137
  This is for Node.js only.
138
138
 
139
- To read from a [`File`](https://developer.mozilla.org/docs/Web/API/File), see [`fileTypeFromBlob()`](#filetypefromblobblob).
139
+ To read from a [`File`](https://developer.mozilla.org/docs/Web/API/File), see [`fileTypeFromBlob()`](#filetypefromblobblob-options).
140
140
 
141
141
  The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
142
142
 
@@ -176,7 +176,7 @@ Type: [Web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/Re
176
176
 
177
177
  A readable stream representing file data.
178
178
 
179
- ### fileTypeFromBlob(blob)
179
+ ### fileTypeFromBlob(blob, options)
180
180
 
181
181
  Detect the file type of a [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob),
182
182
 
@@ -225,7 +225,7 @@ async function readFromBlobWithoutStreaming(blob) {
225
225
 
226
226
  Type: [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
227
227
 
228
- ### fileTypeFromTokenizer(tokenizer)
228
+ ### fileTypeFromTokenizer(tokenizer, options)
229
229
 
230
230
  Detect the file type from an `ITokenizer` source.
231
231
 
@@ -304,6 +304,8 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
304
304
 
305
305
  Type: `object`
306
306
 
307
+ Supports the following options in addition to the [general options](#options):
308
+
307
309
  ##### sampleSize
308
310
 
309
311
  Type: `number`\
@@ -341,6 +343,32 @@ Returns a `Set<string>` of supported file extensions.
341
343
 
342
344
  Returns a `Set<string>` of supported MIME types.
343
345
 
346
+ ### Options
347
+
348
+ #### customDetectors
349
+
350
+ Array of custom file type detectors to run before default detectors.
351
+
352
+ For example:
353
+
354
+ ```js
355
+ import {fileTypeFromFile} from 'file-type';
356
+ import {detectXml} from '@file-type/xml';
357
+
358
+ const fileType = await fileTypeFromFile('sample.kml', {customDetectors: [detectXml]});
359
+ console.log(fileType);
360
+ ```
361
+
362
+ #### mpegOffsetTolerance
363
+
364
+ Default: `0`
365
+
366
+ Specifies the byte tolerance for locating the first MPEG audio frame (e.g. `.mp1`, `.mp2`, `.mp3`, `.aac`).
367
+
368
+ Allows detection to handle slight sync offsets between the expected and actual frame start. Common in malformed or incorrectly muxed files, which, while technically invalid, do occur in the wild.
369
+
370
+ A tolerance of 10 bytes covers most cases.
371
+
344
372
  ## Custom detectors
345
373
 
346
374
  Custom file type detectors are plugins designed to extend the default detection capabilities.
@@ -353,6 +381,8 @@ Detectors can be added via the constructor options or by directly modifying `Fil
353
381
 
354
382
  ### Example adding a detector
355
383
 
384
+ For example:
385
+
356
386
  ```js
357
387
  import {FileTypeParser} from 'file-type';
358
388
  import {detectXml} from '@file-type/xml';
@@ -436,7 +466,6 @@ abortController.abort(); // Abort file-type reading from the Blob stream.
436
466
  - [`aac`](https://en.wikipedia.org/wiki/Advanced_Audio_Coding) - Advanced Audio Coding
437
467
  - [`ac3`](https://www.atsc.org/standard/a522012-digital-audio-compression-ac-3-e-ac-3-standard-12172012/) - ATSC A/52 Audio File
438
468
  - [`ace`](https://en.wikipedia.org/wiki/ACE_(compressed_file_format)) - ACE archive
439
- - [`ai`](https://en.wikipedia.org/wiki/Adobe_Illustrator_Artwork) - Adobe Illustrator Artwork
440
469
  - [`aif`](https://en.wikipedia.org/wiki/Audio_Interchange_File_Format) - Audio Interchange file
441
470
  - [`alias`](https://en.wikipedia.org/wiki/Alias_%28Mac_OS%29) - macOS Alias file
442
471
  - [`amr`](https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec) - Adaptive Multi-Rate audio codec
package/supported.js CHANGED
@@ -128,7 +128,6 @@ export const extensions = [
128
128
  'it',
129
129
  's3m',
130
130
  'xm',
131
- 'ai',
132
131
  'skp',
133
132
  'avif',
134
133
  'eps',
@@ -208,10 +207,10 @@ export const mimeTypes = [
208
207
  'application/x-bzip2',
209
208
  'application/x-7z-compressed',
210
209
  'application/x-apple-diskimage',
211
- 'application/x-apache-arrow',
210
+ 'application/vnd.apache.arrow.file',
212
211
  'video/mp4',
213
212
  'audio/midi',
214
- 'video/x-matroska',
213
+ 'video/matroska',
215
214
  'video/webm',
216
215
  'video/quicktime',
217
216
  'video/vnd.avi',
@@ -228,7 +227,7 @@ export const mimeTypes = [
228
227
  'audio/ogg',
229
228
  'audio/ogg; codecs=opus',
230
229
  'application/ogg',
231
- 'audio/x-flac',
230
+ 'audio/flac',
232
231
  'audio/ape',
233
232
  'audio/wavpack',
234
233
  'audio/amr',
@@ -321,7 +320,7 @@ export const mimeTypes = [
321
320
  'image/jls',
322
321
  'application/vnd.ms-outlook',
323
322
  'image/vnd.dwg',
324
- 'application/x-parquet',
323
+ 'application/vnd.apache.parquet',
325
324
  'application/java-vm',
326
325
  'application/x-arj',
327
326
  'application/x-cpio',