123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- //
- // SLEditVideoClipping.m
- // DarkMode
- //
- // Created by wsl on 2019/10/21.
- // CopyrightTime © 2019 wsl. All rightTimes reserved.
- //
- #import "SLEditVideoClipping.h"
- /// 定义一个结构体 存储裁剪信息
- struct SLVideoClippingState {
- /// 裁剪位置 开头NO 结尾YES
- BOOL position;
- ///时间比例 0 ——1
- CGFloat value;
- /// 裁剪状态 开始、正在、结束
- UIGestureRecognizerState state;
- };
- typedef struct SLVideoClippingState SLVideoClippingState;
- CG_INLINE SLVideoClippingState
- SLVideoClippingStateMake(BOOL position, CGFloat value, UIGestureRecognizerState state)
- {
- SLVideoClippingState videoClippingState;
- videoClippingState.position = position;
- videoClippingState.value = value;
- videoClippingState.state = state;
- return videoClippingState;
- };
- /// 视频编辑裁剪选择框
- @interface SLVideoClippingBox : UIView
- @property (nonatomic, assign) double totalDuration; //总时长
- @property (nonatomic, strong) UIView *boxInside; //裁剪框内
- @property (nonatomic, strong) UILabel *leftTime; //左边 起始时间点
- @property (nonatomic, strong) UILabel *rightTime; // 右边 结束时间点
- @property (nonatomic, copy) void(^changeClippingRange)(SLVideoClippingState clippingState); //改变选择的裁剪区域
- @end
- @implementation SLVideoClippingBox
- #pragma mark - Override
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- self.backgroundColor = [UIColor clearColor];
- }
- return self;
- }
- - (void)didMoveToSuperview {
- [super didMoveToSuperview];
- if (self.superview == nil) {
- // _boxInside = nil;
- // _leftTime = nil;
- // _rightTime = nil;
- [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
- }else {
- [self addSubview:self.boxInside];
- [self addSubview:self.leftTime];
- [self addSubview:self.rightTime];
- }
- }
- - (void)drawRect:(CGRect)rect {
- [super drawRect:rect];
- [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5] setFill];
- UIRectFill(rect);
- CGRect holeRectIntersection = CGRectIntersection(CGRectMake(self.leftTime.sl_x+10, 0, self.rightTime.sl_x - (self.leftTime.sl_x+10), rect.size.height), rect);
- [[UIColor clearColor] setFill];
- UIRectFill(holeRectIntersection);
- }
- -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
- CGRect bounds = self.bounds;
- //扩大响应区域宽至30
- CGFloat widthDelta = MAX(30, 0);
- CGFloat heightDelta = MAX(30, 0);
- bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
- return CGRectContainsPoint(bounds, point);
- }
- - (void)setFrame:(CGRect)frame{
- [super setFrame:frame];
- self.boxInside.frame = CGRectMake(0, 0, self.sl_width, self.sl_height);
- self.leftTime.frame = CGRectMake(-10, 0, 10, self.sl_height);
- self.rightTime.frame = CGRectMake(self.sl_width, 0, 10, self.sl_height);
- }
- #pragma mark - Getter
- - (UIView *)boxInside {
- if (!_boxInside) {
- _boxInside = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.sl_width, self.sl_height)];
- _boxInside.backgroundColor = [UIColor clearColor];
- _boxInside.layer.borderColor = [UIColor whiteColor].CGColor;
- _boxInside.layer.borderWidth = 2;
- }
- return _boxInside;
- }
- - (UILabel *)leftTime {
- if (!_leftTime) {
- _leftTime = [[UILabel alloc] initWithFrame:CGRectMake(-10, 0, 10, self.sl_height)];
- _leftTime.text = @"||";
- _leftTime.textAlignment = NSTextAlignmentCenter;
- _leftTime.backgroundColor = [UIColor whiteColor];
- _leftTime.textColor = [UIColor grayColor];
- _leftTime.font = [UIFont systemFontOfSize:12];
- _leftTime.userInteractionEnabled = YES;
- _leftTime.tag = 0;
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(changeClippingRange:)];
- [_leftTime addGestureRecognizer:pan];
- }
- return _leftTime;
- }
- - (UILabel *)rightTime {
- if (!_rightTime) {
- _rightTime = [[UILabel alloc] initWithFrame:CGRectMake(self.sl_width, 0, 10, self.sl_height)];
- _rightTime.text = @"||";
- _rightTime.tag = 1;
- _rightTime.textAlignment = NSTextAlignmentCenter;
- _rightTime.backgroundColor = [UIColor whiteColor];
- _rightTime.textColor = [UIColor grayColor];
- _rightTime.font = [UIFont systemFontOfSize:12];
- _rightTime.userInteractionEnabled = YES;
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(changeClippingRange:)];
- [_rightTime addGestureRecognizer:pan];
- }
- return _rightTime;
- }
- #pragma mark - Events Handle
- //选择裁剪范围
- - (void)changeClippingRange:(UIPanGestureRecognizer *)pan {
- CGPoint transP = [pan translationInView:self];
- if (pan.state == UIGestureRecognizerStateBegan) {
- self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateBegan));
- } else if (pan.state == UIGestureRecognizerStateChanged ) {
- if (pan.view == self.leftTime ) {
- //裁剪的视频时长必须>=1秒
- if (pan.view.sl_x + transP.x < -10 || (self.rightTime.sl_x - self.boxInside.sl_x - transP.x)/self.sl_width*self.totalDuration < 1) {
- NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s");
- }else {
- self.boxInside.sl_x = self.boxInside.sl_x + transP.x;
- self.boxInside.sl_width = self.boxInside.sl_width - transP.x;
- pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y);
- }
- }else if (pan.view == self.rightTime) {
- if (pan.view.sl_x + transP.x > self.sl_width || (self.rightTime.sl_x + transP.x - self.boxInside.sl_x)/self.sl_width*self.totalDuration < 1) {
- NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s");
- }else {
- self.boxInside.sl_width = self.boxInside.sl_width + transP.x;
- pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y);
- }
- }
- self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateChanged));
- [self setNeedsDisplay];
- [pan setTranslation:CGPointZero inView:self];
- }else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateFailed || pan.state == UIGestureRecognizerStateCancelled) {
- if (pan.view == self.leftTime) {
- if (pan.view.sl_x < -10) {
- pan.view.sl_x = -10;
- }
- }else if (pan.view == self.rightTime) {
- if (pan.view.sl_x > self.sl_width) {
- pan.view.sl_x = self.sl_width;
- }
- }
- self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateEnded));
- }
- }
- @end
- @interface SLEditVideoClipping ()
- /// 视频帧图片解析器
- @property (nonatomic, strong) AVAssetImageGenerator *imageGenerator;
- /// 视频总时长
- @property (nonatomic, assign) double totalDuration;
- /// 图片帧 容器
- @property (nonatomic, strong) UIView *contentView;
- /// 视频裁剪框
- @property (nonatomic, strong) SLVideoClippingBox *clippingBox; //剪辑选择框
- @property (nonatomic, strong) UIButton *cancleBtn;
- @property (nonatomic, strong) UIButton *doneBtn;
- @property (nonatomic, assign) CMTime beginTime; //裁剪的开始
- @property (nonatomic, assign) CMTime endTime; //裁剪的结束
- @end
- @implementation SLEditVideoClipping
- #pragma mark - Override
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- }
- return self;
- }
- - (void)didMoveToSuperview {
- [super didMoveToSuperview];
- if(self.superview == nil) {
- // _clippingBox = nil;
- [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
- }else {
- [self addSubview:self.contentView];
- [self addSubview:self.clippingBox];
- [self addSubview:self.cancleBtn];
- [self addSubview:self.doneBtn];
- [self generateImage];
- }
- }
- #pragma mark - Setter
- - (void)setAsset:(AVAsset *)asset {
- _asset = asset;
- _imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset];
- }
- #pragma mark - Getter
- - (UIView *)contentView {
- if (!_contentView) {
- _contentView = [[UIView alloc] initWithFrame:CGRectMake(40, 10, self.sl_width - 40 * 2, 50)];
- _contentView.backgroundColor = [UIColor blackColor];
- _contentView.layer.borderColor = [UIColor lightGrayColor].CGColor;
- _contentView.clipsToBounds = YES;
- }
- return _contentView;
- }
- - (UIView *)clippingBox {
- if (!_clippingBox) {
- _clippingBox = [[SLVideoClippingBox alloc] initWithFrame:self.contentView.frame];
- __weak typeof(self) weakSelf = self;
- _clippingBox.changeClippingRange = ^(SLVideoClippingState clippingState) {
- switch (clippingState.state) {
- case UIGestureRecognizerStateBegan:
- weakSelf.contentView.layer.borderWidth = 2;
- break;
- case UIGestureRecognizerStateChanged:{
- CMTime duration = weakSelf.asset.duration;
- if (clippingState.position) {
- weakSelf.endTime= CMTimeMakeWithSeconds(floor(weakSelf.totalDuration*clippingState.value), duration.timescale);
- weakSelf.selectedClippingEnd(weakSelf.beginTime, weakSelf.endTime, clippingState.state);
- }else {
- weakSelf.beginTime = CMTimeMakeWithSeconds(floor(weakSelf.totalDuration*clippingState.value), duration.timescale);
- weakSelf.selectedClippingBegin(weakSelf.beginTime, weakSelf.endTime,clippingState.state);
- }
- }
- break;
- case UIGestureRecognizerStateEnded:
- weakSelf.contentView.layer.borderWidth = 0;
- if (clippingState.position) {
- weakSelf.selectedClippingEnd(weakSelf.beginTime, weakSelf.endTime, clippingState.state);
- }else {
- weakSelf.selectedClippingBegin(weakSelf.beginTime, weakSelf.endTime,clippingState.state);
- }
- break;
- default:
- break;
- }
- };
- }
- return _clippingBox;
- }
- - (UIButton *)cancleBtn {
- if (_cancleBtn == nil) {
- _cancleBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, self.sl_height - 10 - 30, 30, 30)];
- [_cancleBtn setTitle:@"取消" forState:UIControlStateNormal];
- [_cancleBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
- _cancleBtn.titleLabel.font = [UIFont systemFontOfSize:14];
- [_cancleBtn addTarget:self action:@selector(cancleBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
- }
- return _cancleBtn;
- }
- - (UIButton *)doneBtn {
- if (_doneBtn == nil) {
- _doneBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_width - 30 - 10, self.sl_height - 10 - 30, 30, 30)];
- [_doneBtn setTitle:@"完成" forState:UIControlStateNormal];
- [_doneBtn setTitleColor:[UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1] forState:UIControlStateNormal];
- _doneBtn.titleLabel.font = [UIFont systemFontOfSize:14];
- [_doneBtn addTarget:self action:@selector(doneBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
- }
- return _doneBtn;
- }
- #pragma mark - Events Handle
- - (void)cancleBtnClicked:(UIButton *)btn {
- [self removeFromSuperview];
- self.exitClipping();
- }
- - (void)doneBtnClicked:(UIButton *)btn {
- [self removeFromSuperview];
- self.exitClipping();
- }
- - (void)generateImage {
- [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
- //最多10帧
- NSInteger maxImageCount = 10;
- NSArray *assetVideoTracks = [_asset tracksWithMediaType:AVMediaTypeVideo];
- //导出的视频帧图片大小 单位是px像素
- CGSize maximumSize = CGSizeMake(self.contentView.frame.size.height* [UIScreen mainScreen].scale, self.contentView.frame.size.height* [UIScreen mainScreen].scale);
- //图片视图大小
- CGSize imageViewSize = CGSizeMake(self.contentView.frame.size.height, self.contentView.frame.size.height);
- if (assetVideoTracks.count > 0) {
- AVAssetTrack *track = [assetVideoTracks firstObject];
- //像素
- CGSize size = CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform);
- CGSize dimensions = CGSizeMake(fabs(size.width), fabs(size.height));
- CGFloat height = self.contentView.frame.size.height * [UIScreen mainScreen].scale;
- maximumSize = CGSizeMake(dimensions.width/dimensions.height*height, height);
- }
- if (maxImageCount * maximumSize.width/[UIScreen mainScreen].scale < self.contentView.frame.size.width) {
- maxImageCount = self.contentView.frame.size.width * [UIScreen mainScreen].scale / maximumSize.width;
- self.contentView.sl_width = maxImageCount * maximumSize.width/[UIScreen mainScreen].scale;
- self.contentView.sl_x = (self.sl_width - self.contentView.sl_width)/2.0;
- self.clippingBox.frame = self.contentView.frame;
- imageViewSize = CGSizeMake(self.contentView.sl_height*maximumSize.width/maximumSize.height, self.contentView.sl_height);
- }else {
- imageViewSize = CGSizeMake(self.contentView.sl_width/maxImageCount, self.contentView.sl_height);
- }
-
- //视频帧大小 像素
- _imageGenerator.maximumSize = maximumSize;
- _imageGenerator.appliesPreferredTrackTransform = YES;
-
- CMTime duration = _asset.duration;
- self.totalDuration = CMTimeGetSeconds(duration);
- self.clippingBox.totalDuration = self.totalDuration;
- self.beginTime= kCMTimeZero;
- self.endTime = duration;
-
- NSInteger index = maxImageCount;
- CMTimeValue intervalSeconds = duration.value/index;
- CMTime time = CMTimeMake(0, duration.timescale);
- NSMutableArray *times = [NSMutableArray array];
- for (NSUInteger i = 0; i < index; i++) {
- [times addObject:[NSValue valueWithCMTime:time]];
- time = CMTimeAdd(time, CMTimeMake(intervalSeconds, duration.timescale));
- }
- //生成视帧图片
- [_imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime,
- CGImageRef cgImage,
- CMTime actualTime,
- AVAssetImageGeneratorResult result,
- NSError *error) {
- UIImage *image = nil;
- if (cgImage) {
- image = [[UIImage alloc] initWithCGImage:cgImage scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- NSInteger imageIndex = [times indexOfObject:[NSValue valueWithCMTime:requestedTime]];
- UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
- imageView.layer.borderColor = [UIColor blackColor].CGColor;
- imageView.layer.borderWidth = .5f;
- imageView.frame = CGRectMake(imageIndex*imageViewSize.width, 0, imageViewSize.width, imageViewSize.height);
- imageView.contentMode = UIViewContentModeScaleAspectFill;
- imageView.clipsToBounds = YES;
- float proportion = imageViewSize.width/image.size.width/[UIScreen mainScreen].scale;
- imageView.layer.contentsRect = CGRectMake((1 - proportion)/2.0, 0, proportion, 1);
- [self.contentView addSubview:imageView];
- });
- }];
-
- }
- @end
|