AVURLAsset getting video size
Asked Answered
A

9

38

This is pretty frustrating. I'm trying to get the size of an AVURLasset, but try to avoid naturalSize since Xcode tells me, this is deprecated in iOS5.

But: What's the replacement?

I can't find any clue on how to get the video-dimensions without using «naturalsize»...

Attorn answered 3/5, 2012 at 14:46 Comment(0)
U
34

I just checked the documentation online, and the naturalSize method is deprecated for the AVAsset object. However, there should always be an AVAssetTrack which refers to the AVAsset, and the AVAssetTrack has a naturalSize method that you can call.

naturalSize

The natural dimensions of the media data referenced by the track. (read-only)

@property(nonatomic, readonly) CGSize naturalSize

Availability

Available in iOS 4.0 and later. Declared In AVAssetTrack.h

Via: AVAssetTrack Reference for iOS

Undertook answered 3/5, 2012 at 16:17 Comment(6)
nautralSize is deprecated in iOS 5Oui
just checked the docs in the link in the answer, and it's not deprecated, it's available in both ObjC and SwiftUndertook
there are 2 naturalSize in the documentations. AVURLAsset.naturalSize is deprecated, but [[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize] is notOui
My answer specifies the AVAssetTrack approach, which isn't deprecated and which is what Dave specifies in his second line of code below... It appears I just didn't add the line of code to my answer.Undertook
Sooo everyone switches to doing basically the same thing as what the deprecated method did. Great.Pembrook
Do you have any idea to change the resolution of selected video? Should I need to use AVAssetExportPreset1280x720?Robbirobbia
R
54

Resolution in Swift 3:

func resolutionSizeForLocalVideo(url:NSURL) -> CGSize? {
    guard let track = AVAsset(URL: url).tracksWithMediaType(AVMediaTypeVideo).first else { return nil }
    let size = CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform)
    return CGSize(width: fabs(size.width), height: fabs(size.height))
}

For Swift 4:

func resolutionSizeForLocalVideo(url:NSURL) -> CGSize? {
    guard let track = AVAsset(url: url as URL).tracks(withMediaType: AVMediaType.video).first else { return nil }
    let size = track.naturalSize.applying(track.preferredTransform)
    return CGSize(width: fabs(size.width), height: fabs(size.height))
}

Solutions without preferredTransform do not return correct values for some videos on the latest devices!

Razzia answered 18/12, 2015 at 22:36 Comment(0)
U
34

I just checked the documentation online, and the naturalSize method is deprecated for the AVAsset object. However, there should always be an AVAssetTrack which refers to the AVAsset, and the AVAssetTrack has a naturalSize method that you can call.

naturalSize

The natural dimensions of the media data referenced by the track. (read-only)

@property(nonatomic, readonly) CGSize naturalSize

Availability

Available in iOS 4.0 and later. Declared In AVAssetTrack.h

Via: AVAssetTrack Reference for iOS

Undertook answered 3/5, 2012 at 16:17 Comment(6)
nautralSize is deprecated in iOS 5Oui
just checked the docs in the link in the answer, and it's not deprecated, it's available in both ObjC and SwiftUndertook
there are 2 naturalSize in the documentations. AVURLAsset.naturalSize is deprecated, but [[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize] is notOui
My answer specifies the AVAssetTrack approach, which isn't deprecated and which is what Dave specifies in his second line of code below... It appears I just didn't add the line of code to my answer.Undertook
Sooo everyone switches to doing basically the same thing as what the deprecated method did. Great.Pembrook
Do you have any idea to change the resolution of selected video? Should I need to use AVAssetExportPreset1280x720?Robbirobbia
I
26

The deprecation warning on the official documentation suggests, "Use the naturalSize and preferredTransform, as appropriate, of the asset’s video tracks instead (see also tracksWithMediaType:)."

I changed my code from:

CGSize size = [movieAsset naturalSize];

to

CGSize size = [[[movieAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize];

It's less pretty and less safe now but won't break when they drop that method.

Intoxication answered 20/8, 2013 at 0:16 Comment(0)
H
17

The deprecation warning says:

Use the naturalSize and preferredTransform, as appropriate, 
of the asset’s video tracks instead (see also tracksWithMediaType:).

So we need an AVAssetTrack, and we want its naturalSize and preferredTransform. This can be accessed with the following:

AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CGSize dimensions = CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform);

asset is obviously your AVAsset.

Hornbeam answered 7/1, 2015 at 19:55 Comment(2)
Hmmm applying the transform as you suggest results in a negative width for me :(Julesjuley
@Julesjuley Just normalise the dimension components using fabsf().Pratte
E
5

This is a fairly simple extension for AVAsset in Swift 4 to get the size of the video, if available:

extension AVAsset {
    var screenSize: CGSize? {
        if let track = tracks(withMediaType: .video).first {
            let size = __CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform)
            return CGSize(width: fabs(size.width), height: fabs(size.height))
        } 
        return nil
    }
}
Escorial answered 20/1, 2018 at 21:45 Comment(2)
Thanks - the app is written in Objective-C, though (I have yet to rewrite it). I'll check it out whether I can adapt your extension...Attorn
Hi I'm facing the same problem did you find a solution ?? because in my case using the natural size + preferredTransform return an obsolete value :( thank youContumacy
J
3

To derive the dimension of an AVAsset, you should calculate the union of all the visual track rects (after applying their corresponding preferred transformation):

CGRect unionRect = CGRectZero;
for (AVAssetTrack *track in [asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]) {
    CGRect trackRect = CGRectApplyAffineTransform(CGRectMake(0.f,
                                                             0.f,
                                                             track.naturalSize.width,
                                                             track.naturalSize.height),
                                                  track.preferredTransform);
    unionRect = CGRectUnion(unionRect, trackRect);
}
CGSize naturalSize = unionRect.size;

Methods that rely on CGSizeApplyAffineTransform fail when your asset contains tracks with non-trivial affine transformation (e.g., 45 degree rotations) or if your asset contains tracks with different origins (e.g., two tracks playing side-by-side with the second track's origin augmented by the width of the first track).

See: MediaPlayerPrivateAVFoundationCF::sizeChanged()at https://opensource.apple.com/source/WebCore/WebCore-7536.30.2/platform/graphics/avfoundation/cf/MediaPlayerPrivateAVFoundationCF.cpp

Jamal answered 10/3, 2017 at 1:21 Comment(2)
Altho David, for AVAsset that contains one Video Track, @Razzia answer works properly. Right?Joejoeann
@RoiMulia: I think it's uncommon for assets to have a single video track with non-zero offset. For that, you may unlikely ever see the bug when using that code. Also, I've seen players that ignore the offset when the asset only contains one track. If you care to about handling these edge cases, I recommend experimenting yourself to verify the actual behavior.Jamal
P
3

For Swift 5

let assetSize =  asset.tracks(withMediaType: .video)[0].naturalSize
Putdown answered 17/8, 2020 at 12:39 Comment(0)
A
3

For iOS versions 15.0 and above,

extension AVAsset {
    func naturalSize() async -> CGSize? {
        guard let tracks = try? await loadTracks(withMediaType: .video) else { return nil }
        guard let track = tracks.first else { return nil }
        guard let size = try? await track.load(.naturalSize) else { return nil }
        return size
    }
}

You can use it like so,

Task.init { 
    let videoSize = await videoView.naturalSize() ?? .zero
    DispatchQueue.main.async { [self] in 
        // do your thing 
    } 
} 
Affrica answered 28/12, 2022 at 20:55 Comment(2)
How to use it from viewDidLoad() ?Incisure
@Md.IkramulMurad, edited original postAffrica
S
1

Swift version of @David_H answer.

extension AVAsset {
    func resolutionSizeForLocalVideo() -> CGSize {
        var unionRect = CGRect.zero;
        for track in self.tracks(withMediaCharacteristic: .visual) {
            let trackRect = CGRect(x: 0, y: 0, width: 
            track.naturalSize.width, height: 
            track.naturalSize.height).applying(track.preferredTransform)
            unionRect = unionRect.union(trackRect)
            
        }
        return unionRect.size
    }
}

Semipermeable answered 10/3, 2022 at 6:10 Comment(2)
Why does it return optional?Satterfield
It won't return nil as I already set unionRect as CGRect.zero. I've already edited.Semipermeable

© 2022 - 2024 — McMap. All rights reserved.