HXPhotoTools.m 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. //
  2. // HXPhotoTools.m
  3. // HXPhotoPickerExample
  4. //
  5. // Created by Silence on 17/2/8.
  6. // Copyright © 2017年 Silence. All rights reserved.
  7. //
  8. #import "HXPhotoTools.h"
  9. #import "HXPhotoModel.h"
  10. #import "UIImage+HXExtension.h"
  11. #import "HXPhotoManager.h"
  12. #import <sys/utsname.h>
  13. #import <MobileCoreServices/MobileCoreServices.h>
  14. #import <AVFoundation/AVFoundation.h>
  15. #import <PhotosUI/PhotosUI.h>
  16. #if __has_include(<SDWebImage/UIImageView+WebCache.h>)
  17. #import <SDWebImage/UIImageView+WebCache.h>
  18. #import <SDWebImage/SDWebImageManager.h>
  19. #elif __has_include("UIImageView+WebCache.h")
  20. #import "UIImageView+WebCache.h"
  21. #import "SDWebImageManager.h"
  22. #endif
  23. #if __has_include(<YYWebImage/YYWebImage.h>)
  24. #import <YYWebImage/YYWebImage.h>
  25. #elif __has_include("YYWebImage.h")
  26. #import "YYWebImage.h"
  27. #elif __has_include(<YYKit/YYKit.h>)
  28. #import <YYKit/YYKit.h>
  29. #elif __has_include("YYKit.h")
  30. #import "YYKit.h"
  31. #endif
  32. #import "HXAssetManager.h"
  33. NSString *const hx_kFigAppleMakerNote_AssetIdentifier = @"17";
  34. NSString *const hx_kKeySpaceQuickTimeMetadata = @"mdta";
  35. NSString *const hx_kKeyStillImageTime = @"com.apple.quicktime.still-image-time";
  36. NSString *const hx_kKeyContentIdentifier = @"com.apple.quicktime.content.identifier";
  37. @implementation HXPhotoTools
  38. + (void)saveModelToCustomAlbumWithAlbumName:(NSString * _Nullable)albumName
  39. photoModel:(HXPhotoModel * _Nullable)photoModel
  40. location:(CLLocation * _Nullable)location
  41. complete:(void (^ _Nullable)(HXPhotoModel * _Nullable model, BOOL success))complete {
  42. if (photoModel.subType == HXPhotoModelMediaSubTypePhoto) {
  43. }else if (photoModel.subType == HXPhotoModelMediaSubTypeVideo) {
  44. }
  45. }
  46. /**
  47. 获取视频的时长
  48. */
  49. + (NSString *)transformVideoTimeToString:(NSTimeInterval)duration {
  50. NSInteger time = roundf(duration);
  51. NSString *newTime;
  52. if (time < 10) {
  53. newTime = [NSString stringWithFormat:@"00:0%zd",time];
  54. } else if (time < 60) {
  55. newTime = [NSString stringWithFormat:@"00:%zd",time];
  56. } else {
  57. NSInteger min = roundf(time / 60);
  58. NSInteger sec = time - (min * 60);
  59. if (sec < 10) {
  60. newTime = [NSString stringWithFormat:@"%zd:0%zd",min,sec];
  61. } else {
  62. newTime = [NSString stringWithFormat:@"%zd:%zd",min,sec];
  63. }
  64. }
  65. return newTime;
  66. }
  67. + (BOOL)assetIsHEIF:(PHAsset *)asset {
  68. if (!asset) return NO;
  69. __block BOOL isHEIF = NO;
  70. if (HX_IOS9Later) {
  71. NSArray *resourceList = [PHAssetResource assetResourcesForAsset:asset];
  72. [resourceList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  73. PHAssetResource *resource = obj;
  74. NSString *UTI = resource.uniformTypeIdentifier;
  75. if ([UTI isEqualToString:@"public.heif"] || [UTI isEqualToString:@"public.heic"]) {
  76. isHEIF = YES;
  77. *stop = YES;
  78. }
  79. }];
  80. } else {
  81. NSString *UTI = [asset valueForKey:@"uniformTypeIdentifier"];
  82. isHEIF = [UTI isEqualToString:@"public.heif"] || [UTI isEqualToString:@"public.heic"];
  83. }
  84. return isHEIF;
  85. }
  86. + (void)fetchPhotosBytes:(NSArray *)photos completion:(void (^)(NSString *))completion
  87. {
  88. __block NSInteger dataLength = 0;
  89. __block NSInteger assetCount = 0;
  90. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  91. for (int i = 0 ; i < photos.count ; i++) {
  92. HXPhotoModel *model = photos[i];
  93. if (model.type == HXPhotoModelMediaTypeCameraPhoto) {
  94. NSData *imageData;
  95. if (UIImagePNGRepresentation(model.thumbPhoto)) {
  96. //返回为png图像。
  97. imageData = UIImagePNGRepresentation(model.thumbPhoto);
  98. }else {
  99. //返回为JPEG图像。
  100. imageData = UIImageJPEGRepresentation(model.thumbPhoto, 1.0);
  101. }
  102. dataLength += imageData.length;
  103. assetCount ++;
  104. if (assetCount >= photos.count) {
  105. NSString *bytes = [self getBytesFromDataLength:dataLength];
  106. dispatch_async(dispatch_get_main_queue(), ^{
  107. if (completion) completion(bytes);
  108. });
  109. }
  110. }else {
  111. [model requestImageDataStartRequestICloud:nil progressHandler:nil success:^(NSData *imageData, UIImageOrientation orientation, HXPhotoModel *model, NSDictionary *info) {
  112. dataLength += imageData.length;
  113. assetCount ++;
  114. if (assetCount >= photos.count) {
  115. NSString *bytes = [self getBytesFromDataLength:dataLength];
  116. dispatch_async(dispatch_get_main_queue(), ^{
  117. if (completion) completion(bytes);
  118. });
  119. }
  120. } failed:^(NSDictionary *info, HXPhotoModel *model) {
  121. dataLength += 0;
  122. assetCount ++;
  123. if (assetCount >= photos.count) {
  124. NSString *bytes = [self getBytesFromDataLength:dataLength];
  125. dispatch_async(dispatch_get_main_queue(), ^{
  126. if (completion) completion(bytes);
  127. });
  128. }
  129. }];
  130. }
  131. }
  132. });
  133. }
  134. + (void)requestAuthorization:(UIViewController *)viewController
  135. handler:(void (^)(PHAuthorizationStatus status))handler {
  136. PHAuthorizationStatus status = [self authorizationStatus];
  137. if (status == PHAuthorizationStatusAuthorized) {
  138. if (handler) handler(status);
  139. }
  140. #ifdef __IPHONE_14_0
  141. else if (@available(iOS 14, *)) {
  142. if (status == PHAuthorizationStatusLimited) {
  143. if (handler) handler(status);
  144. }
  145. #endif
  146. else if (status == PHAuthorizationStatusDenied ||
  147. status == PHAuthorizationStatusRestricted) {
  148. if (handler) handler(status);
  149. [self showNoAuthorizedAlertWithViewController:viewController status:status];
  150. }else {
  151. #ifdef __IPHONE_14_0
  152. if (@available(iOS 14, *)) {
  153. [PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite handler:^(PHAuthorizationStatus status) {
  154. dispatch_async(dispatch_get_main_queue(), ^{
  155. if (handler) handler(status);
  156. if (status != PHAuthorizationStatusAuthorized &&
  157. status != PHAuthorizationStatusLimited) {
  158. [self showNoAuthorizedAlertWithViewController:viewController status:status];
  159. }else {
  160. [[NSNotificationCenter defaultCenter] postNotificationName:@"HXPhotoRequestAuthorizationCompletion" object:nil];
  161. }
  162. });
  163. }];
  164. }
  165. #else
  166. if (NO) {}
  167. #endif
  168. else {
  169. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  170. dispatch_async(dispatch_get_main_queue(), ^{
  171. if (handler) handler(status);
  172. if (status != PHAuthorizationStatusAuthorized) {
  173. [self showNoAuthorizedAlertWithViewController:viewController status:status];
  174. }else {
  175. [[NSNotificationCenter defaultCenter] postNotificationName:@"HXPhotoRequestAuthorizationCompletion" object:nil];
  176. }
  177. });
  178. }];
  179. }
  180. }
  181. #ifdef __IPHONE_14_0
  182. }else {
  183. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  184. dispatch_async(dispatch_get_main_queue(), ^{
  185. if (handler) handler(status);
  186. if (status != PHAuthorizationStatusAuthorized) {
  187. [self showNoAuthorizedAlertWithViewController:viewController status:status];
  188. }else {
  189. [[NSNotificationCenter defaultCenter] postNotificationName:@"HXPhotoRequestAuthorizationCompletion" object:nil];
  190. }
  191. });
  192. }];
  193. }
  194. #endif
  195. }
  196. + (void)showNoAuthorizedAlertWithViewController:(UIViewController *)viewController
  197. status:(PHAuthorizationStatus)status {
  198. if (!viewController) {
  199. return;
  200. }
  201. #ifdef __IPHONE_14_0
  202. if (@available(iOS 14, *)) {
  203. if (status == PHAuthorizationStatusLimited) {
  204. hx_showAlert(viewController, [NSBundle hx_localizedStringForKey:@"无法访问所有照片"], [NSBundle hx_localizedStringForKey:@"请在设置-隐私-相册中允许访问所有照片"], [NSBundle hx_localizedStringForKey:@"取消"], [NSBundle hx_localizedStringForKey:@"设置"], nil, ^{
  205. NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
  206. if (@available(iOS 10.0, *)) {
  207. [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
  208. }else {
  209. [[UIApplication sharedApplication] openURL:url];
  210. }
  211. });
  212. return;;
  213. }
  214. }
  215. #endif
  216. hx_showAlert(viewController, [NSBundle hx_localizedStringForKey:@"无法访问相册"], [NSBundle hx_localizedStringForKey:@"请在设置-隐私-相册中允许访问相册"], [NSBundle hx_localizedStringForKey:@"取消"], [NSBundle hx_localizedStringForKey:@"设置"], nil, ^{
  217. NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
  218. if (@available(iOS 10.0, *)) {
  219. [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
  220. }else {
  221. [[UIApplication sharedApplication] openURL:url];
  222. }
  223. });
  224. }
  225. + (PHAuthorizationStatus)authorizationStatus {
  226. PHAuthorizationStatus status;
  227. #ifdef __IPHONE_14_0
  228. if (@available(iOS 14, *)) {
  229. status = [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite];
  230. #else
  231. if(NO) {
  232. #endif
  233. }else {
  234. status = [PHPhotoLibrary authorizationStatus];
  235. }
  236. return status;
  237. }
  238. + (BOOL)authorizationStatusIsLimited {
  239. PHAuthorizationStatus status = [self authorizationStatus];
  240. #ifdef __IPHONE_14_0
  241. if (@available(iOS 14, *)) {
  242. if (status == PHAuthorizationStatusLimited) {
  243. return YES;
  244. }
  245. }
  246. #endif
  247. return NO;
  248. }
  249. + (void)showUnusableCameraAlert:(UIViewController *)vc {
  250. hx_showAlert(vc, [NSBundle hx_localizedStringForKey:@"无法使用相机"], [NSBundle hx_localizedStringForKey:@"请在设置-隐私-相机中允许访问相机"], [NSBundle hx_localizedStringForKey:@"取消"], [NSBundle hx_localizedStringForKey:@"设置"] , nil, ^{
  251. [self openSetting];
  252. });
  253. }
  254. + (void)openSetting {
  255. NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
  256. if (@available(iOS 10.0, *)) {
  257. [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
  258. }else {
  259. [[UIApplication sharedApplication] openURL:url];
  260. }
  261. }
  262. + (void)exportEditVideoForAVAsset:(AVAsset *)asset
  263. timeRange:(CMTimeRange)timeRange
  264. exportPreset:(HXVideoEditorExportPreset)exportPreset
  265. videoQuality:(NSInteger)videoQuality
  266. success:(void (^)(NSURL *))success
  267. failed:(void (^)(NSError *))failed {
  268. NSString *presetName;
  269. switch (exportPreset) {
  270. case HXVideoEditorExportPresetLowQuality:
  271. presetName = AVAssetExportPresetLowQuality;
  272. break;
  273. case HXVideoEditorExportPresetMediumQuality:
  274. presetName = AVAssetExportPresetMediumQuality;
  275. break;
  276. case HXVideoEditorExportPresetHighQuality:
  277. presetName = AVAssetExportPresetHighestQuality;
  278. break;
  279. case HXVideoEditorExportPresetRatio_640x480:
  280. presetName = AVAssetExportPreset640x480;
  281. break;
  282. case HXVideoEditorExportPresetRatio_960x540:
  283. presetName = AVAssetExportPreset960x540;
  284. break;
  285. case HXVideoEditorExportPresetRatio_1280x720:
  286. presetName = AVAssetExportPreset1280x720;
  287. break;
  288. default:
  289. presetName = AVAssetExportPresetMediumQuality;
  290. break;
  291. }
  292. NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
  293. if ([presets containsObject:presetName]) {
  294. AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
  295. NSTimeInterval videoTotalSeconds = CMTimeGetSeconds(videoTrack.timeRange.duration);
  296. NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);
  297. NSTimeInterval timeRangeDuration = CMTimeGetSeconds(timeRange.duration);
  298. if (startSeconds + timeRangeDuration > videoTotalSeconds) {
  299. timeRange = CMTimeRangeMake(timeRange.start, CMTimeMakeWithSeconds(videoTotalSeconds - startSeconds, timeRange.duration.timescale));
  300. }
  301. NSString *fileName = [[NSString hx_fileName] stringByAppendingString:@".mp4"];
  302. NSString *fullPathToFile = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
  303. NSURL *videoURL = [NSURL fileURLWithPath:fullPathToFile];
  304. AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:presetName];
  305. exportSession.outputURL = videoURL;
  306. NSArray *supportedTypeArray = exportSession.supportedFileTypes;
  307. if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
  308. exportSession.outputFileType = AVFileTypeMPEG4;
  309. }else if (supportedTypeArray.count == 0) {
  310. if (failed) {
  311. failed([NSError errorWithDomain:@"不支持导出该类型视频" code:-222 userInfo:nil]);
  312. }
  313. return;
  314. }else {
  315. exportSession.outputFileType = [supportedTypeArray objectAtIndex:0];
  316. }
  317. exportSession.timeRange = timeRange;
  318. exportSession.shouldOptimizeForNetworkUse = YES;
  319. if (videoQuality > 0) {
  320. exportSession.fileLengthLimit = [self exportSessionFileLengthLimitWithSeconds:CMTimeGetSeconds(asset.duration) exportPreset:exportPreset videoQuality:videoQuality];
  321. }
  322. [exportSession exportAsynchronouslyWithCompletionHandler:^{
  323. dispatch_async(dispatch_get_main_queue(), ^{
  324. if (exportSession.status == AVAssetExportSessionStatusCompleted) {
  325. if (success) {
  326. success(videoURL);
  327. }
  328. }else {
  329. if (failed) {
  330. failed(exportSession.error);
  331. }
  332. }
  333. });
  334. }];
  335. }else {
  336. if (failed) {
  337. failed([NSError errorWithDomain:[NSString stringWithFormat:@"该设备不支持:%@",presetName] code:-111 userInfo:nil]);
  338. }
  339. }
  340. }
  341. + (NSInteger)exportSessionFileLengthLimitWithSeconds:(CGFloat)seconds
  342. exportPreset:(HXVideoEditorExportPreset)exportPreset
  343. videoQuality:(NSInteger)videoQuality {
  344. if (videoQuality > 0) {
  345. CGFloat ratioParam = 0;
  346. if (exportPreset == HXVideoEditorExportPresetRatio_640x480) {
  347. ratioParam = 0.02;
  348. }else if (exportPreset == HXVideoEditorExportPresetRatio_960x540) {
  349. ratioParam = 0.04;
  350. }else if (exportPreset == HXVideoEditorExportPresetRatio_1280x720) {
  351. ratioParam = 0.08;
  352. }
  353. NSInteger quality = MIN(videoQuality, 10);
  354. return seconds * ratioParam * quality * 1000 * 1000;
  355. }
  356. return 0;
  357. }
  358. + (NSString *)getBytesFromDataLength:(NSUInteger)dataLength {
  359. NSString *bytes;
  360. if (dataLength >= 0.5 * (1024 * 1024)) {
  361. bytes = [NSString stringWithFormat:@"%0.1fM",dataLength/1024/1024.0];
  362. } else if (dataLength >= 1024) {
  363. bytes = [NSString stringWithFormat:@"%0.0fK",dataLength/1024.0];
  364. } else {
  365. bytes = [NSString stringWithFormat:@"%zdB",dataLength];
  366. }
  367. return bytes;
  368. }
  369. + (void)saveVideoToCustomAlbumWithName:(NSString *)albumName
  370. videoURL:(NSURL *)videoURL
  371. location:(CLLocation *)location
  372. complete:(void (^)(HXPhotoModel *model, BOOL success))complete {
  373. if (!videoURL) {
  374. if (complete) {
  375. complete(nil, NO);
  376. }
  377. return;
  378. }
  379. if (!albumName) {
  380. albumName = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
  381. }
  382. // 判断授权状态
  383. [self requestAuthorization:nil handler:^(PHAuthorizationStatus status) {
  384. dispatch_async(dispatch_get_main_queue(), ^{
  385. if (status == PHAuthorizationStatusNotDetermined ||
  386. status == PHAuthorizationStatusRestricted ||
  387. status == PHAuthorizationStatusDenied) {
  388. if (complete) {
  389. complete(nil, NO);
  390. }
  391. return;
  392. }
  393. if (!HX_IOS9Later) {
  394. if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([videoURL path])) {
  395. //保存相册
  396. UISaveVideoAtPathToSavedPhotosAlbum([videoURL path], nil, nil, nil);
  397. if (complete) {
  398. HXPhotoModel *photoModel = [HXPhotoModel photoModelWithVideoURL:videoURL];
  399. photoModel.creationDate = [NSDate date];
  400. photoModel.location = location;
  401. complete(photoModel, YES);
  402. }
  403. }else {
  404. if (complete) {
  405. complete(nil, NO);
  406. }
  407. }
  408. return;
  409. }
  410. NSError *error = nil;
  411. // 保存相片到相机胶卷
  412. __block PHObjectPlaceholder *createdAsset = nil;
  413. [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
  414. PHAssetCreationRequest *creationRequest = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:videoURL];
  415. creationRequest.creationDate = [NSDate date];
  416. creationRequest.location = location;
  417. createdAsset = creationRequest.placeholderForCreatedAsset;
  418. } error:&error];
  419. if (error) {
  420. if (complete) {
  421. complete(nil, NO);
  422. }
  423. if (HXShowLog) NSSLog(@"保存失败");
  424. return;
  425. }else {
  426. if (complete) {
  427. if (createdAsset.localIdentifier) {
  428. HXPhotoModel *photoModel = [HXPhotoModel photoModelWithPHAsset:[HXAssetManager fetchAssetWithLocalIdentifier:createdAsset.localIdentifier]];
  429. photoModel.videoURL = videoURL;
  430. photoModel.creationDate = [NSDate date];
  431. complete(photoModel, YES);
  432. }
  433. }
  434. }
  435. // 拿到自定义的相册对象
  436. PHAssetCollection *collection = [self createCollection:albumName];
  437. if (collection == nil) {
  438. if (HXShowLog) NSSLog(@"保存自定义相册失败");
  439. return;
  440. }
  441. [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
  442. [[PHAssetCollectionChangeRequest changeRequestForAssetCollection:collection] insertAssets:@[createdAsset] atIndexes:[NSIndexSet indexSetWithIndex:0]];
  443. } error:&error];
  444. if (error) {
  445. if (HXShowLog) NSSLog(@"保存自定义相册失败");
  446. } else {
  447. if (HXShowLog) NSSLog(@"保存自定义相册成功");
  448. }
  449. });
  450. }];
  451. }
  452. + (void)saveVideoToCustomAlbumWithName:(NSString *)albumName videoURL:(NSURL *)videoURL {
  453. [self saveVideoToCustomAlbumWithName:albumName videoURL:videoURL location:nil complete:nil];
  454. }
  455. + (void)savePhotoToCustomAlbumWithName:(NSString *)albumName
  456. photo:(UIImage *)photo
  457. location:(CLLocation *)location
  458. complete:(void (^)(HXPhotoModel *model, BOOL success))complete {
  459. if (!photo) {
  460. if (complete) {
  461. complete(nil, NO);
  462. }
  463. return;
  464. }
  465. if (photo.imageOrientation != UIImageOrientationUp) {
  466. photo = [photo hx_normalizedImage];
  467. }
  468. if (!albumName) {
  469. albumName = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
  470. }
  471. // 判断授权状态
  472. [self requestAuthorization:nil handler:^(PHAuthorizationStatus status) {
  473. dispatch_async(dispatch_get_main_queue(), ^{
  474. if (status == PHAuthorizationStatusNotDetermined ||
  475. status == PHAuthorizationStatusRestricted ||
  476. status == PHAuthorizationStatusDenied) {
  477. if (complete) {
  478. complete(nil, NO);
  479. }
  480. return;
  481. }
  482. if (!HX_IOS9Later) {
  483. UIImageWriteToSavedPhotosAlbum(photo, nil, nil, nil);
  484. if (complete) {
  485. HXPhotoModel *photoModel = [HXPhotoModel photoModelWithImage:photo];
  486. photoModel.creationDate = [NSDate date];
  487. photoModel.location = location;
  488. complete(photoModel, YES);
  489. }
  490. return;
  491. }
  492. NSError *error = nil;
  493. // 保存相片到相机胶卷
  494. __block PHObjectPlaceholder *createdAsset = nil;
  495. [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
  496. PHAssetCreationRequest *creationRequest = [PHAssetCreationRequest creationRequestForAssetFromImage:photo];
  497. creationRequest.creationDate = [NSDate date];
  498. creationRequest.location = location;
  499. createdAsset = creationRequest.placeholderForCreatedAsset;
  500. } error:&error];
  501. if (error) {
  502. if (complete) {
  503. complete(nil, NO);
  504. }
  505. if (HXShowLog) NSSLog(@"保存失败");
  506. return;
  507. }else {
  508. if (complete) {
  509. if (createdAsset.localIdentifier) {
  510. HXPhotoModel *photoModel = [HXPhotoModel photoModelWithPHAsset:[HXAssetManager fetchAssetWithLocalIdentifier:createdAsset.localIdentifier]];
  511. photoModel.thumbPhoto = photo;
  512. photoModel.previewPhoto = photo;
  513. photoModel.location = location;
  514. photoModel.creationDate = [NSDate date];
  515. complete(photoModel, YES);
  516. }
  517. }
  518. }
  519. // 拿到自定义的相册对象
  520. PHAssetCollection *collection = [self createCollection:albumName];
  521. if (collection == nil) {
  522. if (HXShowLog) NSSLog(@"创建自定义相册失败");
  523. return;
  524. }
  525. [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
  526. [[PHAssetCollectionChangeRequest changeRequestForAssetCollection:collection] insertAssets:@[createdAsset] atIndexes:[NSIndexSet indexSetWithIndex:0]];
  527. } error:&error];
  528. if (error != nil) {
  529. if (HXShowLog) NSSLog(@"保存自定义相册失败");
  530. } else {
  531. if (HXShowLog) NSSLog(@"保存自定义相册成功");
  532. }
  533. });
  534. }];
  535. }
  536. + (void)savePhotoToCustomAlbumWithName:(NSString *)albumName photo:(UIImage *)photo {
  537. [self savePhotoToCustomAlbumWithName:albumName photo:photo location:nil complete:nil];
  538. }
  539. // 创建自己要创建的自定义相册
  540. + (PHAssetCollection * )createCollection:(NSString *)albumName {
  541. NSString * title = albumName;
  542. PHFetchResult<PHAssetCollection *> *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
  543. PHAssetCollection * createCollection = nil;
  544. for (PHAssetCollection * collection in collections) {
  545. if ([collection.localizedTitle isEqualToString:title]) {
  546. createCollection = collection;
  547. break;
  548. }
  549. }
  550. if (createCollection == nil) {
  551. NSError * error1 = nil;
  552. __block NSString * createCollectionID = nil;
  553. [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
  554. createCollectionID = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title].placeholderForCreatedAssetCollection.localIdentifier;
  555. } error:&error1];
  556. if (error1) {
  557. if (HXShowLog) NSSLog(@"创建相册失败...");
  558. return nil;
  559. }
  560. // 创建相册之后我们还要获取此相册 因为我们要往进存储相片
  561. createCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createCollectionID] options:nil].firstObject;
  562. }
  563. return createCollection;
  564. }
  565. + (BOOL)isIphone6 {
  566. struct utsname systemInfo;
  567. uname(&systemInfo);
  568. NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
  569. if([platform isEqualToString:@"iPhone7,2"]){//6
  570. return YES;
  571. }
  572. if ([platform isEqualToString:@"iPhone8,1"]) {//6s
  573. return YES;
  574. }
  575. if([platform isEqualToString:@"iPhone9,1"] || [platform isEqualToString:@"iPhone9,3"]) {//7
  576. return YES;
  577. }
  578. if([platform isEqualToString:@"iPhone10,1"] || [platform isEqualToString:@"iPhone10,4"]) {//8
  579. return YES;
  580. }
  581. return NO;
  582. }
  583. + (BOOL)platform {
  584. struct utsname systemInfo;
  585. uname(&systemInfo);
  586. NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
  587. BOOL have = NO;
  588. if ([platform isEqualToString:@"iPhone8,1"]) { // iphone6s
  589. have = YES;
  590. }else if ([platform isEqualToString:@"iPhone8,2"]) { // iphone6s plus
  591. have = YES;
  592. }else if ([platform isEqualToString:@"iPhone9,1"]) { // iphone7
  593. have = YES;
  594. }else if ([platform isEqualToString:@"iPhone9,2"]) { // iphone7 plus
  595. have = YES;
  596. }else if ([platform isEqualToString:@"iPhone10,1"]) { // iphone7 plus
  597. have = YES;
  598. }else if ([platform isEqualToString:@"iPhone10,2"]) { // iphone7 plus
  599. have = YES;
  600. }else if ([platform isEqualToString:@"iPhone10,3"]) { // iphone7 plus
  601. have = YES;
  602. }else if ([platform isEqualToString:@"iPhone10,4"]) { // iphone7 plus
  603. have = YES;
  604. }else if ([platform isEqualToString:@"iPhone10,5"]) { // iphone7 plus
  605. have = YES;
  606. }else if ([platform isEqualToString:@"iPhone10,6"]) { // iphone7 plus
  607. have = YES;
  608. }
  609. return have;
  610. }
  611. + (BOOL)isIphone12Mini {
  612. struct utsname systemInfo;
  613. uname(&systemInfo);
  614. NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
  615. if([platform isEqualToString:@"iPhone13,1"]) {
  616. return YES;
  617. }else if ([platform isEqualToString:@"x86_64"] || [platform isEqualToString:@"i386"]) {
  618. if (([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) && !HX_UI_IS_IPAD : NO)) {
  619. return YES;
  620. }
  621. }
  622. return NO;
  623. }
  624. + (BOOL)isRTLLanguage
  625. {
  626. return [NSLocale characterDirectionForLanguage:[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]] == NSLocaleLanguageDirectionRightToLeft;
  627. }
  628. + (BOOL)fileExistsAtVideoURL:(NSURL *)videoURL {
  629. if (!videoURL) {
  630. return NO;
  631. }
  632. NSString * downloadPath = HXPhotoPickerDownloadVideosPath;
  633. NSFileManager * fileManager = [NSFileManager defaultManager];
  634. NSString *fullPathToFile = [self getVideoURLFilePath:videoURL];
  635. if (![fileManager fileExistsAtPath:downloadPath]) {
  636. [fileManager createDirectoryAtPath:downloadPath withIntermediateDirectories:YES attributes:nil error:nil];
  637. }else {
  638. if ([fileManager fileExistsAtPath:fullPathToFile]) {
  639. return YES;
  640. }
  641. }
  642. return NO;
  643. }
  644. + (BOOL)fileExistsAtImageURL:(NSURL *)ImageURL {
  645. if (!ImageURL) {
  646. return NO;
  647. }
  648. NSString * downloadPath = HXPhotoPickerDownloadPhotosPath;
  649. NSFileManager * fileManager = [NSFileManager defaultManager];
  650. NSString *fullPathToFile = [self getImageURLFilePath:ImageURL];
  651. if (![fileManager fileExistsAtPath:downloadPath]) {
  652. [fileManager createDirectoryAtPath:downloadPath withIntermediateDirectories:YES attributes:nil error:nil];
  653. }else {
  654. if ([fileManager fileExistsAtPath:fullPathToFile]) {
  655. return YES;
  656. }
  657. }
  658. return NO;
  659. }
  660. + (BOOL)fileExistsAtLivePhotoVideoURL:(NSURL *)videoURL {
  661. if (!videoURL) {
  662. return NO;
  663. }
  664. NSString * downloadPath = HXPhotoPickerLivePhotoVideosPath;
  665. NSFileManager * fileManager = [NSFileManager defaultManager];
  666. NSString *fullPathToFile = [[self getLivePhotoVideoURLFilePath:videoURL] stringByAppendingString:@".mov"];
  667. if (![fileManager fileExistsAtPath:downloadPath]) {
  668. [fileManager createDirectoryAtPath:downloadPath withIntermediateDirectories:YES attributes:nil error:nil];
  669. }else {
  670. if ([fileManager fileExistsAtPath:fullPathToFile]) {
  671. return YES;
  672. }
  673. }
  674. return NO;
  675. }
  676. + (BOOL)fileExistsAtLivePhotoImageURL:(NSURL *)ImageURL {
  677. if (!ImageURL) {
  678. return NO;
  679. }
  680. NSString * downloadPath = HXPhotoPickerLivePhotoImagesPath;
  681. NSFileManager * fileManager = [NSFileManager defaultManager];
  682. NSString *fullPathToFile = [[self getLivePhotoImageURLFilePath:ImageURL] stringByAppendingString:@".jpg"];
  683. if (![fileManager fileExistsAtPath:downloadPath]) {
  684. [fileManager createDirectoryAtPath:downloadPath withIntermediateDirectories:YES attributes:nil error:nil];
  685. }else {
  686. if ([fileManager fileExistsAtPath:fullPathToFile]) {
  687. return YES;
  688. }
  689. }
  690. return NO;
  691. }
  692. + (NSString *)getLivePhotoVideoURLFilePath:(NSURL *)videoURL {
  693. if (!videoURL) {
  694. return nil;
  695. }
  696. NSString *fileName = HXDiskCacheFileNameForKey(videoURL.lastPathComponent, NO);
  697. // NSString * fileName = [videoURL.lastPathComponent stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
  698. // fileName = [fileName stringByReplacingOccurrencesOfString:@"." withString:@"_"];
  699. NSString *fullPathToFile = [HXPhotoPickerLivePhotoVideosPath stringByAppendingPathComponent:fileName];
  700. return fullPathToFile;
  701. }
  702. + (NSString *)getLivePhotoImageURLFilePath:(NSURL *)imageURL {
  703. if (!imageURL) {
  704. return nil;
  705. }
  706. NSString *fileName = HXDiskCacheFileNameForKey(imageURL.lastPathComponent, NO);
  707. // NSString * fileName = [imageURL.lastPathComponent stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
  708. // fileName = [fileName stringByReplacingOccurrencesOfString:@"." withString:@"_"];
  709. NSString *fullPathToFile = [HXPhotoPickerLivePhotoImagesPath stringByAppendingPathComponent:fileName];
  710. return fullPathToFile;
  711. }
  712. + (NSString *)getImageURLFilePath:(NSURL *)imageURL {
  713. if (!imageURL) {
  714. return nil;
  715. }
  716. NSString *fileName = HXDiskCacheFileNameForKey(imageURL.absoluteString, YES);
  717. // NSString * fileName = [imageURL.lastPathComponent stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
  718. NSString *fullPathToFile = [HXPhotoPickerDownloadPhotosPath stringByAppendingPathComponent:fileName];
  719. return fullPathToFile;
  720. }
  721. + (NSString *)getVideoURLFilePath:(NSURL *)videoURL {
  722. if (!videoURL) {
  723. return nil;
  724. }
  725. NSString *fileName = HXDiskCacheFileNameForKey(videoURL.absoluteString, YES);
  726. // NSString * fileName = [videoURL.lastPathComponent stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
  727. NSString *fullPathToFile = [HXPhotoPickerDownloadVideosPath stringByAppendingPathComponent:fileName];
  728. return fullPathToFile;
  729. }
  730. + (void)downloadImageWithURL:(NSURL *)URL completed:(void (^)(UIImage * image, NSError * error))completedBlock {
  731. #if HasSDWebImage
  732. NSString *cacheKey = [[SDWebImageManager sharedManager] cacheKeyForURL:URL];
  733. [[SDWebImageManager sharedManager].imageCache queryImageForKey:cacheKey options:SDWebImageQueryMemoryData context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  734. if (image) {
  735. if (completedBlock) {
  736. completedBlock(image, nil);
  737. }
  738. }else {
  739. NSURL *url = URL;
  740. [[SDWebImageManager sharedManager] loadImageWithURL:url options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  741. if (completedBlock) {
  742. completedBlock(image,error);
  743. }
  744. }];
  745. }
  746. }];
  747. #elif HasYYKitOrWebImage
  748. YYWebImageManager *manager = [YYWebImageManager sharedManager];
  749. [manager.cache getImageForKey:[manager cacheKeyForURL:URL] withType:YYImageCacheTypeAll withBlock:^(UIImage * _Nullable image, YYImageCacheType type) {
  750. if (image) {
  751. if (completedBlock) {
  752. completedBlock(image, nil);
  753. }
  754. }else {
  755. [manager requestImageWithURL:URL options:kNilOptions progress:nil transform:^UIImage * _Nullable(UIImage * _Nonnull image, NSURL * _Nonnull url) {
  756. return image;
  757. } completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
  758. if (completedBlock) {
  759. completedBlock(image, error);
  760. }
  761. }];
  762. }
  763. }];
  764. #else
  765. NSAssert(NO, @"请导入YYWebImage/SDWebImage后再使用网络图片功能,HXPhotoPicker为pod导入的那么YY或者SD也必须是pod导入的否则会找不到");
  766. #endif
  767. }
  768. + (AVAssetWriterInputMetadataAdaptor *)metadataSetAdapter {
  769. NSString *identifier = [hx_kKeySpaceQuickTimeMetadata stringByAppendingFormat:@"/%@",hx_kKeyStillImageTime];
  770. const NSDictionary *spec = @{(__bridge_transfer NSString*)kCMMetadataFormatDescriptionMetadataSpecificationKey_Identifier :
  771. identifier,
  772. (__bridge_transfer NSString*)kCMMetadataFormatDescriptionMetadataSpecificationKey_DataType :
  773. @"com.apple.metadata.datatype.int8"
  774. };
  775. CMFormatDescriptionRef desc;
  776. CMMetadataFormatDescriptionCreateWithMetadataSpecifications(kCFAllocatorDefault, kCMMetadataFormatType_Boxed, (__bridge CFArrayRef)@[spec], &desc);
  777. AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeMetadata outputSettings:nil sourceFormatHint:desc];
  778. CFRelease(desc);
  779. return [AVAssetWriterInputMetadataAdaptor assetWriterInputMetadataAdaptorWithAssetWriterInput:input];
  780. }
  781. + (void)writeToFileWithOriginJPGPath:(NSURL *)originJPGPath
  782. TargetWriteFilePath:(NSURL *)finalJPGPath
  783. completion:(void (^ _Nullable)(BOOL success))completion {
  784. NSString * assetIdentifier = [HXPhotoCommon photoCommon].UUIDString;
  785. CGImageDestinationRef dest = CGImageDestinationCreateWithURL((CFURLRef)finalJPGPath, kUTTypeJPEG, 1, nil);
  786. if (!dest) {
  787. if (completion) {
  788. completion(NO);
  789. }
  790. return;
  791. }
  792. CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((CFDataRef)[NSData dataWithContentsOfFile:originJPGPath.path], nil);
  793. if (!imageSourceRef) {
  794. if (dest) {
  795. CFRelease(dest);
  796. }
  797. if (completion) {
  798. completion(NO);
  799. }
  800. return;
  801. }
  802. NSMutableDictionary *metaData = [(__bridge_transfer NSDictionary*)CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, nil) mutableCopy];
  803. if (!metaData) {
  804. if (dest) {
  805. CFRelease(dest);
  806. }
  807. if (imageSourceRef) {
  808. CFRelease(imageSourceRef);
  809. }
  810. if (completion) {
  811. completion(NO);
  812. }
  813. return;
  814. }
  815. NSMutableDictionary *makerNote = [NSMutableDictionary dictionary];
  816. [makerNote setValue:assetIdentifier forKey:hx_kFigAppleMakerNote_AssetIdentifier];
  817. [metaData setValue:makerNote forKey:(__bridge_transfer NSString*)kCGImagePropertyMakerAppleDictionary];
  818. CGImageDestinationAddImageFromSource(dest, imageSourceRef, 0, (CFDictionaryRef)metaData);
  819. CGImageDestinationFinalize(dest);
  820. CFRelease(imageSourceRef);
  821. if (dest) {
  822. CFRelease(dest);
  823. }
  824. if (completion) {
  825. completion(YES);
  826. }
  827. }
  828. + (void)writeToFileWithOriginMovPath:(NSURL *)originMovPath
  829. TargetWriteFilePath:(NSURL *)finalMovPath
  830. header:(void (^ _Nullable)(AVAssetWriter *, AVAssetReader *, AVAssetReader *))header
  831. completion:(void (^ _Nullable)(BOOL))completion{
  832. NSString * assetIdentifier = [HXPhotoCommon photoCommon].UUIDString;
  833. AVURLAsset* asset = [AVURLAsset assetWithURL:originMovPath];
  834. AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
  835. if (!videoTrack) {
  836. if (completion) {
  837. completion(NO);
  838. }
  839. return;
  840. }
  841. AVAssetReaderOutput *videoOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:@{(__bridge_transfer NSString*)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]}];
  842. NSError *error;
  843. AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:asset error:&error];
  844. if([reader canAddOutput:videoOutput]) {
  845. [reader addOutput:videoOutput];
  846. } else {
  847. NSSLog(@"Add video output error\n");
  848. }
  849. NSString *videoCodeec;
  850. if (@available(iOS 11.0, *)) {
  851. videoCodeec = AVVideoCodecTypeH264;
  852. } else {
  853. videoCodeec = AVVideoCodecH264;
  854. }
  855. NSDictionary * outputSetting = @{AVVideoCodecKey: videoCodeec,
  856. AVVideoWidthKey: [NSNumber numberWithFloat:videoTrack.naturalSize.width],
  857. AVVideoHeightKey: [NSNumber numberWithFloat:videoTrack.naturalSize.height]
  858. };
  859. NSError *error_two;
  860. AVAssetWriter *writer = [AVAssetWriter assetWriterWithURL:finalMovPath fileType:AVFileTypeQuickTimeMovie error:&error_two];
  861. if(error_two) {
  862. NSSLog(@"CreateWriterError:%@\n",error_two);
  863. }
  864. writer.metadata = @[ [self metaDataSet:assetIdentifier]];
  865. AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSetting];
  866. videoInput.expectsMediaDataInRealTime = YES;
  867. videoInput.transform = videoTrack.preferredTransform;
  868. if ([writer canAddInput:videoInput]) {
  869. [writer addInput:videoInput];
  870. }
  871. AVAssetWriterInput *audioInput;
  872. AVAssetReaderTrackOutput *audioOutput;
  873. AVAssetReader *audioReader;
  874. AVAsset *aAudioAsset = [AVAsset assetWithURL:originMovPath];
  875. if (aAudioAsset.tracks.count > 1) {
  876. NSSLog(@"Has Audio");
  877. // setup audio writer
  878. audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:nil];
  879. audioInput.expectsMediaDataInRealTime = NO;
  880. if ([writer canAddInput:audioInput]) {
  881. [writer addInput:audioInput];
  882. }
  883. // setup audio reader
  884. AVAssetTrack *audioTrack = [aAudioAsset tracksWithMediaType:AVMediaTypeAudio].firstObject;
  885. audioOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:audioTrack outputSettings:nil];
  886. NSError *audioReaderError = nil;
  887. audioReader = [AVAssetReader assetReaderWithAsset:aAudioAsset error:&audioReaderError];
  888. if (audioReaderError) {
  889. NSSLog(@"Unable to read Asset, error: %@",audioReaderError);
  890. }
  891. if ([audioReader canAddOutput:audioOutput]) {
  892. [audioReader addOutput:audioOutput];
  893. } else {
  894. NSSLog(@"cant add audio reader");
  895. }
  896. }
  897. AVAssetWriterInputMetadataAdaptor *adapter = [self metadataSetAdapter];
  898. [writer addInput:adapter.assetWriterInput];
  899. [writer startWriting];
  900. [reader startReading];
  901. [writer startSessionAtSourceTime:kCMTimeZero];
  902. if (header) {
  903. header(writer, reader, audioReader);
  904. }
  905. CMTimeRange dummyTimeRange = CMTimeRangeMake(CMTimeMake(0, 1000), CMTimeMake(200, 3000));
  906. //Meta data reset:
  907. AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
  908. item.key = hx_kKeyStillImageTime;
  909. item.keySpace = hx_kKeySpaceQuickTimeMetadata;
  910. item.value = [NSNumber numberWithInt:0];
  911. item.dataType = @"com.apple.metadata.datatype.int8";
  912. [adapter appendTimedMetadataGroup:[[AVTimedMetadataGroup alloc] initWithItems:[NSArray arrayWithObject:item] timeRange:dummyTimeRange]];
  913. dispatch_queue_t createMovQueue = dispatch_queue_create("createMovQueue", DISPATCH_QUEUE_SERIAL);
  914. [videoInput requestMediaDataWhenReadyOnQueue:createMovQueue usingBlock:^{
  915. while ([videoInput isReadyForMoreMediaData]) {
  916. if (reader.status == AVAssetReaderStatusReading) {
  917. CMSampleBufferRef videoBuffer = [videoOutput copyNextSampleBuffer];
  918. if (videoBuffer) {
  919. if (![videoInput appendSampleBuffer:videoBuffer]) {
  920. [reader cancelReading];
  921. }
  922. CFRelease(videoBuffer);
  923. videoBuffer = nil;
  924. }else {
  925. [videoInput markAsFinished];
  926. if (reader.status == AVAssetReaderStatusCompleted && audioInput) {
  927. [audioReader startReading];
  928. [writer startSessionAtSourceTime:kCMTimeZero];
  929. [audioInput requestMediaDataWhenReadyOnQueue:createMovQueue usingBlock:^{
  930. while ([audioInput isReadyForMoreMediaData]) {
  931. CMSampleBufferRef audioBuffer = [audioOutput copyNextSampleBuffer];
  932. if (audioBuffer) {
  933. if (![audioInput appendSampleBuffer:audioBuffer]) {
  934. [audioReader cancelReading];
  935. }
  936. CFRelease(audioBuffer);
  937. audioBuffer = nil;
  938. }else {
  939. [audioInput markAsFinished];
  940. [writer finishWritingWithCompletionHandler:^{
  941. }];
  942. break;
  943. }
  944. }
  945. }];
  946. }else {
  947. [writer finishWritingWithCompletionHandler:^{
  948. }];
  949. }
  950. break;
  951. }
  952. }else {
  953. [writer finishWritingWithCompletionHandler:^{
  954. }];
  955. }
  956. }
  957. }];
  958. while (writer.status == AVAssetWriterStatusWriting) {
  959. @autoreleasepool {
  960. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
  961. }
  962. }
  963. if (writer.status == AVAssetWriterStatusCancelled ||
  964. writer.status == AVAssetWriterStatusFailed) {
  965. [[NSFileManager defaultManager] removeItemAtURL:finalMovPath error:nil];
  966. }
  967. if (writer.error) {
  968. if (completion) {
  969. completion(NO);
  970. }
  971. NSSLog(@"cannot write: %@", writer.error);
  972. } else {
  973. if (completion) {
  974. completion(YES);
  975. }
  976. }
  977. }
  978. + (AVMetadataItem *)metaDataSet:(NSString *)assetIdentifier {
  979. AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
  980. item.key = hx_kKeyContentIdentifier;
  981. item.keySpace = hx_kKeySpaceQuickTimeMetadata;
  982. item.value = assetIdentifier;
  983. item.dataType = @"com.apple.metadata.datatype.UTF-8";
  984. return item;
  985. }
  986. + (long long)fileSizeAtPath:(NSString*)filePath {
  987. NSFileManager* manager = [NSFileManager defaultManager];
  988. if ([manager fileExistsAtPath:filePath]){
  989. return [[manager attributesOfItemAtPath:filePath error:nil] fileSize];
  990. }
  991. return 0;
  992. }
  993. + (long long)folderSizeAtPath:(NSString *)folderPath {
  994. NSFileManager* manager = [NSFileManager defaultManager];
  995. if (![manager fileExistsAtPath:folderPath]) return 0;
  996. NSEnumerator *childFilesEnumerator = [[manager subpathsAtPath:folderPath] objectEnumerator];
  997. NSString* fileName;
  998. long long folderSize = 0;
  999. while ((fileName = [childFilesEnumerator nextObject]) != nil) {
  1000. NSString* fileAbsolutePath = [folderPath stringByAppendingPathComponent:fileName];
  1001. folderSize += [self fileSizeAtPath:fileAbsolutePath];
  1002. }
  1003. return folderSize;
  1004. }
  1005. /// 获取所有缓存的大小
  1006. + (long long)getAllLocalFileSize {
  1007. return [self folderSizeAtPath:HXPhotoPickerAssetCachesPath];
  1008. }
  1009. /// 获取缓存在本地所有的HXPhotoModel的大小
  1010. + (long long)getAllLocalModelsFileSize{
  1011. return [self folderSizeAtPath:HXPhotoPickerLocalModelsPath];
  1012. }
  1013. /// 获取生成LivePhoto缓存在本地的图片视频大小
  1014. + (long long)getLivePhotoAssetFileSize {
  1015. return [self folderSizeAtPath:HXPhotoPickerCachesLivePhotoPath];
  1016. }
  1017. /// 获取下载网络视频缓存的大小
  1018. + (long long)getNetWorkVideoFileSize {
  1019. return [self folderSizeAtPath:HXPhotoPickerCachesDownloadPath];
  1020. }
  1021. /// 删除HXPhotoPicker所有文件
  1022. + (void)deleteAllLocalFile {
  1023. [[NSFileManager defaultManager] removeItemAtPath:HXPhotoPickerAssetCachesPath error:nil];
  1024. }
  1025. /// 删除本地HXPhotoModel缓存文件
  1026. + (void)deleteAllLocalModelsFile {
  1027. [[NSFileManager defaultManager] removeItemAtPath:HXPhotoPickerLocalModelsPath error:nil];
  1028. }
  1029. /// 删除生成LivePhoto相关的缓存文件
  1030. + (void)deleteLivePhotoCachesFile {
  1031. [[NSFileManager defaultManager] removeItemAtPath:HXPhotoPickerCachesLivePhotoPath error:nil];
  1032. }
  1033. /// 删除下载的网络视频缓存文件
  1034. + (void)deleteNetWorkVideoFile {
  1035. [[NSFileManager defaultManager] removeItemAtPath:HXPhotoPickerCachesDownloadPath error:nil];
  1036. }
  1037. + (CGFloat)getStatusBarHeight {
  1038. CGFloat statusBarHeight = 0;
  1039. if (@available(iOS 13.0, *)) {
  1040. UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].windows.firstObject.windowScene.statusBarManager;
  1041. statusBarHeight = statusBarManager.statusBarFrame.size.height;
  1042. if ([HXPhotoTools isIphone12Mini]) {
  1043. statusBarHeight = 50;
  1044. }else {
  1045. if ([UIApplication sharedApplication].statusBarHidden) {
  1046. statusBarHeight = HX_IS_IPhoneX_All ? 44: 20;
  1047. }
  1048. }
  1049. }
  1050. else {
  1051. if ([UIApplication sharedApplication].statusBarHidden) {
  1052. statusBarHeight = HX_IS_IPhoneX_All ? 44: 20;
  1053. }else {
  1054. statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
  1055. }
  1056. }
  1057. return statusBarHeight;
  1058. }
  1059. @end