SLEditVideoClipping.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. //
  2. // SLEditVideoClipping.m
  3. // DarkMode
  4. //
  5. // Created by wsl on 2019/10/21.
  6. // CopyrightTime © 2019 wsl. All rightTimes reserved.
  7. //
  8. #import "SLEditVideoClipping.h"
  9. /// 定义一个结构体 存储裁剪信息
  10. struct SLVideoClippingState {
  11. /// 裁剪位置 开头NO 结尾YES
  12. BOOL position;
  13. ///时间比例 0 ——1
  14. CGFloat value;
  15. /// 裁剪状态 开始、正在、结束
  16. UIGestureRecognizerState state;
  17. };
  18. typedef struct SLVideoClippingState SLVideoClippingState;
  19. CG_INLINE SLVideoClippingState
  20. SLVideoClippingStateMake(BOOL position, CGFloat value, UIGestureRecognizerState state)
  21. {
  22. SLVideoClippingState videoClippingState;
  23. videoClippingState.position = position;
  24. videoClippingState.value = value;
  25. videoClippingState.state = state;
  26. return videoClippingState;
  27. };
  28. /// 视频编辑裁剪选择框
  29. @interface SLVideoClippingBox : UIView
  30. @property (nonatomic, assign) double totalDuration; //总时长
  31. @property (nonatomic, strong) UIView *boxInside; //裁剪框内
  32. @property (nonatomic, strong) UILabel *leftTime; //左边 起始时间点
  33. @property (nonatomic, strong) UILabel *rightTime; // 右边 结束时间点
  34. @property (nonatomic, copy) void(^changeClippingRange)(SLVideoClippingState clippingState); //改变选择的裁剪区域
  35. @end
  36. @implementation SLVideoClippingBox
  37. #pragma mark - Override
  38. - (instancetype)initWithFrame:(CGRect)frame {
  39. self = [super initWithFrame:frame];
  40. if (self) {
  41. self.backgroundColor = [UIColor clearColor];
  42. }
  43. return self;
  44. }
  45. - (void)didMoveToSuperview {
  46. [super didMoveToSuperview];
  47. if (self.superview == nil) {
  48. // _boxInside = nil;
  49. // _leftTime = nil;
  50. // _rightTime = nil;
  51. [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
  52. }else {
  53. [self addSubview:self.boxInside];
  54. [self addSubview:self.leftTime];
  55. [self addSubview:self.rightTime];
  56. }
  57. }
  58. - (void)drawRect:(CGRect)rect {
  59. [super drawRect:rect];
  60. [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5] setFill];
  61. UIRectFill(rect);
  62. CGRect holeRectIntersection = CGRectIntersection(CGRectMake(self.leftTime.sl_x+10, 0, self.rightTime.sl_x - (self.leftTime.sl_x+10), rect.size.height), rect);
  63. [[UIColor clearColor] setFill];
  64. UIRectFill(holeRectIntersection);
  65. }
  66. -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
  67. CGRect bounds = self.bounds;
  68. //扩大响应区域宽至30
  69. CGFloat widthDelta = MAX(30, 0);
  70. CGFloat heightDelta = MAX(30, 0);
  71. bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
  72. return CGRectContainsPoint(bounds, point);
  73. }
  74. - (void)setFrame:(CGRect)frame{
  75. [super setFrame:frame];
  76. self.boxInside.frame = CGRectMake(0, 0, self.sl_width, self.sl_height);
  77. self.leftTime.frame = CGRectMake(-10, 0, 10, self.sl_height);
  78. self.rightTime.frame = CGRectMake(self.sl_width, 0, 10, self.sl_height);
  79. }
  80. #pragma mark - Getter
  81. - (UIView *)boxInside {
  82. if (!_boxInside) {
  83. _boxInside = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.sl_width, self.sl_height)];
  84. _boxInside.backgroundColor = [UIColor clearColor];
  85. _boxInside.layer.borderColor = [UIColor whiteColor].CGColor;
  86. _boxInside.layer.borderWidth = 2;
  87. }
  88. return _boxInside;
  89. }
  90. - (UILabel *)leftTime {
  91. if (!_leftTime) {
  92. _leftTime = [[UILabel alloc] initWithFrame:CGRectMake(-10, 0, 10, self.sl_height)];
  93. _leftTime.text = @"||";
  94. _leftTime.textAlignment = NSTextAlignmentCenter;
  95. _leftTime.backgroundColor = [UIColor whiteColor];
  96. _leftTime.textColor = [UIColor grayColor];
  97. _leftTime.font = [UIFont systemFontOfSize:12];
  98. _leftTime.userInteractionEnabled = YES;
  99. _leftTime.tag = 0;
  100. UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(changeClippingRange:)];
  101. [_leftTime addGestureRecognizer:pan];
  102. }
  103. return _leftTime;
  104. }
  105. - (UILabel *)rightTime {
  106. if (!_rightTime) {
  107. _rightTime = [[UILabel alloc] initWithFrame:CGRectMake(self.sl_width, 0, 10, self.sl_height)];
  108. _rightTime.text = @"||";
  109. _rightTime.tag = 1;
  110. _rightTime.textAlignment = NSTextAlignmentCenter;
  111. _rightTime.backgroundColor = [UIColor whiteColor];
  112. _rightTime.textColor = [UIColor grayColor];
  113. _rightTime.font = [UIFont systemFontOfSize:12];
  114. _rightTime.userInteractionEnabled = YES;
  115. UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(changeClippingRange:)];
  116. [_rightTime addGestureRecognizer:pan];
  117. }
  118. return _rightTime;
  119. }
  120. #pragma mark - Events Handle
  121. //选择裁剪范围
  122. - (void)changeClippingRange:(UIPanGestureRecognizer *)pan {
  123. CGPoint transP = [pan translationInView:self];
  124. if (pan.state == UIGestureRecognizerStateBegan) {
  125. 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));
  126. } else if (pan.state == UIGestureRecognizerStateChanged ) {
  127. if (pan.view == self.leftTime ) {
  128. //裁剪的视频时长必须>=1秒
  129. if (pan.view.sl_x + transP.x < -10 || (self.rightTime.sl_x - self.boxInside.sl_x - transP.x)/self.sl_width*self.totalDuration < 1) {
  130. NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s");
  131. }else {
  132. self.boxInside.sl_x = self.boxInside.sl_x + transP.x;
  133. self.boxInside.sl_width = self.boxInside.sl_width - transP.x;
  134. pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y);
  135. }
  136. }else if (pan.view == self.rightTime) {
  137. 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) {
  138. NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s");
  139. }else {
  140. self.boxInside.sl_width = self.boxInside.sl_width + transP.x;
  141. pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y);
  142. }
  143. }
  144. 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));
  145. [self setNeedsDisplay];
  146. [pan setTranslation:CGPointZero inView:self];
  147. }else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateFailed || pan.state == UIGestureRecognizerStateCancelled) {
  148. if (pan.view == self.leftTime) {
  149. if (pan.view.sl_x < -10) {
  150. pan.view.sl_x = -10;
  151. }
  152. }else if (pan.view == self.rightTime) {
  153. if (pan.view.sl_x > self.sl_width) {
  154. pan.view.sl_x = self.sl_width;
  155. }
  156. }
  157. 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));
  158. }
  159. }
  160. @end
  161. @interface SLEditVideoClipping ()
  162. /// 视频帧图片解析器
  163. @property (nonatomic, strong) AVAssetImageGenerator *imageGenerator;
  164. /// 视频总时长
  165. @property (nonatomic, assign) double totalDuration;
  166. /// 图片帧 容器
  167. @property (nonatomic, strong) UIView *contentView;
  168. /// 视频裁剪框
  169. @property (nonatomic, strong) SLVideoClippingBox *clippingBox; //剪辑选择框
  170. @property (nonatomic, strong) UIButton *cancleBtn;
  171. @property (nonatomic, strong) UIButton *doneBtn;
  172. @property (nonatomic, assign) CMTime beginTime; //裁剪的开始
  173. @property (nonatomic, assign) CMTime endTime; //裁剪的结束
  174. @end
  175. @implementation SLEditVideoClipping
  176. #pragma mark - Override
  177. - (instancetype)initWithFrame:(CGRect)frame {
  178. self = [super initWithFrame:frame];
  179. if (self) {
  180. }
  181. return self;
  182. }
  183. - (void)didMoveToSuperview {
  184. [super didMoveToSuperview];
  185. if(self.superview == nil) {
  186. // _clippingBox = nil;
  187. [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
  188. }else {
  189. [self addSubview:self.contentView];
  190. [self addSubview:self.clippingBox];
  191. [self addSubview:self.cancleBtn];
  192. [self addSubview:self.doneBtn];
  193. [self generateImage];
  194. }
  195. }
  196. #pragma mark - Setter
  197. - (void)setAsset:(AVAsset *)asset {
  198. _asset = asset;
  199. _imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset];
  200. }
  201. #pragma mark - Getter
  202. - (UIView *)contentView {
  203. if (!_contentView) {
  204. _contentView = [[UIView alloc] initWithFrame:CGRectMake(40, 10, self.sl_width - 40 * 2, 50)];
  205. _contentView.backgroundColor = [UIColor blackColor];
  206. _contentView.layer.borderColor = [UIColor lightGrayColor].CGColor;
  207. _contentView.clipsToBounds = YES;
  208. }
  209. return _contentView;
  210. }
  211. - (UIView *)clippingBox {
  212. if (!_clippingBox) {
  213. _clippingBox = [[SLVideoClippingBox alloc] initWithFrame:self.contentView.frame];
  214. __weak typeof(self) weakSelf = self;
  215. _clippingBox.changeClippingRange = ^(SLVideoClippingState clippingState) {
  216. switch (clippingState.state) {
  217. case UIGestureRecognizerStateBegan:
  218. weakSelf.contentView.layer.borderWidth = 2;
  219. break;
  220. case UIGestureRecognizerStateChanged:{
  221. CMTime duration = weakSelf.asset.duration;
  222. if (clippingState.position) {
  223. weakSelf.endTime= CMTimeMakeWithSeconds(floor(weakSelf.totalDuration*clippingState.value), duration.timescale);
  224. weakSelf.selectedClippingEnd(weakSelf.beginTime, weakSelf.endTime, clippingState.state);
  225. }else {
  226. weakSelf.beginTime = CMTimeMakeWithSeconds(floor(weakSelf.totalDuration*clippingState.value), duration.timescale);
  227. weakSelf.selectedClippingBegin(weakSelf.beginTime, weakSelf.endTime,clippingState.state);
  228. }
  229. }
  230. break;
  231. case UIGestureRecognizerStateEnded:
  232. weakSelf.contentView.layer.borderWidth = 0;
  233. if (clippingState.position) {
  234. weakSelf.selectedClippingEnd(weakSelf.beginTime, weakSelf.endTime, clippingState.state);
  235. }else {
  236. weakSelf.selectedClippingBegin(weakSelf.beginTime, weakSelf.endTime,clippingState.state);
  237. }
  238. break;
  239. default:
  240. break;
  241. }
  242. };
  243. }
  244. return _clippingBox;
  245. }
  246. - (UIButton *)cancleBtn {
  247. if (_cancleBtn == nil) {
  248. _cancleBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, self.sl_height - 10 - 30, 30, 30)];
  249. [_cancleBtn setTitle:@"取消" forState:UIControlStateNormal];
  250. [_cancleBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
  251. _cancleBtn.titleLabel.font = [UIFont systemFontOfSize:14];
  252. [_cancleBtn addTarget:self action:@selector(cancleBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
  253. }
  254. return _cancleBtn;
  255. }
  256. - (UIButton *)doneBtn {
  257. if (_doneBtn == nil) {
  258. _doneBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_width - 30 - 10, self.sl_height - 10 - 30, 30, 30)];
  259. [_doneBtn setTitle:@"完成" forState:UIControlStateNormal];
  260. [_doneBtn setTitleColor:[UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1] forState:UIControlStateNormal];
  261. _doneBtn.titleLabel.font = [UIFont systemFontOfSize:14];
  262. [_doneBtn addTarget:self action:@selector(doneBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
  263. }
  264. return _doneBtn;
  265. }
  266. #pragma mark - Events Handle
  267. - (void)cancleBtnClicked:(UIButton *)btn {
  268. [self removeFromSuperview];
  269. self.exitClipping();
  270. }
  271. - (void)doneBtnClicked:(UIButton *)btn {
  272. [self removeFromSuperview];
  273. self.exitClipping();
  274. }
  275. - (void)generateImage {
  276. [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
  277. //最多10帧
  278. NSInteger maxImageCount = 10;
  279. NSArray *assetVideoTracks = [_asset tracksWithMediaType:AVMediaTypeVideo];
  280. //导出的视频帧图片大小 单位是px像素
  281. CGSize maximumSize = CGSizeMake(self.contentView.frame.size.height* [UIScreen mainScreen].scale, self.contentView.frame.size.height* [UIScreen mainScreen].scale);
  282. //图片视图大小
  283. CGSize imageViewSize = CGSizeMake(self.contentView.frame.size.height, self.contentView.frame.size.height);
  284. if (assetVideoTracks.count > 0) {
  285. AVAssetTrack *track = [assetVideoTracks firstObject];
  286. //像素
  287. CGSize size = CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform);
  288. CGSize dimensions = CGSizeMake(fabs(size.width), fabs(size.height));
  289. CGFloat height = self.contentView.frame.size.height * [UIScreen mainScreen].scale;
  290. maximumSize = CGSizeMake(dimensions.width/dimensions.height*height, height);
  291. }
  292. if (maxImageCount * maximumSize.width/[UIScreen mainScreen].scale < self.contentView.frame.size.width) {
  293. maxImageCount = self.contentView.frame.size.width * [UIScreen mainScreen].scale / maximumSize.width;
  294. self.contentView.sl_width = maxImageCount * maximumSize.width/[UIScreen mainScreen].scale;
  295. self.contentView.sl_x = (self.sl_width - self.contentView.sl_width)/2.0;
  296. self.clippingBox.frame = self.contentView.frame;
  297. imageViewSize = CGSizeMake(self.contentView.sl_height*maximumSize.width/maximumSize.height, self.contentView.sl_height);
  298. }else {
  299. imageViewSize = CGSizeMake(self.contentView.sl_width/maxImageCount, self.contentView.sl_height);
  300. }
  301. //视频帧大小 像素
  302. _imageGenerator.maximumSize = maximumSize;
  303. _imageGenerator.appliesPreferredTrackTransform = YES;
  304. CMTime duration = _asset.duration;
  305. self.totalDuration = CMTimeGetSeconds(duration);
  306. self.clippingBox.totalDuration = self.totalDuration;
  307. self.beginTime= kCMTimeZero;
  308. self.endTime = duration;
  309. NSInteger index = maxImageCount;
  310. CMTimeValue intervalSeconds = duration.value/index;
  311. CMTime time = CMTimeMake(0, duration.timescale);
  312. NSMutableArray *times = [NSMutableArray array];
  313. for (NSUInteger i = 0; i < index; i++) {
  314. [times addObject:[NSValue valueWithCMTime:time]];
  315. time = CMTimeAdd(time, CMTimeMake(intervalSeconds, duration.timescale));
  316. }
  317. //生成视帧图片
  318. [_imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime,
  319. CGImageRef cgImage,
  320. CMTime actualTime,
  321. AVAssetImageGeneratorResult result,
  322. NSError *error) {
  323. UIImage *image = nil;
  324. if (cgImage) {
  325. image = [[UIImage alloc] initWithCGImage:cgImage scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
  326. }
  327. dispatch_async(dispatch_get_main_queue(), ^{
  328. NSInteger imageIndex = [times indexOfObject:[NSValue valueWithCMTime:requestedTime]];
  329. UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
  330. imageView.layer.borderColor = [UIColor blackColor].CGColor;
  331. imageView.layer.borderWidth = .5f;
  332. imageView.frame = CGRectMake(imageIndex*imageViewSize.width, 0, imageViewSize.width, imageViewSize.height);
  333. imageView.contentMode = UIViewContentModeScaleAspectFill;
  334. imageView.clipsToBounds = YES;
  335. float proportion = imageViewSize.width/image.size.width/[UIScreen mainScreen].scale;
  336. imageView.layer.contentsRect = CGRectMake((1 - proportion)/2.0, 0, proportion, 1);
  337. [self.contentView addSubview:imageView];
  338. });
  339. }];
  340. }
  341. @end