Skip to main content

continue fleshing out get_asset_jpeg; add a web server

ID
2779ea2
date
2023-05-13 07:38:58+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
12212bb
message
continue fleshing out get_asset_jpeg; add a web server
changed files
4 files, 195 additions, 37 deletions

Changed files

get_asset_data.swift (1491) → get_asset_data.swift (0)

diff --git a/get_asset_data.swift b/get_asset_data.swift
deleted file mode 100644
index 1bb6250..0000000
--- a/get_asset_data.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Cocoa
-import Photos
-
-let assetId = "55CF4C5B-8BE3-4216-B158-3EF86AAAC5C5/L0/001"
-
-let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil).firstObject!
-
-// https://stackoverflow.com/a/48755517/1558022
-func getAssetThumbnail(asset: PHAsset, size: Double) -> NSImage {
-    let manager = PHImageManager.default()
-    let option = PHImageRequestOptions()
-    var thumbnail = NSImage()
-    option.isSynchronous = true
-    manager.requestImage(for: asset, targetSize: CGSize(width: size, height: size), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
-            thumbnail = result!
-    })
-    return thumbnail
-}
-
-func jpegDataFrom(image:NSImage) -> Data {
-    let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!
-    let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
-    let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!
-    return jpegData
-}
-
-let thumbnailPath = "/tmp/photos-reviewer/\(asset.localIdentifier.prefix(1))/\(asset.localIdentifier)_65.jpg"
-
-if !FileManager.default.fileExists(atPath: thumbnailPath) {
-  let jpegData = jpegDataFrom(image: getAssetThumbnail(asset: asset, size: 65.0))
-
-  try! FileManager.default.createDirectory(atPath: NSString(string: thumbnailPath).deletingLastPathComponent, withIntermediateDirectories: true, attributes: nil)
-
-  try! jpegData.write(to: URL(fileURLWithPath: thumbnailPath), options: [])
-}
-
-print(thumbnailPath)

get_asset_jpeg.swift (0) → get_asset_jpeg.swift (3316)

diff --git a/get_asset_jpeg.swift b/get_asset_jpeg.swift
new file mode 100644
index 0000000..ca5c930
--- /dev/null
+++ b/get_asset_jpeg.swift
@@ -0,0 +1,102 @@
+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
+}
+
+func jpegDataFrom(image: NSImage) -> Data {
+  let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!
+  let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
+  let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!
+  return jpegData
+}
+
+let arguments = CommandLine.arguments
+
+if arguments.count != 3 {
+  fputs("Usage: \(arguments[0]) ASSET_ID SIZE\n", stderr)
+  exit(1)
+}
+
+let assetId = arguments[1]
+let size = Int(arguments[2])
+
+if size == nil {
+  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 thumbnailPath =
+  "/tmp/photos-reviewer/\(asset.localIdentifier.prefix(1))/\(asset.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: [])
+}
+
+fputs(thumbnailPath, stdout)

server.py (0) → server.py (1715)

diff --git a/server.py b/server.py
new file mode 100755
index 0000000..d79b08d
--- /dev/null
+++ b/server.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+import functools
+import json
+import subprocess
+
+from flask import Flask, redirect, render_template, request, send_file, url_for
+
+
+app = Flask(__name__)
+app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 24 * 60 * 60
+
+
+@app.route("/")
+def index():
+    data = json.load(open('out.json'))
+
+    all_assets = sorted(data['assets'], key=lambda a: a['creationDate'])
+
+    try:
+        local_identifier = request.args['localIdentifier']
+    except KeyError:
+        return redirect(url_for('index', localIdentifier=all_assets[-1]['localIdentifier']))
+
+    position = next(i for i, asset in enumerate(all_assets) if asset['localIdentifier'] == local_identifier)
+
+    prev_five = all_assets[position - 5:position]
+    this_asset = all_assets[position]
+    next_five = all_assets[position + 1:position + 6]
+
+    return render_template('index.html', assets=all_assets, position=position, prev_five=prev_five, this_asset=this_asset, next_five=next_five)
+
+
+@functools.cache
+def get_thumbnail_path(local_identifier):
+    return subprocess.check_output(['swift', 'get_asset_jpeg.swift', local_identifier, '65']).decode('utf8')
+
+
+@app.route('/thumbnail')
+def thumbnail():
+    local_identifier = request.args['localIdentifier']
+    thumbnail_path = get_thumbnail_path(local_identifier)
+    return send_file(thumbnail_path)
+
+
+@functools.cache
+def get_image_path(local_identifier):
+    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_image_path(local_identifier)
+    return send_file(image_path)
+
+
+if __name__ == '__main__':
+    app.run(debug=True)

templates/index.html (0) → templates/index.html (861)

diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..dd28497
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,34 @@
+<style>
+  .thumbnail {
+    object-fit: cover;
+    aspect-ratio: 1 / 1;
+    border: 2px solid red;
+  }
+
+  .thumbnail_small {
+    width: 65px;
+    height: 65px;
+  }
+
+  .thumbnail_big {
+    width: 85px;
+    height: 85px;
+  }
+</style>
+
+{% for a in prev_five %}
+  <img src="{{ url_for('thumbnail', localIdentifier=a['localIdentifier']) }}" class="thumbnail thumbnail_small">
+{% endfor %}
+
+<img src="{{ url_for('thumbnail', localIdentifier=this_asset['localIdentifier']) }}" class="thumbnail thumbnail_big">
+
+{% for a in next_five %}
+  <img src="{{ url_for('thumbnail', localIdentifier=a['localIdentifier']) }}" class="thumbnail thumbnail_small">
+{% endfor %}
+
+<img src="{{ url_for('image', localIdentifier=this_asset['localIdentifier']) }}">
+
+
+<!--{% for a in assets %}
+  <img src="{{ url_for('thumbnail', localIdentifier=a['localIdentifier']) }}">
+{% endfor %} -->