Skip to content

Instantly share code, notes, and snippets.

@philfreo
Created March 5, 2016 02:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philfreo/54933d8eccd5a250a23a to your computer and use it in GitHub Desktop.
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
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