file-type
Advanced tools
Comparing version 18.6.0 to 18.7.0
105
core.d.ts
@@ -156,2 +156,3 @@ import type {Readable as ReadableStream} from 'node:stream'; | ||
| 'icc' | ||
| 'fbx' | ||
; // eslint-disable-line semi-style | ||
@@ -306,2 +307,3 @@ | ||
| 'application/vnd.iccprofile' | ||
| 'application/x.autodesk.fbx' | ||
; // eslint-disable-line semi-style | ||
@@ -332,4 +334,4 @@ | ||
@param buffer - An Uint8Array or Buffer representing file data. It works best if the buffer contains the entire file, it may work with a smaller portion as well. | ||
@returns The detected file type and MIME type, or `undefined` when there is no match. | ||
@param buffer - An Uint8Array or Buffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well. | ||
@returns The detected file type, or `undefined` when there is no match. | ||
*/ | ||
@@ -344,3 +346,3 @@ export function fileTypeFromBuffer(buffer: Uint8Array | ArrayBuffer): Promise<FileTypeResult | undefined>; | ||
@param stream - A readable stream representing file data. | ||
@returns The detected file type and MIME type, or `undefined` when there is no match. | ||
@returns The detected file type, or `undefined` when there is no match. | ||
*/ | ||
@@ -357,3 +359,3 @@ export function fileTypeFromStream(stream: ReadableStream): Promise<FileTypeResult | undefined>; | ||
@param tokenizer - File source implementing the tokenizer interface. | ||
@returns The detected file type and MIME type, or `undefined` when there is no match. | ||
@returns The detected file type, or `undefined` when there is no match. | ||
@@ -429,4 +431,7 @@ 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. | ||
/** | ||
Detect the file type of a [`Blob`](https://nodejs.org/api/buffer.html#class-blob). | ||
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). | ||
@param blob - The [`Blob`](https://nodejs.org/api/buffer.html#class-blob) used for file detection. | ||
@returns The detected file type, or `undefined` when there is no match. | ||
@example | ||
@@ -437,3 +442,3 @@ ``` | ||
const blob = new Blob(['<?xml version="1.0" encoding="ISO-8859-1" ?>'], { | ||
type: 'plain/text', | ||
type: 'text/plain', | ||
endings: 'native' | ||
@@ -443,5 +448,91 @@ }); | ||
console.log(await fileTypeFromBlob(blob)); | ||
//=> {ext: 'txt', mime: 'plain/text'} | ||
//=> {ext: 'txt', mime: 'text/plain'} | ||
``` | ||
*/ | ||
export declare function fileTypeFromBlob(blob: Blob): Promise<FileTypeResult | undefined>; | ||
/** | ||
Function that allows specifying custom detection mechanisms. | ||
An iterable of detectors can be provided via the `fileTypeOptions` argument for the {@link FileTypeParser.constructor}. | ||
The detectors are called before the default detections in the provided order. | ||
Custom detectors can be used to add new `FileTypeResults` or to modify return behavior of existing `FileTypeResult` detections. | ||
If the detector returns `undefined`, there are 2 possible scenarios: | ||
1. The detector has not read from the tokenizer, it will be proceeded with the next available detector. | ||
2. The detector has read from the tokenizer (`tokenizer.position` has been increased). | ||
In that case no further detectors will be executed and the final conclusion is that file-type returns undefined. | ||
Note that this an exceptional scenario, as the detector takes the opportunity from any other detector to determine the file type. | ||
Example detector array which can be extended and provided via the fileTypeOptions argument: | ||
``` | ||
import {FileTypeParser} from 'file-type'; | ||
const customDetectors = [ | ||
async tokenizer => { | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // 'UNICORN' as decimal string | ||
const buffer = Buffer.alloc(7); | ||
await tokenizer.peekBuffer(buffer, {length: unicornHeader.length, mayBeLess: true}); | ||
if (unicornHeader.every((value, index) => value === buffer[index])) { | ||
return {ext: 'unicorn', mime: 'application/unicorn'}; | ||
} | ||
return undefined; | ||
}, | ||
]; | ||
const buffer = Buffer.from('UNICORN'); | ||
const parser = new FileTypeParser({customDetectors}); | ||
const fileType = await parser.fromBuffer(buffer); | ||
console.log(fileType); | ||
``` | ||
@param tokenizer - The [tokenizer](https://github.com/Borewit/strtok3#tokenizer) used to read the file content from. | ||
@param fileType - The file type detected by the standard detections or a previous custom detection, or `undefined`` if no matching file type could be found. | ||
@returns The detected file type, or `undefined` when there is no match. | ||
*/ | ||
export type Detector = (tokenizer: ITokenizer, fileType?: FileTypeResult) => Promise<FileTypeResult | undefined>; | ||
export type FileTypeOptions = { | ||
customDetectors?: Iterable<Detector>; | ||
}; | ||
export declare class TokenizerPositionError extends Error { | ||
constructor(message?: string); | ||
} | ||
export declare class FileTypeParser { | ||
detectors: Iterable<Detector>; | ||
constructor(options?: {customDetectors?: Iterable<Detector>}); | ||
/** | ||
Works the same way as {@link fileTypeFromBuffer}, additionally taking into account custom detectors (if any were provided to the constructor). | ||
*/ | ||
fromBuffer(buffer: Uint8Array | ArrayBuffer): Promise<FileTypeResult | undefined>; | ||
/** | ||
Works the same way as {@link fileTypeFromStream}, additionally taking into account custom detectors (if any were provided to the constructor). | ||
*/ | ||
fromStream(stream: ReadableStream): Promise<FileTypeResult | undefined>; | ||
/** | ||
Works the same way as {@link fileTypeFromTokenizer}, additionally taking into account custom detectors (if any were provided to the constructor). | ||
*/ | ||
fromTokenizer(tokenizer: ITokenizer): Promise<FileTypeResult | undefined>; | ||
/** | ||
Works the same way as {@link fileTypeFromBlob}, additionally taking into account custom detectors (if any were provided to the constructor). | ||
*/ | ||
fromBlob(blob: Blob): Promise<FileTypeResult | undefined>; | ||
/** | ||
Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor). | ||
*/ | ||
toDetectionStream(readableStream: ReadableStream, options?: StreamOptions): Promise<FileTypeResult | undefined>; | ||
} |
162
core.js
@@ -14,27 +14,11 @@ import {Buffer} from 'node:buffer'; | ||
export async function fileTypeFromStream(stream) { | ||
const tokenizer = await strtok3.fromStream(stream); | ||
try { | ||
return await fileTypeFromTokenizer(tokenizer); | ||
} finally { | ||
await tokenizer.close(); | ||
} | ||
return new FileTypeParser().fromStream(stream); | ||
} | ||
export async function fileTypeFromBuffer(input) { | ||
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) { | ||
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``); | ||
} | ||
const buffer = input instanceof Uint8Array ? input : new Uint8Array(input); | ||
if (!(buffer?.length > 1)) { | ||
return; | ||
} | ||
return fileTypeFromTokenizer(strtok3.fromBuffer(buffer)); | ||
return new FileTypeParser().fromBuffer(input); | ||
} | ||
export async function fileTypeFromBlob(blob) { | ||
const buffer = await blob.arrayBuffer(); | ||
return fileTypeFromBuffer(new Uint8Array(buffer)); | ||
return new FileTypeParser().fromBlob(blob); | ||
} | ||
@@ -64,12 +48,94 @@ | ||
export async function fileTypeFromTokenizer(tokenizer) { | ||
try { | ||
return new FileTypeParser().parse(tokenizer); | ||
} catch (error) { | ||
if (!(error instanceof strtok3.EndOfStreamError)) { | ||
throw error; | ||
return new FileTypeParser().fromTokenizer(tokenizer); | ||
} | ||
export class FileTypeParser { | ||
constructor(options) { | ||
this.detectors = options?.customDetectors; | ||
this.fromTokenizer = this.fromTokenizer.bind(this); | ||
this.fromBuffer = this.fromBuffer.bind(this); | ||
this.parse = this.parse.bind(this); | ||
} | ||
async fromTokenizer(tokenizer) { | ||
const initialPosition = tokenizer.position; | ||
for (const detector of this.detectors || []) { | ||
const fileType = await detector(tokenizer); | ||
if (fileType) { | ||
return fileType; | ||
} | ||
if (initialPosition !== tokenizer.position) { | ||
return undefined; // Cannot proceed scanning of the tokenizer is at an arbitrary position | ||
} | ||
} | ||
return this.parse(tokenizer); | ||
} | ||
} | ||
class FileTypeParser { | ||
async fromBuffer(input) { | ||
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) { | ||
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``); | ||
} | ||
const buffer = input instanceof Uint8Array ? input : new Uint8Array(input); | ||
if (!(buffer?.length > 1)) { | ||
return; | ||
} | ||
return this.fromTokenizer(strtok3.fromBuffer(buffer)); | ||
} | ||
async fromBlob(blob) { | ||
const buffer = await blob.arrayBuffer(); | ||
return this.fromBuffer(new Uint8Array(buffer)); | ||
} | ||
async fromStream(stream) { | ||
const tokenizer = await strtok3.fromStream(stream); | ||
try { | ||
return await this.fromTokenizer(tokenizer); | ||
} finally { | ||
await tokenizer.close(); | ||
} | ||
} | ||
async toDetectionStream(readableStream, options = {}) { | ||
const {default: stream} = await import('node:stream'); | ||
const {sampleSize = minimumBytes} = options; | ||
return new Promise((resolve, reject) => { | ||
readableStream.on('error', reject); | ||
readableStream.once('readable', () => { | ||
(async () => { | ||
try { | ||
// Set up output stream | ||
const pass = new stream.PassThrough(); | ||
const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass); | ||
// Read the input stream and detect the filetype | ||
const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? Buffer.alloc(0); | ||
try { | ||
pass.fileType = await this.fromBuffer(chunk); | ||
} catch (error) { | ||
if (error instanceof strtok3.EndOfStreamError) { | ||
pass.fileType = undefined; | ||
} else { | ||
reject(error); | ||
} | ||
} | ||
resolve(outputStream); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})(); | ||
}); | ||
}); | ||
} | ||
check(header, options) { | ||
@@ -216,3 +282,3 @@ return _check(this.buffer, header, options); | ||
await tokenizer.ignore(id3HeaderLength); | ||
return fileTypeFromTokenizer(tokenizer); // Skip ID3 header, recursion | ||
return this.fromTokenizer(tokenizer); // Skip ID3 header, recursion | ||
} | ||
@@ -1446,2 +1512,9 @@ | ||
if (this.checkString('Kaydara FBX Binary \u0000')) { | ||
return { | ||
ext: 'fbx', | ||
mime: 'application/x.autodesk.fbx', // Invented by us | ||
}; | ||
} | ||
if ( | ||
@@ -1616,35 +1689,4 @@ this.check([0x4C, 0x50], {offset: 34}) | ||
export async function fileTypeStream(readableStream, {sampleSize = minimumBytes} = {}) { | ||
const {default: stream} = await import('node:stream'); | ||
return new Promise((resolve, reject) => { | ||
readableStream.on('error', reject); | ||
readableStream.once('readable', () => { | ||
(async () => { | ||
try { | ||
// Set up output stream | ||
const pass = new stream.PassThrough(); | ||
const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass); | ||
// Read the input stream and detect the filetype | ||
const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? Buffer.alloc(0); | ||
try { | ||
const fileType = await fileTypeFromBuffer(chunk); | ||
pass.fileType = fileType; | ||
} catch (error) { | ||
if (error instanceof strtok3.EndOfStreamError) { | ||
pass.fileType = undefined; | ||
} else { | ||
reject(error); | ||
} | ||
} | ||
resolve(outputStream); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})(); | ||
}); | ||
}); | ||
export async function fileTypeStream(readableStream, options = {}) { | ||
return new FileTypeParser().toDetectionStream(readableStream, options); | ||
} | ||
@@ -1651,0 +1693,0 @@ |
import * as strtok3 from 'strtok3'; | ||
import {fileTypeFromTokenizer} from './core.js'; | ||
import {FileTypeParser} from './core.js'; | ||
export async function fileTypeFromFile(path) { | ||
export async function fileTypeFromFile(path, fileTypeOptions) { | ||
const tokenizer = await strtok3.fromFile(path); | ||
try { | ||
return await fileTypeFromTokenizer(tokenizer); | ||
const parser = new FileTypeParser(fileTypeOptions); | ||
return await parser.fromTokenizer(tokenizer); | ||
} finally { | ||
@@ -9,0 +10,0 @@ await tokenizer.close(); |
{ | ||
"name": "file-type", | ||
"version": "18.6.0", | ||
"version": "18.7.0", | ||
"description": "Detect the file type of a Buffer/Uint8Array/ArrayBuffer", | ||
@@ -21,2 +21,3 @@ "license": "MIT", | ||
}, | ||
"sideEffects": false, | ||
"engines": { | ||
@@ -209,3 +210,4 @@ "node": ">=14.16" | ||
"avro", | ||
"icc" | ||
"icc", | ||
"fbx" | ||
], | ||
@@ -212,0 +214,0 @@ "dependencies": { |
@@ -116,3 +116,3 @@ # file-type | ||
Returns a `Promise` for an object with the detected file type and MIME type: | ||
Returns a `Promise` for an object with the detected file type: | ||
@@ -128,3 +128,3 @@ - `ext` - One of the [supported file types](#supported-file-types) | ||
A buffer representing file data. It works best if the buffer contains the entire file, it may work with a smaller portion as well. | ||
A buffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well. | ||
@@ -137,3 +137,3 @@ ### fileTypeFromFile(filePath) | ||
Returns a `Promise` for an object with the detected file type and MIME type: | ||
Returns a `Promise` for an object with the detected file type: | ||
@@ -157,3 +157,3 @@ - `ext` - One of the [supported file types](#supported-file-types) | ||
Returns a `Promise` for an object with the detected file type and MIME type: | ||
Returns a `Promise` for an object with the detected file type: | ||
@@ -177,3 +177,3 @@ - `ext` - One of the [supported file types](#supported-file-types) | ||
Returns a `Promise` for an object with the detected file type and MIME type: | ||
Returns a `Promise` for an object with the detected file type: | ||
@@ -189,3 +189,3 @@ - `ext` - One of the [supported file types](#supported-file-types) | ||
const blob = new Blob(['<?xml version="1.0" encoding="ISO-8859-1" ?>'], { | ||
type: 'plain/text', | ||
type: 'text/plain', | ||
endings: 'native' | ||
@@ -195,5 +195,9 @@ }); | ||
console.log(await fileTypeFromBlob(blob)); | ||
//=> {ext: 'txt', mime: 'plain/text'} | ||
//=> {ext: 'txt', mime: 'text/plain'} | ||
``` | ||
#### blob | ||
Type: [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) | ||
### fileTypeFromTokenizer(tokenizer) | ||
@@ -207,3 +211,3 @@ | ||
Returns a `Promise` for an object with the detected file type and MIME type: | ||
Returns a `Promise` for an object with the detected file type: | ||
@@ -316,2 +320,45 @@ - `ext` - One of the [supported file types](#supported-file-types) | ||
## Custom detectors | ||
A custom detector is a function that allows specifying custom detection mechanisms. | ||
An iterable of detectors can be provided via the `fileTypeOptions` argument for the `FileTypeParser` constructor. | ||
The detectors are called before the default detections in the provided order. | ||
Custom detectors can be used to add new `FileTypeResults` or to modify return behaviour of existing `FileTypeResult` detections. | ||
If the detector returns `undefined`, there are 2 possible scenarios: | ||
1. The detector has not read from the tokenizer, it will be proceeded with the next available detector. | ||
2. The detector has read from the tokenizer (`tokenizer.position` has been increased). | ||
In that case no further detectors will be executed and the final conclusion is that file-type returns undefined. | ||
Note that this an exceptional scenario, as the detector takes the opportunity from any other detector to determine the file type. | ||
Example detector array which can be extended and provided to each public method via the `fileTypeOptions` argument: | ||
```js | ||
import {FileTypeParser} from 'file-type'; | ||
const customDetectors = [ | ||
async tokenizer => { | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // 'UNICORN' as decimal string | ||
const buffer = Buffer.alloc(7); | ||
await tokenizer.peekBuffer(buffer, {length: unicornHeader.length, mayBeLess: true}); | ||
if (unicornHeader.every((value, index) => value === buffer[index])) { | ||
return {ext: 'unicorn', mime: 'application/unicorn'}; | ||
} | ||
return undefined; | ||
}, | ||
]; | ||
const buffer = Buffer.from('UNICORN'); | ||
const parser = new FileTypeParser({customDetectors}); | ||
const fileType = await parser.fromBuffer(buffer); | ||
console.log(fileType); | ||
``` | ||
## Supported file types | ||
@@ -347,3 +394,3 @@ | ||
- [`cab`](https://en.wikipedia.org/wiki/Cabinet_(file_format)) - Cabinet file | ||
- [`cfb`](https://en.wikipedia.org/wiki/Compound_File_Binary_Format) - Compount File Binary Format | ||
- [`cfb`](https://en.wikipedia.org/wiki/Compound_File_Binary_Format) - Compound File Binary Format | ||
- [`chm`](https://en.wikipedia.org/wiki/Microsoft_Compiled_HTML_Help) - Microsoft Compiled HTML Help | ||
@@ -360,3 +407,3 @@ - [`class`](https://en.wikipedia.org/wiki/Java_class_file) - Java class file | ||
- [`dng`](https://en.wikipedia.org/wiki/Digital_Negative) - Adobe Digital Negative image file | ||
- [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Word | ||
- [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Word document | ||
- [`dsf`](https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf) - Sony DSD Stream File (DSF) | ||
@@ -373,2 +420,3 @@ - [`dwg`](https://en.wikipedia.org/wiki/.dwg) - Autodesk CAD file | ||
- [`f4v`](https://en.wikipedia.org/wiki/Flash_Video) - ISO base media file format used by Adobe Flash Player | ||
- [`fbx`](https://en.wikipedia.org/wiki/FBX) - Filmbox is a proprietary file format used to provide interoperability between digital content creation apps. | ||
- [`flac`](https://en.wikipedia.org/wiki/FLAC) - Free Lossless Audio Codec | ||
@@ -397,3 +445,3 @@ - [`flif`](https://en.wikipedia.org/wiki/Free_Lossless_Image_Format) - Free Lossless Image Format | ||
- [`lnk`](https://en.wikipedia.org/wiki/Shortcut_%28computing%29#Microsoft_Windows) - Microsoft Windows file shortcut | ||
- [`lz`](https://en.wikipedia.org/wiki/Lzip) - Arhive file | ||
- [`lz`](https://en.wikipedia.org/wiki/Lzip) - Archive file | ||
- [`lzh`](https://en.wikipedia.org/wiki/LHA_(file_format)) - LZH archive | ||
@@ -437,3 +485,3 @@ - [`m4a`](https://en.wikipedia.org/wiki/M4A) - Audio-only MPEG-4 files | ||
- [`png`](https://en.wikipedia.org/wiki/Portable_Network_Graphics) - Portable Network Graphics | ||
- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Powerpoint | ||
- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Powerpoint document | ||
- [`ps`](https://en.wikipedia.org/wiki/Postscript) - Postscript | ||
@@ -468,3 +516,3 @@ - [`psd`](https://en.wikipedia.org/wiki/Adobe_Photoshop#File_format) - Adobe Photoshop document | ||
- [`xcf`](https://en.wikipedia.org/wiki/XCF_(file_format)) - eXperimental Computing Facility | ||
- [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Excel | ||
- [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Excel document | ||
- [`xm`](https://wiki.openmpt.org/Manual:_Module_formats#The_FastTracker_2_format_.28.xm.29) - Audio module format: FastTracker 2 | ||
@@ -477,3 +525,3 @@ - [`xml`](https://en.wikipedia.org/wiki/XML) - eXtensible Markup Language | ||
*Pull requests are welcome for additional commonly used file types.* | ||
*[Pull requests](.github/pull_request_template.md) are welcome for additional commonly used file types.* | ||
@@ -489,5 +537,20 @@ The following file types will not be accepted: | ||
#### tokenizer | ||
Type: [`ITokenizer`](https://github.com/Borewit/strtok3#tokenizer) | ||
Usable as source of the examined file. | ||
#### fileType | ||
Type: `FileTypeResult` | ||
An object having an `ext` (extension) and `mime` (mime type) property. | ||
Detected by the standard detections or a previous custom detection. Undefined if no matching fileTypeResult could be found. | ||
## Related | ||
- [file-type-cli](https://github.com/sindresorhus/file-type-cli) - CLI for this module | ||
- [image-dimensions](https://github.com/sindresorhus/image-dimensions) - Get the dimensions of an image | ||
@@ -498,6 +561,1 @@ ## Maintainers | ||
- [Borewit](https://github.com/Borewit) | ||
**Former** | ||
- [Mikael Finstad](https://github.com/mifi) | ||
- [Ben Brook](https://github.com/bencmbrook) |
@@ -153,2 +153,3 @@ export const extensions = [ | ||
'icc', | ||
'fbx', | ||
]; | ||
@@ -303,2 +304,3 @@ | ||
'application/vnd.iccprofile', | ||
'application/x.autodesk.fbx', // Invented by us | ||
]; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
92171
2322
544