NCCameraRoll.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. //
  2. // NCCameraRoll.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 21/12/22.
  6. // Copyright © 2022 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import Foundation
  24. import NextcloudKit
  25. import JGProgressHUD
  26. class NCCameraRoll: NSObject {
  27. func extractCameraRoll(from metadata: tableMetadata, viewController: UIViewController?, hud: JGProgressHUD, completition: @escaping (_ metadatas: [tableMetadata]) -> Void) {
  28. let chunckSize = CCUtility.getChunkSize() * 1000000
  29. var metadatas: [tableMetadata] = []
  30. let metadataSource = tableMetadata.init(value: metadata)
  31. guard !metadata.isExtractFile else { return completition([metadataSource]) }
  32. guard !metadataSource.assetLocalIdentifier.isEmpty else {
  33. let filePath = CCUtility.getDirectoryProviderStorageOcId(metadataSource.ocId, fileNameView: metadataSource.fileName)!
  34. metadataSource.size = NCUtilityFileSystem.shared.getFileSize(filePath: filePath)
  35. let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadataSource.fileNameView, mimeType: metadataSource.contentType, directory: false)
  36. metadataSource.contentType = results.mimeType
  37. metadataSource.iconName = results.iconName
  38. metadataSource.classFile = results.classFile
  39. if let date = NCUtilityFileSystem.shared.getFileCreationDate(filePath: filePath) {
  40. metadataSource.creationDate = date
  41. }
  42. if let date = NCUtilityFileSystem.shared.getFileModificationDate(filePath: filePath) {
  43. metadataSource.date = date
  44. }
  45. metadataSource.chunk = chunckSize != 0 && metadata.size > chunckSize
  46. metadataSource.isExtractFile = true
  47. if let metadata = NCManageDatabase.shared.addMetadata(metadataSource) {
  48. metadatas.append(metadata)
  49. }
  50. return completition(metadatas)
  51. }
  52. extractImageVideoFromAssetLocalIdentifier(metadata: metadataSource, modifyMetadataForUpload: true, viewController: viewController, hud: hud) { metadata, fileNamePath, error in
  53. if let metadata = metadata, let fileNamePath = fileNamePath, !error {
  54. metadatas.append(metadata)
  55. let toPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
  56. NCUtilityFileSystem.shared.moveFile(atPath: fileNamePath, toPath: toPath)
  57. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadataSource.assetLocalIdentifier], options: nil)
  58. if metadata.livePhoto, fetchAssets.count > 0 {
  59. self.createMetadataLivePhoto(metadata: metadata, asset: fetchAssets.firstObject) { metadata in
  60. if let metadata = metadata, let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  61. metadatas.append(metadata)
  62. }
  63. completition(metadatas)
  64. }
  65. } else {
  66. completition(metadatas)
  67. }
  68. } else {
  69. completition(metadatas)
  70. }
  71. }
  72. }
  73. func extractImageVideoFromAssetLocalIdentifier(metadata: tableMetadata,
  74. modifyMetadataForUpload: Bool,
  75. viewController: UIViewController?,
  76. hud: JGProgressHUD,
  77. completion: @escaping (_ metadata: tableMetadata?, _ fileNamePath: String?, _ error: Bool) -> Void) {
  78. var fileNamePath: String?
  79. let metadata = tableMetadata.init(value: metadata)
  80. let chunckSize = CCUtility.getChunkSize() * 1000000
  81. var compatibilityFormat: Bool = false
  82. func callCompletionWithError(_ error: Bool = true) {
  83. if error {
  84. completion(nil, nil, true)
  85. } else {
  86. var metadataReturn = metadata
  87. if modifyMetadataForUpload {
  88. metadata.chunk = chunckSize != 0 && metadata.size > chunckSize
  89. metadata.isExtractFile = true
  90. if let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  91. metadataReturn = metadata
  92. }
  93. }
  94. completion(metadataReturn, fileNamePath, error)
  95. }
  96. }
  97. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadata.assetLocalIdentifier], options: nil)
  98. guard fetchAssets.count > 0, let asset = fetchAssets.firstObject, let extensionAsset = (asset.value(forKey: "filename") as? NSString)?.pathExtension.uppercased() else {
  99. return callCompletionWithError()
  100. }
  101. let creationDate = asset.creationDate ?? Date()
  102. let modificationDate = asset.modificationDate ?? Date()
  103. if asset.mediaType == PHAssetMediaType.image && (extensionAsset == "HEIC" || extensionAsset == "DNG") && CCUtility.getFormatCompatibility() {
  104. let fileName = (metadata.fileNameView as NSString).deletingPathExtension + ".jpg"
  105. metadata.contentType = "image/jpeg"
  106. fileNamePath = NSTemporaryDirectory() + fileName
  107. metadata.fileNameView = fileName
  108. if !metadata.isDirectoryE2EE {
  109. metadata.fileName = fileName
  110. }
  111. compatibilityFormat = true
  112. } else {
  113. fileNamePath = NSTemporaryDirectory() + metadata.fileNameView
  114. }
  115. guard let fileNamePath = fileNamePath else { return callCompletionWithError() }
  116. if asset.mediaType == PHAssetMediaType.image {
  117. let options = PHImageRequestOptions()
  118. options.isNetworkAccessAllowed = true
  119. if compatibilityFormat {
  120. options.deliveryMode = .opportunistic
  121. } else {
  122. options.deliveryMode = .highQualityFormat
  123. }
  124. options.isSynchronous = true
  125. if extensionAsset == "DNG" {
  126. options.version = PHImageRequestOptionsVersion.original
  127. }
  128. options.progressHandler = { progress, error, _, _ in
  129. print(progress)
  130. if error != nil { return callCompletionWithError() }
  131. }
  132. PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
  133. guard var data = data else { return callCompletionWithError() }
  134. if compatibilityFormat {
  135. guard let ciImage = CIImage(data: data), let colorSpace = ciImage.colorSpace, let dataJPEG = CIContext().jpegRepresentation(of: ciImage, colorSpace: colorSpace) else { return callCompletionWithError() }
  136. data = dataJPEG
  137. }
  138. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  139. do {
  140. try data.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
  141. } catch { return callCompletionWithError() }
  142. metadata.creationDate = creationDate as NSDate
  143. metadata.date = modificationDate as NSDate
  144. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  145. return callCompletionWithError(false)
  146. }
  147. } else if asset.mediaType == PHAssetMediaType.video {
  148. let options = PHVideoRequestOptions()
  149. options.isNetworkAccessAllowed = true
  150. options.version = PHVideoRequestOptionsVersion.current
  151. options.progressHandler = { progress, error, _, _ in
  152. print(progress)
  153. if error != nil { return callCompletionWithError() }
  154. }
  155. PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
  156. if let asset = asset as? AVURLAsset {
  157. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  158. do {
  159. try FileManager.default.copyItem(at: asset.url, to: URL(fileURLWithPath: fileNamePath))
  160. metadata.creationDate = creationDate as NSDate
  161. metadata.date = modificationDate as NSDate
  162. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  163. return callCompletionWithError(false)
  164. } catch { return callCompletionWithError() }
  165. } else if let asset = asset as? AVComposition, asset.tracks.count > 1, let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality), let viewController = viewController {
  166. DispatchQueue.main.async {
  167. hud.indicatorView = JGProgressHUDRingIndicatorView()
  168. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  169. indicatorView.ringWidth = 1.5
  170. }
  171. hud.textLabel.text = NSLocalizedString("_exporting_video_", comment: "")
  172. hud.show(in: viewController.view)
  173. hud.tapOnHUDViewBlock = { _ in
  174. exporter.cancelExport()
  175. }
  176. }
  177. exporter.outputURL = URL(fileURLWithPath: fileNamePath)
  178. exporter.outputFileType = AVFileType.mp4
  179. exporter.shouldOptimizeForNetworkUse = true
  180. exporter.exportAsynchronously {
  181. DispatchQueue.main.async { hud.dismiss() }
  182. if exporter.status == .completed {
  183. metadata.creationDate = creationDate as NSDate
  184. metadata.date = modificationDate as NSDate
  185. metadata.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  186. return callCompletionWithError(false)
  187. } else { return callCompletionWithError() }
  188. }
  189. while exporter.status == AVAssetExportSession.Status.exporting || exporter.status == AVAssetExportSession.Status.waiting {
  190. hud.progress = exporter.progress
  191. }
  192. } else {
  193. return callCompletionWithError()
  194. }
  195. }
  196. } else {
  197. return callCompletionWithError()
  198. }
  199. }
  200. private func createMetadataLivePhoto(metadata: tableMetadata,
  201. asset: PHAsset?,
  202. completion: @escaping (_ metadata: tableMetadata?) -> Void) {
  203. guard let asset = asset else { return completion(nil) }
  204. let options = PHLivePhotoRequestOptions()
  205. options.deliveryMode = PHImageRequestOptionsDeliveryMode.fastFormat
  206. options.isNetworkAccessAllowed = true
  207. let chunckSize = CCUtility.getChunkSize() * 1000000
  208. let ocId = NSUUID().uuidString
  209. let fileName = (metadata.fileName as NSString).deletingPathExtension + ".mov"
  210. let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!
  211. PHImageManager.default().requestLivePhoto(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.default, options: options) { livePhoto, _ in
  212. guard let livePhoto = livePhoto else { return completion(nil) }
  213. var videoResource: PHAssetResource?
  214. for resource in PHAssetResource.assetResources(for: livePhoto) where resource.type == PHAssetResourceType.pairedVideo {
  215. videoResource = resource
  216. break
  217. }
  218. guard let videoResource = videoResource else { return completion(nil) }
  219. NCUtilityFileSystem.shared.deleteFile(filePath: fileNamePath)
  220. PHAssetResourceManager.default().writeData(for: videoResource, toFile: URL(fileURLWithPath: fileNamePath), options: nil) { error in
  221. if error != nil { return completion(nil) }
  222. let metadataLivePhoto = NCManageDatabase.shared.createMetadata(account: metadata.account,
  223. user: metadata.user,
  224. userId: metadata.userId,
  225. fileName: fileName,
  226. fileNameView: fileName,
  227. ocId: ocId,
  228. serverUrl: metadata.serverUrl,
  229. urlBase: metadata.urlBase,
  230. url: "",
  231. contentType: "",
  232. isLivePhoto: true)
  233. metadataLivePhoto.classFile = NKCommon.TypeClassFile.video.rawValue
  234. metadataLivePhoto.isExtractFile = true
  235. metadataLivePhoto.session = metadata.session
  236. metadataLivePhoto.sessionSelector = metadata.sessionSelector
  237. metadataLivePhoto.size = NCUtilityFileSystem.shared.getFileSize(filePath: fileNamePath)
  238. metadataLivePhoto.status = metadata.status
  239. metadataLivePhoto.chunk = chunckSize != 0 && metadata.size > chunckSize
  240. metadataLivePhoto.creationDate = metadata.creationDate
  241. metadataLivePhoto.date = metadata.date
  242. metadataLivePhoto.uploadDate = metadata.uploadDate
  243. return completion(NCManageDatabase.shared.addMetadata(metadataLivePhoto))
  244. }
  245. }
  246. }
  247. }