读取文件编码格式mime

代码示例之原生js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>获取文件code</title>
    <script src="./assets/mp4box.all.js"></script>
</head>

<body>
    <div>
        <input type="file" name="" id="uploadFile">
        <script>
            function readFileCode(fileobj = { mp4boxfile: null, objectToLoad: null }, progressbar) {
                var fileSize = fileobj.objectToLoad.size;
                var offset = 0;
                var self = this; // we need a reference to the current object
                var readBlock = null;
                var startDate = new Date();

                fileobj.mp4boxfile = MP4Box.createFile(false);

                fileobj.mp4boxfile.onError = function (e) {
                    console.log("Failed to parse ISOBMFF data");
                };
                fileobj.mp4boxfile.onReady = function (info) {
          console.log('onReady-info', info)
        }

                var onparsedbuffer = function (mp4boxfileobj, buffer) {
                    console.log("Appending buffer with offset " + offset);
                    buffer.fileStart = offset;
                    mp4boxfileobj.appendBuffer(buffer);
                }

                var onBlockRead = function (evt) {
                    if (evt.target.error == null) {
                        onparsedbuffer(fileobj.mp4boxfile, evt.target.result); // callback for handling read chunk
                        offset += evt.target.result.byteLength;
                        // progressbar.progressbar({ value: Math.ceil(100*offset/fileSize) });
                    } else {
                        console.log("Read error: " + evt.target.error);
                        return;
                    }
                    if (offset >= fileSize) {
                        // progressbar.progressbar({ value: 100 });
                        console.log("Done reading file (" + fileSize + " bytes) in " + (new Date() - startDate) + " ms");
                        fileobj.mp4boxfile.flush();
                        console.log(fileobj.mp4boxfile.getInfo())
                        return;
                    }

                    readBlock(offset, fileobj.objectToLoad);
                }

                readBlock = function (_offset, _file) {
                    // 分片大小 每片100M
                    let chunkSize  = 100 * 1024 * 1024; // bytes
                    var r = new FileReader();
                    var blob = _file.slice(_offset, chunkSize + _offset);
                    r.onload = onBlockRead;
                    r.readAsArrayBuffer(blob);
                }

                readBlock(offset, fileobj.objectToLoad);
            }

            document.getElementById('uploadFile').onchange = function (e) {
                console.log('fileInfo', e.target.files[0])
                readFileCode({objectToLoad: e.target.files[0]})
            }
        </script>
    </div>
</body>

</html>

代码示例之vue

插件安装
npm i mp4box
封装代码
import MP4Box from 'mp4box'
export function checkVideoCode (fileobj = { mp4boxfile: null, objectToLoad: null }) {
  return new Promise((resolve, reject) => {
    var fileSize = fileobj.objectToLoad.size
    var offset = 0
    // var self = this // we need a reference to the current object
    var readBlock = null
    // var startDate = new Date()

    fileobj.mp4boxfile = MP4Box.createFile(false)

    fileobj.mp4boxfile.onError = function (e) {
      console.log('Failed to parse ISOBMFF data')
      reject(e)
    }
    // 监听到(大文件会分片读取文件,实际是读取文件第一片,前100M)就返回
    fileobj.mp4boxfile.onReady = function (info) {
      // console.log('onReady', info)
      resolve(info)
    }

    var onparsedbuffer = function (mp4boxfileobj, buffer) {
      // console.log('Appending buffer with offset ' + offset)
      buffer.fileStart = offset
      mp4boxfileobj.appendBuffer(buffer)
    }

    var onBlockRead = function (evt) {
      if (evt.target.error == null) {
        onparsedbuffer(fileobj.mp4boxfile, evt.target.result) // callback for handling read chunk
        offset += evt.target.result.byteLength
        // progressbar.progressbar({ value: Math.ceil(100*offset/fileSize) });
      } else {
        console.log('Read error: ' + evt.target.error)
        return
      }
      if (offset >= fileSize) {
        // progressbar.progressbar({ value: 100 });
        // console.log('Done reading file (' + fileSize + ' bytes) in ' + (new Date() - startDate) + ' ms')
        fileobj.mp4boxfile.flush()
        // console.log(fileobj.mp4boxfile.getInfo())
        // 等分片读取完毕再返回
        // resolve(fileobj.mp4boxfile.getInfo())
        return
      }

      readBlock(offset, fileobj.objectToLoad)
    }

    readBlock = function (_offset, _file) {
      // 分片大小 每片100M
      let chunkSize = 100 * 1024 * 1024 // bytes
      var r = new FileReader()
      var blob = _file.slice(_offset, chunkSize + _offset)
      r.onload = onBlockRead
      r.readAsArrayBuffer(blob)
    }

    readBlock(offset, fileobj.objectToLoad)
  })
}
应用封装代码
<template>
  <div class='mat-video-wrapper' v-loading='loading'>
    <!-- 编辑区域 -->
    <section class='edit-body'>
      <!-- 添加图文 -->
      <div v-show="!videoFile" class="add-video">
        <div class='add-icon'>添加视频</div>
        <input
          class="video-uploader"
          type="file"
          @change="subVideo"
          ref="fileInput"
          accept="video/mp4">
      </div>
      <!-- 图文预览区域 -->
      <div
        class='prev-box'
        v-show="videoFile">
        <div class='prev-main'
          v-loading="loadingVideo"
          element-loading-spinner="el-icon-loading"
          element-loading-text="上传中"
          element-loading-background="rgba(0, 0, 0, 0.8)"
          :style="{backgroundImage:`url('${picUrl}')`}">
          <video class="video-dom" ref='videoDOM' :src="fileSrc"></video>
          <span class='prev-icon' @click='startPreview()'></span>
        </div>
        <div class='prev-title'>{{videoFile ? videoFile.name || '标题' : '标题'}}</div>
        <div class="del" @click="delVideo()">删除</div>
      </div>
    </section>
    <div class='video-tip'>
      提示: 视频素材大小控制在{{wechatAccord ? '10M' : '5G'}}以内,支持MP4(H264编码格式)
    </div>
    <!-- 按钮区域 -->
    <section class='edit-btn'>
      <el-button size="small" type="primary" @click="saveEdit">保 存</el-button>
      <el-button size="small" @click="cancelEdit">取 消</el-button>
    </section>
    <!-- 视频的预览 -->
    <previewVideo
      v-show="showVideoPreview"
      :videoSrc="fileSrc"
      @handleClose="closePreviewVideo()"/>
    <!-- 图片上传 -->
    <shardUploader ref='upload1' :showSuccMsg='false'/>
  </div>
</template>

<script>
import shardUploader from '@/public/shardUploader'
import previewVideo from '@/public/preview/previewVideo'

import { checkVideoCode } from '@/utils/readFileCode'
export default {
  name: 'matVideo',
  props: {
    mode: {
      type: String,
      default: 'create'
    },
    mat: {
      type: Object,
      default () {
        return {}
      }
    },
    // 是否按照威微信标准过滤素材列表
    wechatAccord: {
      type: Boolean,
      default: false
    }
  },
  components: {
    shardUploader,
    previewVideo
  },
  data () {
    return {
      loading: false,
      videoFile: null,
      fileSrc: '', // 视频本地播放地址
      loadingVideo: false, // 获取封面的loading
      picUrl: null, // 视频封面图
      videoDuration: null, // 视频播放时长
      videoDOM: null,
      showVideoPreview: false
    }
  },
  methods: {
    delVideo () {
      this.$refs.fileInput.value = ''
      this.videoFile = null
      this.picUrl = null
      this.videoDuration = null
    },
    subVideo () {
      let video = this.$refs.fileInput.files[0]
      if (video) {
        // 文件格式验证
        if (!/^video\//.test(video.type) || !/\.mp4$/.test(video.name)) {
          this.showWarning('视频文件只支持mp4格式')
          this.$refs.fileInput.value = ''
          return
        }
        // 大小验证, 用于微信消息则限制是10M,否则限制为5G
        let maxSize = this.wechatAccord ? 1024 * 1024 * 10 : 1024 * 1024 * 1024 * 5
        if (video.size > maxSize) {
          this.showWarning(`视频文件大小不能超过${this.wechatAccord ? '10M' : '5G'}`)
          this.$refs.fileInput.value = ''
          return
        }
        // 正确的视频后缀会有mime信息
        checkVideoCode({objectToLoad: video}).then(res => {
          console.log('mime:', res.mime)
          let valid = this.getCodecValid(res.mime)
          if (!valid) {
            this.showWarning('请上传正确的视频编码格式')
          } else {
            this.loading = true
            this.fileSrc = URL.createObjectURL(video)
            this.videoFile = video
            // this.loadingVideo = true
            // 截取视频的第一帧
            if (!this.videoDOM) {
              this.videoDOM = this.$refs.videoDOM
            }
            this.videoDOM.addEventListener('loadeddata', this.getVideoFirstImg(video))
          }
        }).catch(() => {
          this.showWarning('获取视频编码失败,请检查视频文件')
        })
      }
    },
    // 获取视频第一帧
    getVideoFirstImg (video) {
      this.videoDOM.removeEventListener('loadeddata', this.getVideoFirstImg)
      // 获取第一帧
      setTimeout(() => {
        // 获取视频时长
        this.videoDuration = this.videoDOM.duration
        if (!this.videoDuration) {
          this.loading = false
          return this.showWarning('视频格式或mp4编码格式不支持')
        }
        let canvas = document.createElement('canvas') // 创建一个画布
        canvas.width = this.videoDOM.videoWidth
        canvas.height = this.videoDOM.videoHeight
        canvas.getContext('2d').drawImage(this.videoDOM, 0, 0, canvas.width, canvas.height) // getContext:设置画布环境;drawImage:画画
        let imgDataUrl = canvas.toDataURL('image/png') // 获取图片的url
        // console.log('imgDataUrl: ', imgDataUrl)
        // 判断生成的base64是否符合格式
        if (!/;base64/.test(imgDataUrl)) {
          this.showWarning('视频格式或mp4编码格式不支持')
          this.loading = false
        } else {
          // 转换为图片file文件,并上传到服务器
          let imgFile = this.dataURLtoFile(imgDataUrl, new Date().getTime())
          this.$refs.upload1.uploadFile(imgFile, result => {
            this.loadingVideo = false
            this.picUrl = result.url
            this.loading = false
          })
        }
      }, 1000)
    },
    startPreview () {
      this.showVideoPreview = true
    },
    closePreviewVideo () {
      this.showVideoPreview = false
    },
    saveEdit () {
      // 验证视频的标题不能为空
      if (!this.videoFile) {
        this.loading = false
        return this.$message({ message: '请先上传视频素材', type: 'warning' })
      }
      if (!this.videoDuration) {
        this.loading = false
        return this.showWarning('获取视频长度失败, 请检查视频格式')
      }
      if (this.loading) {
        return false
      }
      // this.loading = true
      // 首先上传图片到服务器,获取图片链接
      this.$refs.upload1.uploadFile(this.videoFile, result => {
        setTimeout(() => {
          let formData = new FormData()
          formData.append('fileUrl', result.url)
          formData.append('fileName', result.name)
          formData.append('fileSize', result.size)
          formData.append('picUrl', this.picUrl)
          formData.append('duration', parseInt(this.videoDuration * 1000))
          let appid = sessionStorage.getItem('appid')
          let url = `${this.SERVICE_WECHAT}/upgrade/media/${appid}/video/upload`
          this.posts(url, formData).then(res => {
            if (res.data.code === 200) {
              this.showSucc(res.data.message)
              let data = res.data.data
              let info = {
                picUrl: this.picUrl,
                mediaName: this.videoFile.name
              }
              if (data) {
                info.id = data
              }
              this.$emit('success', info)
            }
            this.loading = false
          }).catch(e => {
            this.loading = false
            this.handleError(e)
          })
        }, 1000)
      })
    },
    cancelEdit () {
      this.$emit('cancel')
    },
    getCodecValid (str) {
      let arr = str.split(';')
      // 排除非video格式的
      if (!arr[0].includes('video/mp4')) {
        return false
      }
      // 特定的有问题视频
      return !!(arr[1].includes('avc1') && !(arr[1].includes('avc1.640029') && arr[1].includes('mp4a.40.2')))
    }
  },
  created () {
    // console.log('wechatAccord: ', this.wechatAccord)
  }
}
</script>

<style lang="stylus" scoped>
.mat-video-wrapper
  min-height calc(100% - 70px)
  padding-bottom 70px
  position relative
  .edit-body
    padded_box(border-box,0 10px)
    display flex
    align-items center
    .add-video
      width 275px
      height 160px
      background #f2f2f6
      position relative
      .add-icon
        position absolute
        left 0
        right 0
        top 0
        bottom 0
        margin auto
        width 64px
        height: 21px;
        font-size: 14px;
        color: #5E5E66;
        line-height: 21px;
        padding-top 24px
        text-align center
        &:before, &:after
          content ''
          display block
          width 20px
          height 2px
          background: #ACAEBC;
          position absolute
          left 0
          right 0
          top 0
          margin auto
        &:after
          transform rotate(90deg)
      .video-uploader
        position absolute
        z-index 1
        left 0
        top 0
        width 100%
        height 100%
        opacity 0
        cursor pointer
      .video-box
        width 0
        height 0
        opacity 0
    .prev-box
      width 275px
      position relative
      .del
        line-height 18px
        font-size 14px
        color #4c84ff
        cursor pointer
        position absolute
        right -44px
        bottom 0px
      .prev-main
        position relative
        width 100%
        height 108px
        background-color #F7F7F7;
        background-position center
        background-repeat no-repeat
        background-size cover
        cursor pointer
        .video-dom
          object-fit cover
          width 100%
          height 100%
          position absolute
          top 0
          left 0
          visibility hidden
        .prev-icon
          position absolute
          left 0
          right 0
          top 0
          bottom 0
          background url('~assets/img/enterprise/video_ic_play@2x.png') no-repeat center / 30px
          z-index 4
      .prev-title
        height 50px
        line-height 50px
        border 1px solid #ddd
        padding 0 9px
        font-size 14px
        color #333
        no-wrap()
  .video-tip
    padding 15px 10px
    color #666
  .edit-btn
    position absolute
    left 0
    right 0
    bottom 0
    padding 20px 0
    text-align center
</style>

在线获取文件编码的地址:https://gpac.github.io/mp4box.js/test/filereader.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容