mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
Compare commits
4 Commits
75d87fee9d
...
8a95e40e0c
Author | SHA1 | Date | |
---|---|---|---|
|
8a95e40e0c | ||
|
556a5bcf4d | ||
|
6de44223f2 | ||
|
fb8c470e9d |
@ -640,9 +640,10 @@
|
|||||||
E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; };
|
E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; };
|
||||||
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
|
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
|
||||||
E04A37C62B544F090029650D /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; };
|
E04A37C62B544F090029650D /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; };
|
||||||
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E024102B7C19C20075735D /* TranslationTests.swift */; };
|
|
||||||
E06336AA2B75832100A88E6B /* ImageMetadataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06336A92B75832100A88E6B /* ImageMetadataTest.swift */; };
|
E06336AA2B75832100A88E6B /* ImageMetadataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06336A92B75832100A88E6B /* ImageMetadataTest.swift */; };
|
||||||
E06336AB2B75850100A88E6B /* img_with_location.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = E06336A82B7582E000A88E6B /* img_with_location.jpeg */; };
|
E06336AB2B75850100A88E6B /* img_with_location.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = E06336A82B7582E000A88E6B /* img_with_location.jpeg */; };
|
||||||
|
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E024102B7C19C20075735D /* TranslationTests.swift */; };
|
||||||
|
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */; };
|
||||||
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
|
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
|
||||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||||
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||||
@ -1430,9 +1431,10 @@
|
|||||||
E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
|
E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
|
||||||
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
|
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
|
||||||
E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = "<group>"; };
|
E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = "<group>"; };
|
||||||
E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = "<group>"; };
|
|
||||||
E06336A82B7582E000A88E6B /* img_with_location.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = img_with_location.jpeg; sourceTree = "<group>"; };
|
E06336A82B7582E000A88E6B /* img_with_location.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = img_with_location.jpeg; sourceTree = "<group>"; };
|
||||||
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMetadataTest.swift; sourceTree = "<group>"; };
|
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMetadataTest.swift; sourceTree = "<group>"; };
|
||||||
|
E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = "<group>"; };
|
||||||
|
E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = "<group>"; };
|
||||||
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
|
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
|
||||||
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
||||||
@ -1687,6 +1689,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C198DF429F88D2E004C165C /* ImageMetadata.swift */,
|
4C198DF429F88D2E004C165C /* ImageMetadata.swift */,
|
||||||
|
E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */,
|
||||||
);
|
);
|
||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3388,6 +3391,7 @@
|
|||||||
4C5E54062A9671F800FF6E60 /* UserStatusSheet.swift in Sources */,
|
4C5E54062A9671F800FF6E60 /* UserStatusSheet.swift in Sources */,
|
||||||
F71694F42A6732B7001F4053 /* GradientFollowButton.swift in Sources */,
|
F71694F42A6732B7001F4053 /* GradientFollowButton.swift in Sources */,
|
||||||
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
|
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
|
||||||
|
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */,
|
||||||
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
|
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
|
||||||
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
|
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
|
||||||
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
enum PreUploadedMedia {
|
||||||
|
case uiimage(UIImage)
|
||||||
|
case processed_image(URL)
|
||||||
|
case unprocessed_image(URL)
|
||||||
|
case processed_video(URL)
|
||||||
|
case unprocessed_video(URL)
|
||||||
|
}
|
||||||
|
|
||||||
enum MediaUpload {
|
enum MediaUpload {
|
||||||
case image(URL)
|
case image(URL)
|
||||||
|
@ -210,51 +210,3 @@ func process_image_metadatas(cache: EventCache, ev: NostrEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func canGetSourceTypeFromUrl(url: URL) -> Bool {
|
|
||||||
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else {
|
|
||||||
print("Failed to create image source.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return CGImageSourceGetType(source) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeGPSDataFromImage(fromImageURL imageURL: URL) -> Bool {
|
|
||||||
guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, nil) else {
|
|
||||||
print("Failed to create image source.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let data = NSMutableData()
|
|
||||||
|
|
||||||
let totalCount = CGImageSourceGetCount(source)
|
|
||||||
|
|
||||||
guard totalCount > 0 else {
|
|
||||||
print("No images found.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let type = CGImageSourceGetType(source),
|
|
||||||
let destination = CGImageDestinationCreateWithData(data, type, totalCount, nil) else {
|
|
||||||
print("Failed to create image destination.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let removeGPSProperties: CFDictionary = [kCGImagePropertyGPSDictionary as String: kCFNull] as CFDictionary
|
|
||||||
|
|
||||||
for i in 0...totalCount {
|
|
||||||
CGImageDestinationAddImageFromSource(destination, source, i, removeGPSProperties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if CGImageDestinationFinalize(destination) {
|
|
||||||
do {
|
|
||||||
try data.write(to: imageURL, options: .atomic)
|
|
||||||
return true
|
|
||||||
} catch {
|
|
||||||
print("Failed to write image data: \(error)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("Failed to finalize image destination.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
151
damus/Util/Images/ImageProcessing.swift
Normal file
151
damus/Util/Images/ImageProcessing.swift
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
// ImageProcessing.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by KernelKind on 2/27/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// Removes GPS data from image at url and writes changes to new file
|
||||||
|
func processImage(url: URL) -> URL? {
|
||||||
|
let fileExtension = url.pathExtension
|
||||||
|
guard let imageData = try? Data(contentsOf: url) else {
|
||||||
|
print("Failed to load image data from URL.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
|
||||||
|
|
||||||
|
return processImage(source: source, fileExtension: fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes GPS data from image and writes changes to new file
|
||||||
|
func processImage(image: UIImage) -> URL? {
|
||||||
|
let fixedImage = image.fixOrientation()
|
||||||
|
guard let imageData = fixedImage.jpegData(compressionQuality: 1.0) else { return nil }
|
||||||
|
guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
|
||||||
|
|
||||||
|
return processImage(source: source, fileExtension: "jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func processImage(source: CGImageSource, fileExtension: String) -> URL? {
|
||||||
|
let destinationURL = createMediaURL(fileExtension: fileExtension)
|
||||||
|
|
||||||
|
guard let destination = removeGPSDataFromImage(source: source, url: destinationURL) else { return nil }
|
||||||
|
|
||||||
|
if !CGImageDestinationFinalize(destination) { return nil }
|
||||||
|
|
||||||
|
return destinationURL
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: strip GPS data from video
|
||||||
|
func processVideo(videoURL: URL) -> URL? {
|
||||||
|
saveVideoToTemporaryFolder(videoURL: videoURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
|
||||||
|
let destinationURL = createMediaURL(fileExtension: videoURL.pathExtension)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try FileManager.default.copyItem(at: videoURL, to: destinationURL)
|
||||||
|
return destinationURL
|
||||||
|
} catch {
|
||||||
|
print("Error copying file: \(error.localizedDescription)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a temporary URL with a unique filename
|
||||||
|
fileprivate func createMediaURL(fileExtension: String) -> URL {
|
||||||
|
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
|
let uniqueMediaName = "\(UUID().uuidString).\(fileExtension)"
|
||||||
|
let temporaryMediaURL = temporaryDirectoryURL.appendingPathComponent(uniqueMediaName)
|
||||||
|
|
||||||
|
return temporaryMediaURL
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Take the PreUploadedMedia payload, process it, if necessary, and convert it into a URL
|
||||||
|
which is ready to be uploaded to the upload service.
|
||||||
|
|
||||||
|
URLs containing media that hasn't been processed were generated from the system and were granted
|
||||||
|
access as a security scoped resource. The data will need to be processed to strip GPS data
|
||||||
|
and saved to a new location which isn't security scoped.
|
||||||
|
*/
|
||||||
|
func generateMediaUpload(_ media: PreUploadedMedia?) -> MediaUpload? {
|
||||||
|
guard let media else { return nil }
|
||||||
|
|
||||||
|
switch media {
|
||||||
|
case .uiimage(let image):
|
||||||
|
guard let url = processImage(image: image) else { return nil }
|
||||||
|
return .image(url)
|
||||||
|
case .unprocessed_image(let url):
|
||||||
|
guard let newUrl = processImage(url: url) else { return nil }
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
return .image(newUrl)
|
||||||
|
case .processed_image(let url):
|
||||||
|
return .image(url)
|
||||||
|
case .processed_video(let url):
|
||||||
|
return .video(url)
|
||||||
|
case .unprocessed_video(let url):
|
||||||
|
guard let newUrl = processVideo(videoURL: url) else { return nil }
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
return .video(newUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIImage {
|
||||||
|
func fixOrientation() -> UIImage {
|
||||||
|
guard imageOrientation != .up else { return self }
|
||||||
|
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||||
|
draw(in: CGRect(origin: .zero, size: size))
|
||||||
|
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
|
||||||
|
return normalizedImage ?? self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func canGetSourceTypeFromUrl(url: URL) -> Bool {
|
||||||
|
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else {
|
||||||
|
print("Failed to create image source.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return CGImageSourceGetType(source) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeGPSDataFromImageAndWrite(fromImageURL imageURL: URL) -> Bool {
|
||||||
|
guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, nil) else {
|
||||||
|
print("Failed to create image source.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let destination = removeGPSDataFromImage(source: source, url: imageURL) else { return false }
|
||||||
|
|
||||||
|
return CGImageDestinationFinalize(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func removeGPSDataFromImage(source: CGImageSource, url: URL) -> CGImageDestination? {
|
||||||
|
let totalCount = CGImageSourceGetCount(source)
|
||||||
|
|
||||||
|
guard totalCount > 0 else {
|
||||||
|
print("No images found.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let type = CGImageSourceGetType(source),
|
||||||
|
let destination = CGImageDestinationCreateWithURL(url as CFURL, type, totalCount, nil) else {
|
||||||
|
print("Failed to create image destination.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let removeGPSProperties: CFDictionary = [kCGImageMetadataShouldExcludeGPS: kCFBooleanTrue] as CFDictionary
|
||||||
|
|
||||||
|
for i in 0..<totalCount {
|
||||||
|
CGImageDestinationAddImageFromSource(destination, source, i, removeGPSProperties)
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination
|
||||||
|
}
|
@ -16,7 +16,8 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
|
|
||||||
@Binding var image_upload_confirm: Bool
|
@Binding var image_upload_confirm: Bool
|
||||||
var imagesOnly: Bool = false
|
var imagesOnly: Bool = false
|
||||||
let onMediaPicked: (MediaUpload) -> Void
|
let onMediaPicked: (PreUploadedMedia) -> Void
|
||||||
|
|
||||||
|
|
||||||
final class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
final class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
||||||
let parent: MediaPicker
|
let parent: MediaPicker
|
||||||
@ -37,17 +38,17 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
|
|
||||||
if canGetSourceTypeFromUrl(url: url) {
|
if canGetSourceTypeFromUrl(url: url) {
|
||||||
// Media was not taken from camera
|
// Media was not taken from camera
|
||||||
if let savedURL = self.saveImageToTemporaryFolder(from: url) {
|
self.attemptAcquireResourceAndChooseMedia(
|
||||||
self.chooseImage(url: savedURL)
|
url: url,
|
||||||
}
|
fallback: processImage,
|
||||||
|
unprocessedEnum: {.unprocessed_image($0)},
|
||||||
|
processedEnum: {.processed_image($0)}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Media was taken from camera
|
// Media was taken from camera
|
||||||
result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
|
result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
|
||||||
guard let image = image as? UIImage, error == nil else { return }
|
if let image = image as? UIImage, error == nil {
|
||||||
let fixedImage = image.fixOrientation()
|
self.chooseMedia(.uiimage(image))
|
||||||
|
|
||||||
if let savedURL = self.saveImageToTemporaryFolder(image: fixedImage) {
|
|
||||||
self.chooseImage(url: savedURL)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,78 +57,34 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { (url, error) in
|
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { (url, error) in
|
||||||
guard let url, error == nil else { return }
|
guard let url, error == nil else { return }
|
||||||
|
|
||||||
guard let url = self.saveVideoToTemporaryFolder(videoURL: url) else { return }
|
self.attemptAcquireResourceAndChooseMedia(
|
||||||
self.parent.onMediaPicked(.video(url))
|
url: url,
|
||||||
|
fallback: processVideo,
|
||||||
|
unprocessedEnum: {.unprocessed_video($0)},
|
||||||
|
processedEnum: {.processed_video($0)}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func chooseMedia(_ media: PreUploadedMedia) {
|
||||||
|
self.parent.onMediaPicked(media)
|
||||||
self.parent.image_upload_confirm = true
|
self.parent.image_upload_confirm = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func chooseImage(url: URL) {
|
private func attemptAcquireResourceAndChooseMedia(url: URL, fallback: (URL) -> URL?, unprocessedEnum: (URL) -> PreUploadedMedia, processedEnum: (URL) -> PreUploadedMedia) {
|
||||||
if removeGPSDataFromImage(fromImageURL: url) {
|
if url.startAccessingSecurityScopedResource() {
|
||||||
self.parent.onMediaPicked(.image(url))
|
// Have permission from system to use url out of scope
|
||||||
self.parent.image_upload_confirm = true
|
print("Acquired permission to security scoped resource")
|
||||||
}
|
self.chooseMedia(unprocessedEnum(url))
|
||||||
}
|
|
||||||
|
|
||||||
func saveImageToTemporaryFolder(from imageUrl: URL) -> URL? {
|
|
||||||
let fileExtension = imageUrl.pathExtension
|
|
||||||
guard let imageData = try? Data(contentsOf: imageUrl) else {
|
|
||||||
print("Failed to load image data from URL.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveImageToTemporaryFolder(imageData: imageData, imageType: fileExtension)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveImageToTemporaryFolder(image: UIImage, imageType: String = "png") -> URL? {
|
|
||||||
// Convert UIImage to Data
|
|
||||||
let imageData: Data?
|
|
||||||
if imageType.lowercased() == "jpeg" {
|
|
||||||
imageData = image.jpegData(compressionQuality: 1.0)
|
|
||||||
} else {
|
} else {
|
||||||
imageData = image.pngData()
|
// Need to copy URL to non-security scoped location
|
||||||
}
|
guard let newUrl = fallback(url) else { return }
|
||||||
|
self.chooseMedia(processedEnum(newUrl))
|
||||||
guard let data = imageData else {
|
|
||||||
print("Failed to convert UIImage to Data.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveImageToTemporaryFolder(imageData: data, imageType: imageType)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func saveImageToTemporaryFolder(imageData: Data, imageType: String) -> URL? {
|
|
||||||
// Generate a temporary URL with a unique filename
|
|
||||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
||||||
let uniqueImageName = "\(UUID().uuidString).\(imageType)"
|
|
||||||
let temporaryImageURL = temporaryDirectoryURL.appendingPathComponent(uniqueImageName)
|
|
||||||
|
|
||||||
// Save the image data to the temporary URL
|
|
||||||
do {
|
|
||||||
try imageData.write(to: temporaryImageURL)
|
|
||||||
return temporaryImageURL
|
|
||||||
} catch {
|
|
||||||
print("Error saving image data to temporary URL: \(error.localizedDescription)")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
|
|
||||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
||||||
let fileExtension = videoURL.pathExtension
|
|
||||||
let uniqueFileName = UUID().uuidString + (fileExtension.isEmpty ? "" : ".\(fileExtension)")
|
|
||||||
let destinationURL = temporaryDirectoryURL.appendingPathComponent(uniqueFileName)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try FileManager.default.copyItem(at: videoURL, to: destinationURL)
|
|
||||||
return destinationURL
|
|
||||||
} catch {
|
|
||||||
print("Error copying file: \(error.localizedDescription)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
@ -147,16 +104,3 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIImage {
|
|
||||||
func fixOrientation() -> UIImage {
|
|
||||||
guard imageOrientation != .up else { return self }
|
|
||||||
|
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|
||||||
draw(in: CGRect(origin: .zero, size: size))
|
|
||||||
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
|
|
||||||
return normalizedImage ?? self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -57,7 +57,7 @@ struct PostView: View {
|
|||||||
@State var newCursorIndex: Int?
|
@State var newCursorIndex: Int?
|
||||||
@State var textHeight: CGFloat? = nil
|
@State var textHeight: CGFloat? = nil
|
||||||
|
|
||||||
@State var mediaToUpload: MediaUpload? = nil
|
@State var preUploadedMedia: PreUploadedMedia? = nil
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||||
@StateObject var tagModel: TagModel = TagModel()
|
@StateObject var tagModel: TagModel = TagModel()
|
||||||
@ -421,11 +421,11 @@ struct PostView: View {
|
|||||||
.background(DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all))
|
.background(DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all))
|
||||||
.sheet(isPresented: $attach_media) {
|
.sheet(isPresented: $attach_media) {
|
||||||
MediaPicker(image_upload_confirm: $image_upload_confirm){ media in
|
MediaPicker(image_upload_confirm: $image_upload_confirm){ media in
|
||||||
self.mediaToUpload = media
|
self.preUploadedMedia = media
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) {
|
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) {
|
||||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
||||||
if let mediaToUpload {
|
if let mediaToUpload = generateMediaUpload(preUploadedMedia) {
|
||||||
self.handle_upload(media: mediaToUpload)
|
self.handle_upload(media: mediaToUpload)
|
||||||
self.attach_media = false
|
self.attach_media = false
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ struct EditPictureControl: View {
|
|||||||
@State private var show_library = false
|
@State private var show_library = false
|
||||||
@State var image_upload_confirm: Bool = false
|
@State var image_upload_confirm: Bool = false
|
||||||
|
|
||||||
@State var mediaToUpload: MediaUpload? = nil
|
@State var preUploadedMedia: PreUploadedMedia? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
@ -60,31 +60,18 @@ struct EditPictureControl: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_camera) {
|
.sheet(isPresented: $show_camera) {
|
||||||
|
CameraController(uploader: uploader) {
|
||||||
MediaPicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in
|
|
||||||
self.mediaToUpload = media
|
|
||||||
}
|
|
||||||
.alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
|
|
||||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
|
||||||
if let mediaToUpload {
|
|
||||||
self.handle_upload(media: mediaToUpload)
|
|
||||||
self.show_camera = false
|
self.show_camera = false
|
||||||
}
|
self.show_library = true
|
||||||
}
|
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_library) {
|
.sheet(isPresented: $show_library) {
|
||||||
MediaPicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in
|
MediaPicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in
|
||||||
if case .image = media {
|
self.preUploadedMedia = media
|
||||||
self.mediaToUpload = media
|
|
||||||
} else {
|
|
||||||
print("Cannot upload videos as profile image")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
|
.alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
|
||||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
||||||
if let mediaToUpload {
|
if let mediaToUpload = generateMediaUpload(preUploadedMedia) {
|
||||||
self.handle_upload(media: mediaToUpload)
|
self.handle_upload(media: mediaToUpload)
|
||||||
self.show_library = false
|
self.show_library = false
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ final class ImageMetadataTest : XCTestCase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let removalSuccess = removeGPSDataFromImage(fromImageURL: testOutputURL)
|
let removalSuccess = removeGPSDataFromImageAndWrite(fromImageURL: testOutputURL)
|
||||||
|
|
||||||
XCTAssertTrue(removalSuccess, "GPS data removal was not successful")
|
XCTAssertTrue(removalSuccess, "GPS data removal was not successful")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user