NCCameraRoll.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. let utilityFileSystem = NCUtilityFileSystem()
  28. func extractCameraRoll(from metadata: tableMetadata, viewController: UIViewController?, hud: JGProgressHUD, completition: @escaping (_ metadatas: [tableMetadata]) -> Void) {
  29. var chunkSize = NCGlobal.shared.chunkSizeMBCellular
  30. if NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
  31. chunkSize = NCGlobal.shared.chunkSizeMBEthernetOrWiFi
  32. }
  33. var metadatas: [tableMetadata] = []
  34. let metadataSource = tableMetadata.init(value: metadata)
  35. guard !metadata.isExtractFile else { return completition([metadataSource]) }
  36. guard !metadataSource.assetLocalIdentifier.isEmpty else {
  37. let filePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadataSource.ocId, fileNameView: metadataSource.fileName)
  38. metadataSource.size = utilityFileSystem.getFileSize(filePath: filePath)
  39. let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadataSource.fileNameView, mimeType: metadataSource.contentType, directory: false)
  40. metadataSource.contentType = results.mimeType
  41. metadataSource.iconName = results.iconName
  42. metadataSource.classFile = results.classFile
  43. if let date = utilityFileSystem.getFileCreationDate(filePath: filePath) {
  44. metadataSource.creationDate = date
  45. }
  46. if let date = utilityFileSystem.getFileModificationDate(filePath: filePath) {
  47. metadataSource.date = date
  48. }
  49. if metadataSource.size > chunkSize {
  50. metadataSource.chunk = chunkSize
  51. } else {
  52. metadataSource.chunk = 0
  53. }
  54. metadataSource.e2eEncrypted = metadata.isDirectoryE2EE
  55. if metadataSource.chunk > 0 || metadataSource.e2eEncrypted {
  56. metadataSource.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload
  57. }
  58. metadataSource.isExtractFile = true
  59. if let metadata = NCManageDatabase.shared.addMetadata(metadataSource) {
  60. metadatas.append(metadata)
  61. }
  62. return completition(metadatas)
  63. }
  64. extractImageVideoFromAssetLocalIdentifier(metadata: metadataSource, modifyMetadataForUpload: true, viewController: viewController, hud: hud) { metadata, fileNamePath, error in
  65. if let metadata = metadata, let fileNamePath = fileNamePath, !error {
  66. metadatas.append(metadata)
  67. let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
  68. self.utilityFileSystem.moveFile(atPath: fileNamePath, toPath: toPath)
  69. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadataSource.assetLocalIdentifier], options: nil)
  70. // swiftlint:disable empty_count
  71. if metadata.livePhoto, fetchAssets.count > 0 {
  72. self.createMetadataLivePhoto(metadata: metadata, asset: fetchAssets.firstObject) { metadata in
  73. if let metadata = metadata, let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  74. metadatas.append(metadata)
  75. }
  76. completition(metadatas)
  77. }
  78. } else {
  79. completition(metadatas)
  80. }
  81. // swiftlint:enable empty_count
  82. } else {
  83. completition(metadatas)
  84. }
  85. }
  86. }
  87. func extractImageVideoFromAssetLocalIdentifier(metadata: tableMetadata,
  88. modifyMetadataForUpload: Bool,
  89. viewController: UIViewController?,
  90. hud: JGProgressHUD,
  91. completion: @escaping (_ metadata: tableMetadata?, _ fileNamePath: String?, _ error: Bool) -> Void) {
  92. var fileNamePath: String?
  93. let metadata = tableMetadata.init(value: metadata)
  94. var compatibilityFormat: Bool = false
  95. var chunkSize = NCGlobal.shared.chunkSizeMBCellular
  96. if NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
  97. chunkSize = NCGlobal.shared.chunkSizeMBEthernetOrWiFi
  98. }
  99. func callCompletionWithError(_ error: Bool = true) {
  100. if error {
  101. completion(nil, nil, true)
  102. } else {
  103. var metadataReturn = metadata
  104. if modifyMetadataForUpload {
  105. if metadata.size > chunkSize {
  106. metadata.chunk = chunkSize
  107. } else {
  108. metadata.chunk = 0
  109. }
  110. metadata.e2eEncrypted = metadata.isDirectoryE2EE
  111. if metadata.chunk > 0 || metadata.e2eEncrypted {
  112. metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload
  113. }
  114. metadata.isExtractFile = true
  115. if let metadata = NCManageDatabase.shared.addMetadata(metadata) {
  116. metadataReturn = metadata
  117. }
  118. }
  119. completion(metadataReturn, fileNamePath, error)
  120. }
  121. }
  122. let fetchAssets = PHAsset.fetchAssets(withLocalIdentifiers: [metadata.assetLocalIdentifier], options: nil)
  123. // swiftlint:disable empty_count
  124. guard fetchAssets.count > 0, let asset = fetchAssets.firstObject else {
  125. return callCompletionWithError()
  126. }
  127. // swiftlint:enable empty_count
  128. let extensionAsset = asset.originalFilename.pathExtension.uppercased()
  129. let creationDate = asset.creationDate ?? Date()
  130. let modificationDate = asset.modificationDate ?? Date()
  131. if asset.mediaType == PHAssetMediaType.image && (extensionAsset == "HEIC" || extensionAsset == "DNG") && NCKeychain().formatCompatibility {
  132. let fileName = (metadata.fileNameView as NSString).deletingPathExtension + ".jpg"
  133. metadata.contentType = "image/jpeg"
  134. fileNamePath = NSTemporaryDirectory() + fileName
  135. metadata.fileNameView = fileName
  136. if !metadata.isDirectoryE2EE {
  137. metadata.fileName = fileName
  138. }
  139. compatibilityFormat = true
  140. } else {
  141. fileNamePath = NSTemporaryDirectory() + metadata.fileNameView
  142. }
  143. guard let fileNamePath = fileNamePath else { return callCompletionWithError() }
  144. if asset.mediaType == PHAssetMediaType.image {
  145. let options = PHImageRequestOptions()
  146. options.isNetworkAccessAllowed = true
  147. if compatibilityFormat {
  148. options.deliveryMode = .opportunistic
  149. } else {
  150. options.deliveryMode = .highQualityFormat
  151. }
  152. options.isSynchronous = true
  153. if extensionAsset == "DNG" {
  154. options.version = PHImageRequestOptionsVersion.original
  155. }
  156. options.progressHandler = { progress, error, _, _ in
  157. print(progress)
  158. if error != nil { return callCompletionWithError() }
  159. }
  160. PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
  161. guard var data = data else { return callCompletionWithError() }
  162. if compatibilityFormat {
  163. guard let ciImage = CIImage(data: data), let colorSpace = ciImage.colorSpace, let dataJPEG = CIContext().jpegRepresentation(of: ciImage, colorSpace: colorSpace) else { return callCompletionWithError() }
  164. data = dataJPEG
  165. }
  166. self.utilityFileSystem.removeFile(atPath: fileNamePath)
  167. do {
  168. try data.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
  169. } catch { return callCompletionWithError() }
  170. metadata.creationDate = creationDate as NSDate
  171. metadata.date = modificationDate as NSDate
  172. metadata.size = self.utilityFileSystem.getFileSize(filePath: fileNamePath)
  173. return callCompletionWithError(false)
  174. }
  175. } else if asset.mediaType == PHAssetMediaType.video {
  176. let options = PHVideoRequestOptions()
  177. options.isNetworkAccessAllowed = true
  178. options.version = PHVideoRequestOptionsVersion.current
  179. options.progressHandler = { progress, error, _, _ in
  180. print(progress)
  181. if error != nil { return callCompletionWithError() }
  182. }
  183. PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
  184. if let asset = asset as? AVURLAsset {
  185. self.utilityFileSystem.removeFile(atPath: fileNamePath)
  186. do {
  187. try FileManager.default.copyItem(at: asset.url, to: URL(fileURLWithPath: fileNamePath))
  188. metadata.creationDate = creationDate as NSDate
  189. metadata.date = modificationDate as NSDate
  190. metadata.size = self.utilityFileSystem.getFileSize(filePath: fileNamePath)
  191. return callCompletionWithError(false)
  192. } catch { return callCompletionWithError() }
  193. } else if let asset = asset as? AVComposition, asset.tracks.count > 1, let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality), let viewController = viewController {
  194. DispatchQueue.main.async {
  195. hud.indicatorView = JGProgressHUDRingIndicatorView()
  196. if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
  197. indicatorView.ringWidth = 1.5
  198. }
  199. hud.textLabel.text = NSLocalizedString("_exporting_video_", comment: "")
  200. hud.show(in: viewController.view)
  201. hud.tapOnHUDViewBlock = { _ in
  202. exporter.cancelExport()
  203. }
  204. }
  205. exporter.outputURL = URL(fileURLWithPath: fileNamePath)
  206. exporter.outputFileType = AVFileType.mp4
  207. exporter.shouldOptimizeForNetworkUse = true
  208. exporter.exportAsynchronously {
  209. DispatchQueue.main.async { hud.dismiss() }
  210. if exporter.status == .completed {
  211. metadata.creationDate = creationDate as NSDate
  212. metadata.date = modificationDate as NSDate
  213. metadata.size = self.utilityFileSystem.getFileSize(filePath: fileNamePath)
  214. return callCompletionWithError(false)
  215. } else { return callCompletionWithError() }
  216. }
  217. while exporter.status == AVAssetExportSession.Status.exporting || exporter.status == AVAssetExportSession.Status.waiting {
  218. hud.progress = exporter.progress
  219. }
  220. } else {
  221. return callCompletionWithError()
  222. }
  223. }
  224. } else {
  225. return callCompletionWithError()
  226. }
  227. }
  228. private func createMetadataLivePhoto(metadata: tableMetadata,
  229. asset: PHAsset?,
  230. completion: @escaping (_ metadata: tableMetadata?) -> Void) {
  231. guard let asset = asset else { return completion(nil) }
  232. let options = PHLivePhotoRequestOptions()
  233. options.deliveryMode = PHImageRequestOptionsDeliveryMode.fastFormat
  234. options.isNetworkAccessAllowed = true
  235. let ocId = NSUUID().uuidString
  236. let fileName = (metadata.fileName as NSString).deletingPathExtension + ".mov"
  237. let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)
  238. var chunkSize = NCGlobal.shared.chunkSizeMBCellular
  239. if NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
  240. chunkSize = NCGlobal.shared.chunkSizeMBEthernetOrWiFi
  241. }
  242. PHImageManager.default().requestLivePhoto(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.default, options: options) { livePhoto, _ in
  243. guard let livePhoto = livePhoto else { return completion(nil) }
  244. var videoResource: PHAssetResource?
  245. for resource in PHAssetResource.assetResources(for: livePhoto) where resource.type == PHAssetResourceType.pairedVideo {
  246. videoResource = resource
  247. break
  248. }
  249. guard let videoResource = videoResource else { return completion(nil) }
  250. self.utilityFileSystem.removeFile(atPath: fileNamePath)
  251. PHAssetResourceManager.default().writeData(for: videoResource, toFile: URL(fileURLWithPath: fileNamePath), options: nil) { error in
  252. if error != nil { return completion(nil) }
  253. let metadataLivePhoto = NCManageDatabase.shared.createMetadata(account: metadata.account,
  254. user: metadata.user,
  255. userId: metadata.userId,
  256. fileName: fileName,
  257. fileNameView: fileName,
  258. ocId: ocId,
  259. serverUrl: metadata.serverUrl,
  260. urlBase: metadata.urlBase,
  261. url: "",
  262. contentType: "",
  263. isLivePhoto: true)
  264. metadataLivePhoto.classFile = NKCommon.TypeClassFile.video.rawValue
  265. metadataLivePhoto.isExtractFile = true
  266. metadataLivePhoto.session = metadata.session
  267. metadataLivePhoto.sessionSelector = metadata.sessionSelector
  268. metadataLivePhoto.size = self.utilityFileSystem.getFileSize(filePath: fileNamePath)
  269. metadataLivePhoto.status = metadata.status
  270. if metadataLivePhoto.size > chunkSize {
  271. metadataLivePhoto.chunk = chunkSize
  272. } else {
  273. metadataLivePhoto.chunk = 0
  274. }
  275. metadataLivePhoto.e2eEncrypted = metadata.isDirectoryE2EE
  276. if metadataLivePhoto.chunk > 0 || metadataLivePhoto.e2eEncrypted {
  277. metadataLivePhoto.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload
  278. }
  279. metadataLivePhoto.creationDate = metadata.creationDate
  280. metadataLivePhoto.date = metadata.date
  281. metadataLivePhoto.uploadDate = metadata.uploadDate
  282. return completion(NCManageDatabase.shared.addMetadata(metadataLivePhoto))
  283. }
  284. }
  285. }
  286. }