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