photogal.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ;;;;;;;;;;;;;;;;;;;;;; PHOTOGAL ;;;;;;;;;;;;;;;;;;;;;;;
  3. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; v1.0 ;;;;;;
  4. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  5. ;; ;;
  6. ;; author: jordyn , - * ;;
  7. ;; authored: spokane valley, summer '22 . ` ;;
  8. ;; ^ ~ ';
  9. ;; PHOTO * , ' . ` ` * , ;;
  10. ;; , Grouper ' ` . ,* - . ;;
  11. ;; . And , ^ ' . ' . ` ` ' ;;
  12. ;; ` Labeler ' , * ' * ;;
  13. ;; , . , ` ' . ;;
  14. ;; ' - ' , ;;
  15. ;; ;;
  16. ;; ;;
  17. ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;;
  18. ;;;; -- ---- -- - DATA - -- ---- -- ;;;;
  19. (defvar *photogal/operating-photo-dir* nil)
  20. (defvar *photogal/all-photos* nil)
  21. (defvar *photogal/operating-table* nil)
  22. (defvar *photogal/--resize-photo* nil)
  23. (defvar *photogal/commands*
  24. '(("RET" . "Next")
  25. ("P" . "Prev")
  26. ("A" . "Add tag")
  27. ("D" . "Delete tag")
  28. ("F" . "Show filename")
  29. ("G" . "Refresh buffer")
  30. ("R" . "Resize photo")
  31. ("C" . "Compile and commit")))
  32. (defcustom photogal-default-directory "/Users/jwd/bench/photos/"
  33. "This is where photogal will look for photos.")
  34. (defcustom photogal/tags
  35. '(("a" . "art")
  36. ("c" . "cityscape")
  37. ("f" . "family")
  38. ("g" . "good")
  39. ("h" . "screenshot")
  40. ("l" . "politics")
  41. ("m" . "meme")
  42. ("o" . "computer")
  43. ("p" . "portrait")
  44. ("r" . "reaction-photo")
  45. ("t" . "photography")
  46. ("s" . "selfie"))
  47. "These are photo tags and the key to activate them.")
  48. ;;;; -- ---- -- - THE APP - -- ---- -- ;;;;
  49. (defun photogal (photo-dir)
  50. (interactive
  51. (list (read-directory-name
  52. "where are ur photos? " photogal-default-directory)))
  53. (let ((photos-origin-directory (directory-file-name photo-dir)))
  54. (setq *photogal/operating-photo-dir* (concat photos-origin-directory "-photogal"))
  55. (setq *photogal/all-photos* (photogal-all-photos photos-origin-directory))
  56. (photogal-init (photogal-current-file)))
  57. (photogal-init-operating-table)
  58. (photogal-generate-tag-commands))
  59. ;;;; -- ---- -- - INITIALIZATION - -- ---- -- ;;;;
  60. (defun photogal-init-operating-table ()
  61. (setq *photogal/operating-table*
  62. (mapcar (lambda (photo)
  63. (cons '() photo))
  64. *photogal/all-photos*)))
  65. (defun photogal-generate-tag-commands ()
  66. "Generate and activate M-x (photogal-toggle-tag-TAG) to tag curent photo,
  67. for all tags defined -- one function per tag."
  68. (mapcar (lambda (tag)
  69. (let ((tag-key (car tag))
  70. (tag-name (cdr tag)))
  71. (eval `(photogal-generate-tagger ,(intern tag-name)))
  72. (define-key photogal-mode-map (kbd tag-key)
  73. (intern (format "photogal-toggle-tag-%s" tag-name)))))
  74. photogal/tags))
  75. ;;;; -- ---- -- - TAG - -- ---- -- ;;;;
  76. (defun photogal-add-tag (new-tag new-tag-code)
  77. (interactive "sNew tag: \nsTag code (length 1): ")
  78. (let ((tag-code-too-long (> (length new-tag-code) 1))
  79. (tag-code-in-use (photogal-tag-code-in-use new-tag-code)))
  80. (if (or tag-code-too-long tag-code-in-use)
  81. (progn (message "tag code must be a single character and can't be already in use")
  82. (call-interactively 'photogal-add-tag))
  83. (photogal--add-tag new-tag new-tag-code))))
  84. (defun photogal-delete-tag (tag-code)
  85. (interactive "sDelete tag: ")
  86. (customize-save-variable
  87. 'photogal/tags
  88. (seq-remove (lambda (tag) (string= (car tag) tag-code)) photogal/tags))
  89. (photogal-refresh-buffer))
  90. (defun photogal--add-tag (new-tag new-tag-code)
  91. (let ((escaped-str-tag (string-replace " " "-" new-tag)))
  92. (customize-save-variable
  93. 'photogal/tags
  94. (cons (cons new-tag-code escaped-str-tag) photogal/tags)))
  95. (photogal-generate-tag-commands)
  96. (photogal-refresh-buffer))
  97. (defun photogal-tag-code-in-use (tag-code)
  98. (seq-contains-p
  99. photogal/tags tag-code
  100. (lambda (tag test) (string= (car tag) test))))
  101. (defun photogal-tags-for-file (file)
  102. "Give the tags that this file has."
  103. (car (rassoc file *photogal/operating-table*)))
  104. ;;;; -- ---- -- - TAGGING FILES - -- ---- -- ;;;;
  105. (defun photogal-for-file-toggle-tag (file tag)
  106. "If a file has the tag, remove it. If it doesn't have it, add it."
  107. (if (photogal-file-has-tag? file tag)
  108. (photogal-rm-tag-from-file file tag)
  109. (photogal-add-tag-to-file file tag)))
  110. (defun photogal-add-tag-to-file (file tag)
  111. "Append new tag for a file."
  112. (let ((tags (photogal-tags-for-file file)))
  113. (setcar (rassoc file *photogal/operating-table*)
  114. (seq-sort #'string< (seq-uniq (cons tag tags))))))
  115. (defun photogal-rm-tag-from-file (file tag)
  116. "Dissociate tag from file."
  117. (defun tags-without-tag (tags tag)
  118. (seq-sort
  119. #'string<
  120. (seq-uniq
  121. (seq-remove
  122. (lambda (tg) (string= tg tag)) tags))))
  123. (let ((tags (photogal-tags-for-file file)))
  124. (setcar (rassoc file *photogal/operating-table*)
  125. (tags-without-tag tags tag))))
  126. (defun photogal-file-has-tag? (file tag)
  127. "Does this file have this tag?"
  128. (let ((tags (photogal-tags-for-file file)))
  129. (seq-contains-p tags tag)))
  130. (defun photogal-get-tags-for-file (file)
  131. "What are all the tags for this file?"
  132. (car (rassoc file *photogal/operating-table*)))
  133. ;;;; -- ---- -- - FILE OPS - -- ---- -- ;;;;
  134. (defun photogal-all-photos (directory)
  135. "Give me a list of all the photos in my operating directory."
  136. (directory-files directory
  137. t directory-files-no-dot-files-regexp))
  138. (defun photogal-current-file ()
  139. "What is the file currently being operated on?"
  140. (car *photogal/all-photos*))
  141. ;;;; -- ---- -- - U I - -- ---- -- ;;;;
  142. (defun photogal-init (photo-file-path &optional show-filepath)
  143. "Set everything up in the buffer."
  144. (let ((buf (get-buffer-create "photogal")))
  145. (with-current-buffer buf
  146. (photogal-mode)
  147. (erase-buffer)
  148. (insert "\n")
  149. (insert " ")
  150. (insert-image
  151. (if *photogal/--resize-photo*
  152. (create-image photo-file-path 'imagemagick nil
  153. :width (- (window-pixel-width) 75))
  154. (create-image photo-file-path 'imagemagick nil
  155. :height (/ (window-pixel-height) 2))))
  156. (insert "\n\nCurrent tags: ")
  157. (insert (format "%s" (photogal-get-tags-for-file photo-file-path)))
  158. (insert "\n\n")
  159. (photogal--insert-print-color "Tag:\n\n" "red")
  160. (photogal-insert-tags-to-buffer photogal/tags)
  161. (photogal--insert-print-color "\n\nCommands:\n\n" "red")
  162. (photogal-insert-commands-to-buffer
  163. *photogal/commands*)
  164. (when show-filepath
  165. (insert "\n\n")
  166. (insert (photogal-current-file)))
  167. )
  168. (switch-to-buffer buf)))
  169. (defun photogal-next-file ()
  170. (interactive)
  171. (setq *photogal/all-photos*
  172. (append (cdr *photogal/all-photos*) (list (car *photogal/all-photos*))))
  173. (photogal-refresh-buffer))
  174. (defun photogal-prev-file ()
  175. (interactive)
  176. (setq *photogal/all-photos*
  177. (append (last *photogal/all-photos*) (butlast *photogal/all-photos*)))
  178. (photogal-refresh-buffer))
  179. (defun photogal-refresh-buffer (&optional show-filepath)
  180. "Refresh buffer."
  181. (interactive)
  182. (message "refreshing buffer")
  183. (photogal-init (photogal-current-file) show-filepath)
  184. (photogal-generate-tag-commands)
  185. (beginning-of-buffer))
  186. (defun photogal-resize-photo ()
  187. (interactive)
  188. (setq *photogal/--resize-photo* (not *photogal/--resize-photo*))
  189. (photogal-refresh-buffer))
  190. (defun photogal-show-filepath ()
  191. (interactive)
  192. (photogal-refresh-buffer t))
  193. (defun photogal-insert-tags-to-buffer (tags)
  194. "Pretty print the tags with their toggle key."
  195. (mapcar (lambda (tag)
  196. (let* ((key-command (car tag))
  197. (tag-name (cdr tag))
  198. (activated (photogal-file-has-tag? (photogal-current-file) tag-name)))
  199. (photogal--pprint-key-command key-command tag-name activated)))
  200. (seq-sort (lambda (t1 t2) (string< (car t1) (car t2))) tags)))
  201. (defun photogal-insert-commands-to-buffer (commands)
  202. "Pretty print the commands with their invoke key."
  203. (mapcar (lambda (command)
  204. (let ((key-command (car command))
  205. (command-name (cdr command)))
  206. (photogal--pprint-key-command key-command command-name)))
  207. commands))
  208. (defun photogal--pprint-key-command (key-to-type name-of-command &optional activated)
  209. "Make the low-level insertions to the buffer to render a key-command."
  210. (let ((length-of-unit (+ (length key-to-type) (length name-of-command) 3)))
  211. (when (> (+ (+ (current-column) length-of-unit)
  212. 10)
  213. (window-width))
  214. (insert "\n"))
  215. (insert "[")
  216. (if activated
  217. (photogal--insert-print-color key-to-type "SeaGreen2")
  218. (photogal--insert-print-color key-to-type "dark gray"))
  219. (insert "] ")
  220. (photogal--insert-print-color name-of-command "blue" (- 16 (length key-to-type)))
  221. (insert " ")))
  222. (defun photogal--insert-print-color (string-to-insert-to-buffer color &optional padding)
  223. "Insert some text in this color."
  224. (let ((beg (point))
  225. (padding
  226. (if padding
  227. (format "%s" padding)
  228. "0")))
  229. (insert (format (concat "%-" padding "s") string-to-insert-to-buffer))
  230. (put-text-property beg (point) 'font-lock-face `(:foreground ,color))))
  231. ;;;; -- ---- -- - META SHIT - -- ---- -- ;;;;
  232. (defmacro photogal-generate-tagger (name)
  233. "Generate function to toggle a tag which is itself on the current file.
  234. One of these is needed per tag. For instance if you want to create the tag
  235. 'cool', you can run and evaluate (photogal-generate-tagger cool) to create a new
  236. function `photogal-toggle-tag-cool` that will toggle the tag 'cool' for
  237. the current file."
  238. (let ((my-funcname (intern (format "photogal-toggle-tag-%s" name))))
  239. `(defun ,my-funcname ()
  240. (interactive)
  241. (photogal-for-file-toggle-tag (photogal-current-file) ,(format "%s" name))
  242. (photogal-refresh-buffer))))
  243. ;;;; -- ---- -- -MOVING FILES AROUND- -- ---- -- ;;;;
  244. (defun photogal-files--get-extension (filepath)
  245. (file-name-extension filepath))
  246. (defun photogal-files--generate-unique-identifier (filepath)
  247. "Not GUARANTEED unique, but probably unique enough for my purposes."
  248. (seq-take (md5 (concat (current-time-string) filepath))
  249. 6))
  250. (defun photogal-files--new-file-name-for-photo (filepath tags)
  251. (cons
  252. filepath
  253. (file-name-with-extension
  254. (file-name-concat
  255. *photogal/operating-photo-dir*
  256. (concat
  257. (photogal-files--generate-unique-identifier filepath)
  258. "-"
  259. (format-time-string "%M%H,%d%m%y")
  260. "_"
  261. (string-join tags "_")
  262. ))
  263. (file-name-extension filepath))))
  264. (defun photogal-files--new-filenames-for-photos ()
  265. (mapcar
  266. (lambda (photo)
  267. (let ((filepath (cdr photo))
  268. (tags (car photo)))
  269. (photogal-files--new-file-name-for-photo filepath tags)))
  270. *photogal/operating-table*))
  271. (defun photogal-compile-and-commit ()
  272. (interactive)
  273. (if (y-or-n-p (format "Are u sure? "))
  274. (mapcar
  275. (lambda (file-rename)
  276. (let ((origin (car file-rename))
  277. (destination (cdr file-rename)))
  278. (copy-file origin destination)))
  279. (photogal-files--new-filenames-for-photos))
  280. (message "whoops")))
  281. ;;;; -- ---- -- - KEY BINDINGS - -- ---- -- ;;;;
  282. (defvar photogal-mode-map nil "Keymap for `photogal-mode`")
  283. (progn
  284. (setq photogal-mode-map (make-sparse-keymap))
  285. (define-key photogal-mode-map (kbd "G") 'photogal-refresh-buffer)
  286. (define-key photogal-mode-map (kbd "RET") 'photogal-next-file)
  287. (define-key photogal-mode-map (kbd "<right>") 'photogal-next-file)
  288. (define-key photogal-mode-map (kbd "P") 'photogal-prev-file)
  289. (define-key photogal-mode-map (kbd "<left>") 'photogal-prev-file)
  290. (define-key photogal-mode-map (kbd "A") 'photogal-add-tag)
  291. (define-key photogal-mode-map (kbd "D") 'photogal-delete-tag)
  292. (define-key photogal-mode-map (kbd "F") 'photogal-show-filepath)
  293. (define-key photogal-mode-map (kbd "R") 'photogal-resize-photo)
  294. (define-key photogal-mode-map (kbd "C") 'photogal-compile-and-commit))
  295. (define-derived-mode photogal-mode text-mode "photogal"
  296. "Major mode for grouping and labeling images.")