From bc823dc31c9bf93cdd77e269493f74ffa83dd8ea Mon Sep 17 00:00:00 2001 From: VoronovAlexander Date: Sat, 6 Aug 2022 04:32:54 +0500 Subject: [PATCH] rotate video --- .../video_compress/VideoCompressPlugin.kt | 4 +- ios/Classes/SwiftVideoCompressPlugin.swift | 68 +++++++++++++------ lib/src/video_compress/video_compressor.dart | 2 + 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt index 7eeadae9..b499f2ef 100644 --- a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt +++ b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt @@ -84,6 +84,7 @@ class VideoCompressPlugin : MethodCallHandler, FlutterPlugin { val duration = call.argument("duration") val includeAudio = call.argument("includeAudio") ?: true val frameRate = if (call.argument("frameRate")==null) 30 else call.argument("frameRate") + val rotation = if (call.argument("rotation")==null) 0 else call.argument("rotation") val tempDir: String = context.getExternalFilesDir("video_compress")!!.absolutePath val out = SimpleDateFormat("yyyy-MM-dd hh-mm-ss").format(Date()) @@ -124,7 +125,7 @@ class VideoCompressPlugin : MethodCallHandler, FlutterPlugin { } 7 -> { videoTrackStrategy = DefaultVideoStrategy.atMost(1080, 1920).build() - } + } } audioTrackStrategy = if (includeAudio) { @@ -151,6 +152,7 @@ class VideoCompressPlugin : MethodCallHandler, FlutterPlugin { .addDataSource(dataSource) .setAudioTrackStrategy(audioTrackStrategy) .setVideoTrackStrategy(videoTrackStrategy) + .setVideoRotation(rotation!!) .setListener(object : TranscoderListener { override fun onTranscodeProgress(progress: Double) { channel.invokeMethod("updateProgress", progress * 100.00) diff --git a/ios/Classes/SwiftVideoCompressPlugin.swift b/ios/Classes/SwiftVideoCompressPlugin.swift index 1e727ff4..9ac28ac2 100644 --- a/ios/Classes/SwiftVideoCompressPlugin.swift +++ b/ios/Classes/SwiftVideoCompressPlugin.swift @@ -42,8 +42,9 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { let duration = args!["duration"] as? Double let includeAudio = args!["includeAudio"] as? Bool let frameRate = args!["frameRate"] as? Int + let rotation = args!["rotation"] as? Int compressVideo(path, quality, deleteOrigin, startTime, duration, includeAudio, - frameRate, result) + frameRate, rotation, result) case "cancelCompression": cancelCompression(result) case "deleteAllCache": @@ -175,7 +176,7 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { } private func compressVideo(_ path: String,_ quality: NSNumber,_ deleteOrigin: Bool,_ startTime: Double?, - _ duration: Double?,_ includeAudio: Bool?,_ frameRate: Int?, + _ duration: Double?,_ includeAudio: Bool?,_ frameRate: Int?, _ rotation: Int?, _ result: @escaping FlutterResult) { let sourceVideoUrl = Utility.getPathUrl(path) let sourceVideoType = "mp4" @@ -199,31 +200,58 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { let timeRange: CMTimeRange = CMTimeRangeMake(start: cmStartTime, duration: cmDurationTime) let isIncludeAudio = includeAudio != nil ? includeAudio! : true - - let session = getComposition(isIncludeAudio, timeRange, sourceVideoTrack!) - - let exporter = AVAssetExportSession(asset: session, presetName: getExportPreset(quality))! - - exporter.outputURL = compressionUrl - exporter.outputFileType = AVFileType.mp4 - exporter.shouldOptimizeForNetworkUse = true - - if frameRate != nil { - let videoComposition = AVMutableVideoComposition(propertiesOf: sourceVideoAsset) - videoComposition.frameDuration = CMTimeMake(value: 1, timescale: Int32(frameRate!)) - exporter.videoComposition = videoComposition + + //input file + let asset = AVAsset.init(url: sourceVideoUrl) + print(asset) + let composition = AVMutableComposition.init() + composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) + + //input clip + let clipVideoTrack = asset.tracks(withMediaType: AVMediaType.video)[0] + + //make it composition + let videoComposition = AVMutableVideoComposition() + switch rotation { + case 0: videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.width), height: CGFloat(clipVideoTrack.naturalSize.height)) + case 180: videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.width), height: CGFloat(clipVideoTrack.naturalSize.height)) + case -90: videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.height), height: CGFloat(clipVideoTrack.naturalSize.width)) + case 90: videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.height), height: CGFloat(clipVideoTrack.naturalSize.width)) + default: videoComposition.renderSize = CGSize(width: CGFloat(clipVideoTrack.naturalSize.width), height: CGFloat(clipVideoTrack.naturalSize.height)) } - - if !isIncludeAudio { - exporter.timeRange = timeRange + videoComposition.frameDuration = CMTimeMake(value: 1, timescale: Int32(frameRate!)) + let instruction = AVMutableVideoCompositionInstruction() + instruction.timeRange = timeRange + + //rotate + let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: clipVideoTrack) + let t1 :CGAffineTransform + switch rotation { + case 0: t1 = CGAffineTransform(translationX: 0, y: 0) + case -90: t1 = CGAffineTransform(translationX: 0, y: clipVideoTrack.naturalSize.width) + case 90: t1 = CGAffineTransform(translationX: clipVideoTrack.naturalSize.height, y: 0) + case 180: t1 = CGAffineTransform(translationX: clipVideoTrack.naturalSize.width, y: clipVideoTrack.naturalSize.height) + default: t1 = CGAffineTransform(translationX: 0, y: 0) } - + let t2: CGAffineTransform = t1.rotated(by: CGFloat(rotation!) * .pi / 180) + let finalTransform: CGAffineTransform = t2 + transformer.setTransform(finalTransform, at: CMTime.zero) + instruction.layerInstructions = [transformer] + videoComposition.instructions = [instruction] + + //exporter + let exporter = AVAssetExportSession.init(asset: asset, presetName: getExportPreset(quality)) + exporter?.outputFileType = AVFileType.mp4 + exporter?.outputURL = compressionUrl + exporter?.videoComposition = videoComposition + exporter?.shouldOptimizeForNetworkUse = true + Utility.deleteFile(compressionUrl.absoluteString) let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.updateProgress), userInfo: exporter, repeats: true) - exporter.exportAsynchronously(completionHandler: { + exporter?.exportAsynchronously(completionHandler: { timer.invalidate() if(self.stopCommand) { self.stopCommand = false diff --git a/lib/src/video_compress/video_compressor.dart b/lib/src/video_compress/video_compressor.dart index 44b3f7c4..1ea8bab2 100644 --- a/lib/src/video_compress/video_compressor.dart +++ b/lib/src/video_compress/video_compressor.dart @@ -127,6 +127,7 @@ extension Compress on IVideoCompress { int? duration, bool? includeAudio, int frameRate = 30, + int rotation = 0, }) async { if (isCompressing) { throw StateError('''VideoCompress Error: @@ -149,6 +150,7 @@ extension Compress on IVideoCompress { 'duration': duration, 'includeAudio': includeAudio, 'frameRate': frameRate, + 'rotation': rotation }); // ignore: invalid_use_of_protected_member