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 %} -->