Created
March 5, 2016 02:37
-
-
Save philfreo/54933d8eccd5a250a23a to your computer and use it in GitHub Desktop.
Detect if a File is a certain type based on its file signature in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define([ | |
'underscore', | |
], | |
function(_) { | |
'use strict'; | |
// https://en.wikipedia.org/wiki/List_of_file_signatures | |
var fileSignatures = { | |
'mp3': [ | |
// MPEG-1 Layer 3 file without an ID3 tag or with an ID3v1 tag (which's appended at the end of the file) | |
Uint8Array.from([0xFF, 0xFB]), | |
// MP3 file with an ID3v2 container | |
Uint8Array.from([0x49, 0x44, 0x33]) | |
], | |
'wav': [ | |
// Waveform Audio File Format | |
// Empty slots can be any byte. Can't look at only first 4 or else .avi files match | |
Uint8Array.from([0x52, 0x49, 0x46, 0x46, , , , , 0x57, 0x41, 0x56, 0x45]) | |
], | |
}; | |
/** | |
* Compare two Uint8Arrays. This function could be replaced by _.isEqual except | |
* for the fact that the signatures (e.g. for wav files) can have wildcard slots. | |
* @param {Uint8Array} sig - pattern from fileSignatures | |
* @param {Uint8Array} actual - bytes from file (should already be sliced to match length of sig) | |
* @returns {boolean} | |
*/ | |
var compareSignature = function(sig, actual) { | |
if (sig.length !== actual.length) return false; | |
for (var i = 0, l = sig.length; i < l; i++) { | |
if (sig[i] !== actual[i] && typeof sig[i] !== 'undefined') return false; | |
} | |
return true; | |
}; | |
/** | |
* @param {Uint8Array} uint8 | |
* @param {string} type | |
* @returns {boolean} | |
*/ | |
var matchesFileType = function(uint8, type) { | |
return _.find(fileSignatures[type], function(sig) { | |
return compareSignature(sig, uint8.slice(0, sig.length)); | |
}); | |
}; | |
/** | |
* Detect, through file signature / mime sniffing detection, if a given File | |
* matches an expected type or types. The types supported are the keys in | |
* fileSignatures above. | |
* @param {File} file | |
* @param {(string|string[])} types - e.g. 'mp3' or ['mp3'] | |
* @param {function} - callback which is passed a boolean | |
*/ | |
var verifyFileType = function(file, types, cb) { | |
if (_.isString(types)) types = [types]; | |
// Calculate the longest file signature for any of the requested | |
// types, so we know how many bytes of this file to look at. | |
var bytesNeeded = fileSignatures[types].reduce(function(prev, el, idx, arr) { | |
return Math.max(prev, el.length); | |
}, 0); | |
// Load file into ArrayBuffer and see if its first few bytes match | |
// the signature of any of our requested types. Let callback know. | |
var reader = new FileReader(); | |
reader.onload = function(e) { | |
// Load only as many bytes from the array buffer as necessary | |
var arrayBuffer = e.currentTarget.result; | |
var bytes = new Uint8Array(arrayBuffer, 0, bytesNeeded); | |
var match = _.find(types, function(type) { | |
return matchesFileType(bytes, type); | |
}); | |
cb(match); | |
}; | |
reader.readAsArrayBuffer(file); | |
}; | |
// Expose public interface | |
FileDetector = { | |
verifyFileType: verifyFileType | |
}; | |
return FileDetector; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment