NCCameraRoll.swift 15 KB

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