Making the best use of my iPhone Pictures

Context

Nowadays, pictures don't carry the same sentimental value as they did for our parents. They used to be more precious and meaningful. Thanks to out smartphones, we take so many pictures, but spend little time looking at them and reflecting on the moments they seized. I want to make make every picture I take count. Therefore, I built a way to seamlessly integrate pictures from my iPhone into my org roam notes. This in turn helps me build photo albums for my daughter and my dog that we can all enjoy!

High level architecture

The system is made of two parts: the image converter and image linker.

The image converter runs continuously and processes incoming pictures making them suitable for insertion. It converts HEIC pictures to png and resize them to not take too much space.

The image linker is an Emacs extension that helps select and insert those pictures.

The image converter

The converter is a program watches new image files showing up in my Downloads folder. When it sees one, it converts it to PNG (if necessary), and resizes it. Finally, it copies the processed picture into a folder that I call the landing zone and copies the original into a backup folder. This is illustrated below:


┌────────────────┐       ┌────────────────────────────┐
│ iOS device     │       │ MacOS Laptop               │
│       ┌─────┐  │       │   ┌────────┐               │
│       │ HEIC│  │       │   │ HEIC   │               │
│       │ File│─Airdrop──┼──▶│ File   │────────┐      │
│       │     │  │       │   │        │        │      │
│       └─────┘  │       │   └────────┘        │      │
└────────────────┘       │        │            │      │
                         │        ▼            │      │
                         │   ┌────────┐        │      │
                         │   │ PNG    │        │      │
                         │   │ File   │        │      │
                         │   │        │        │      │
                         │   └────────┘        │      │
                         │        │            │      │
                         │        ▼            │      │
                         │   ┌────────┐        │      │
                         │   │ Resized│        │      │
                         │   │ PNG    │        │      │
                         │   │ File   │        │      │
                         │   └────────┘        │      │
                         │        │            │      │
                         │        ▼            ▼      │
                         │   ┏━━━━━━━━┓   ┏━━━━━━━━┓  │
                         │   ┃ Landing┃   ┃ Backup ┃  │
                         │   ┃ Zone   ┃   ┃        ┃  │
                         │   ┃ Folder ┃   ┃ Folder ┃  │
                         │   ┗━━━━━━━━┛   ┗━━━━━━━━┛  │
                         └────────────────────────────┘

When I airdrop files to my Mac, they show up in the Downloads folder and those files get picked up and transformed by the image converter and put in the landing zone.

I wrote it in Clojure as it is my go-to language for side projects. The code polls the Downloads folder for new image files and once it finds on, it processes it. The whole code is in this repo.

At the core of it are two commands from imagemagick I leveraged to convert HEIC to PNG and resize a PNG (to reduce its size):

mogrify -resize "50%" $file
mogrify -format png $file

You can try it out by running something like:

mkdir ~/Downloads/{backup,landing}

lein run  -- \
--dest-folder "~/Downloads/landing" \
--backup-folder "~/Downloads/backup" \
--input-folder "~/Downloads"

The image linker

The image linker is an emacs package that helps me insert pictures from the landing zone into org buffers.

I built it with a single command, for convenience:

  • When editing org files, running the command opens image-dired to show thumbnails of all the images in the landing zone
  • Running the command again while hovering over an image from the landing zone prompts for a subject, caption, and date. It moves the image to a folder on Dropbox (one per subject) and inserts it into the org buffer.
  • How often the image is inserted?

    • Most of the images are inserted twice: once at the point and once in the album file for that specific subject (my dog for example). This lets me have an "album" for each of my subjects while inserting images in my daily note.
    • If the image is inserted directly into the album file of the subject, there is no need to insert it twice.
             ┌─────────────────────┐
             │   Calling the       │
             │ image-attach command│
             └──────────┬──────────┘
                        │
                        ▼
             ┌─────────────────────┐
             │   Editing an org    │
             │       file?         │
             └─────────────────────┘
                        │
                        │
         ┌────Yes───────┴─────No──────┐
         │                            │
         ▼                            ▼
┌───────────────────┐      ┌─────────────────────┐
│Open image-dired   │      │   Prompt for        │
│in the landing     │      │   information       │
│zone to show       │      │   about the image   │
│thumbnails         │      │   at point          │
│                   │      │                     │
│Remember the file  │      └─────────────────────┘
│we were called     │                 │
│from               │               Then
└───────────────────┘                 │
                                      │
                                      ▼
                          ┌──────────────────────┐
                          │   Insert the         │
                          │   image at point     │
                          │   in the last opened │
                          │   org buffer         │
                          └──────────────────────┘

First, I defined a couple of settings that the user can customize:

(defcustom image-link-subjects-settings '()
  "A-list of settings for picture subjects.
The keys are subject names and the values are a-list containing:
'album', a folder to put pictures into once inserted.
'file', an org file that will contain all the pictures for that subject.

Example:
'((\"armand\" . ((\"album\" . \"/Users/laurent/.roam/album_armand/\")
   (\"file\" . \"/Users/laurent/.roam/20220119215323-armand_s_photo_album_2022.org\")))
 (\"josephine\" . ((\"album\" . \"/Users/laurent/.roam/album_josephine/\")
   (\"file\" . \"/Users/laurent/.roam/20220119215313-josephine_s_photo_album_2022.org\"))))"
  :group 'image-link
  :type 'a-list)

(defcustom image-link-folder nil
  "Path to the folder containing images to insert."
  :group 'image-link
  :type 'string)

Then I defined a function to show an image picker in a folder and save details of the current file:

(defun image-link-launch-image-picker (folder)
  "Open an image picked in FOLDER and remember the file we are coming from."
  (let ((bfn (buffer-file-name))
        (bf (current-buffer)))
    (image-dired folder)
    (setq-local image-link-destination-file bfn
                image-link-destination-buffer bf)
    (delete-other-windows)))

When I select an image I want to insert it as follows:

#+DATE:<2022-01-30 Sun>
#+CAPTION: Josephine in the backyard with Armand
#+ATTR_ORG: :width 300
#+ATTR_HTML: :width 600
[[/Users/laurent/.roam/album_josephine/2165fd09-b1f8-3820-3629-b6cdea367f50.png]]

I built the following function to ask the user for all those fields:

(defun image-link-attach-image-at-point ()
  "Attach image at point into the previously visited 'org-mode' buffer."
  (let* ((fname                 (image-dired-original-file-name))
         (kind                  (completing-read "Kind: " (a-keys image-link-subjects-settings)))
         (dateraw               (completing-read "Date: " '("today" "other")))
         (ts                    (if (string= dateraw "today")
                                    (format-time-string "<%Y-%m-%d %a>" (current-time))
                                  (with-temp-buffer
                                    (org-time-stamp nil)
                                    org-last-inserted-timestamp)))
         (description           (read-from-minibuffer "Enter a description: "))
         (uuid                  (s-concat (image-link-uuid-create) ".png"))
         (subject               (a-get image-link-subjects-settings kind))
         (album                 (a-get subject "album"))
         (albumfile             (a-get subject "file"))
         (path                  (s-concat album uuid))
         (link                  (s-concat
                                 "#+DATE:" ts
                                 "\n#+CAPTION: " description
                                 "\n#+ATTR_ORG: :width 300"
                                 "\n#+ATTR_HTML: :width 600"
                                 "\n[[" path "]]\n\n\n"))
         (link-with-star        (s-concat "* " ts "\n\n" link)))
    (message "Attaching %s as %s in %s" fname path image-link-destination-file)
    ;; Quit the buffer
    (pop-to-buffer image-link-destination-buffer)
    (delete-other-windows)
    ;; Move the image where it should go
    (f-move fname path)
    ;; Insert a block here
    (if (string= albumfile image-link-destination-file)
        ;; Insert just here
        (insert link-with-star)
      (progn
        (insert link)
        (f-append-text link-with-star 'utf-8 albumfile)))))

Then all we have to do is wrap the two functions we defined in a top level interactive command:

(defun image-link-run ()
  "Entry point for image-link.
If in 'org-mode' pops to select a file from the landing zone.
In 'image-dired' mode inserts the file in the 'org-mode' buffer we came from."
  (interactive)
  (cond
   ;; User picked a file
   ((bound-and-true-p image-link-destination-file)
    (image-link-attach-image-at-point))

   ((< (length (f-glob "*.png" image-link-folder)) 1)
    (message "No pictures in the landing zone"))

   ((string-equal major-mode "org-mode")
    (image-link-launch-image-picker image-link-folder)
    (message "Please run the same command again while over a file you want to insert"))

   (t (message "Need to run from an org mode buffer"))))

Calling image-link-run from an org buffer takes me to the list of images with thumbnails, calling it again insert the image in the org buffer.

The code for the emacs package is at https://github.com/charignon/image-link

These two little programs not only saved me hours of manual work but are also building beautiful albums of my daughter and dog!