Skip to main content

get the original filename

ID
9159541
date
2023-05-14 07:36:33+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
18e63ef
message
get the original filename
changed files
4 files, 150 additions, 106 deletions

Changed files

actions/get_structural_metadata.swift (2010) → actions/get_structural_metadata.swift (2148)

diff --git a/actions/get_structural_metadata.swift b/actions/get_structural_metadata.swift
index 3e4fdf7..ff8a5fa 100644
--- a/actions/get_structural_metadata.swift
+++ b/actions/get_structural_metadata.swift
@@ -19,6 +19,7 @@ struct AssetData: Codable {
   var localIdentifier: String
   var creationDate: String?
   var isFavorite: Bool
+  var filename: String
 }
 
 struct Response: Codable {
@@ -60,11 +61,14 @@ func getAllAssets() -> [AssetData] {
   PHAsset
     .fetchAssets(with: PHAssetMediaType.image, options: nil)
     .enumerateObjects({ (asset, _, _) in
+      let resource = PHAssetResource.assetResources(for: asset)[0]
+
       allPhotos.append(
         AssetData(
           localIdentifier: asset.localIdentifier,
           creationDate: asset.creationDate?.ISO8601Format(),
-          isFavorite: asset.isFavorite
+          isFavorite: asset.isFavorite,
+          filename: resource.originalFilename
         )
       )
     })

actions/open_photos_app.applescript (143) → actions/open_photos_app.applescript (331)

diff --git a/actions/open_photos_app.applescript b/actions/open_photos_app.applescript
index 281465a..b353d8b 100644
--- a/actions/open_photos_app.applescript
+++ b/actions/open_photos_app.applescript
@@ -1,6 +1,13 @@
+#!/usr/bin/env osascript
+-- This script brings Photos.app to the front, and opens the selected photo.
+
 on run argv
+  if (count of argv) ≠ 1 then
+    tell me to error "Usage: open_photos_app.applescript [PHOTO_ID]"
+  end if
+
   tell application "Photos"
-  	spotlight media item id "3EF7B3B2-E7F9-4775-A568-3F21F278B833/L0/001"
-  	activate
+    spotlight media item id (item 1 of argv)
+    activate
   end tell
-end run
\ No newline at end of file
+end run

server.py (5892) → server.py (5919)

diff --git a/server.py b/server.py
index fdee9fc..22a410d 100755
--- a/server.py
+++ b/server.py
@@ -10,93 +10,115 @@ 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.config["SEND_FILE_MAX_AGE_DEFAULT"] = 24 * 60 * 60
 
 
 def get_asset_state(asset):
-    state_albums = [alb for alb in asset['albums'] if alb in {'Flagged', 'Rejected', 'Needs Action'}]
+    state_albums = [
+        alb for alb in asset["albums"] if alb in {"Flagged", "Rejected", "Needs Action"}
+    ]
 
     assert len(state_albums) <= 1
 
-    asset['display_albums'] = [alb for alb in asset['albums'] if alb not in state_albums]
+    asset["display_albums"] = [
+        alb for alb in asset["albums"] if alb not in state_albums
+    ]
 
     if len(state_albums) == 1:
         return state_albums[0]
     elif len(state_albums) == 0:
-        return 'Unknown'
+        return "Unknown"
     else:
-        raise RuntimeError(f'Asset {asset["localIdentifier"]} is in multiple states: {state_albums.join(", ")}')
+        raise RuntimeError(
+            f'Asset {asset["localIdentifier"]} is in multiple states: {state_albums.join(", ")}'
+        )
 
 
 class PhotosData:
     def __init__(self):
-        data = json.loads(subprocess.check_output(['swift', 'actions/get_structural_metadata.swift']))
+        data = json.loads(
+            subprocess.check_output(["swift", "actions/get_structural_metadata.swift"])
+        )
 
-        all_assets = sorted(data['assets'], key=lambda a: a['creationDate'])
-        self.all_positions = {asset['localIdentifier']: i for i, asset in enumerate(all_assets)}
+        all_assets = sorted(data["assets"], key=lambda a: a["creationDate"])
+        self.all_positions = {
+            asset["localIdentifier"]: i for i, asset in enumerate(all_assets)
+        }
 
-        all_albums = data['albums']
+        all_albums = data["albums"]
 
         for alb in all_albums:
-            alb['assetIdentifiers'] = set(alb['assetIdentifiers'])
+            alb["assetIdentifiers"] = set(alb["assetIdentifiers"])
 
         for asset in all_assets:
-            asset['albums'] = {alb['localizedTitle'] for alb in all_albums if asset['localIdentifier'] in alb['assetIdentifiers']}
-            asset['state'] = get_asset_state(asset)
+            asset["albums"] = {
+                alb["localizedTitle"]
+                for alb in all_albums
+                if asset["localIdentifier"] in alb["assetIdentifiers"]
+            }
+            asset["state"] = get_asset_state(asset)
 
         self.all_assets = all_assets
 
-    @functools.lru_cache(maxsize=0 if '--debug' in sys.argv else None)
+    @functools.lru_cache(maxsize=0 if "--debug" in sys.argv else None)
     def get_response(self, local_identifier):
         all_assets = self.all_assets
 
         position = self.all_positions[local_identifier]
 
-        prev_five = all_assets[position - 5:position]
+        prev_five = all_assets[position - 5 : position]
         this_asset = all_assets[position]
-        next_five = all_assets[position + 1:position + 6]
+        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)
+        return render_template(
+            "index.html",
+            assets=all_assets,
+            position=position,
+            prev_five=prev_five,
+            this_asset=this_asset,
+            next_five=next_five,
+        )
 
     def run_action(self, local_identifier, action):
-        subprocess.check_call(['swift', 'actions/run_action.swift', local_identifier, action])
+        subprocess.check_call(
+            ["swift", "actions/run_action.swift", local_identifier, action]
+        )
 
         this_asset = self.all_assets[self.all_positions[local_identifier]]
 
-        if action == 'toggle-favorite':
-            this_asset['isFavorite'] = not this_asset['isFavorite']
-        elif action == 'toggle-flagged':
-            this_asset['albums'].discard('Rejected')
-            this_asset['albums'].discard('Needs Action')
+        if action == "toggle-favorite":
+            this_asset["isFavorite"] = not this_asset["isFavorite"]
+        elif action == "toggle-flagged":
+            this_asset["albums"].discard("Rejected")
+            this_asset["albums"].discard("Needs Action")
 
             try:
-                this_asset['albums'].remove('Flagged')
+                this_asset["albums"].remove("Flagged")
             except KeyError:
-                this_asset['albums'].add('Flagged')
-        elif action == 'toggle-rejected':
-            this_asset['albums'].discard('Flagged')
-            this_asset['albums'].discard('Needs Action')
+                this_asset["albums"].add("Flagged")
+        elif action == "toggle-rejected":
+            this_asset["albums"].discard("Flagged")
+            this_asset["albums"].discard("Needs Action")
 
             try:
-                this_asset['albums'].remove('Rejected')
+                this_asset["albums"].remove("Rejected")
             except KeyError:
-                this_asset['albums'].add('Rejected')
-        elif action == 'toggle-needs-action':
-            this_asset['albums'].discard('Flagged')
-            this_asset['albums'].discard('Rejected')
+                this_asset["albums"].add("Rejected")
+        elif action == "toggle-needs-action":
+            this_asset["albums"].discard("Flagged")
+            this_asset["albums"].discard("Rejected")
 
             try:
-                this_asset['albums'].remove('Needs Action')
+                this_asset["albums"].remove("Needs Action")
             except KeyError:
-                this_asset['albums'].add('Needs Action')
-        elif action == 'toggle-cross-stitch':
+                this_asset["albums"].add("Needs Action")
+        elif action == "toggle-cross-stitch":
             try:
-                this_asset['albums'].remove('Cross stitch')
+                this_asset["albums"].remove("Cross stitch")
             except KeyError:
-                this_asset['albums'].add('Cross stitch')
+                this_asset["albums"].add("Cross stitch")
 
-
-        this_asset['state'] = get_asset_state(this_asset)
+        this_asset["state"] = get_asset_state(this_asset)
 
         self.get_response.cache_clear()
 
@@ -107,74 +129,70 @@ photos_data = PhotosData()
 @app.route("/")
 def index():
     try:
-        local_identifier = request.args['localIdentifier']
+        local_identifier = request.args["localIdentifier"]
     except KeyError:
         all_assets = photos_data.all_assets
-        return redirect(url_for('index', localIdentifier=all_assets[-1]['localIdentifier']))
+        return redirect(
+            url_for("index", localIdentifier=all_assets[-1]["localIdentifier"])
+        )
 
     return photos_data.get_response(local_identifier)
 
 
 @functools.cache
 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'
+    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"
+        )
 
-    return subprocess.check_output(['swift', 'actions/get_asset_jpeg.swift', local_identifier, str(size)]).decode('utf8')
+    return subprocess.check_output(
+        ["swift", "actions/get_asset_jpeg.swift", local_identifier, str(size)]
+    ).decode("utf8")
 
 
-@app.route('/thumbnail')
+@app.route("/thumbnail")
 def thumbnail():
-    local_identifier = request.args['localIdentifier']
+    local_identifier = request.args["localIdentifier"]
 
-    thumbnail_path = get_jpeg(local_identifier, size = 85 * 2)
+    thumbnail_path = get_jpeg(local_identifier, size=85 * 2)
     return send_file(thumbnail_path)
 
 
-@app.route('/image')
+@app.route("/image")
 def image():
-    local_identifier = request.args['localIdentifier']
+    local_identifier = request.args["localIdentifier"]
     image_path = get_jpeg(local_identifier, size=2048)
     return send_file(image_path)
 
 
-def _perform_action(request, callback):
-    local_identifier = request.args['localIdentifier']
-
-    position = photos_data.all_positions[local_identifier]
-
-    callback(local_identifier)
-
-    redirect_to = photos_data.all_assets[position - 1]['localIdentifier']
-
-    return redirect(url_for('index', localIdentifier=redirect_to))
-
-
-@app.route('/actions/reject')
-def reject():
-    return _perform_action(request, photos_data.reject)
-
-
-@app.route('/actions/needs_action')
-def needs_action():
-    return _perform_action(request, photos_data.needs_action)
-
-
-@app.route('/actions')
+@app.route("/actions")
 def run_action():
-    local_identifier = request.args['localIdentifier']
-    action = request.args['action']
+    local_identifier = request.args["localIdentifier"]
+    action = request.args["action"]
 
     photos_data.run_action(local_identifier, action)
 
-    if action in {'toggle-favorite', 'toggle-cross-stitch'} :
-        return redirect(url_for('index', localIdentifier=local_identifier))
-    elif action in {'toggle-flagged', 'toggle-rejected', 'toggle-needs-action'}:
+    if action in {"toggle-favorite", "toggle-cross-stitch"}:
+        return redirect(url_for("index", localIdentifier=local_identifier))
+    elif action in {"toggle-flagged", "toggle-rejected", "toggle-needs-action"}:
         position = photos_data.all_positions[local_identifier]
-        redirect_to = photos_data.all_assets[position - 1]['localIdentifier']
-        return redirect(url_for('index', localIdentifier=redirect_to))
+        redirect_to = photos_data.all_assets[position - 1]["localIdentifier"]
+        return redirect(url_for("index", localIdentifier=redirect_to))
+
+
+@app.route("/open", methods=["POST"])
+def open_photo():
+    local_identifier = request.args["localIdentifier"]
+
+    subprocess.check_call(
+        ["osascript", "actions/open_photos_app.applescript", local_identifier]
+    )
 
+    return b"", 204
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     app.run(debug="--debug" in sys.argv)

templates/index.html (5499) → templates/index.html (5900)

diff --git a/templates/index.html b/templates/index.html
index 06814b6..81d41c9 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,5 +1,7 @@
 <head>
   <link rel="stylesheet" href="/static/style.css">
+  
+  <title>{{ this_asset['filename'] }}</title>
 </head>
 
 <details id="debug">
@@ -44,29 +46,42 @@
 <img id="big" src="{{ url_for('image', localIdentifier=this_asset['localIdentifier']) }}">
 
 <script>
-document.onkeydown = function(e) {
-  if (e.key === "ArrowLeft") {
-    window.location = "/?localIdentifier={{ prev_five[-1]['localIdentifier'] }}";
-  } else if (e.key === "ArrowRight") {
-    {% if next_five %}
-      window.location = "/?localIdentifier={{ next_five[0]['localIdentifier'] }}";
-    {% endif %}
-  } else
-  if (e.key === "1") {
-    window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-flagged";
-  } else if (e.key === "2") {
-    window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-rejected";
-  } else if (e.key === "3") {
-    window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-needs-action";
-  }
-  else
-  if (e.key === "f") {
-    window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-favorite";
+  function httpPOST(url) {
+    var xmlHttp = null;
+
+    xmlHttp = new XMLHttpRequest();
+    xmlHttp.open("POST", url, false);
+    xmlHttp.send(null);
+    return xmlHttp.responseText;
   }
-  else
-  if (e.key === "c") {
-    window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-cross-stitch";
+
+  document.onkeydown = function(e) {
+    if (e.key === "ArrowLeft") {
+      window.location = "/?localIdentifier={{ prev_five[-1]['localIdentifier'] }}";
+    } else if (e.key === "ArrowRight") {
+      {% if next_five %}
+        window.location = "/?localIdentifier={{ next_five[0]['localIdentifier'] }}";
+      {% endif %}
+    } else
+    if (e.key === "1") {
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-flagged";
+    } else if (e.key === "2") {
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-rejected";
+    } else if (e.key === "3") {
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-needs-action";
+    }
+    else
+    if (e.key === "f") {
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-favorite";
+    }
+    else
+    if (e.key === "c") {
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-cross-stitch";
+    }
+    else
+    if (e.key === "o") {
+      httpPOST("/open?localIdentifier={{ this_asset['localIdentifier'] }}")
+    }
   }
-}
 </script>