mp4filechecker
Version:
Check details of an mp4 file, e.g. to determine if transcoding is required
94 lines (93 loc) • 2.98 kB
JavaScript
import * as Mp4Box from 'mp4box';
export const DEFAULT_OPTIONS = {
maxWidth: 1280,
maxHeight: 720,
supportedMimeTypes: [
'video/mp4'
],
supportedVideoCodecs: [
/^avc1\.42e0/,
/^avc1\.4d40/,
/^avc1\.6400/
],
supportedAudioCodecs: [
'mp4a.40.2',
'mp4a.40.5'
]
};
const loadBuffer = (blob)=>new Promise(function(resolve, reject) {
const fileReader = new FileReader();
fileReader.onload = function(evt) {
if (!evt.target) {
reject(new Error('File could not be read'));
} else if (evt.target.error) {
reject(evt.target.error);
} else {
resolve(evt.target.result);
}
};
fileReader.readAsArrayBuffer(blob);
});
async function* loadChunks(file, chunkSize = 64 * 1024) {
const fileSize = file.size;
let offset = 0;
while(offset <= fileSize){
const chunkBlob = file.slice(offset, chunkSize + offset);
const chunkData = await loadBuffer(chunkBlob);
chunkData.fileStart = offset;
yield chunkData;
offset += chunkData.byteLength;
}
}
const matchesOne = (candidateValue, testValues)=>{
return testValues.some((testCase)=>{
if (testCase instanceof RegExp) {
return testCase.test(candidateValue);
} else {
return candidateValue === testCase;
}
});
};
const isMatch = (info, options)=>{
const tracks = info.tracks;
return tracks.every((track)=>{
if (track.type === 'video') {
const isSupportedCodec = matchesOne(track.codec, options.supportedVideoCodecs);
const isSupportedWidth = track.video.width <= options.maxWidth;
const isSupportedHeight = track.video.height <= options.maxHeight;
return isSupportedCodec && isSupportedWidth && isSupportedHeight;
} else if (track.type === 'audio') {
return matchesOne(track.codec, options.supportedAudioCodecs);
} else {
return true;
}
});
};
/**
* Checks whether an mp4 video file matches the provided criteria.
*
* @param file The provided mp4 video as a File or Blob
* @param options The video/audio track options which need to match
*
* @returns Promise.resolved(true) when the mp4 file matches the criteria provided in options, Promise.resolved(false) otherwise.
*/ export const checkMp4File = async (file, options = DEFAULT_OPTIONS)=>{
if (!file || !matchesOne(file.type, options.supportedMimeTypes)) {
return false;
}
const box = Mp4Box.createFile();
let result = null;
box.onReady = (info)=>{
result = info;
};
for await (const chunk of loadChunks(file)){
box.appendBuffer(chunk);
if (result) {
break;
}
}
if (result === null) {
return false;
} else {
return isMatch(result, options);
}
};