前端上傳數據-圖片和視頻格式校驗
阿新 • • 發佈:2019-01-31
上傳 類型 無法識別 視頻格式 height subst 視頻 indexof 允許
上一篇用 promise 嵌套實現了按 excel 行順序上傳數據,這篇要解決的問題是圖片和視頻格式校驗,圖片主要有 jpg png gif 視頻 mp4
由於用戶選擇的資源可能並不是真正的多媒體文件,使用 js 的 file.type 方法獲取的文件類型可能不準確,比如將 .xlsx 改為 .jpg, file.type 得到的類型是image/jpeg
客戶端拉取資源時,圖片和視頻的分辨率也一並獲取,而上傳由前端控制,所以上傳時對資源要進行比較準確的判斷。
我的判斷策略:
- 判斷文件後綴,若不是 jpg/png/gif/mp4 中的一種,則報錯
- 對 jpg/png/gif 的文件,讀取二進制頭信息,滿足任一格式則返回相應格式,否則為非法格式
- 獲取圖片和視頻的分辨率,獲取成功則是真的成功,否則還是報錯
後綴名校驗
// 獲取圖片的 width height getImgSize(file) { const imgFileType = [‘image/jpeg‘, ‘image/png‘, ‘image/gif‘] const filetype = file.type const suffix = filetype.substring(filetype.lastIndexOf(‘/‘)+1) // 返回一個 promise return new Promise((resolve, reject) => { let reader = new FileReader() reader.onload = function(e){ const data = e.target.result const img = new Image() img.onload = function(){ resolve({width: img.width, height: img.height, ext: suffix }) } img.onerror = function(){ reject(`[${file.name}]解析失敗,可能圖片格式不正確`) } img.src = data } reader.readAsDataURL(file) }) }, // 獲取視頻的 width height getVideoSize(file) { const videoType = [‘video/mp4‘,] const filetype = file.type const suffix = filetype.substring(filetype.lastIndexOf(‘/‘)+1) // 返回一個 promise return new Promise((resolve, reject) => { const url = window.URL.createObjectURL(file) const video = document.createElement(‘video‘) video.onloadedmetadata = evt => { // Revoke when you don‘t need the url any more to release any reference window.URL.revokeObjectURL(url) resolve({width: video.videoWidth, height: video.videoHeight, ext: suffix }) } video.onerror = evt => { reject(`[${file.name}]解析失敗,可能視頻文件格式不正確`) } video.src = url video.load() }) },
二進制頭信息
依據 ISO 標準, jpg 文件的前2個字節為 0xFF, 0xD8
png 前8個字節 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
gif 有"GIF87a" 和 "GIF89a",依據前6個字節判斷
- GIF87a 0x47, 0x49, 0x46, 0x38, 0x37, 0x61
- GIF89a 0x47, 0x49, 0x46, 0x38, 0x39, 0x61
js 提供了 getUint8 以便讀取字節碼,只需要傳入偏移量即可
校驗代碼
const JPEG_SOI = [0xFF, 0xD8] const JPEG_EOI = [0xFF, 0xD9] // png的文件頭就是png圖片的前8個字節,其值為[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] const PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] // GIF files start with a fixed-length header ("GIF87a" or "GIF89a") giving the version const GIF89A_HEADER = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] const GIF87A_HEADER = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61] // 是否小端序 const isLittleEndian = (function() { var buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true) return new Int16Array(buffer)[0] === 256 })() // byte數組元素是否相等 function isArrayEqual(a, b){ for(let i=0; i<a.length; i++){ if(a[i] !== b[i]){ return false } } return true } export function getImageTypeByHeadContent(file){ // file 實際上是一個 Blob 對象 // 讀取 Blob 對象的前8個字節 const fileHeader = file.slice(0, 8) return new Promise((resolve, reject) => { let reader = new FileReader() reader.onload = function(e){ const data = e.target.result const header = new DataView(data) let bytesArr = [] for(let i=0; i<header.byteLength; i++){ bytesArr.push(header.getUint8(i, isLittleEndian)) } if(isArrayEqual(JPEG_SOI, bytesArr.slice(0,2))){ resolve(‘jpg‘) }else if(isArrayEqual(PNG_HEADER, bytesArr)){ resolve(‘png‘) }else if(isArrayEqual(GIF89A_HEADER, bytesArr.slice(0,6)) || isArrayEqual(GIF87A_HEADER, bytesArr.slice(0,6)) ){ resolve(‘gif‘) }else{ reject() } } reader.readAsArrayBuffer(fileHeader) }) }
那麽多媒體文件的校驗函數
// 如果傳入的類型與實際不符,則不上傳,防止圖片類型上傳視頻,或視頻類型上傳圖片
getMediaSize(file, validtype){
const _this = this
return new Promise((resolve, reject) => {
if(! _this.hasGotSizeObj.hasOwnProperty(file.name)){
if(file.type.startsWith(‘image‘) && validtype === ‘image‘){
_this.getImgSize(file)
.then(data => {
// 從文件頭信息無法識別圖片類型時,以後綴名為圖片類型
getImageTypeByHeadContent(file)
.then(type => {
data.ext = type
_this.hasGotSizeObj[file.name] = {extra: data}
resolve(data)
})
.catch(()=>{
_this.hasGotSizeObj[file.name] = {extra: data}
resolve(data)
})
})
.catch(err => {
reject(err)
})
}else if(file.type.startsWith(‘video‘) && validtype === ‘video‘){
_this.getVideoSize(file)
.then(data => {
_this.hasGotSizeObj[file.name] = {extra: data}
resolve(data)
}).catch(err => {
reject(err)
})
}else{
reject(`不允許的文件類型: ${file.type}`)
}
}else{
resolve(_this.hasGotSizeObj[file.name].extra)
}
})
},
前端上傳數據-圖片和視頻格式校驗