@@ -4,7 +4,11 @@ import Foundation
44struct VideoAttachmentInfo {
55 let duration : String
66 let resolution : String
7+ let pixelWidth : Int ?
8+ let pixelHeight : Int ?
79 let codec : String
10+ let colorSpace : String
11+ let containsHDR : Bool
812 let bitrate : String
913 let frameRate : String
1014 let fileSize : String
@@ -14,9 +18,10 @@ struct VideoAttachmentInfo {
1418 let durationSeconds = await durationSeconds ( for: asset)
1519 let videoTrack = await firstVideoTrack ( for: asset)
1620 let fileSizeBytes = fileSizeBytes ( for: url)
17- let resolutionText = await resolution ( for: videoTrack)
21+ let resolutionDetails = await resolutionDetails ( for: videoTrack)
1822 let trackBitrate = await trackBitrate ( for: videoTrack)
1923 let trackFrameRate = await trackFrameRate ( for: videoTrack)
24+ let colorSpaceDetails = await colorSpaceDetails ( for: videoTrack)
2025 let durationText : String = formattedDuration ( durationSeconds)
2126 let codecText : String = await codecName ( for: videoTrack) ?? " Unknown "
2227 let bitrateText : String = formattedBitrate (
@@ -29,8 +34,12 @@ struct VideoAttachmentInfo {
2934
3035 return VideoAttachmentInfo (
3136 duration: durationText,
32- resolution: resolutionText,
37+ resolution: resolutionDetails. text,
38+ pixelWidth: resolutionDetails. width,
39+ pixelHeight: resolutionDetails. height,
3340 codec: codecText,
41+ colorSpace: colorSpaceDetails. text,
42+ containsHDR: colorSpaceDetails. containsHDR,
3443 bitrate: bitrateText,
3544 frameRate: frameRateText,
3645 fileSize: fileSizeText
@@ -60,22 +69,67 @@ struct VideoAttachmentInfo {
6069 return Int64 ( fileSize)
6170 }
6271
63- private static func resolution( for track: AVAssetTrack ? ) async -> String {
72+ private static func resolutionDetails(
73+ for track: AVAssetTrack ?
74+ ) async -> ( text: String , width: Int ? , height: Int ? ) {
6475 guard let track,
6576 let naturalSize = try ? await track. load ( . naturalSize)
6677 else {
67- return " Unknown "
78+ return ( " Unknown " , nil , nil )
6879 }
6980
7081 let preferredTransform = ( try ? await track. load ( . preferredTransform) ) ?? . identity
7182 let transformedSize = naturalSize. applying ( preferredTransform)
7283 let width = Int ( abs ( transformedSize. width) . rounded ( ) )
7384 let height = Int ( abs ( transformedSize. height) . rounded ( ) )
7485 guard width > 0 , height > 0 else {
75- return " Unknown "
86+ return ( " Unknown " , nil , nil )
87+ }
88+
89+ return ( " \( width) x \( height) " , width, height)
90+ }
91+
92+ private static func colorSpaceDetails(
93+ for track: AVAssetTrack ?
94+ ) async -> ( text: String , containsHDR: Bool ) {
95+ guard let track else {
96+ return ( " Unknown " , false )
97+ }
98+
99+ let formatDescriptions = ( try ? await track. load ( . formatDescriptions) ) ?? [ ]
100+ let extensions : NSDictionary ? = formatDescriptions. first. flatMap { formatDescription in
101+ guard let rawExtensions = CMFormatDescriptionGetExtensions ( formatDescription) else {
102+ return nil
103+ }
104+ return rawExtensions as NSDictionary
105+ }
106+
107+ let colorPrimaries =
108+ extensions ? [ kCMFormatDescriptionExtension_ColorPrimaries as String ] as? String
109+ let transferFunction =
110+ extensions ? [ kCMFormatDescriptionExtension_TransferFunction as String ] as? String
111+ let containsHDR =
112+ track. hasMediaCharacteristic ( . containsHDRVideo)
113+ || transferFunction == AVVideoTransferFunction_SMPTE_ST_2084_PQ
114+ || transferFunction == AVVideoTransferFunction_ITU_R_2100_HLG
115+
116+ let primariesName = colorPrimariesName ( for: colorPrimaries)
117+ let transferFunctionName = hdrTransferFunctionName ( for: transferFunction)
118+ let details = [ primariesName, transferFunctionName] . compactMap { $0 }
119+
120+ if containsHDR {
121+ if details. isEmpty {
122+ return ( " HDR " , true )
123+ } else {
124+ return ( " HDR ( \( details. joined ( separator: " , " ) ) ) " , true )
125+ }
76126 }
77127
78- return " \( width) x \( height) "
128+ if let primariesName {
129+ return ( primariesName, false )
130+ }
131+
132+ return ( " Unknown " , false )
79133 }
80134
81135 private static func formattedDuration( _ durationSeconds: Double ? ) -> String {
@@ -219,6 +273,30 @@ struct VideoAttachmentInfo {
219273 return ByteCountFormatter . string ( fromByteCount: fileSizeBytes, countStyle: . file)
220274 }
221275
276+ private static func colorPrimariesName( for value: String ? ) -> String ? {
277+ switch value {
278+ case AVVideoColorPrimaries_ITU_R_709_2:
279+ return " BT.709 "
280+ case AVVideoColorPrimaries_ITU_R_2020:
281+ return " BT.2020 "
282+ case AVVideoColorPrimaries_P3_D65:
283+ return " P3 D65 "
284+ default :
285+ return nil
286+ }
287+ }
288+
289+ private static func hdrTransferFunctionName( for value: String ? ) -> String ? {
290+ switch value {
291+ case AVVideoTransferFunction_SMPTE_ST_2084_PQ:
292+ return " PQ "
293+ case AVVideoTransferFunction_ITU_R_2100_HLG:
294+ return " HLG "
295+ default :
296+ return nil
297+ }
298+ }
299+
222300 private static func fourCCString( _ value: FourCharCode ) -> String {
223301 let bytes : [ UInt8 ] = [
224302 UInt8 ( truncatingIfNeeded: value >> 24 ) ,
0 commit comments