Skip to main content

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__':