file-type
Advanced tools
Comparing version 19.6.0 to 20.0.0
371
core.d.ts
@@ -14,313 +14,2 @@ /** | ||
export type FileExtension = | ||
| 'jpg' | ||
| 'png' | ||
| 'apng' | ||
| 'gif' | ||
| 'webp' | ||
| 'flif' | ||
| 'xcf' | ||
| 'cr2' | ||
| 'cr3' | ||
| 'orf' | ||
| 'arw' | ||
| 'dng' | ||
| 'nef' | ||
| 'rw2' | ||
| 'raf' | ||
| 'tif' | ||
| 'bmp' | ||
| 'icns' | ||
| 'jxr' | ||
| 'psd' | ||
| 'indd' | ||
| 'zip' | ||
| 'tar' | ||
| 'rar' | ||
| 'gz' | ||
| 'bz2' | ||
| '7z' | ||
| 'dmg' | ||
| 'mp4' | ||
| 'mid' | ||
| 'mkv' | ||
| 'webm' | ||
| 'mov' | ||
| 'avi' | ||
| 'mpg' | ||
| 'mp2' | ||
| 'mp3' | ||
| 'm4a' | ||
| 'ogg' | ||
| 'opus' | ||
| 'flac' | ||
| 'wav' | ||
| 'qcp' | ||
| 'amr' | ||
| 'pdf' | ||
| 'epub' | ||
| 'mobi' | ||
| 'elf' | ||
| 'macho' | ||
| 'exe' | ||
| 'swf' | ||
| 'rtf' | ||
| 'woff' | ||
| 'woff2' | ||
| 'eot' | ||
| 'ttf' | ||
| 'otf' | ||
| 'ico' | ||
| 'flv' | ||
| 'ps' | ||
| 'xz' | ||
| 'sqlite' | ||
| 'nes' | ||
| 'crx' | ||
| 'xpi' | ||
| 'cab' | ||
| 'deb' | ||
| 'ar' | ||
| 'rpm' | ||
| 'Z' | ||
| 'lz' | ||
| 'cfb' | ||
| 'mxf' | ||
| 'mts' | ||
| 'wasm' | ||
| 'blend' | ||
| 'bpg' | ||
| 'docx' | ||
| 'pptx' | ||
| 'xlsx' | ||
| '3gp' | ||
| '3g2' | ||
| 'j2c' | ||
| 'jp2' | ||
| 'jpm' | ||
| 'jpx' | ||
| 'mj2' | ||
| 'aif' | ||
| 'odt' | ||
| 'ods' | ||
| 'odp' | ||
| 'xml' | ||
| 'heic' | ||
| 'cur' | ||
| 'ktx' | ||
| 'ape' | ||
| 'wv' | ||
| 'asf' | ||
| 'dcm' | ||
| 'mpc' | ||
| 'ics' | ||
| 'glb' | ||
| 'pcap' | ||
| 'dsf' | ||
| 'lnk' | ||
| 'alias' | ||
| 'voc' | ||
| 'ac3' | ||
| 'm4b' | ||
| 'm4p' | ||
| 'm4v' | ||
| 'f4a' | ||
| 'f4b' | ||
| 'f4p' | ||
| 'f4v' | ||
| 'mie' | ||
| 'ogv' | ||
| 'ogm' | ||
| 'oga' | ||
| 'spx' | ||
| 'ogx' | ||
| 'arrow' | ||
| 'shp' | ||
| 'aac' | ||
| 'mp1' | ||
| 'it' | ||
| 's3m' | ||
| 'xm' | ||
| 'ai' | ||
| 'skp' | ||
| 'avif' | ||
| 'eps' | ||
| 'lzh' | ||
| 'pgp' | ||
| 'asar' | ||
| 'stl' | ||
| 'chm' | ||
| '3mf' | ||
| 'zst' | ||
| 'jxl' | ||
| 'vcf' | ||
| 'jls' | ||
| 'pst' | ||
| 'dwg' | ||
| 'parquet' | ||
| 'class' | ||
| 'arj' | ||
| 'cpio' | ||
| 'ace' | ||
| 'avro' | ||
| 'icc' | ||
| 'fbx' | ||
| 'vsdx' | ||
| 'vtt' | ||
| 'apk' | ||
; // eslint-disable-line semi-style | ||
export type MimeType = | ||
| 'image/jpeg' | ||
| 'image/png' | ||
| 'image/gif' | ||
| 'image/webp' | ||
| 'image/flif' | ||
| 'image/x-xcf' | ||
| 'image/x-canon-cr2' | ||
| 'image/x-canon-cr3' | ||
| 'image/tiff' | ||
| 'image/bmp' | ||
| 'image/icns' | ||
| 'image/vnd.ms-photo' | ||
| 'image/vnd.adobe.photoshop' | ||
| 'application/x-indesign' | ||
| 'application/epub+zip' | ||
| 'application/x-xpinstall' | ||
| 'application/vnd.oasis.opendocument.text' | ||
| 'application/vnd.oasis.opendocument.spreadsheet' | ||
| 'application/vnd.oasis.opendocument.presentation' | ||
| 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | ||
| 'application/vnd.openxmlformats-officedocument.presentationml.presentation' | ||
| 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 'application/zip' | ||
| 'application/x-tar' | ||
| 'application/x-rar-compressed' | ||
| 'application/gzip' | ||
| 'application/x-bzip2' | ||
| 'application/x-7z-compressed' | ||
| 'application/x-apple-diskimage' | ||
| 'video/mp4' | ||
| 'audio/midi' | ||
| 'video/x-matroska' | ||
| 'video/webm' | ||
| 'video/quicktime' | ||
| 'video/vnd.avi' | ||
| 'audio/wav' | ||
| 'audio/qcelp' | ||
| 'audio/x-ms-asf' | ||
| 'video/x-ms-asf' | ||
| 'application/vnd.ms-asf' | ||
| 'video/mpeg' | ||
| 'video/3gpp' | ||
| 'audio/mpeg' | ||
| 'audio/mp4' // RFC 4337 | ||
| 'video/ogg' | ||
| 'audio/ogg' | ||
| 'audio/ogg; codecs=opus' | ||
| 'application/ogg' | ||
| 'audio/x-flac' | ||
| 'audio/ape' | ||
| 'audio/wavpack' | ||
| 'audio/amr' | ||
| 'application/pdf' | ||
| 'application/x-elf' | ||
| 'application/x-mach-binary' | ||
| 'application/x-msdownload' | ||
| 'application/x-shockwave-flash' | ||
| 'application/rtf' | ||
| 'application/wasm' | ||
| 'font/woff' | ||
| 'font/woff2' | ||
| 'application/vnd.ms-fontobject' | ||
| 'font/ttf' | ||
| 'font/otf' | ||
| 'image/x-icon' | ||
| 'video/x-flv' | ||
| 'application/postscript' | ||
| 'application/eps' | ||
| 'application/x-xz' | ||
| 'application/x-sqlite3' | ||
| 'application/x-nintendo-nes-rom' | ||
| 'application/x-google-chrome-extension' | ||
| 'application/vnd.ms-cab-compressed' | ||
| 'application/x-deb' | ||
| 'application/x-unix-archive' | ||
| 'application/x-rpm' | ||
| 'application/x-compress' | ||
| 'application/x-lzip' | ||
| 'application/x-cfb' | ||
| 'application/x-mie' | ||
| 'application/x-apache-arrow' | ||
| 'application/mxf' | ||
| 'video/mp2t' | ||
| 'application/x-blender' | ||
| 'image/bpg' | ||
| 'image/j2c' | ||
| 'image/jp2' | ||
| 'image/jpx' | ||
| 'image/jpm' | ||
| 'image/mj2' | ||
| 'audio/aiff' | ||
| 'application/xml' | ||
| 'application/x-mobipocket-ebook' | ||
| 'image/heif' | ||
| 'image/heif-sequence' | ||
| 'image/heic' | ||
| 'image/heic-sequence' | ||
| 'image/ktx' | ||
| 'application/dicom' | ||
| 'audio/x-musepack' | ||
| 'text/calendar' | ||
| 'text/vcard' | ||
| 'text/vtt' | ||
| 'model/gltf-binary' | ||
| 'application/vnd.tcpdump.pcap' | ||
| 'audio/x-dsf' // Non-standard | ||
| 'application/x.ms.shortcut' // Invented by us | ||
| 'application/x.apple.alias' // Invented by us | ||
| 'audio/x-voc' | ||
| 'audio/vnd.dolby.dd-raw' | ||
| 'audio/x-m4a' | ||
| 'image/apng' | ||
| 'image/x-olympus-orf' | ||
| 'image/x-sony-arw' | ||
| 'image/x-adobe-dng' | ||
| 'image/x-nikon-nef' | ||
| 'image/x-panasonic-rw2' | ||
| 'image/x-fujifilm-raf' | ||
| 'video/x-m4v' | ||
| 'video/3gpp2' | ||
| 'application/x-esri-shape' | ||
| 'audio/aac' | ||
| 'audio/x-it' | ||
| 'audio/x-s3m' | ||
| 'audio/x-xm' | ||
| 'video/MP1S' | ||
| 'video/MP2P' | ||
| 'application/vnd.sketchup.skp' | ||
| 'image/avif' | ||
| 'application/x-lzh-compressed' | ||
| 'application/pgp-encrypted' | ||
| 'application/x-asar' | ||
| 'model/stl' | ||
| 'application/vnd.ms-htmlhelp' | ||
| 'model/3mf' | ||
| 'image/jxl' | ||
| 'application/zstd' | ||
| 'image/jls' | ||
| 'application/vnd.ms-outlook' | ||
| 'image/vnd.dwg' | ||
| 'application/x-parquet' | ||
| 'application/java-vm' | ||
| 'application/x-arj' | ||
| 'application/x-cpio' | ||
| 'application/x-ace-compressed' | ||
| 'application/avro' | ||
| 'application/vnd.iccprofile' | ||
| 'application/x.autodesk.fbx' | ||
| 'application/vnd.visio' | ||
| 'application/vnd.android.package-archive' | ||
; // eslint-disable-line semi-style | ||
export type FileTypeResult = { | ||
@@ -330,3 +19,3 @@ /** | ||
*/ | ||
readonly ext: FileExtension; | ||
readonly ext: string; | ||
@@ -336,3 +25,3 @@ /** | ||
*/ | ||
readonly mime: MimeType; | ||
readonly mime: string; | ||
}; | ||
@@ -393,3 +82,3 @@ | ||
*/ | ||
export const supportedExtensions: ReadonlySet<FileExtension>; | ||
export const supportedExtensions: ReadonlySet<string>; | ||
@@ -399,3 +88,3 @@ /** | ||
*/ | ||
export const supportedMimeTypes: ReadonlySet<MimeType>; | ||
export const supportedMimeTypes: ReadonlySet<string>; | ||
@@ -433,19 +122,23 @@ export type StreamOptions = { | ||
/** | ||
Function that allows specifying custom detection mechanisms. | ||
A custom file type detector. | ||
An iterable of detectors can be provided via the `fileTypeOptions` argument for the {@link FileTypeParser.constructor}. | ||
Detectors can be added via the constructor options or by directly modifying `FileTypeParser#detectors`. | ||
The detectors are called before the default detections in the provided order. | ||
Detectors provided through the constructor options are executed before the default detectors. | ||
Custom detectors can be used to add new `FileTypeResults` or to modify return behavior of existing `FileTypeResult` detections. | ||
Custom detectors allow for: | ||
- Introducing new `FileTypeResult` entries. | ||
- Modifying the detection behavior of existing `FileTypeResult` types. | ||
If the detector returns `undefined`, there are 2 possible scenarios: | ||
### Detector execution flow | ||
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. | ||
If a detector returns `undefined`, the following rules apply: | ||
Example detector array which can be extended and provided via the fileTypeOptions argument: | ||
1. **No Tokenizer Interaction**: If the detector does not modify the tokenizer's position, the next detector in the sequence is executed. | ||
2. **Tokenizer Interaction**: If the detector modifies the tokenizer's position (`tokenizer.position` is advanced), no further detectors are executed. In this case, the file type remains `undefined`, as subsequent detectors cannot evaluate the content. This is an exceptional scenario, as it prevents any other detectors from determining the file type. | ||
### Example usage | ||
Below is an example of a custom detector array. This can be passed to the `FileTypeParser` via the `fileTypeOptions` argument. | ||
``` | ||
@@ -456,5 +149,5 @@ import {FileTypeParser} from 'file-type'; | ||
async tokenizer => { | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // 'UNICORN' as decimal string | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // "UNICORN" in ASCII decimal | ||
const buffer = Buffer.alloc(7); | ||
const buffer = new Uint8Array(unicornHeader.length); | ||
await tokenizer.peekBuffer(buffer, {length: unicornHeader.length, mayBeLess: true}); | ||
@@ -469,13 +162,16 @@ if (unicornHeader.every((value, index) => value === buffer[index])) { | ||
const buffer = Buffer.from('UNICORN'); | ||
const buffer = new Uint8Array([85, 78, 73, 67, 79, 82, 78]); | ||
const parser = new FileTypeParser({customDetectors}); | ||
const fileType = await parser.fromBuffer(buffer); | ||
console.log(fileType); | ||
console.log(fileType); // {ext: 'unicorn', mime: 'application/unicorn'} | ||
``` | ||
@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. | ||
@param tokenizer - The [tokenizer](https://github.com/Borewit/strtok3#tokenizer) used to read file content. | ||
@param fileType - The file type detected by standard or previous custom detectors, or `undefined` if no match is found. | ||
@returns The detected file type, or `undefined` if no match is found. | ||
*/ | ||
export type Detector = (tokenizer: ITokenizer, fileType?: FileTypeResult) => Promise<FileTypeResult | undefined>; | ||
export type Detector = { | ||
id: string; | ||
detect: (tokenizer: ITokenizer, fileType?: FileTypeResult) => Promise<FileTypeResult | undefined>; | ||
}; | ||
@@ -502,6 +198,11 @@ export type FileTypeOptions = { | ||
export declare class FileTypeParser { | ||
detectors: Iterable<Detector>; | ||
/** | ||
File type detectors. | ||
constructor(options?: {customDetectors?: Iterable<Detector>; signal: AbortSignal}); | ||
Initialized with a single entry holding the built-in detector function. | ||
*/ | ||
detectors: Detector[]; | ||
constructor(options?: {customDetectors?: Iterable<Detector>; signal?: AbortSignal}); | ||
/** | ||
@@ -508,0 +209,0 @@ Works the same way as {@link fileTypeFromBuffer}, additionally taking into account custom detectors (if any were provided to the constructor). |
544
core.js
@@ -7,3 +7,4 @@ /** | ||
import * as strtok3 from 'strtok3/core'; | ||
import {includes, indexOf, getUintBE} from 'uint8array-extras'; | ||
import {ZipHandler} from '@tokenizer/inflate'; | ||
import {includes, getUintBE} from 'uint8array-extras'; | ||
import { | ||
@@ -30,2 +31,123 @@ stringToBytes, | ||
function getFileTypeFromMimeType(mimeType) { | ||
switch (mimeType) { | ||
case 'application/epub+zip': | ||
return { | ||
ext: 'epub', | ||
mime: 'application/epub+zip', | ||
}; | ||
case 'application/vnd.oasis.opendocument.text': | ||
return { | ||
ext: 'odt', | ||
mime: 'application/vnd.oasis.opendocument.text', | ||
}; | ||
case 'application/vnd.oasis.opendocument.text-template': | ||
return { | ||
ext: 'ott', | ||
mime: 'application/vnd.oasis.opendocument.text-template', | ||
}; | ||
case 'application/vnd.oasis.opendocument.spreadsheet': | ||
return { | ||
ext: 'ods', | ||
mime: 'application/vnd.oasis.opendocument.spreadsheet', | ||
}; | ||
case 'application/vnd.oasis.opendocument.spreadsheet-template': | ||
return { | ||
ext: 'ots', | ||
mime: 'application/vnd.oasis.opendocument.spreadsheet-template', | ||
}; | ||
case 'application/vnd.oasis.opendocument.presentation': | ||
return { | ||
ext: 'odp', | ||
mime: 'application/vnd.oasis.opendocument.presentation', | ||
}; | ||
case 'application/vnd.oasis.opendocument.presentation-template': | ||
return { | ||
ext: 'otp', | ||
mime: 'application/vnd.oasis.opendocument.presentation-template', | ||
}; | ||
case 'application/vnd.oasis.opendocument.graphics': | ||
return { | ||
ext: 'odg', | ||
mime: 'application/vnd.oasis.opendocument.graphics', | ||
}; | ||
case 'application/vnd.oasis.opendocument.graphics-template': | ||
return { | ||
ext: 'otg', | ||
mime: 'application/vnd.oasis.opendocument.graphics-template', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': | ||
return { | ||
ext: 'xlsx', | ||
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
}; | ||
case 'application/vnd.ms-excel.sheet.macroEnabled': | ||
return { | ||
ext: 'xlsm', | ||
mime: 'application/vnd.ms-excel.sheet.macroEnabled.12', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': | ||
return { | ||
ext: 'xltx', | ||
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', | ||
}; | ||
case 'application/vnd.ms-excel.template.macroEnabled': | ||
return { | ||
ext: 'xltm', | ||
mime: 'application/vnd.ms-excel.template.macroenabled.12', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': | ||
return { | ||
ext: 'docx', | ||
mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||
}; | ||
case 'application/vnd.ms-word.document.macroEnabled': | ||
return { | ||
ext: 'docm', | ||
mime: 'application/vnd.ms-word.document.macroEnabled.12', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.template': | ||
return { | ||
ext: 'dotx', | ||
mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', | ||
}; | ||
case 'application/vnd.ms-word.template.macroEnabledTemplate': | ||
return { | ||
ext: 'dotm', | ||
mime: 'application/vnd.ms-word.template.macroEnabled.12', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.presentationml.template': | ||
return { | ||
ext: 'potx', | ||
mime: 'application/vnd.openxmlformats-officedocument.presentationml.template', | ||
}; | ||
case 'application/vnd.ms-powerpoint.template.macroEnabled': | ||
return { | ||
ext: 'potm', | ||
mime: 'application/vnd.ms-powerpoint.template.macroEnabled.12', | ||
}; | ||
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': | ||
return { | ||
ext: 'pptx', | ||
mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', | ||
}; | ||
case 'application/vnd.ms-powerpoint.presentation.macroEnabled': | ||
return { | ||
ext: 'pptm', | ||
mime: 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', | ||
}; | ||
case 'application/vnd.ms-visio.drawing': | ||
return { | ||
ext: 'vsdx', | ||
mime: 'application/vnd.visio', | ||
}; | ||
case 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml': | ||
return { | ||
ext: '3mf', | ||
mime: 'model/3mf', | ||
}; | ||
default: | ||
} | ||
} | ||
function _check(buffer, headers, options) { | ||
@@ -62,9 +184,8 @@ options = { | ||
constructor(options) { | ||
this.detectors = options?.customDetectors; | ||
this.detectors = [...(options?.customDetectors ?? []), | ||
{id: 'core', detect: this.detectConfident}, | ||
{id: 'core.imprecise', detect: this.detectImprecise}]; | ||
this.tokenizerOptions = { | ||
abortSignal: options?.signal, | ||
}; | ||
this.fromTokenizer = this.fromTokenizer.bind(this); | ||
this.fromBuffer = this.fromBuffer.bind(this); | ||
this.parse = this.parse.bind(this); | ||
} | ||
@@ -75,4 +196,5 @@ | ||
for (const detector of this.detectors || []) { | ||
const fileType = await detector(tokenizer); | ||
// Iterate through all file-type detectors | ||
for (const detector of this.detectors) { | ||
const fileType = await detector.detect(tokenizer); | ||
if (fileType) { | ||
@@ -86,4 +208,2 @@ return fileType; | ||
} | ||
return this.parse(tokenizer); | ||
} | ||
@@ -171,3 +291,4 @@ | ||
async parse(tokenizer) { | ||
// Detections with a high degree of certainty in identifying the correct file type | ||
detectConfident = async tokenizer => { | ||
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes); | ||
@@ -262,3 +383,3 @@ | ||
this.tokenizer.ignore(3); | ||
return this.parse(tokenizer); | ||
return this.detectConfident(tokenizer); | ||
} | ||
@@ -397,136 +518,65 @@ | ||
if (this.check([0x50, 0x4B, 0x3, 0x4])) { // Local file header signature | ||
try { | ||
while (tokenizer.position + 30 < tokenizer.fileInfo.size) { | ||
await tokenizer.readBuffer(this.buffer, {length: 30}); | ||
const view = new DataView(this.buffer.buffer); | ||
// https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers | ||
const zipHeader = { | ||
compressedSize: view.getUint32(18, true), | ||
uncompressedSize: view.getUint32(22, true), | ||
filenameLength: view.getUint16(26, true), | ||
extraFieldLength: view.getUint16(28, true), | ||
}; | ||
zipHeader.filename = await tokenizer.readToken(new Token.StringType(zipHeader.filenameLength, 'utf-8')); | ||
await tokenizer.ignore(zipHeader.extraFieldLength); | ||
if (/classes\d*\.dex/.test(zipHeader.filename)) { | ||
let fileType; | ||
await new ZipHandler(tokenizer).unzip(zipHeader => { | ||
switch (zipHeader.filename) { | ||
case 'META-INF/mozilla.rsa': | ||
fileType = { | ||
ext: 'xpi', | ||
mime: 'application/x-xpinstall', | ||
}; | ||
return { | ||
ext: 'apk', | ||
mime: 'application/vnd.android.package-archive', | ||
stop: true, | ||
}; | ||
} | ||
// Assumes signed `.xpi` from addons.mozilla.org | ||
if (zipHeader.filename === 'META-INF/mozilla.rsa') { | ||
case 'META-INF/MANIFEST.MF': | ||
fileType = { | ||
ext: 'jar', | ||
mime: 'application/java-archive', | ||
}; | ||
return { | ||
ext: 'xpi', | ||
mime: 'application/x-xpinstall', | ||
stop: true, | ||
}; | ||
} | ||
if (zipHeader.filename.endsWith('.rels') || zipHeader.filename.endsWith('.xml')) { | ||
const type = zipHeader.filename.split('/')[0]; | ||
switch (type) { | ||
case '_rels': | ||
break; | ||
case 'word': | ||
return { | ||
ext: 'docx', | ||
mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||
}; | ||
case 'ppt': | ||
return { | ||
ext: 'pptx', | ||
mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', | ||
}; | ||
case 'xl': | ||
return { | ||
ext: 'xlsx', | ||
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
}; | ||
case 'visio': | ||
return { | ||
ext: 'vsdx', | ||
mime: 'application/vnd.visio', | ||
}; | ||
default: | ||
break; | ||
} | ||
} | ||
if (zipHeader.filename.startsWith('xl/')) { | ||
case 'mimetype': | ||
return { | ||
ext: 'xlsx', | ||
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
async handler(fileData) { | ||
// Use TextDecoder to decode the UTF-8 encoded data | ||
const mimeType = new TextDecoder('utf-8').decode(fileData).trim(); | ||
fileType = getFileTypeFromMimeType(mimeType); | ||
}, | ||
stop: true, | ||
}; | ||
} | ||
if (zipHeader.filename.startsWith('3D/') && zipHeader.filename.endsWith('.model')) { | ||
case '[Content_Types].xml': | ||
return { | ||
ext: '3mf', | ||
mime: 'model/3mf', | ||
async handler(fileData) { | ||
// Use TextDecoder to decode the UTF-8 encoded data | ||
let xmlContent = new TextDecoder('utf-8').decode(fileData); | ||
const endPos = xmlContent.indexOf('.main+xml"'); | ||
if (endPos === -1) { | ||
const mimeType = 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'; | ||
if (xmlContent.includes(`ContentType="${mimeType}"`)) { | ||
fileType = getFileTypeFromMimeType(mimeType); | ||
} | ||
} else { | ||
xmlContent = xmlContent.slice(0, Math.max(0, endPos)); | ||
const firstPos = xmlContent.lastIndexOf('"'); | ||
const mimeType = xmlContent.slice(Math.max(0, firstPos + 1)); | ||
fileType = getFileTypeFromMimeType(mimeType); | ||
} | ||
}, | ||
stop: true, | ||
}; | ||
} | ||
// The docx, xlsx and pptx file types extend the Office Open XML file format: | ||
// https://en.wikipedia.org/wiki/Office_Open_XML_file_formats | ||
// We look for: | ||
// - one entry named '[Content_Types].xml' or '_rels/.rels', | ||
// - one entry indicating specific type of file. | ||
// MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it. | ||
if (zipHeader.filename === 'mimetype' && zipHeader.compressedSize === zipHeader.uncompressedSize) { | ||
let mimeType = await tokenizer.readToken(new Token.StringType(zipHeader.compressedSize, 'utf-8')); | ||
mimeType = mimeType.trim(); | ||
switch (mimeType) { | ||
case 'application/epub+zip': | ||
return { | ||
ext: 'epub', | ||
mime: 'application/epub+zip', | ||
}; | ||
case 'application/vnd.oasis.opendocument.text': | ||
return { | ||
ext: 'odt', | ||
mime: 'application/vnd.oasis.opendocument.text', | ||
}; | ||
case 'application/vnd.oasis.opendocument.spreadsheet': | ||
return { | ||
ext: 'ods', | ||
mime: 'application/vnd.oasis.opendocument.spreadsheet', | ||
}; | ||
case 'application/vnd.oasis.opendocument.presentation': | ||
return { | ||
ext: 'odp', | ||
mime: 'application/vnd.oasis.opendocument.presentation', | ||
}; | ||
default: | ||
default: | ||
if (/classes\d*\.dex/.test(zipHeader.filename)) { | ||
fileType = { | ||
ext: 'apk', | ||
mime: 'application/vnd.android.package-archive', | ||
}; | ||
return {stop: true}; | ||
} | ||
} | ||
// Try to find next header manually when current one is corrupted | ||
if (zipHeader.compressedSize === 0) { | ||
let nextHeaderIndex = -1; | ||
while (nextHeaderIndex < 0 && (tokenizer.position < tokenizer.fileInfo.size)) { | ||
await tokenizer.peekBuffer(this.buffer, {mayBeLess: true}); | ||
nextHeaderIndex = indexOf(this.buffer, new Uint8Array([0x50, 0x4B, 0x03, 0x04])); | ||
// Move position to the next header if found, skip the whole buffer otherwise | ||
await tokenizer.ignore(nextHeaderIndex >= 0 ? nextHeaderIndex : this.buffer.length); | ||
} | ||
} else { | ||
await tokenizer.ignore(zipHeader.compressedSize); | ||
} | ||
return {}; | ||
} | ||
} catch (error) { | ||
if (!(error instanceof strtok3.EndOfStreamError)) { | ||
throw error; | ||
} | ||
} | ||
}); | ||
return { | ||
return fileType ?? { | ||
ext: 'zip', | ||
@@ -609,64 +659,2 @@ mime: 'application/zip', | ||
// | ||
// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format) | ||
// It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box. | ||
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters. | ||
// Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character). | ||
if ( | ||
this.checkString('ftyp', {offset: 4}) | ||
&& (this.buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII? | ||
) { | ||
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect. | ||
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension. | ||
const brandMajor = new Token.StringType(4, 'latin1').get(this.buffer, 8).replace('\0', ' ').trim(); | ||
switch (brandMajor) { | ||
case 'avif': | ||
case 'avis': | ||
return {ext: 'avif', mime: 'image/avif'}; | ||
case 'mif1': | ||
return {ext: 'heic', mime: 'image/heif'}; | ||
case 'msf1': | ||
return {ext: 'heic', mime: 'image/heif-sequence'}; | ||
case 'heic': | ||
case 'heix': | ||
return {ext: 'heic', mime: 'image/heic'}; | ||
case 'hevc': | ||
case 'hevx': | ||
return {ext: 'heic', mime: 'image/heic-sequence'}; | ||
case 'qt': | ||
return {ext: 'mov', mime: 'video/quicktime'}; | ||
case 'M4V': | ||
case 'M4VH': | ||
case 'M4VP': | ||
return {ext: 'm4v', mime: 'video/x-m4v'}; | ||
case 'M4P': | ||
return {ext: 'm4p', mime: 'video/mp4'}; | ||
case 'M4B': | ||
return {ext: 'm4b', mime: 'audio/mp4'}; | ||
case 'M4A': | ||
return {ext: 'm4a', mime: 'audio/x-m4a'}; | ||
case 'F4V': | ||
return {ext: 'f4v', mime: 'video/mp4'}; | ||
case 'F4P': | ||
return {ext: 'f4p', mime: 'video/mp4'}; | ||
case 'F4A': | ||
return {ext: 'f4a', mime: 'audio/mp4'}; | ||
case 'F4B': | ||
return {ext: 'f4b', mime: 'audio/mp4'}; | ||
case 'crx': | ||
return {ext: 'cr3', mime: 'image/x-canon-cr3'}; | ||
default: | ||
if (brandMajor.startsWith('3g')) { | ||
if (brandMajor.startsWith('3g2')) { | ||
return {ext: '3g2', mime: 'video/3gpp2'}; | ||
} | ||
return {ext: '3gp', mime: 'video/3gpp'}; | ||
} | ||
return {ext: 'mp4', mime: 'video/mp4'}; | ||
} | ||
} | ||
if (this.checkString('MThd')) { | ||
@@ -853,5 +841,5 @@ return { | ||
const re = await readElement(); | ||
const docType = await readChildren(re.len); | ||
const documentType = await readChildren(re.len); | ||
switch (docType) { | ||
switch (documentType) { | ||
case 'webm': | ||
@@ -979,2 +967,9 @@ return { | ||
if (this.check([0x04, 0x22, 0x4D, 0x18])) { | ||
return { | ||
ext: 'lz4', | ||
mime: 'application/x-lz4', // Invented by us | ||
}; | ||
} | ||
// -- 5-byte signatures -- | ||
@@ -1070,2 +1065,9 @@ | ||
if (this.checkString('DRACO')) { | ||
return { | ||
ext: 'drc', | ||
mime: 'application/vnd.google.draco', // Invented by us | ||
}; | ||
} | ||
// -- 6-byte signatures -- | ||
@@ -1256,2 +1258,62 @@ | ||
// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format) | ||
// It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box. | ||
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters. | ||
// Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character). | ||
if ( | ||
this.checkString('ftyp', {offset: 4}) | ||
&& (this.buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII? | ||
) { | ||
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect. | ||
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension. | ||
const brandMajor = new Token.StringType(4, 'latin1').get(this.buffer, 8).replace('\0', ' ').trim(); | ||
switch (brandMajor) { | ||
case 'avif': | ||
case 'avis': | ||
return {ext: 'avif', mime: 'image/avif'}; | ||
case 'mif1': | ||
return {ext: 'heic', mime: 'image/heif'}; | ||
case 'msf1': | ||
return {ext: 'heic', mime: 'image/heif-sequence'}; | ||
case 'heic': | ||
case 'heix': | ||
return {ext: 'heic', mime: 'image/heic'}; | ||
case 'hevc': | ||
case 'hevx': | ||
return {ext: 'heic', mime: 'image/heic-sequence'}; | ||
case 'qt': | ||
return {ext: 'mov', mime: 'video/quicktime'}; | ||
case 'M4V': | ||
case 'M4VH': | ||
case 'M4VP': | ||
return {ext: 'm4v', mime: 'video/x-m4v'}; | ||
case 'M4P': | ||
return {ext: 'm4p', mime: 'video/mp4'}; | ||
case 'M4B': | ||
return {ext: 'm4b', mime: 'audio/mp4'}; | ||
case 'M4A': | ||
return {ext: 'm4a', mime: 'audio/x-m4a'}; | ||
case 'F4V': | ||
return {ext: 'f4v', mime: 'video/mp4'}; | ||
case 'F4P': | ||
return {ext: 'f4p', mime: 'video/mp4'}; | ||
case 'F4A': | ||
return {ext: 'f4a', mime: 'audio/mp4'}; | ||
case 'F4B': | ||
return {ext: 'f4b', mime: 'audio/mp4'}; | ||
case 'crx': | ||
return {ext: 'cr3', mime: 'image/x-canon-cr3'}; | ||
default: | ||
if (brandMajor.startsWith('3g')) { | ||
if (brandMajor.startsWith('3g2')) { | ||
return {ext: '3g2', mime: 'video/3gpp2'}; | ||
} | ||
return {ext: '3gp', mime: 'video/3gpp'}; | ||
} | ||
return {ext: 'mp4', mime: 'video/mp4'}; | ||
} | ||
} | ||
// -- 12-byte signatures -- | ||
@@ -1396,35 +1458,2 @@ | ||
// -- Unsafe signatures -- | ||
if ( | ||
this.check([0x0, 0x0, 0x1, 0xBA]) | ||
|| this.check([0x0, 0x0, 0x1, 0xB3]) | ||
) { | ||
return { | ||
ext: 'mpg', | ||
mime: 'video/mpeg', | ||
}; | ||
} | ||
if (this.check([0x00, 0x01, 0x00, 0x00, 0x00])) { | ||
return { | ||
ext: 'ttf', | ||
mime: 'font/ttf', | ||
}; | ||
} | ||
if (this.check([0x00, 0x00, 0x01, 0x00])) { | ||
return { | ||
ext: 'ico', | ||
mime: 'image/x-icon', | ||
}; | ||
} | ||
if (this.check([0x00, 0x00, 0x02, 0x00])) { | ||
return { | ||
ext: 'cur', | ||
mime: 'image/x-icon', | ||
}; | ||
} | ||
if (this.check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) { | ||
@@ -1635,3 +1664,41 @@ // Detected Microsoft Compound File Binary File (MS-CFB) Format. | ||
} | ||
}; | ||
// Detections with limited supporting data, resulting in a higher likelihood of false positives | ||
detectImprecise = async tokenizer => { | ||
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes); | ||
// Read initial sample size of 8 bytes | ||
await tokenizer.peekBuffer(this.buffer, {length: Math.min(8, tokenizer.fileInfo.size), mayBeLess: true}); | ||
if ( | ||
this.check([0x0, 0x0, 0x1, 0xBA]) | ||
|| this.check([0x0, 0x0, 0x1, 0xB3]) | ||
) { | ||
return { | ||
ext: 'mpg', | ||
mime: 'video/mpeg', | ||
}; | ||
} | ||
if (this.check([0x00, 0x01, 0x00, 0x00, 0x00])) { | ||
return { | ||
ext: 'ttf', | ||
mime: 'font/ttf', | ||
}; | ||
} | ||
if (this.check([0x00, 0x00, 0x01, 0x00])) { | ||
return { | ||
ext: 'ico', | ||
mime: 'image/x-icon', | ||
}; | ||
} | ||
if (this.check([0x00, 0x00, 0x02, 0x00])) { | ||
return { | ||
ext: 'cur', | ||
mime: 'image/x-icon', | ||
}; | ||
} | ||
// Check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE) | ||
@@ -1680,3 +1747,3 @@ if (this.buffer.length >= 2 && this.check([0xFF, 0xE0], {offset: 0, mask: [0xFF, 0xE0]})) { | ||
} | ||
} | ||
}; | ||
@@ -1725,7 +1792,14 @@ async readTiffTag(bigEndian) { | ||
if (ifdOffset >= 8 && (this.check([0x1C, 0x00, 0xFE, 0x00], {offset: 8}) || this.check([0x1F, 0x00, 0x0B, 0x00], {offset: 8}))) { | ||
return { | ||
ext: 'nef', | ||
mime: 'image/x-nikon-nef', | ||
}; | ||
if (ifdOffset >= 8) { | ||
const someId1 = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 8); | ||
const someId2 = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 10); | ||
if ( | ||
(someId1 === 0x1C && someId2 === 0xFE) | ||
|| (someId1 === 0x1F && someId2 === 0x0B)) { | ||
return { | ||
ext: 'nef', | ||
mime: 'image/x-nikon-nef', | ||
}; | ||
} | ||
} | ||
@@ -1732,0 +1806,0 @@ } |
@@ -7,4 +7,10 @@ /** | ||
import type {AnyWebByteStream} from 'strtok3'; | ||
import type {FileTypeResult, StreamOptions, AnyWebReadableStream, Detector, AnyWebReadableByteStreamWithFileType} from './core.js'; | ||
import {FileTypeParser} from './core.js'; | ||
import { | ||
type FileTypeResult, | ||
type StreamOptions, | ||
type AnyWebReadableStream, | ||
type Detector, | ||
type AnyWebReadableByteStreamWithFileType, | ||
FileTypeParser as DefaultFileTypeParser, | ||
} from './core.js'; | ||
@@ -18,3 +24,3 @@ export type ReadableStreamWithFileType = NodeReadableStream & { | ||
*/ | ||
export declare class NodeFileTypeParser extends FileTypeParser { | ||
export declare class FileTypeParser extends DefaultFileTypeParser { | ||
/** | ||
@@ -21,0 +27,0 @@ @param stream - Node.js `stream.Readable` or web `ReadableStream`. |
18
index.js
@@ -8,5 +8,5 @@ /** | ||
import * as strtok3 from 'strtok3'; | ||
import {FileTypeParser, reasonableDetectionSizeInBytes} from './core.js'; | ||
import {FileTypeParser as DefaultFileTypeParser, reasonableDetectionSizeInBytes} from './core.js'; | ||
export class NodeFileTypeParser extends FileTypeParser { | ||
export class FileTypeParser extends DefaultFileTypeParser { | ||
async fromStream(stream) { | ||
@@ -70,13 +70,19 @@ const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream, this.tokenizerOptions) : strtok3.fromStream(stream, this.tokenizerOptions)); | ||
export async function fileTypeFromFile(path, fileTypeOptions) { | ||
return (new NodeFileTypeParser(fileTypeOptions)).fromFile(path, fileTypeOptions); | ||
return (new FileTypeParser(fileTypeOptions)).fromFile(path, fileTypeOptions); | ||
} | ||
export async function fileTypeFromStream(stream, fileTypeOptions) { | ||
return (new NodeFileTypeParser(fileTypeOptions)).fromStream(stream); | ||
return (new FileTypeParser(fileTypeOptions)).fromStream(stream); | ||
} | ||
export async function fileTypeStream(readableStream, options = {}) { | ||
return new NodeFileTypeParser(options).toDetectionStream(readableStream, options); | ||
return new FileTypeParser(options).toDetectionStream(readableStream, options); | ||
} | ||
export {fileTypeFromTokenizer, fileTypeFromBuffer, fileTypeFromBlob, FileTypeParser, supportedMimeTypes, supportedExtensions} from './core.js'; | ||
export { | ||
fileTypeFromTokenizer, | ||
fileTypeFromBuffer, | ||
fileTypeFromBlob, | ||
supportedMimeTypes, | ||
supportedExtensions, | ||
} from './core.js'; |
{ | ||
"name": "file-type", | ||
"version": "19.6.0", | ||
"version": "20.0.0", | ||
"description": "Detect the file type of a file, stream, or data", | ||
@@ -220,18 +220,36 @@ "license": "MIT", | ||
"vtt", | ||
"apk" | ||
"apk", | ||
"drc", | ||
"lz4", | ||
"potx", | ||
"xltx", | ||
"dotx", | ||
"xltm", | ||
"ots", | ||
"odg", | ||
"otg", | ||
"otp", | ||
"ott", | ||
"xlsm", | ||
"docm", | ||
"dotm", | ||
"potm", | ||
"pptm", | ||
"jar" | ||
], | ||
"dependencies": { | ||
"get-stream": "^9.0.1", | ||
"strtok3": "^9.0.1", | ||
"@tokenizer/inflate": "^0.2.6", | ||
"strtok3": "^10.0.1", | ||
"token-types": "^6.0.0", | ||
"uint8array-extras": "^1.3.0" | ||
"uint8array-extras": "^1.4.0" | ||
}, | ||
"devDependencies": { | ||
"@tokenizer/token": "^0.3.0", | ||
"@types/node": "^20.10.7", | ||
"@types/node": "^22.10.5", | ||
"ava": "^6.0.1", | ||
"commonmark": "^0.30.0", | ||
"commonmark": "^0.31.2", | ||
"get-stream": "^9.0.1", | ||
"noop-stream": "^1.0.0", | ||
"tsd": "^0.30.3", | ||
"xo": "^0.56.0" | ||
"tsd": "^0.31.2", | ||
"xo": "^0.60.0" | ||
}, | ||
@@ -238,0 +256,0 @@ "xo": { |
102
readme.md
@@ -19,3 +19,3 @@ <h1 align="center" title="file-type"> | ||
**This package is an ESM package. Your project needs to be ESM too. [Read more](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** | ||
**This package is an ESM package. Your project needs to be ESM too. [Read more](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). For TypeScript + CommonJS, see [`load-esm`](https://github.com/Borewit/load-esm).** | ||
@@ -26,3 +26,3 @@ If you use it with Webpack, you need the latest Webpack version and ensure you configure it correctly for ESM. | ||
#### Node.js | ||
### Node.js | ||
@@ -96,3 +96,3 @@ Determine file type from a file: | ||
#### Browser | ||
### Browser | ||
@@ -184,6 +184,6 @@ ```js | ||
[!TIP] | ||
> [!TIP] | ||
> A [`File` object](https://developer.mozilla.org/docs/Web/API/File) is a `Blob` and can be passed in here. | ||
It will **stream** the underlying Blob, and required a [ReadableStreamBYOBReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) which **require Node.js ≥ 20**. | ||
It will **stream** the underlying Blob. | ||
@@ -211,2 +211,17 @@ The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the blob. | ||
> [!WARNING] | ||
> This method depends on [ReadableStreamBYOBReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) which **requires Node.js ≥ 20** | ||
> and [may not be available in all modern browsers](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader#browser_compatibility). | ||
To work around this limitation, you can use an alternative approach to read and process the `Blob` without relying on streaming: | ||
```js | ||
import {fileTypeFromBuffer} from 'file-type'; | ||
async function readFromBlobWithoutStreaming(blob) { | ||
const buffer = await blob.arrayBuffer(); | ||
return fileTypeFromBuffer(buffer); | ||
} | ||
``` | ||
#### blob | ||
@@ -333,30 +348,33 @@ | ||
A custom detector is a function that allows specifying custom detection mechanisms. | ||
A custom file type detector. | ||
An iterable of detectors can be provided via the `fileTypeOptions` argument for the `FileTypeParser` constructor. | ||
In Node.js, you should use `NodeFileTypeParser`, which extends `FileTypeParser` and provides access to Node.js specific functions. | ||
Detectors can be added via the constructor options or by directly modifying `FileTypeParser#detectors`. | ||
The detectors are called before the default detections in the provided order. | ||
Detectors provided through the constructor options are executed before the default detectors. | ||
Custom detectors can be used to add new `FileTypeResults` or to modify return behaviour of existing `FileTypeResult` detections. | ||
Custom detectors allow for: | ||
- Introducing new `FileTypeResult` entries. | ||
- Modifying the detection behavior of existing `FileTypeResult` types. | ||
If the detector returns `undefined`, there are 2 possible scenarios: | ||
### Detector execution flow | ||
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. | ||
If a detector returns `undefined`, the following rules apply: | ||
Example detector array which can be extended and provided to each public method via the `fileTypeOptions` argument: | ||
1. **No Tokenizer Interaction**: If the detector does not modify the tokenizer's position, the next detector in the sequence is executed. | ||
2. **Tokenizer Interaction**: If the detector modifies the tokenizer's position (`tokenizer.position` is advanced), no further detectors are executed. In this case, the file type remains `undefined`, as subsequent detectors cannot evaluate the content. This is an exceptional scenario, as it prevents any other detectors from determining the file type. | ||
### Example usage | ||
Below is an example of a custom detector array. This can be passed to the `FileTypeParser` via the `fileTypeOptions` argument. | ||
```js | ||
import {FileTypeParser} from 'file-type'; // or `NodeFileTypeParser` in Node.js | ||
import {FileTypeParser} from 'file-type'; | ||
const customDetectors = [ | ||
async tokenizer => { | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // 'UNICORN' as decimal string | ||
const unicornDetector = { | ||
id: 'unicorn', // May be used to recognize the detector in the detector list | ||
async detect(tokenizer) { | ||
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // "UNICORN" in ASCII decimal | ||
const buffer = new Uint8Array(7); | ||
const buffer = new Uint8Array(unicornHeader.length); | ||
await tokenizer.peekBuffer(buffer, {length: unicornHeader.length, mayBeLess: true}); | ||
if (unicornHeader.every((value, index) => value === buffer[index])) { | ||
@@ -367,11 +385,20 @@ return {ext: 'unicorn', mime: 'application/unicorn'}; | ||
return undefined; | ||
}, | ||
]; | ||
} | ||
} | ||
const buffer = new Uint8Array(new TextEncoder().encode('UNICORN')); | ||
const parser = new FileTypeParser({customDetectors}); // `NodeFileTypeParser({customDetectors})` in Node.js | ||
const buffer = new Uint8Array([85, 78, 73, 67, 79, 82, 78]); | ||
const parser = new FileTypeParser({customDetectors: [unicornDetector]}); | ||
const fileType = await parser.fromBuffer(buffer); | ||
console.log(fileType); | ||
console.log(fileType); // {ext: 'unicorn', mime: 'application/unicorn'} | ||
``` | ||
```ts | ||
/** | ||
@param tokenizer - The [tokenizer](https://github.com/Borewit/strtok3#tokenizer) used to read file content. | ||
@param fileType - The file type detected by standard or previous custom detectors, or `undefined` if no match is found. | ||
@returns The detected file type, or `undefined` if no match is found. | ||
*/ | ||
export type Detector = (tokenizer: ITokenizer, fileType?: FileTypeResult) => Promise<FileTypeResult | undefined>; | ||
``` | ||
## Abort signal | ||
@@ -382,3 +409,3 @@ | ||
```js | ||
import {FileTypeParser} from 'file-type'; // or `NodeFileTypeParser` in Node.js | ||
import {FileTypeParser} from 'file-type'; | ||
@@ -437,3 +464,7 @@ const abortController = new AbortController() | ||
- [`dng`](https://en.wikipedia.org/wiki/Digital_Negative) - Adobe Digital Negative image file | ||
- [`docm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Word macro-enabled document | ||
- [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Word document | ||
- [`dotm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Word macro-enabled template | ||
- [`dotx`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Word template | ||
- [`drc`](https://en.wikipedia.org/wiki/Zstandard) - Google's Draco 3D Data Compression | ||
- [`dsf`](https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf) - Sony DSD Stream File (DSF) | ||
@@ -465,2 +496,3 @@ - [`dwg`](https://en.wikipedia.org/wiki/.dwg) - Autodesk CAD file | ||
- [`j2c`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000 | ||
- [`jar`](https://en.wikipedia.org/wiki/JAR_(file_format)) - Java archive | ||
- [`jls`](https://en.wikipedia.org/wiki/Lossless_JPEG#JPEG-LS) - Lossless/near-lossless compression standard for continuous-tone images | ||
@@ -476,2 +508,3 @@ - [`jp2`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000 | ||
- [`lz`](https://en.wikipedia.org/wiki/Lzip) - Archive file | ||
- [`lz4`](https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)) - Compressed archive created by one of a variety of LZ4 compression utilities | ||
- [`lzh`](https://en.wikipedia.org/wiki/LHA_(file_format)) - LZH archive | ||
@@ -499,2 +532,3 @@ - [`m4a`](https://en.wikipedia.org/wiki/M4A) - Audio-only MPEG-4 files | ||
- [`nes`](https://fileinfo.com/extension/nes) - Nintendo NES ROM | ||
- [`odg`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for drawing | ||
- [`odp`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for presentations | ||
@@ -511,2 +545,6 @@ - [`ods`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for spreadsheets | ||
- [`otf`](https://en.wikipedia.org/wiki/OpenType) - OpenType font | ||
- [`otg`](https://en.wikipedia.org/wiki/OpenDocument_technical_specification#Templates) - OpenDocument template for drawing | ||
- [`otp`](https://en.wikipedia.org/wiki/OpenDocument_technical_specification#Templates) - OpenDocument template for presentations | ||
- [`ots`](https://en.wikipedia.org/wiki/OpenDocument_technical_specification#Templates) - OpenDocument template for spreadsheets | ||
- [`ott`](https://en.wikipedia.org/wiki/OpenDocument_technical_specification#Templates) - OpenDocument template for word processing | ||
- [`parquet`](https://en.wikipedia.org/wiki/Apache_Parquet) - Apache Parquet | ||
@@ -517,3 +555,6 @@ - [`pcap`](https://wiki.wireshark.org/Development/LibpcapFileFormat) - Libpcap File Format | ||
- [`png`](https://en.wikipedia.org/wiki/Portable_Network_Graphics) - Portable Network Graphics | ||
- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Powerpoint document | ||
- [`potm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft PowerPoint macro-enabled template | ||
- [`potx`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft PowerPoint template | ||
- [`pptm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft PowerPoint macro-enabled document | ||
- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft PowerPoint document | ||
- [`ps`](https://en.wikipedia.org/wiki/Postscript) - Postscript | ||
@@ -550,3 +591,6 @@ - [`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 | ||
- [`xlsm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Excel macro-enabled document | ||
- [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML) - Microsoft Excel document | ||
- [`xltm`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Excel macro-enabled template | ||
- [`xltx`](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions) - Microsoft Excel template | ||
- [`xm`](https://wiki.openmpt.org/Manual:_Module_formats#The_FastTracker_2_format_.28.xm.29) - Audio module format: FastTracker 2 | ||
@@ -553,0 +597,0 @@ - [`xml`](https://en.wikipedia.org/wiki/XML) - eXtensible Markup Language |
@@ -157,2 +157,19 @@ export const extensions = [ | ||
'apk', | ||
'drc', | ||
'lz4', | ||
'potx', | ||
'xltx', | ||
'dotx', | ||
'xltm', | ||
'ott', | ||
'ots', | ||
'otp', | ||
'odg', | ||
'otg', | ||
'xlsm', | ||
'docm', | ||
'dotm', | ||
'potm', | ||
'pptm', | ||
'jar', | ||
]; | ||
@@ -311,2 +328,19 @@ | ||
'application/vnd.android.package-archive', | ||
'application/vnd.google.draco', // Invented by us | ||
'application/x-lz4', // Invented by us | ||
'application/vnd.openxmlformats-officedocument.presentationml.template', | ||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template', | ||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template', | ||
'application/vnd.ms-excel.template.macroenabled.12', | ||
'application/vnd.oasis.opendocument.text-template', | ||
'application/vnd.oasis.opendocument.spreadsheet-template', | ||
'application/vnd.oasis.opendocument.presentation-template', | ||
'application/vnd.oasis.opendocument.graphics', | ||
'application/vnd.oasis.opendocument.graphics-template', | ||
'application/vnd.ms-excel.sheet.macroEnabled.12', | ||
'application/vnd.ms-word.document.macroEnabled.12', | ||
'application/vnd.ms-word.template.macroEnabled.12', | ||
'application/vnd.ms-powerpoint.template.macroEnabled.12', | ||
'application/vnd.ms-powerpoint.presentation.macroEnabled.12', | ||
'application/java-archive', | ||
]; |
102308
622
8
2266
+ Added@tokenizer/inflate@^0.2.6
+ Added@tokenizer/inflate@0.2.6(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedfflate@0.8.2(transitive)
+ Addedms@2.1.3(transitive)
+ Addedpeek-readable@6.0.1(transitive)
+ Addedstrtok3@10.1.1(transitive)
- Removedget-stream@^9.0.1
- Removed@sec-ant/readable-stream@0.4.1(transitive)
- Removedget-stream@9.0.1(transitive)
- Removedis-stream@4.0.1(transitive)
- Removedpeek-readable@5.4.2(transitive)
- Removedstrtok3@9.1.1(transitive)
Updatedstrtok3@^10.0.1
Updateduint8array-extras@^1.4.0