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>