Skip to main content

approved, not flagged

ID
3584f96
date
2023-05-14 14:50:45+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
5710777
message
approved, not flagged
changed files
8 files, 44 additions, 29 deletions

Changed files

README.md (621) → README.md (1548)

diff --git a/README.md b/README.md
index 3e22e07..491b161 100644
--- a/README.md
+++ b/README.md
@@ -3,13 +3,29 @@
 This is a lightweight app for reviewing my photos from Photos.app.
 I can navigate and review photos entirely using my keyboard.
 
-This video gives an overview of the basic commands:
+This video shows you how I use it:
 
-In this demo video, I use the arrow keys to quickly switch between two photos and compare them (a ["blink diff"][blink_diff]), then I type `2` to reject one photo and `1` to approve another.
-When I move to the next photo, I type `f` to mark it as a favourite.
+https://github.com/alexwlchan/photo-reviewer/assets/301220/dffbe69d-0717-40ef-a091-d06f40cc948e
 
+I can use the following commands:
 
+*   ⬅️ / ➡️ – navigate back and forth
+*   1️⃣ – approve the photo, which adds it to the "Approved" album
+*   2️⃣ – reject the photo, which adds it to the "Rejected" album
+*   3️⃣ – add the photo to the "Needs Action" album
+*   `f` – mark the photo as a favourite
+*   `c` – add the photo to my "Cross stitch" album
+*   `o` – open the photo in Photos.app
 
-https://github.com/alexwlchan/photo-reviewer/assets/301220/dffbe69d-0717-40ef-a091-d06f40cc948e
+## Motivation
+
+When I take photos, I often take a lot of similar shots so that I'm more likely to get a good picture.
+This means I have a growing photo library full of pictures which are blurry/similar/duplicates – I'd like to filter down to just the best version of each shot.
+
+A while back I tried using [Darkroom] and their [Flag & Reject workflow].
+I like the idea, and I used it for a while, but the app was very buggy and slow on my Mac – it would sometimes crash, sometimes take 10+ seconds to switch between photos.
+
+This app is my replacement for the reviewing portion of Darkroom – I can switch between photos quickly, and control it entirely using the keyboard.
 
-[blink_diff]: https://en.wikipedia.org/wiki/Blink_comparator
+[Darkroom]: https://darkroom.co/
+[Flag & Reject workflow]: https://medium.com/@jasperhauser/manage-your-growing-darkroom-photo-library-with-flag-reject-77c9e1816ef2

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

diff --git a/actions/get_structural_metadata.swift b/actions/get_structural_metadata.swift
index ff8a5fa..66a1b0c 100644
--- a/actions/get_structural_metadata.swift
+++ b/actions/get_structural_metadata.swift
@@ -19,7 +19,6 @@ struct AssetData: Codable {
   var localIdentifier: String
   var creationDate: String?
   var isFavorite: Bool
-  var filename: String
 }
 
 struct Response: Codable {
@@ -61,14 +60,12 @@ 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,
-          filename: resource.originalFilename
+          isFavorite: asset.isFavorite
         )
       )
     })

actions/run_action.swift (4889) → actions/run_action.swift (4897)

diff --git a/actions/run_action.swift b/actions/run_action.swift
index 123a25a..aaeb821 100644
--- a/actions/run_action.swift
+++ b/actions/run_action.swift
@@ -12,11 +12,11 @@
 ///       If an image is already a favorite, unmark it as such.
 ///       If an image isn't a favorite, mark it as a favorite.
 ///
-///     toggle-flagged
+///     toggle-approved
 ///     toggle-rejected
 ///     toggle-needs-action
 ///       When I review an image, it gets sorted into one of three buckets,
-///       which have corresponding albums in Photos: Flagged, Rejected,
+///       which have corresponding albums in Photos: Approved, Rejected,
 ///       Needs Action.
 ///
 ///       These actions add an asset to the appropriate album, and remove it
@@ -125,7 +125,7 @@ guard arguments.count == 3 else {
 
 let action = arguments[2]
 
-let flagged = getAlbum(withName: "Flagged")
+let approved = getAlbum(withName: "Approved")
 let rejected = getAlbum(withName: "Rejected")
 let needsAction = getAlbum(withName: "Needs Action")
 let crossStitch = getAlbum(withName: "Cross stitch")
@@ -137,16 +137,16 @@ try PHPhotoLibrary.shared().performChangesAndWait {
 
   if action == "toggle-favorite" {
     changeAsset.isFavorite = !photo.isFavorite
-  } else if action == "toggle-flagged" {
-    photo.toggle(inAlbum: flagged)
+  } else if action == "toggle-approved" {
+    photo.toggle(inAlbum: approved)
     photo.remove(fromAlbum: rejected)
     photo.remove(fromAlbum: needsAction)
   } else if action == "toggle-rejected" {
-    photo.remove(fromAlbum: flagged)
+    photo.remove(fromAlbum: approved)
     photo.toggle(inAlbum: rejected)
     photo.remove(fromAlbum: needsAction)
   } else if action == "toggle-needs-action" {
-    photo.remove(fromAlbum: flagged)
+    photo.remove(fromAlbum: approved)
     photo.remove(fromAlbum: rejected)
     photo.toggle(inAlbum: needsAction)
   } else if action == "toggle-cross-stitch" {

recording_720.mov (14403734) → recording_720.mov (0)

diff --git a/recording_720.mov b/recording_720.mov
deleted file mode 100644
index 66f8ba3..0000000
Binary files a/recording_720.mov and /dev/null differ

server.py (5919) → server.py (6041)

diff --git a/server.py b/server.py
index 22a410d..025abc3 100755
--- a/server.py
+++ b/server.py
@@ -15,7 +15,7 @@ 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"}
+        alb for alb in asset["albums"] if alb in {"Approved", "Rejected", "Needs Action"}
     ]
 
     assert len(state_albums) <= 1
@@ -36,6 +36,10 @@ def get_asset_state(asset):
 
 class PhotosData:
     def __init__(self):
+        self.fetch_metadata()
+
+    def fetch_metadata(self):
+        print("Fetching metadata from Photos.app...")
         data = json.loads(
             subprocess.check_output(["swift", "actions/get_structural_metadata.swift"])
         )
@@ -88,16 +92,16 @@ class PhotosData:
 
         if action == "toggle-favorite":
             this_asset["isFavorite"] = not this_asset["isFavorite"]
-        elif action == "toggle-flagged":
+        elif action == "toggle-approved":
             this_asset["albums"].discard("Rejected")
             this_asset["albums"].discard("Needs Action")
 
             try:
-                this_asset["albums"].remove("Flagged")
+                this_asset["albums"].remove("Approved")
             except KeyError:
-                this_asset["albums"].add("Flagged")
+                this_asset["albums"].add("Approved")
         elif action == "toggle-rejected":
-            this_asset["albums"].discard("Flagged")
+            this_asset["albums"].discard("Approved")
             this_asset["albums"].discard("Needs Action")
 
             try:
@@ -105,7 +109,7 @@ class PhotosData:
             except KeyError:
                 this_asset["albums"].add("Rejected")
         elif action == "toggle-needs-action":
-            this_asset["albums"].discard("Flagged")
+            this_asset["albums"].discard("Approved")
             this_asset["albums"].discard("Rejected")
 
             try:
@@ -177,7 +181,7 @@ def run_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"}:
+    elif action in {"toggle-approved", "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))

static/style.css (3058) → static/style.css (3060)

diff --git a/static/style.css b/static/style.css
index 4cd780f..ae9a6fd 100644
--- a/static/style.css
+++ b/static/style.css
@@ -63,13 +63,13 @@ a {
   font-family: serif;
 }
 
-#thumbnails div.thumbnail.state-Flagged {
+#thumbnails div.thumbnail.state-Approved {
   border-color: green;
   border-width: 2px;
   margin: 0;
 }
 
-#thumbnails div.thumbnail.state-Flagged .state {
+#thumbnails div.thumbnail.state-Approved .state {
   background: green;
 }
 

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

diff --git a/templates/index.html b/templates/index.html
index 81d41c9..35df884 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,7 +1,5 @@
 <head>
   <link rel="stylesheet" href="/static/style.css">
-  
-  <title>{{ this_asset['filename'] }}</title>
 </head>
 
 <details id="debug">
@@ -64,7 +62,7 @@
       {% endif %}
     } else
     if (e.key === "1") {
-      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-flagged";
+      window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-approved";
     } else if (e.key === "2") {
       window.location = "/actions?localIdentifier={{ this_asset['localIdentifier'] }}&action=toggle-rejected";
     } else if (e.key === "3") {

templates/thumbnail.html (665) → templates/thumbnail.html (666)

diff --git a/templates/thumbnail.html b/templates/thumbnail.html
index 229bb16..89d1703 100644
--- a/templates/thumbnail.html
+++ b/templates/thumbnail.html
@@ -1,6 +1,6 @@
 <a href="{{ url_for('index', localIdentifier=asset['localIdentifier'])}}">
   <div class="thumbnail state-{{ asset['state'] | replace(' ', '-') }}">
-    {% if asset['state'] == 'Flagged' %}
+    {% if asset['state'] == 'Approved' %}
       <div class="state">✓</div>
     {% elif asset['state'] == 'Rejected' %}
       <div class="state">✘</div>