前端上传数据-图片和视频格式校验

摘要:
上一篇文章使用promise嵌套以excel行顺序上传数据。本文要解决的问题是验证图像和视频格式。图像主要包括jpgpggif视频mp4。由于用户选择的资源可能不是真正的多媒体文件,因此使用js文件获得的文件类型。类型方法可能不准确,例如更改。xlsx到.jpg,file按类型获取的类型为image/jpeg。当客户端提取资源时,还可以获得图像和视频的分辨率。上传由前端控制,因此上传时应准确判断资源。

上一篇用 promise 嵌套实现了按 excel 行顺序上传数据,这篇要解决的问题是图片和视频格式校验,图片主要有 jpg png gif 视频 mp4

由于用户选择的资源可能并不是真正的多媒体文件,使用 js 的 file.type 方法获取的文件类型可能不准确,比如将 .xlsx 改为 .jpg, file.type 得到的类型是image/jpeg

客户端拉取资源时,图片和视频的分辨率也一并获取,而上传由前端控制,所以上传时对资源要进行比较准确的判断。

我的判断策略:

  1. 判断文件后缀,若不是 jpg/png/gif/mp4 中的一种,则报错
  2. 对 jpg/png/gif 的文件,读取二进制头信息,满足任一格式则返回相应格式,否则为非法格式
  3. 获取图片和视频的分辨率,获取成功则是真的成功,否则还是报错

后缀名校验

// 获取图片的 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({ 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({ 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)
    }
  })
},

免责声明:文章转载自《前端上传数据-图片和视频格式校验》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇GitHub 和 Gitee 开源免费 10 个超赞后台管理面板,看完惊呆了!使用appcmd命令创建iis站点及应用程序池下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Zookeeper入门

Zookeeper相关介绍和选举算法、应用场景等: Zookeeper 的简介 Zookeeper是一个开源的分布式的,一个针对大型分布式系统的可靠协调系统的Apache项目。 目标是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户; ZooKeeper已经成为Hadoop生态系统的基础组件;功能包括:配置服务、名字服务、分...

1/28 对于Vue.js 中 Object.freeze( )的理解

开门见山先总结,如有错误,大佬请指正 1. const  用于单一的变量上,对于对象的属性,const 不能做到 阻止 添加、修改 对象属性 2. freeze 只要是 对象 都可以 阻止 其发生改变 ( 弥补了 const 的不足,即 不能阻止 添加 修改属性) 3. Vue 是响应式的,所以对于 data 是无法 freeze 的   ( /* 上面的...

Qt之log数据展示模块简要实现

Log模块主要用于实时测井数据的显示和测后曲线数据的预览和打印,为更好的展示对Qt中相关知识点的应用,特以Log模块为例对其进行简要实现。 内容导图: 一、功能需求 1、界面效果图 Log模块实现曲线数据的显示及相关属性(曲线颜色、画笔类型、画笔宽度等)的设置 将上图划分为三个区域:右边为轨道信息显示和管理区,可以参看和设置相关轨道信息 左上为轨道的L...

Chrome浏览器自定义设置个人信息存储路径

序言 Chrome浏览器很好用,感觉也很快,但是,也是有那么几个小瑕疵的。例如,Chrome浏览器无法设置安装路径,只能安装在默认的C盘,个人信息默认放在C盘,详细路径如下: C:\Users\XXXX\AppData\Local\Google\Chrome\User Data\Default Copy 对于我这种对C盘有洁癖的人来说,不能忍受啊。 之前我...

C++(四十八) — string容器的基本操作

参考博客:https://blog.csdn.net/qq_37941471/article/details/82107077 https://www.cnblogs.com/danielStudy/p/7127564.html#top 1、声明一个字符串 标准库类型string表示可变长的字符序列,为了在程序中使用string类型,我们必须包含头文件:#...

vue后台(一)

一,项目准备工作 1.拿到已经是二次开发的代码, npm i , 安装依赖包 ES6模块暴露和导入复习 1. 导出方式一: 默认一次性导出 (只能有一个) export default xxx 本质: 整个模块是一个对象, 对象中有default属性, 即: {default: xxx} 2....