Thank you very much, Oleg Kudinov, you are amazing!
Below is a quick version if someone needs it.
func rotateVideoPlayer(player: AVPlayer, degrees: CGFloat) -> AVPlayer? { let urlAsset = player.currentItem?.asset as! AVURLAsset let url = urlAsset.URL var composition: AVMutableComposition? var videoComposition: AVMutableVideoComposition? var instruction: AVMutableVideoCompositionInstruction? let asset = AVURLAsset(URL: url) var layerInstruction: AVMutableVideoCompositionLayerInstruction? var t1: CGAffineTransform? var t2: CGAffineTransform? var assetVideoTrack: AVAssetTrack? var assetAudioTrack: AVAssetTrack? // Check if the asset contains video and audio tracks if asset.tracksWithMediaType(AVMediaTypeVideo).count != 0 { assetVideoTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] } if asset.tracksWithMediaType(AVMediaTypeAudio).count != 0 { assetAudioTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] } let insertionPoint = kCMTimeInvalid // Step 1 // Create a new composition composition = AVMutableComposition() // Insert a new composition if assetVideoTrack != nil { let compositionVideoTrack: AVMutableCompositionTrack = (composition?.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid))! let timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration) try! compositionVideoTrack.insertTimeRange(timeRange, ofTrack: assetVideoTrack!, atTime: insertionPoint) } if assetAudioTrack != nil { let compositionAudioTrack: AVMutableCompositionTrack = (composition?.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid))! let timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration) try! compositionAudioTrack.insertTimeRange(timeRange, ofTrack: assetAudioTrack!, atTime: insertionPoint) } // Step 2 // Calculate position and size of render video after rotating let width = Float((assetVideoTrack?.naturalSize.width)!) let height = Float((assetVideoTrack?.naturalSize.height)!) let toDiagonal = Float(sqrt(width * width + height * height)) let toDiagonalAngle = Float(radiansToDegrees(acosf(width/toDiagonal))) let toDiagonalAngle2 = Float(90 - radiansToDegrees(acosf(width/toDiagonal))) var toDiagonalAngleComple: Float var toDiagonalAngleComple2: Float var finalHeight: Float = 0 var finalWidth: Float = 0 if degrees >= 0 && degrees <= 90 { toDiagonalAngleComple = toDiagonalAngle + Float(degrees) toDiagonalAngleComple2 = toDiagonalAngle2 + Float(degrees) let sinfValue = sinf(degreesToRadians(toDiagonalAngleComple)) let sinfValue2 = sinf(degreesToRadians(toDiagonalAngleComple2)) finalHeight = abs(toDiagonal * sinfValue) finalWidth = abs(toDiagonal * sinfValue2) let side1 = height * sinf(degreesToRadians(Float(degrees))) let side2 = 0.0 t1 = CGAffineTransformMakeTranslation(CGFloat(side1), CGFloat(side2)) } else if degrees > 90 && degrees <= 180 { let degrees2 = Float(degrees - 90) toDiagonalAngleComple = toDiagonalAngle + degrees2 toDiagonalAngleComple2 = toDiagonalAngle2 + degrees2 let sinfValue = sinf(degreesToRadians(toDiagonalAngleComple2)) let sinfValue2 = sinf(degreesToRadians(toDiagonalAngleComple)) finalHeight = abs(toDiagonal * sinfValue) finalWidth = abs(toDiagonal * sinfValue2) let side1 = width * sinf(degreesToRadians(degrees2)) + height * cosf(degreesToRadians(degrees2)) let side2 = height * sinf(degreesToRadians(degrees2)) t1 = CGAffineTransformMakeTranslation(CGFloat(side1), CGFloat(side2)) } else if degrees >= -90 && degrees < 0 { let degrees2 = Float(degrees - 90) let degrees2abs = Float(abs(degrees)) toDiagonalAngleComple = toDiagonalAngle + degrees2 toDiagonalAngleComple2 = toDiagonalAngle2 + degrees2 let sinfValue = sinf(degreesToRadians(toDiagonalAngleComple2)) let sinfValue2 = sinf(degreesToRadians(toDiagonalAngleComple)) finalHeight = abs(toDiagonal * sinfValue) finalWidth = abs(toDiagonal * sinfValue2) let side1 = 0 let side2 = width * sinf(degreesToRadians(degreesabs)) t1 = CGAffineTransformMakeTranslation(CGFloat(side1), CGFloat(side2)) } else if degrees >= -180 && degrees < -90 { let degreesabs = Float(abs(degrees)) let degreesPlus = degreesabs - 90 toDiagonalAngleComple = toDiagonalAngle + Float(degrees) toDiagonalAngleComple2 = toDiagonalAngle2 + Float(degrees) let sinfValue = sinf(degreesToRadians(toDiagonalAngleComple)) let sinfValue2 = sinf(degreesToRadians(toDiagonalAngleComple2)) finalHeight = abs(toDiagonal * sinfValue) finalWidth = abs(toDiagonal * sinfValue2) let side1 = width * sinf(degreesToRadians(degreesPlus)) let side2 = height * sinf(degreesToRadians(degreesPlus)) + width * cosf(degreesToRadians(degreesPlus)) t1 = CGAffineTransformMakeTranslation(CGFloat(side1), CGFloat(side2)) } // Rotate transformation t2 = CGAffineTransformRotate(t1!, CGFloat(degreesToRadians(Float(degrees)))) // // Step 3 // // Set the appropriate render sizes and rotational transforms // // Create a new video composition // videoComposition = AVMutableComposition videoComposition = AVMutableVideoComposition() videoComposition?.renderSize = CGSizeMake(CGFloat(finalWidth), CGFloat(finalHeight)) videoComposition?.frameDuration = CMTimeMake(1, 30) // The rotate transform is set on a layer instruction instruction = AVMutableVideoCompositionInstruction() instruction?.timeRange = CMTimeRangeMake(kCMTimeZero, composition!.duration) // // + videoCompositionLayerInstructionWithAssetTrack: // Returns a new mutable video composition layer instruction for the given track. // // Swift // convenience init(assetTrack track: AVAssetTrack) // // Objective-C // + (instancetype)videoCompositionLayerInstructionWithAssetTrack:(AVAssetTrack *)track // objectiv-c: // layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:[composition.tracks objectAtIndex:0]]; // Swift layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: (composition?.tracks[0])!) layerInstruction?.setTransform(t2!, atTime: kCMTimeZero) // // Step 4 // // Add the transfor instructions to the video composition instruction?.layerInstructions = NSArray(object: layerInstruction!) as! [AVVideoCompositionLayerInstruction] videoComposition?.instructions = NSArray(object: instruction!) as! [AVVideoCompositionInstructionProtocol] let playItem_ = AVPlayerItem(asset: composition!) playItem_.videoComposition = videoComposition var time: CMTime! time = kCMTimeZero player.replaceCurrentItemWithPlayerItem(playItem_) player.seekToTime(time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) // Export rotated video to the file let exportSession = AVAssetExportSession(asset: composition!, presetName: AVAssetExportPresetMediumQuality) exportSession?.outputURL = NSURL(string: String(format: "%@_rotated", url)) exportSession?.outputFileType = AVFileTypeQuickTimeMovie exportSession?.videoComposition = videoComposition exportSession?.shouldOptimizeForNetworkUse = true exportSession?.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration) exportSession?.exportAsynchronouslyWithCompletionHandler({ () -> Void in print("Video exported") }) return player }
charles.cc.hsu
source share