10

I am capturing the video using UIImagePickerController, i can crop the video using the following code,

AVAsset *asset = [AVAsset assetWithURL:url];

//create an avassetrack with our asset
AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

//create a video composition and preset some settings
AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.frameDuration = CMTimeMake(1, 30);
//here we are setting its render size to its height x height (Square)

videoComposition.renderSize = CGSizeMake(clipVideoTrack.naturalSize.height, clipVideoTrack.naturalSize.height);

//create a video instruction
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30));

AVMutableVideoCompositionLayerInstruction* transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:clipVideoTrack];

//Here we shift the viewing square up to the TOP of the video so we only see the top
CGAffineTransform t1 = CGAffineTransformMakeTranslation(clipVideoTrack.naturalSize.height, -20);

//Use this code if you want the viewing square to be in the middle of the video
//CGAffineTransform t1 = CGAffineTransformMakeTranslation(clipVideoTrack.naturalSize.height, -(clipVideoTrack.naturalSize.width - clipVideoTrack.naturalSize.height) /2 );

//Make sure the square is portrait
CGAffineTransform t2 = CGAffineTransformRotate(t1, M_PI_2);

CGAffineTransform finalTransform = t2;
[transformer setTransform:finalTransform atTime:kCMTimeZero];

//add the transformer layer instructions, then add to video composition
instruction.layerInstructions = [NSArray arrayWithObject:transformer];
videoComposition.instructions = [NSArray arrayWithObject: instruction];

//Create an Export Path to store the cropped video
NSString *outputPath = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"video.mp4"];
NSURL *exportUrl = [NSURL fileURLWithPath:outputPath];

//Remove any prevouis videos at that path
[[NSFileManager defaultManager]  removeItemAtURL:exportUrl error:nil];

//Export    
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetLowQuality] ;
exporter.videoComposition = videoComposition;
exporter.outputURL = exportUrl;
exporter.outputFileType = AVFileTypeMPEG4;

[exporter exportAsynchronouslyWithCompletionHandler:^
 {
     dispatch_async(dispatch_get_main_queue(), ^{
         //Call when finished
         [self exportDidFinish:exporter];
     });
 }];

But i dont know how to fix the orientation issue. Like instagram and vine app , (i.e) if i capture the video even in landscape mode, it should be in portrait mode and need to crop the video as square. Pls give me the solution... i am struggling with this issue...

Surfer
  • 1,370
  • 3
  • 19
  • 34
  • hey, I am using the code in the answer and got my crop working but I am seeing this weird green outline around the bottom and the right side, have you had this problem? – iqueqiorio Apr 07 '15 at 15:57

3 Answers3

30

I suppose the source code come from this link ( project code included )

http://www.one-dreamer.com/cropping-video-square-like-vine-instagram-xcode/

You need first to know the REAL video orientation:

- (UIImageOrientation)getVideoOrientationFromAsset:(AVAsset *)asset
{
    AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    CGSize size = [videoTrack naturalSize];
    CGAffineTransform txf = [videoTrack preferredTransform];

    if (size.width == txf.tx && size.height == txf.ty)
        return UIImageOrientationLeft; //return UIInterfaceOrientationLandscapeLeft;
    else if (txf.tx == 0 && txf.ty == 0)
        return UIImageOrientationRight; //return UIInterfaceOrientationLandscapeRight;
    else if (txf.tx == 0 && txf.ty == size.width)
        return UIImageOrientationDown; //return UIInterfaceOrientationPortraitUpsideDown;
    else
        return UIImageOrientationUp;  //return UIInterfaceOrientationPortrait;
}

I made that function in a way that it return the right orientation as if it was an image

Then, i modified the function to fix the right orientation, supporting any crop region not just a square, like this:

// apply the crop to passed video asset (set outputUrl to avoid the saving on disk ). Return the exporter session object
- (AVAssetExportSession*)applyCropToVideoWithAsset:(AVAsset*)asset AtRect:(CGRect)cropRect OnTimeRange:(CMTimeRange)cropTimeRange ExportToUrl:(NSURL*)outputUrl ExistingExportSession:(AVAssetExportSession*)exporter WithCompletion:(void(^)(BOOL success, NSError* error, NSURL* videoUrl))completion
{

    //create an avassetrack with our asset
    AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

    //create a video composition and preset some settings
    AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition];
    videoComposition.frameDuration = CMTimeMake(1, 30);

    CGFloat cropOffX = cropRect.origin.x;
    CGFloat cropOffY = cropRect.origin.y;
    CGFloat cropWidth = cropRect.size.width;
    CGFloat cropHeight = cropRect.size.height;

    videoComposition.renderSize = CGSizeMake(cropWidth, cropHeight);

    //create a video instruction
    AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    instruction.timeRange = cropTimeRange;

    AVMutableVideoCompositionLayerInstruction* transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:clipVideoTrack];

    UIImageOrientation videoOrientation = [self getVideoOrientationFromAsset:asset];

    CGAffineTransform t1 = CGAffineTransformIdentity;
    CGAffineTransform t2 = CGAffineTransformIdentity;

    switch (videoOrientation) {
        case UIImageOrientationUp:
            t1 = CGAffineTransformMakeTranslation(clipVideoTrack.naturalSize.height - cropOffX, 0 - cropOffY );
            t2 = CGAffineTransformRotate(t1, M_PI_2 );
            break;
        case UIImageOrientationDown:
            t1 = CGAffineTransformMakeTranslation(0 - cropOffX, clipVideoTrack.naturalSize.width - cropOffY ); // not fixed width is the real height in upside down
            t2 = CGAffineTransformRotate(t1, - M_PI_2 );
            break;
        case UIImageOrientationRight:
            t1 = CGAffineTransformMakeTranslation(0 - cropOffX, 0 - cropOffY );
            t2 = CGAffineTransformRotate(t1, 0 );
            break;
        case UIImageOrientationLeft:
            t1 = CGAffineTransformMakeTranslation(clipVideoTrack.naturalSize.width - cropOffX, clipVideoTrack.naturalSize.height - cropOffY );
            t2 = CGAffineTransformRotate(t1, M_PI  );
            break;
        default:
            NSLog(@"no supported orientation has been found in this video");
            break;
    }

    CGAffineTransform finalTransform = t2;
    [transformer setTransform:finalTransform atTime:kCMTimeZero];

    //add the transformer layer instructions, then add to video composition
    instruction.layerInstructions = [NSArray arrayWithObject:transformer];
    videoComposition.instructions = [NSArray arrayWithObject: instruction];

    //Remove any prevouis videos at that path
    [[NSFileManager defaultManager]  removeItemAtURL:outputUrl error:nil];

    if (!exporter){
        exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality] ;
    }

    // assign all instruction for the video processing (in this case the transformation for cropping the video
    exporter.videoComposition = videoComposition;
    //exporter.outputFileType = AVFileTypeQuickTimeMovie;

    if (outputUrl){

        exporter.outputURL = outputUrl;
        [exporter exportAsynchronouslyWithCompletionHandler:^{

             switch ([exporter status]) {
                 case AVAssetExportSessionStatusFailed:
                     NSLog(@"crop Export failed: %@", [[exporter error] localizedDescription]);
                     if (completion){
                         dispatch_async(dispatch_get_main_queue(), ^{
                             completion(NO,[exporter error],nil);
                         });
                         return;
                     }
                     break;
                 case AVAssetExportSessionStatusCancelled:
                     NSLog(@"crop Export canceled");
                     if (completion){
                         dispatch_async(dispatch_get_main_queue(), ^{
                             completion(NO,nil,nil);
                         });
                         return;
                     }
                     break;
                 default:
                     break;
            }

            if (completion){
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion(YES,nil,outputUrl);
                });
            }

        }];
    }

    return exporter;
}

Tested in all recorded video orientation (Up,Down,Lanscape R, Landscape L) in both normal and front camera cases. I tested it on iPhone 5S (iOS 8.1), iPhone 6 Plus (iOS 8.1)

Hope it helps

Luca Iaco
  • 3,387
  • 1
  • 19
  • 20
  • Thanks... its working fine.. but only the problem is, i set the cropOffY = 60.. when record in landscape mode, the video cropped is not correct also there is a black area displayed in the bottom of the video... pls let me know how to fix this problem? – Surfer Nov 17 '14 at 06:13
  • i fixed the above issue.. but if i choose video from photo library, the cropped video looks zoomed in.. pls let know how to solve this problem – Surfer Nov 17 '14 at 06:42
  • Seems strange, since it doesn't apply any direct scaling on the video, it just define the render area, and for the orientation it just rotate and translate. What can you try is to to set the scale to 1:1 like this: t2 = CGAffineTransformScale(t1, 1, 1) then t2 = CGAffineTransformRotate(t2, M_PI_2 ); ( this is for the first case in the switch block, and so for all the cases ) but i don't think it change. Maybe could be the fact that when you take a video from the picker, it is compressed. If you take a video from picker, it pass from 1920x1080 to 1280x720 for the AVAsset (video.naturalSize) – Luca Iaco Nov 17 '14 at 16:30
  • i tried also to use the videoComposition.renderScale property and nothing change setting renderScale to 1.f. Honestly i don't find any problem for that – Luca Iaco Nov 17 '14 at 16:43
  • I get this when I execute. "crop Export failed: Cannot create file" . Any hints? – Vijay Tholpadi Mar 24 '15 at 18:12
  • @Vijayts Have you checked if you have enough space on the device? And also, even if i delete any previous file, have you checked if the deletion works fine? Can you print the error code also? Maybe could be useful – Luca Iaco Mar 26 '15 at 08:18
  • Managed to solve it @LucaIaco. Was not sending a proper outputURL to the block hence it was unable to complete the export. Thanks. – Vijay Tholpadi Mar 26 '15 at 12:08
  • @LucaIaco thanks would you mind posting the source of your app that you made a tested? – iqueqiorio Apr 05 '15 at 23:31
  • @Vijayts I fixed the previous problem, but I have cropped the video and I am seeing a green border around the right and bottom part? Have any of you have this problem? And know how to fix it? It seems very weird this border comes out of no where? – iqueqiorio Apr 06 '15 at 21:15
  • @iqueqiorio what you could try is to crop the video using multiple of two for width and height (eg: 320 x 386, 512 x 512, etc), i had that same problem, and i was able to solve it in this way – Luca Iaco Apr 08 '15 at 06:58
  • @LucaIaco hey man thanks, solved my problem, Ive ran into one more with my crop area not being the same as the video area? I would really love it if you could take a look at the question http://stackoverflow.com/questions/29523857/selected-area-and-crop-area-differ-in-ios. Thanks for your great post :) – iqueqiorio Apr 08 '15 at 19:39
  • 1
    @LucaIaco the question is here actually and there is a bounty on it http://stackoverflow.com/questions/29450308/crop-area-different-than-selected-area-in-ios – iqueqiorio Apr 08 '15 at 21:14
  • I'm getting "UIInterfaceOrientationLandscapeLeft" for a video that is in upright portrait – etayluz Jul 17 '15 at 14:40
  • @Lucalaco i'm having a similar problem with the zoom after a photo is cropped for some of the photos in the camera library. Was a solution found? – brian Scroggins Sep 18 '15 at 20:46
  • I used this code exactly the same way as explained but the video is black. Any idea? – Subash May 13 '16 at 16:56
  • What part of the code is adjusting for the mirror different between front and rear facing cameras? – Eugene May 14 '16 at 06:56
  • 1
    You are seriously my hero. – Roi Mulia Dec 02 '16 at 16:40
6

This is my code to create a vine-like video from a video on disk. This is written in swift:

static let MaxDuration: CMTimeValue = 12

class func compressVideoAsset(_ asset: AVAsset, output: URL, completion: @escaping (_ data: Data?) -> Void)
{
    let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality)!
    session.videoComposition = self.squareVideoCompositionForAsset(asset)
    session.outputURL = output
    session.outputFileType = AVFileTypeMPEG4
    session.shouldOptimizeForNetworkUse = true
    session.canPerformMultiplePassesOverSourceMediaData = true

    let duration = CMTimeValue(CGFloat(asset.duration.value) / CGFloat(asset.duration.timescale) * 30)
    session.timeRange = CMTimeRange(start: kCMTimeZero, duration: CMTime(value: min(duration, VideoCompressor.MaxDuration * 30), timescale: 30))

    session.exportAsynchronously(completionHandler: { () -> Void in
        let data = try? Data(contentsOf: output)

        DispatchQueue.main.async(execute: { () -> Void in
            completion(data)
        })
    })
}

private class func squareVideoCompositionForAsset(_ asset: AVAsset) -> AVVideoComposition
{
    let track = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
    let length = min(track.naturalSize.width, track.naturalSize.height)

    var transform = track.preferredTransform

    let size = track.naturalSize
    let scale: CGFloat = (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) ? -1 : 1 // check for inversion

    transform = transform.translatedBy(x: scale * -(size.width - length) / 2, y: scale * -(size.height - length) / 2)

    let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
    transformer.setTransform(transform, at: kCMTimeZero)

    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRange(start: kCMTimeZero, duration: kCMTimePositiveInfinity)
    instruction.layerInstructions = [transformer]

    let composition = AVMutableVideoComposition()
    composition.frameDuration = CMTime(value: 1, timescale: 30)
    composition.renderSize = CGSize(width: length, height: length)
    composition.instructions = [instruction]

    return composition
}
Aaron Scherbing
  • 507
  • 6
  • 6
  • please take a look at [my question](http://stackoverflow.com/questions/43558021/swift-square-video-composition) – pigeon_39 Apr 22 '17 at 11:58
  • Hi pigeon_39. I edited my code above. I believe I ran into the same rotation issue, and this updated logic should fix it. – Aaron Scherbing Apr 22 '17 at 19:18
  • thanks. It perfectly worked for landscape video but it didn't work for portrait video. Can you please help me a little bit? This is my [updated question](http://stackoverflow.com/questions/43558021/swift-square-video-composition) – pigeon_39 Apr 23 '17 at 08:17
  • @AaronScherbing what's `VideoCompressor.MaxDuration` – Lance Samaria Apr 30 '22 at 19:58
5

I know this question is old but some people may still be wondering why some of the videos from the camera roll zoom in after they're cropped. I faced this problem and realized that the cropRect I was using as a frame was not scaled for the different aspect ratios of the video. To fix this problem I simply added the code below to crop the very top of the video into a square. If you want to change the position just change the y value but make sure to scale it according to the video. Luca Iaco provided some great code to get started with. I appreciate it!

CGSize videoSize = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize];
float scaleFactor;

if (videoSize.width > videoSize.height) {

    scaleFactor = videoSize.height/320;
}
else if (videoSize.width == videoSize.height){

    scaleFactor = videoSize.height/320;
}
else{
    scaleFactor = videoSize.width/320;
}

CGFloat cropOffX = 0;
CGFloat cropOffY = 0;
CGFloat cropWidth = 320 *scaleFactor;
CGFloat cropHeight = 320 *scaleFactor;
brian Scroggins
  • 2,701
  • 3
  • 17
  • 17