damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 5e0ff1a6a02540c4d7b1453e01c7975b071ae4c4
parent 6517dcba3f6114f86e25886db335887663f49559
Author: Swift <scoder1747@gmail.com>
Date:   Fri, 24 Mar 2023 18:39:22 -0400

Video Uploads

Changelog-Added: Add support for video uploads

Diffstat:
Mdamus/Models/ImageUploadModel.swift | 4++--
Mdamus/Models/UserSettingsStore.swift | 14+++++++-------
Mdamus/Views/AttachMediaUtility.swift | 74+++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mdamus/Views/ConfigView.swift | 4++--
Mdamus/Views/PostView.swift | 12+++++++-----
5 files changed, 65 insertions(+), 43 deletions(-)

diff --git 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 @@ -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 @@ -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<PresentationMode>, 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<ImagePicker>) -> 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..<endIndex] - let nostrBuildURL = "https://nostr.build/i/\(nostrBuildImageName)" + let nostrBuildURL = mediaIsImage ? "https://nostr.build/i/\(nostrBuildImageName)" : "https://nostr.build/av/\(nostrBuildImageName)" return nostrBuildURL case .nostrImg: diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift @@ -228,8 +228,8 @@ struct ConfigView: View { } Picker(NSLocalizedString("Select image uploader", comment: "Prompt selection of user's image uploader"), - selection: $settings.default_image_uploader) { - ForEach(ImageUploader.allCases, id: \.self) { uploader in + selection: $settings.default_media_uploader) { + ForEach(MediaUploader.allCases, id: \.self) { uploader in Text(uploader.model.displayName) .tag(uploader.model.tag) } diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift @@ -168,11 +168,11 @@ struct PostView: View { post = combinedAttributedString } - func handle_upload(image: UIImage) { - let uploader = get_image_uploader(damus_state.pubkey) + func handle_upload(media: Any) { + let uploader = get_media_uploader(damus_state.pubkey) Task.init { - let res = await image_upload.start(img: image, uploader: uploader) + let res = await image_upload.start(media: media, uploader: uploader) switch res { case .success(let url): @@ -215,8 +215,10 @@ struct PostView: View { } .padding() .sheet(isPresented: $attach_media) { - ImagePicker(sourceType: .photoLibrary) { img in - handle_upload(image: img) + ImagePicker(sourceType: .photoLibrary, damusState: damus_state) { img in + handle_upload(media: img) + } onVideoPicked: { url in + handle_upload(media: url) } } .onAppear() {