comment the get_asset_jpeg script
- ID
4af2577- date
2023-05-13 22:12:36+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
42b9cd1- message
comment the get_asset_jpeg script- changed files
2 files, 103 additions, 84 deletions
Changed files
get_asset_jpeg.swift (3316) → actions/get_asset_jpeg.swift (4238)
diff --git a/get_asset_jpeg.swift b/actions/get_asset_jpeg.swift
similarity index 65%
rename from get_asset_jpeg.swift
rename to actions/get_asset_jpeg.swift
index ca5c930..0cc7c52 100644
--- a/get_asset_jpeg.swift
+++ b/actions/get_asset_jpeg.swift
@@ -1,58 +1,90 @@
+#!/usr/bin/env swift
+/// This script creates a JPEG for a photo in my Photos Library.
+///
+/// It takes two arguments: the localIdentifier for the asset, and a
+/// target size. It prints a path to the generated JPEG.
+///
+/// $ swift get_asset_jpeg.swift ADC872E4-A7B3-4E4F-95AE-BA96C359F532/L0/001 2048
+/// /tmp/photos-reviewer/A/ADC872E4-A7B3-4E4F-95AE-BA96C359F532/L0/001_2048.jpg⏎
+///
+
import Cocoa
import Photos
-/// Create an NSImage at the given size for a given asset.
-func getAssetThumbnail(asset: PHAsset, size: Double) -> NSImage {
-
- // This implementation is based on code in a Stack Overflow answer
- // by Francois Nadeau: https://stackoverflow.com/a/48755517/1558022
- //
- // I've added more comments and error-handling logic.
-
- let options = PHImageRequestOptions()
- options.isSynchronous = true
-
- // If i don't set this value, then sometimes I get an error like
- // this in the `info` variable:
- //
- // Error Domain=PHPhotosErrorDomain Code=3164 "(null)"
- //
- // This means that the asset is in the cloud, and by default Photos
- // isn't allowed to download assets here. Apple's documentation
- // suggests adding this option as the fix.
- //
- // See https://developer.apple.com/documentation/photokit/phphotoserror/phphotoserrornetworkaccessrequired
- options.isNetworkAccessAllowed = true
-
- var thumbnail = NSImage()
-
- PHImageManager.default()
- .requestImage(
- for: asset,
- targetSize: CGSize(width: size, height: size),
- contentMode: .aspectFit,
- options: options,
- resultHandler: { (result, info) -> Void in
-
- // If we fail to get a result, print a message to the user that
- // includes the value of `info`. For information about interpreting
- // these keys, see Apple's documentation:
- // https://developer.apple.com/documentation/photokit/phimagemanager/image_result_info_keys
- switch (result, info) {
- case let (result?, _):
- thumbnail = result
- case let (.none, info?):
- fputs("Unable to create thumbnail:\n", stderr)
- fputs("\(info)\n", stderr)
- exit(1)
- case (.none, .none):
- fputs("Unable to create thumbnail:\n", stderr)
- fputs("(unknown error)\n", stderr)
- exit(1)
- }
- })
-
- return thumbnail
+/// Returns the PHAsset with the given identifier, or throws if it
+/// can't be found.
+func getPhoto(withLocalIdentifier localIdentifier: String) -> PHAsset {
+ let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
+
+ if fetchResult.count == 1 {
+ return fetchResult.firstObject!
+ } else {
+ fputs("Unable to find photo with ID: \(localIdentifier).\n", stderr)
+ exit(1)
+ }
+}
+
+extension PHAsset {
+ /// Create an NSImage at the given size.
+ func getImage(atSize size: Double) -> NSImage {
+ // This implementation is based on code in a Stack Overflow answer
+ // by Francois Nadeau: https://stackoverflow.com/a/48755517/1558022
+ //
+ // I've added more comments and error-handling logic.
+
+ let options = PHImageRequestOptions()
+ options.isSynchronous = true
+
+ // If i don't set this value, then sometimes I get an error like
+ // this in the `info` variable:
+ //
+ // Error Domain=PHPhotosErrorDomain Code=3164 "(null)"
+ //
+ // This means that the asset is in the cloud, and by default Photos
+ // isn't allowed to download assets here. Apple's documentation
+ // suggests adding this option as the fix.
+ //
+ // See https://developer.apple.com/documentation/photokit/phphotoserror/phphotoserrornetworkaccessrequired
+ options.isNetworkAccessAllowed = true
+
+ var image = NSImage()
+
+ PHImageManager.default()
+ .requestImage(
+ for: self,
+ targetSize: CGSize(width: size, height: size),
+ contentMode: .aspectFit,
+ options: options,
+ resultHandler: { (result, info) -> Void in
+
+ // If we fail to get a result, print a message to the user that
+ // includes the value of `info`. For information about interpreting
+ // these keys, see Apple's documentation:
+ // https://developer.apple.com/documentation/photokit/phimagemanager/image_result_info_keys
+ switch (result, info) {
+ case let (result?, _):
+ image = result
+ case let (.none, info?):
+ fputs("Unable to create image:\n", stderr)
+ fputs("\(info)\n", stderr)
+ exit(1)
+ case (.none, .none):
+ fputs("Unable to create image:\n", stderr)
+ fputs("(unknown error)\n", stderr)
+ exit(1)
+ }
+ })
+
+ return image
+ }
+}
+
+extension NSImage {
+ func jpegData() -> Data {
+ let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil)!
+ let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
+ return bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!
+ }
}
func jpegDataFrom(image: NSImage) -> Data {
@@ -64,39 +96,33 @@ func jpegDataFrom(image: NSImage) -> Data {
let arguments = CommandLine.arguments
-if arguments.count != 3 {
+guard arguments.count == 3 {
fputs("Usage: \(arguments[0]) ASSET_ID SIZE\n", stderr)
exit(1)
}
-let assetId = arguments[1]
+let localIdentifier = arguments[1]
let size = Int(arguments[2])
-if size == nil {
+if size == nil || size <= 0 {
fputs("Unrecognised size: \(arguments[2])\n", stderr)
exit(1)
}
-let assetLookup = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
-
-if assetLookup.count == 0 {
- fputs("Unrecognised asset ID: \(assetId)\n", stderr)
- exit(1)
-}
-
-let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil).firstObject!
+let asset = getPhoto(withLocalIdentifier: localIdentifier)
let thumbnailPath =
- "/tmp/photos-reviewer/\(asset.localIdentifier.prefix(1))/\(asset.localIdentifier)_\(size!).jpg"
+ "/tmp/photos-reviewer/\(localIdentifier.prefix(1))/\(localIdentifier)_\(size!).jpg"
if !FileManager.default.fileExists(atPath: thumbnailPath) {
- let jpegData = jpegDataFrom(image: getAssetThumbnail(asset: asset, size: Double(size!)))
-
try! FileManager.default.createDirectory(
atPath: NSString(string: thumbnailPath).deletingLastPathComponent,
withIntermediateDirectories: true, attributes: nil)
- try! jpegData.write(to: URL(fileURLWithPath: thumbnailPath), options: [])
+ try! asset
+ .getImage(atSize: Double(size!))
+ .jpegData()
+ .write(to: URL(fileURLWithPath: thumbnailPath), options: [])
}
fputs(thumbnailPath, stdout)
server.py (6233) → server.py (5892)
diff --git a/server.py b/server.py
index b5200cc..fdee9fc 100755
--- a/server.py
+++ b/server.py
@@ -116,28 +116,26 @@ def index():
@functools.cache
-def get_thumbnail_path(local_identifier):
- if os.path.exists(f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_170.jpg'):
- return f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_170.jpg'
+def get_jpeg(local_identifier, *, size):
+ if os.path.exists(f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_{size}.jpg'):
+ return f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_{size}.jpg'
- # 85 * 2x
- return subprocess.check_output(['swift', 'get_asset_jpeg.swift', local_identifier, '170']).decode('utf8')
+ return subprocess.check_output(['swift', 'actions/get_asset_jpeg.swift', local_identifier, str(size)]).decode('utf8')
@app.route('/thumbnail')
def thumbnail():
local_identifier = request.args['localIdentifier']
- thumbnail_path = get_thumbnail_path(local_identifier)
+ thumbnail_path = get_jpeg(local_identifier, size = 85 * 2)
return send_file(thumbnail_path)
-@functools.cache
-def get_image_path(local_identifier):
- if os.path.exists(f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_2048.jpg'):
- return f'/tmp/photos-reviewer/{local_identifier[0]}/{local_identifier}_2048.jpg'
-
- return subprocess.check_output(['swift', 'get_asset_jpeg.swift', local_identifier, '2048']).decode('utf8')
+@app.route('/image')
+def image():
+ local_identifier = request.args['localIdentifier']
+ image_path = get_jpeg(local_identifier, size=2048)
+ return send_file(image_path)
def _perform_action(request, callback):
@@ -176,11 +174,6 @@ def run_action():
redirect_to = photos_data.all_assets[position - 1]['localIdentifier']
return redirect(url_for('index', localIdentifier=redirect_to))
-@app.route('/image')
-def image():
- local_identifier = request.args['localIdentifier']
- image_path = get_image_path(local_identifier)
- return send_file(image_path)
if __name__ == '__main__':