VideoConversionHelper.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. @objc public class VideoConversionHelper : NSObject {
  23. private static let fileOverhead : Int64 = 48 * 1024
  24. private struct MovieRate {
  25. var videoRate : Int32
  26. var videoSize : Int32
  27. var audioRate : Int32
  28. var audioChannels : Int32
  29. }
  30. private static let movieRateHigh = MovieRate(videoRate: kVideoBitrateHigh,
  31. videoSize: kMaxVideoSizeHigh,
  32. audioRate: kAudioBitrateHigh,
  33. audioChannels: kAudioChannelsHigh)
  34. private static let movieRateMedium = MovieRate(videoRate: kVideoBitrateMedium,
  35. videoSize: kMaxVideoSizeHigh,
  36. audioRate: kAudioBitrateMedium,
  37. audioChannels: kAudioChannelsHigh)
  38. private static let movieRateLow = MovieRate(videoRate: kVideoBitrateLow,
  39. videoSize: kMaxVideoSizeLow,
  40. audioRate: kAudioBitrateLow,
  41. audioChannels: kAudioChannelsLow)
  42. private static let rates = [movieRateHigh, movieRateMedium, movieRateLow]
  43. private static func getHighestPossibleBitrate(duration : Int, audioBitrate : Int, videoBitrate : Int) -> MovieRate? {
  44. if UserSettings.shared()?.videoQuality == "low" {
  45. return rates.last!
  46. }
  47. for rate in rates {
  48. let totAudioRate = (rate.audioRate * rate.audioChannels) / 8
  49. let totVideoRate = rate.videoRate / 8
  50. let fileSize = Int((totVideoRate + totAudioRate)) * duration
  51. DDLogInfo("Video File Size is \(fileSize)")
  52. if fileSize <= kMaxFileSize {
  53. return rate
  54. }
  55. }
  56. return nil
  57. }
  58. @objc public static func videoHasAllowedSize(at url : URL) -> Bool {
  59. let asset = AVURLAsset(url: url)
  60. return assetHasAllowedSize(asset: asset)
  61. }
  62. @objc public static func assetHasAllowedSize(asset : AVAsset) -> Bool {
  63. let duration : Int = Int(asset.duration.seconds)
  64. guard let videoTrack = asset.tracks(withMediaType: .video).first else {
  65. return false
  66. }
  67. let audioTrack = asset.tracks(withMediaType: .audio).first
  68. let videoRate : Int = Int(videoTrack.estimatedDataRate)
  69. var audioRate : Int = 0
  70. if audioTrack != nil {
  71. audioRate = Int(audioTrack!.estimatedDataRate)
  72. }
  73. let bitRate = VideoConversionHelper.getHighestPossibleBitrate(duration: duration,
  74. audioBitrate: audioRate,
  75. videoBitrate: videoRate)
  76. return bitRate != nil
  77. }
  78. @objc public static func getMaxdurationFor(videoBitrate : Int64, audioBitrate : Int64) -> Int64 {
  79. return Int64(kMaxFileSize) / (videoBitrate + audioBitrate + fileOverhead)
  80. }
  81. @objc public static func getAVAssetExportSession(from asset : AVAsset, outputURL : URL) -> SDAVAssetExportSession? {
  82. guard let videoTrack = asset.tracks(withMediaType: .video).first else {
  83. return nil
  84. }
  85. let audioTrackRate = asset.tracks(withMediaType: .audio).first?.estimatedDataRate ?? 0
  86. var srcVideoSize = __CGSizeApplyAffineTransform(videoTrack.naturalSize, videoTrack.preferredTransform)
  87. if srcVideoSize.width < 0 {
  88. srcVideoSize.width = -srcVideoSize.width
  89. }
  90. if srcVideoSize.height < 0 {
  91. srcVideoSize.height = -srcVideoSize.height
  92. }
  93. let duration : Int = Int(asset.duration.seconds)
  94. guard let rate = VideoConversionHelper.getHighestPossibleBitrate(duration: duration,
  95. audioBitrate: Int(audioTrackRate),
  96. videoBitrate: Int(videoTrack.estimatedDataRate)) else {
  97. return nil
  98. }
  99. var targetVideoSize : CGSize = srcVideoSize;
  100. if (targetVideoSize.width > CGFloat(rate.videoSize)) {
  101. targetVideoSize.height *= (CGFloat(rate.videoSize) / targetVideoSize.width);
  102. targetVideoSize.width = CGFloat(rate.videoSize);
  103. }
  104. if (targetVideoSize.height > CGFloat(rate.videoSize)) {
  105. targetVideoSize.width *= (CGFloat(rate.videoSize) / targetVideoSize.height);
  106. targetVideoSize.height = CGFloat(rate.videoSize);
  107. }
  108. let videoWidth = roundf(Float(targetVideoSize.width / 16.0)) * 16
  109. let videoHeight = roundf(Float(targetVideoSize.height / 16.0)) * 16
  110. guard let exportSession = SDAVAssetExportSession(asset: asset) else {
  111. return nil
  112. }
  113. exportSession.outputURL = outputURL
  114. exportSession.shouldOptimizeForNetworkUse = true
  115. exportSession.outputFileType = AVFileType.mp4.rawValue
  116. exportSession.videoSettings = [
  117. AVVideoCodecKey : AVVideoCodecH264,
  118. AVVideoWidthKey : NSNumber(value: videoWidth),
  119. AVVideoHeightKey : NSNumber(value: videoHeight),
  120. AVVideoCompressionPropertiesKey : [
  121. AVVideoAverageBitRateKey : rate.videoRate,
  122. AVVideoProfileLevelKey : AVVideoProfileLevelH264Baseline31
  123. ],
  124. ]
  125. exportSession.audioSettings = [
  126. AVFormatIDKey : kAudioFormatMPEG4AAC,
  127. AVNumberOfChannelsKey : rate.audioChannels,
  128. AVSampleRateKey : 44100,
  129. AVEncoderBitRateKey : rate.audioRate
  130. ]
  131. return exportSession
  132. }
  133. }