macOS: Add ability to save images/videos directly to photos app

This commit is contained in:
Leon Böttger committed 2025-11-18 19:16:07 +01:00
1 parent 4ddf39817c
commit 658a2ff28e
7 files changed
+2251 -867

No files matched your search

-666
View File
@@ -5,233 +5,11 @@
"comment" : "Bundle display name",
"extractionState" : "extracted_with_value",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ca" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"cs" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"da" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"de" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"el" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "QuickDrop"
}
},
"en-AU" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-GB" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es-419" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"he" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hu" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"id" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"it" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ja" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ko" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ms" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nb" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ro" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ru" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"sv" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"th" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"tr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"uk" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"vi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
}
},
@@ -239,233 +17,11 @@
"comment" : "Bundle name",
"extractionState" : "extracted_with_value",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ca" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"cs" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"da" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"de" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"el" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "QuickDropiOS"
}
},
"en-AU" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-GB" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es-419" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"he" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hu" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"id" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"it" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ja" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ko" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ms" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nb" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ro" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ru" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"sv" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"th" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"tr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"uk" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"vi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
}
},
@@ -473,233 +29,11 @@
"comment" : "Copyright (human-readable)",
"extractionState" : "extracted_with_value",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ca" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"cs" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"da" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"de" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"el" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-AU" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en-GB" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es-419" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"he" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hu" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"id" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"it" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ja" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ko" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ms" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nb" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ro" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ru" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"sv" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"th" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"tr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"uk" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"vi" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
}
},
+21 -18
View File
@@ -5,11 +5,15 @@
// Created by Leon Böttger on 16.11.25.
//
#if os(iOS)
import UIKit
#else
import AppKit
#endif
import Photos
import UniformTypeIdentifiers
struct PhotoManager {
/// Returns if there are photos or videos among the given URLs.
@@ -44,7 +48,7 @@ struct PhotoManager {
let exifDate = EXIFUtils.exifOriginalDate(from: sourceURL)
imageItems.append(ImageItem(fileURL: sourceURL, creationDate: exifDate))
} else {
let videoDate = await getVideoCreationDate(from: sourceURL)
let videoDate = getVideoCreationDate(from: sourceURL)
videoItems.append(VideoItem(fileURL: sourceURL, creationDate: videoDate))
}
}
@@ -96,10 +100,16 @@ struct PhotoManager {
// Open the Photos app
if openPhotosOnSuccess {
if let url = URL(string: "photos-redirect://") {
DispatchQueue.main.async {
DispatchQueue.main.async {
#if os(iOS)
if let url = URL(string: "photos-redirect://") {
UIApplication.shared.open(url)
}
#else
if let url = URL(string: "photos-navigation://album?name=recently-saved") {
NSWorkspace.shared.open(url)
}
#endif
}
}
}
@@ -126,23 +136,16 @@ struct PhotoManager {
}
private static func getVideoCreationDate(from url: URL) async -> Date? {
private static func getVideoCreationDate(from url: URL) -> Date? {
let asset = AVURLAsset(url: url)
do {
// AVMetadataItem that typically contains an ISO‑8601 string
guard let item = try await asset.load(.creationDate),
let dateString = try await item.load(.stringValue) else {
return nil
}
let formatter = ISO8601DateFormatter()
return formatter.date(from: dateString)
}
catch {
log("Failed to load creation date from \(url): \(error)")
// AVMetadataItem that typically contains an ISO‑8601 string
guard let item = asset.creationDate,
let dateString = item.stringValue else {
return nil
}
let formatter = ISO8601DateFormatter()
return formatter.date(from: dateString)
}