MP4Box.js 获取视频旋转信息

MP4Box.js 获取视频旋转信息

2024年05月22日

声明:本文部分内容使用 ChatGPT 生成

序言

公司的一个项目中用到 MP4Box.js 在上传视频前去解析视频的宽高,并且根据宽高的比例做一些拦截,只允许 16:9 横屏的素材。后来发现一个问题,部分竖屏的素材也被提交上来了。经过研究,发现这类视频可能是由手机拍摄的,带了旋转信息,因此 MP4Box.js 中的原始宽高有问题。

什么是 MP4Box.js

MP4Box.js 是一个支持在浏览器中处理 MP4 文件的 JS 库,可以实现获取 MP4 文件的元数据信息、分割文件、提取媒体样本等高级处理能力。

通过 MP4Box.js 可以从 videoTrack 中的 widthheight 中获取视频的宽高,对于一般的视频都是 OK 的,但是带了旋转信息,通过 MP4Box 读出的宽高仍是旋转前的宽高,导致在一些场景下的判断会有问题。那么,如何获取到视频的旋转信息呢?

MP4Box.js 如何获取旋转信息

在 MP4 和 MOV 文件中,旋转信息通常存储在 tkhd (Track Header Box) 或 mvhd (Movie Header Box) 中。这个信息会影响视频轨道的显示方式。

Track Header Box (tkhd):包含一个 matrix 的矩阵,描述视频帧的旋转。

matrix 字段中的旋转信息是通过一个 3x3 矩阵来表示的,具体可以参考 ISO/IEC 14496-12 标准。

具体实现代码:

Math.atan2(videoTrack.matrix[1], videoTrack.matrix[0]) * (180 / Math.PI)

其中,Math.atan2 是 JS 中的一个数学函数,用于计算从点 (0, 0) 到点 (x, y) 之间的直线与 x 轴正方向之间的角度,角度的单位为弧度。这个函数能够处理所有的象限,因此可以返回从 -π 到 π 之间的值。Math.atan2 函数在计算几何、游戏开发、图形编程以及需要处理极坐标转换等场景中非常有用。与 Math.atan 不同,Math.atan2 可以处理 (x, y) 都为零的情况,并根据 x 和 y 的符号确定正确的象限。

完整实现代码

function getVideoMetaInfo(file: File): Promise<any> {
  return new Promise((resolve, reject) => {
    const mp4boxFile = MP4Box.createFile()

    mp4boxFile.onReady = function (info: any) {
      if (info && info.videoTracks?.length) {
        const videoTrack = info.videoTracks[0]
        const result = {
          duration: videoTrack.duration / videoTrack.timescale,
          codec: videoTrack.codec,
          fps: videoTrack.nb_samples / (videoTrack.duration / videoTrack.timescale),
          width: videoTrack.video.width,
          height: videoTrack.video.height,
          rotation: Math.atan2(videoTrack.matrix[1], videoTrack.matrix[0]) * (180 / Math.PI),
        }

        resolve(result)
      }
    }

    mp4boxFile.onError = function (info: any) {
      console.error('mp4box.js error', info)
      reject(info)
    }

    const reader = file.stream().getReader()
    let offset = 0
    reader.read().then(function getNextChunk({ done, value }: any) {
      if (done) {
        mp4boxFile.flush()
        return
      }

      const copy = value.buffer
      copy.fileStart = offset
      offset += value.length
      mp4boxFile.appendBuffer(copy)
      reader.read().then(getNextChunk)
    })
  })
}

后记

在解决这个问题的过程中,我发现另一个强大的 JS 库:mediainfo.js,已经帮我们做好了这一切,并且支持解析更多格式的文件。

我基于这个库,做了一个可视化的工具页:媒体文件元数据解析,方便解析媒体文件的元数据,纯浏览器本地解析,性能优异,并且不需要读完整个文件,读完头就可以了。