我的视频只有 (4.0, 3.0) 像素的自然大小,这也是提取的帧大小

My videos have a naturalSize of only (4.0, 3.0) pixels, which is also extracted frame size

上下文

我正在处理 1280x920 的视频文件,这是它们在 QuickTime 中显示甚至在我的 AVPlayer 中播放时的实际像素大小。

我的文件夹里有一堆视频,我需要把它们放在一个 AVMutableComposition 上播放。

我还需要为每个视频提取最后一帧。

到目前为止我所做的是在我个人 AVAsset 上对每个人使用 AVAssetImageGenerator 并且它有效,无论我使用的是 generateCGImagesAsynchronously 还是 copyCGImage.

但我认为 运行 generateCGImagesAsynchronously 在我的合成资产上会更有效率,所以我只有一个调用而不是循环播放每个原始曲目。

而不是:

                   v-Get Frame
AVAsset1 |---------|
AVAsset2 |---------|
AVAsset3 |---------|

我想做:

                                v----------v----------v- Get Frames
AVMutableComposition: |---------||---------||---------|

问题

这是实际问题:

import AVKit

var video1URL = URL(fileReferenceLiteralResourceName: "video_bad.mp4") // One of my video file
let asset1 = AVAsset(url: video1URL)
let track1 = asset1.tracks(withMediaType: .video).first!

_ = track1.naturalSize // {w 4 h 3}

var video2URL = URL(fileReferenceLiteralResourceName: "video_ok.mp4") // Some mp4 I got from internet
let asset2 = AVAsset(url: video2URL)
let track2 = asset2.tracks(withMediaType: .video).first!

_ = track2.naturalSize // {w 1920 h 1080}

这是 playground 的实际截图(您可以下载 here):

这里还有一些东西:

查看 QuickTime 检查器中的“当前比例”信息。视频显示很好,但显示确实被放大了(注意没有像素模糊之类的,这与一些元数据有关)

我在 QuickTime 中处理的视频文件:

视频文件来自网络:

问题

所以如果你无意中看到这个post,那可能是你想弄明白特斯拉写视频的方式。

该问题没有简单的解决方案,这是由于 Tesla 软件在 .mov 视频文件中错误设置元数据造成的。我向 Apple 发起了一个事件,他们能够证实这一点。

所以我写了一些代码,通过重写指示视频轨道大小的字节来实际修复视频文件。

我们开始吧,它很难看,但为了完整起见,我想 post a 解决方案,如果不是最好的话。

import Foundation

struct VideoFixer {
    var url: URL
    
    private var fh: FileHandle?
    
    static func fix(_ url: URL) {
        var fixer = VideoFixer(url)
        fixer.fix()
    }
    
    init(_ url: URL) {
        self.url = url
    }
    
    mutating func fix() {
        guard let fh = try? FileHandle(forUpdating: url) else {
            return
        }
        
        var atom = Atom(fh)
        atom.seekTo(AtomType.moov)
        atom.enter()
        if atom.atom_type != AtomType.trak {
            atom.seekTo(AtomType.trak)
        }
        atom.enter()
        if atom.atom_type != AtomType.tkhd {
            atom.seekTo(AtomType.tkhd)
        }
        atom.seekTo(AtomType.tkhd)
        
        let data = atom.data()
        
        let width = data?.withUnsafeBytes { [=10=].load(fromByteOffset: 76, as: UInt16.self).bigEndian }
        let height = data?.withUnsafeBytes { [=10=].load(fromByteOffset: 80, as: UInt16.self).bigEndian }
        
        if width==4 && height==3 {
            guard let offset = try? fh.offset() else {
                return
            }
            try? fh.seek(toOffset: offset+76)
            //1280x960
            var newWidth = UInt16(1280).byteSwapped
            var newHeight = UInt16(960).byteSwapped
            let dataWidth = Data(bytes: &newWidth, count: 2)
            let dataHeight = Data(bytes: &newHeight, count: 2)
            fh.write(dataWidth)
            try? fh.seek(toOffset: offset+80)
            fh.write(dataHeight)
        }
        try? fh.close()

        
    }
}

typealias AtomType = UInt32

extension UInt32 {
    static var ftyp = UInt32(1718909296)
    static var mdat = UInt32(1835295092)
    static var free = UInt32(1718773093)
    static var moov = UInt32(1836019574)
    static var trak = UInt32(1953653099)
    static var tkhd = UInt32(1953196132)
}

struct Atom {
    var fh: FileHandle
    
    var atom_size: UInt32 = 0
    var atom_type: UInt32 = 0
    
    init(_ fh: FileHandle) {
        self.fh = fh
        
        self.read()
    }
    mutating func seekTo(_ type:AtomType) {
        while self.atom_type != type {
            self.next()
        }
    }
    mutating func next() {
        guard var offset = try? fh.offset() else {
            return
        }
        
        offset = offset-8+UInt64(atom_size)
        
        if (try? self.fh.seek(toOffset: UInt64(offset))) == nil {
            return
        }
        self.read()
    }
    mutating func read() {
        self.atom_size = fh.nextUInt32().bigEndian
        self.atom_type = fh.nextUInt32().bigEndian
    }
    mutating func enter() {
        self.atom_size = fh.nextUInt32().bigEndian
        self.atom_type = fh.nextUInt32().bigEndian
    }
    func data() -> Data? {
        guard let offset = try? fh.offset() else {
            return nil
        }
        let data = fh.readData(ofLength: Int(self.atom_size))
        try? fh.seek(toOffset: offset)
        return data
    }
}
extension FileHandle {
    func nextUInt32() -> UInt32 {
        let data = self.readData(ofLength: 4)
        let i32array = data.withUnsafeBytes { [=10=].load(as: UInt32.self) }
        //print(i32array)
        return i32array
    }
}