From 5e0ff1a6a02540c4d7b1453e01c7975b071ae4c4 Mon Sep 17 00:00:00 2001 From: Swift Date: Fri, 24 Mar 2023 18:39:22 -0400 Subject: [PATCH] Video Uploads Changelog-Added: Add support for video uploads --- damus/Models/ImageUploadModel.swift | 4 +- damus/Models/UserSettingsStore.swift | 14 +++--- damus/Views/AttachMediaUtility.swift | 74 ++++++++++++++++++---------- damus/Views/ConfigView.swift | 4 +- damus/Views/PostView.swift | 12 +++-- 5 files changed, 65 insertions(+), 43 deletions(-) diff --git a/damus/Models/ImageUploadModel.swift b/damus/Models/ImageUploadModel.swift index 996bfbb0..5f905ced 100644 --- a/damus/Models/ImageUploadModel.swift +++ b/damus/Models/ImageUploadModel.swift @@ -12,8 +12,8 @@ import UIKit class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject { @Published var progress: Double? = nil - func start(img: UIImage, uploader: ImageUploader) async -> ImageUploadResult { - let res = await create_image_upload_request(imageToUpload: img, imageUploader: uploader, progress: self) + func start(media: Any, uploader: MediaUploader) async -> ImageUploadResult { + let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self) DispatchQueue.main.async { self.progress = nil } diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift index 63c66ede..1cb6a51d 100644 --- a/damus/Models/UserSettingsStore.swift +++ b/damus/Models/UserSettingsStore.swift @@ -50,10 +50,10 @@ func get_default_wallet(_ pubkey: String) -> Wallet { } } -func get_image_uploader(_ pubkey: String) -> ImageUploader { - if let defaultImageUploader = UserDefaults.standard.string(forKey: "default_image_uploader"), - let defaultImageUploader = ImageUploader(rawValue: defaultImageUploader) { - return defaultImageUploader +func get_media_uploader(_ pubkey: String) -> MediaUploader { + if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"), + let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) { + return defaultMediaUploader } else { return .nostrBuild } @@ -98,9 +98,9 @@ class UserSettingsStore: ObservableObject { } } - @Published var default_image_uploader: ImageUploader { + @Published var default_media_uploader: MediaUploader { didSet { - UserDefaults.standard.set(default_image_uploader.rawValue, forKey: "default_image_uploader") + UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader") } } @@ -217,7 +217,7 @@ class UserSettingsStore: ObservableObject { show_wallet_selector = should_show_wallet_selector(pubkey) always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false - default_image_uploader = get_image_uploader(pubkey) + default_media_uploader = get_media_uploader(pubkey) left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false diff --git a/damus/Views/AttachMediaUtility.swift b/damus/Views/AttachMediaUtility.swift index 6062af5e..1c77f5fc 100644 --- a/damus/Views/AttachMediaUtility.swift +++ b/damus/Views/AttachMediaUtility.swift @@ -15,23 +15,24 @@ enum ImageUploadResult { case failed(Error?) } -fileprivate func create_upload_body(imageDataKey: Data, boundary: String, imageUploader: ImageUploader) -> Data { +fileprivate func create_upload_body(mediaData: Data, boundary: String, mediaUploader: MediaUploader, mediaIsImage: Bool) -> Data { let body = NSMutableData(); - let contentType = "image/jpg" + let contentType = mediaIsImage ? "image/jpg" : "video/mp4" + let genericFileName = mediaIsImage ? "damus_generic_filename.jpg" : "damus_generic_filename.mp4" body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n") body.appendString(string: "--\(boundary)\r\n") - body.appendString(string: "Content-Disposition: form-data; name=\(imageUploader.nameParam); filename=\"damus_generic_filename.jpg\"\r\n") + body.appendString(string: "Content-Disposition: form-data; name=\(mediaUploader.nameParam); filename=\(genericFileName)\r\n") body.appendString(string: "Content-Type: \(contentType)\r\n\r\n") - body.append(imageDataKey as Data) + body.append(mediaData as Data) body.appendString(string: "\r\n") body.appendString(string: "--\(boundary)--\r\n") return body as Data } - -func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult { - - guard let url = URL(string: imageUploader.postAPI) else { +func create_upload_request(mediaToUpload: Any, mediaUploader: MediaUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult { + var mediaIsImage: Bool = false + var mediaData: Data? + guard let url = URL(string: mediaUploader.postAPI) else { return .failed(nil) } @@ -39,14 +40,19 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl request.httpMethod = "POST"; let boundary = "Boundary-\(UUID().description)" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - - // otherwise convert to jpg - guard let jpegData = imageToUpload.jpegData(compressionQuality: 0.8) else { - // somehow failed, just return original + + if let imageToUpload = mediaToUpload as? UIImage { + mediaData = imageToUpload.jpegData(compressionQuality: 0.8) + mediaIsImage = true + } else if let videoToUpload = mediaToUpload as? URL { + mediaData = try? Data(contentsOf: videoToUpload) + } + + guard let mediaData = mediaData else { return .failed(nil) } - - request.httpBody = create_upload_body(imageDataKey: jpegData, boundary: boundary, imageUploader: imageUploader) + + request.httpBody = create_upload_body(mediaData: mediaData, boundary: boundary, mediaUploader: mediaUploader, mediaIsImage: mediaIsImage) do { let (data, _) = try await URLSession.shared.data(for: request, delegate: progress) @@ -56,8 +62,8 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl return .failed(nil) } - guard let url = imageUploader.getImageURL(from: responseString) else { - print("Upload failed getting image url") + guard let url = mediaUploader.getMediaURL(from: responseString, mediaIsImage: mediaIsImage) else { + print("Upload failed getting media url") return .failed(nil) } @@ -66,7 +72,6 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl } catch { return .failed(error) } - } extension PostView { @@ -76,7 +81,9 @@ extension PostView { private var presentationMode let sourceType: UIImagePickerController.SourceType + let damusState: DamusState let onImagePicked: (UIImage) -> Void + let onVideoPicked: (URL) -> Void final class Coordinator: NSObject, UINavigationControllerDelegate, @@ -86,19 +93,27 @@ extension PostView { private var presentationMode: PresentationMode private let sourceType: UIImagePickerController.SourceType private let onImagePicked: (UIImage) -> Void + private let onVideoPicked: (URL) -> Void init(presentationMode: Binding, sourceType: UIImagePickerController.SourceType, - onImagePicked: @escaping (UIImage) -> Void) { + onImagePicked: @escaping (UIImage) -> Void, + onVideoPicked: @escaping (URL) -> Void) { _presentationMode = presentationMode self.sourceType = sourceType self.onImagePicked = onImagePicked + self.onVideoPicked = onVideoPicked } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage - onImagePicked(uiImage) + if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL { + // Handle the selected video + onVideoPicked(videoURL) + } else if let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + // Handle the selected image + onImagePicked(uiImage) + } presentationMode.dismiss() } @@ -112,12 +127,17 @@ extension PostView { func makeCoordinator() -> Coordinator { return Coordinator(presentationMode: presentationMode, sourceType: sourceType, - onImagePicked: onImagePicked) + onImagePicked: onImagePicked, onVideoPicked: onVideoPicked) } func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { let picker = UIImagePickerController() picker.sourceType = sourceType + let mediaUploader = get_media_uploader(damusState.keypair.pubkey) + picker.mediaTypes = ["public.image"] + if mediaUploader.supportsVideo { + picker.mediaTypes.append("public.movie") + } picker.delegate = context.coordinator return picker } @@ -138,7 +158,7 @@ extension NSMutableData { } } -enum ImageUploader: String, CaseIterable, Identifiable { +enum MediaUploader: String, CaseIterable, Identifiable { var id: String { self.rawValue } case nostrBuild case nostrImg @@ -152,12 +172,12 @@ enum ImageUploader: String, CaseIterable, Identifiable { } } - var displayImageUploaderName: String { + var supportsVideo: Bool { switch self { case .nostrBuild: - return "NostrBuild" + return true case .nostrImg: - return "NostrImg" + return false } } @@ -187,7 +207,7 @@ enum ImageUploader: String, CaseIterable, Identifiable { } } - func getImageURL(from responseString: String) -> String? { + func getMediaURL(from responseString: String, mediaIsImage: Bool) -> String? { switch self { case .nostrBuild: guard let startIndex = responseString.range(of: "nostr.build_")?.lowerBound else { @@ -199,7 +219,7 @@ enum ImageUploader: String, CaseIterable, Identifiable { return nil } let nostrBuildImageName = responseString[startIndex..