photogal.el 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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/photos-origin-directory* nil)
  24. (defvar *photogal/commands*
  25. '(("RET" . "Next")
  26. ("P" . "Prev")
  27. ("A" . "Add tag")
  28. ("D" . "Delete tag")
  29. ("F" . "Show filename")
  30. ("G" . "Refresh buffer")
  31. ("R" . "Resize photo")
  32. ("C" . "Compile and commit")
  33. ("N" . "Name the file")))
  34. (defcustom photogal-default-directory "/Users/jwd/bench/photos/"
  35. "This is where photogal will look for photos.")
  36. (defcustom photogal/tags
  37. '(("a" . "art")
  38. ("c" . "cityscape")
  39. ("f" . "family")
  40. ("g" . "good")
  41. ("h" . "screenshot")
  42. ("l" . "politics")
  43. ("m" . "meme")
  44. ("o" . "computer")
  45. ("p" . "portrait")
  46. ("r" . "reaction-photo")
  47. ("t" . "photography")
  48. ("s" . "selfie"))
  49. "Tags and key-command to associate to photos.")
  50. (defun photogal-get-tags-for-file (photo-filepath)
  51. "what tags does this file have?"
  52. (photogal--get-tags (photogal--lookup-photo photo-filepath)))
  53. (defun photogal-set-tags-for-file (photo-filepath tags)
  54. "make this file have these tags"
  55. (photogal--set-tags (photogal--lookup-photo photo-filepath) tags))
  56. (defun photogal-get-name-for-file (photo-filepath)
  57. "does this file have a user-given name?"
  58. (photogal--get-name (photogal--lookup-photo photo-filepath)))
  59. (defun photogal-set-name-for-file (photo-filepath name)
  60. "give this file a Proper name. (embedded in final filename) (optional)"
  61. (photogal--set-name (photogal--lookup-photo photo-filepath) name))
  62. ;; // internal \\ ;;
  63. (defun photogal--get-filepath (photo)
  64. (car photo))
  65. (defun photogal--lookup-photo (photo-filepath)
  66. (assoc photo-filepath *photogal/operating-table*))
  67. (defun photogal--get-tags (photo)
  68. "What are all the tags for this file?"
  69. (caadr photo))
  70. (defun photogal--set-tags (photo tags)
  71. (setcar (cadr photo)
  72. tags))
  73. (defun photogal--get-name (photo)
  74. (cadadr photo))
  75. (defun photogal--set-name (photo name)
  76. (setcar (cdadr photo)
  77. name))
  78. ;; \\ internal // ;;
  79. (defun photogal-advance-photo ()
  80. "Move forward by one photo."
  81. (setq *photogal/all-photos*
  82. (append (cdr *photogal/all-photos*) (list (car *photogal/all-photos*)))))
  83. (defun photogal-rewind-photo ()
  84. "Reverse by one photo."
  85. (setq *photogal/all-photos*
  86. (append (last *photogal/all-photos*) (butlast *photogal/all-photos*))))
  87. ;;;; -- ---- -- - THE APP - -- ---- -- ;;;;
  88. (defun photogal (photo-dir)
  89. (interactive
  90. (list (read-directory-name
  91. "where are ur photos? " photogal-default-directory)))
  92. (setq *photogal/photos-origin-directory* (directory-file-name photo-dir))
  93. (setq *photogal/operating-photo-dir* (concat *photogal/photos-origin-directory* "-photogal"))
  94. (setq *photogal/all-photos* (photogal-all-photos *photogal/photos-origin-directory*))
  95. (photogal-init (photogal-current-file))
  96. (photogal-init-operating-table)
  97. (photogal-generate-tag-commands))
  98. ;;;; -- ---- -- - INITIALIZATION - -- ---- -- ;;;;
  99. (defun photogal-init-operating-table ()
  100. (setq *photogal/operating-table*
  101. (mapcar (lambda (photo)
  102. ;; '(old-file-path (tags optional-name))
  103. (list photo (list '() nil)))
  104. *photogal/all-photos*)))
  105. (defun photogal-generate-tag-commands ()
  106. "Generate and activate M-x (photogal-toggle-tag-TAG) to tag curent photo,
  107. for all tags defined -- one function per tag."
  108. (mapcar (lambda (tag)
  109. (let ((tag-key (car tag))
  110. (tag-name (cdr tag)))
  111. (eval `(photogal-generate-tagger ,(intern tag-name)))
  112. (define-key photogal-mode-map (kbd tag-key)
  113. (intern (format "photogal-toggle-tag-%s" tag-name)))))
  114. photogal/tags))
  115. ;;;; -- ---- -- - TAG - -- ---- -- ;;;;
  116. (defun photogal-add-tag (new-tag new-tag-code)
  117. "Add a user-generated tag to the tag library."
  118. (interactive "sNew tag: \nsTag code (length 1): ")
  119. (let ((tag-code-too-long (> (length new-tag-code) 1))
  120. (tag-code-in-use (photogal-tag-code-in-use new-tag-code)))
  121. (if (or tag-code-too-long tag-code-in-use)
  122. (progn (message "tag code must be a single character and can't be already in use")
  123. (call-interactively 'photogal-add-tag))
  124. (photogal--add-tag new-tag new-tag-code)
  125. (photogal-refresh-buffer))))
  126. (defun photogal-delete-tag (tag-code)
  127. "Remove a tag from the library."
  128. (interactive "sDelete tag: ")
  129. (customize-save-variable
  130. 'photogal/tags
  131. (seq-remove (lambda (tag) (string= (car tag) tag-code)) photogal/tags))
  132. (photogal-refresh-buffer))
  133. (defun photogal--add-tag (new-tag new-tag-code)
  134. "Modify the defcustom var to the new collection of tags."
  135. (let ((escaped-str-tag (string-replace " " "-" new-tag)))
  136. (customize-save-variable
  137. 'photogal/tags
  138. (cons (cons new-tag-code escaped-str-tag) photogal/tags)))
  139. (photogal-generate-tag-commands))
  140. (defun photogal-tag-code-in-use (tag-code)
  141. (seq-contains-p
  142. photogal/tags tag-code
  143. (lambda (tag test) (string= (car tag) test))))
  144. ;;;; -- ---- -- - TAGGING FILES - -- ---- -- ;;;;
  145. (defun photogal-for-file-toggle-tag (file tag)
  146. "If a file has the tag, remove it. If it doesn't have it, add it."
  147. (if (photogal-file-has-tag? file tag)
  148. (photogal-rm-tag-from-file file tag)
  149. (photogal-add-tag-to-file file tag)))
  150. (defun photogal-add-tag-to-file (file tag)
  151. "Append new tag for a file."
  152. (let ((tags (photogal-get-tags-for-file file)))
  153. (photogal-set-tags-for-file file
  154. (seq-sort #'string< (seq-uniq (cons tag tags))))))
  155. (defun photogal-rm-tag-from-file (file tag)
  156. "Dissociate tag from file."
  157. (defun tags-without-tag (tags tag)
  158. (seq-sort
  159. #'string<
  160. (seq-uniq
  161. (seq-remove
  162. (lambda (tg) (string= tg tag)) tags))))
  163. (let ((tags (photogal-get-tags-for-file file)))
  164. (photogal-set-tags-for-file file
  165. (tags-without-tag tags tag))))
  166. (defun photogal-file-has-tag? (file tag)
  167. "Does this file have this tag?"
  168. (let ((tags (photogal-get-tags-for-file file)))
  169. (seq-contains-p tags tag)))
  170. ;;;; -- ---- -- - FILE NAME - -- ---- -- ;;;;
  171. (defun photogal-name-the-file (name)
  172. (interactive "sWhat do u want to name this file? ")
  173. (photogal-set-name-for-file
  174. (photogal-current-file)
  175. (string-replace " " "-" name))
  176. (photogal-refresh-buffer))
  177. ;;;; -- ---- -- - FILE OPS - -- ---- -- ;;;;
  178. (defun photogal-all-photos (directory)
  179. "Give me a list of all the photos in my operating directory."
  180. (directory-files directory
  181. t directory-files-no-dot-files-regexp))
  182. (defun photogal-current-file ()
  183. "What is the file currently being operated on?"
  184. (car *photogal/all-photos*))
  185. ;;;; -- ---- -- - U I - -- ---- -- ;;;;
  186. (defun photogal-init (photo-file-path &optional show-filepath)
  187. "Set everything up in the buffer."
  188. (let ((buf (get-buffer-create "photogal")))
  189. (with-current-buffer buf
  190. (photogal-mode)
  191. (erase-buffer)
  192. (photogal-index-tracker)
  193. (insert "\n")
  194. (insert " ")
  195. (insert-image
  196. (if *photogal/--resize-photo*
  197. (create-image photo-file-path 'imagemagick nil
  198. :width (- (window-pixel-width) 75))
  199. (create-image photo-file-path 'imagemagick nil
  200. :height (/ (window-pixel-height) 2))))
  201. (insert "\n\nCurrent tags: ")
  202. (insert (format "%s" (photogal-get-tags-for-file photo-file-path)))
  203. (if (photogal-get-name-for-file photo-file-path)
  204. (insert (format "\nName: %s" (photogal-get-name-for-file photo-file-path)))
  205. (insert "\n"))
  206. (insert "\n\n")
  207. (photogal--insert-print-color "Tag:\n\n" "red")
  208. (photogal-insert-tags-to-buffer photogal/tags)
  209. (photogal--insert-print-color "\n\nCommands:\n\n" "red")
  210. (photogal-insert-commands-to-buffer
  211. *photogal/commands*)
  212. (when show-filepath
  213. (insert "\n\n")
  214. (insert (photogal-current-file))))
  215. (switch-to-buffer buf)))
  216. (defun photogal-next-file ()
  217. (interactive)
  218. (photogal-advance-photo)
  219. (photogal-refresh-buffer))
  220. (defun photogal-prev-file ()
  221. (interactive)
  222. (photogal-rewind-photo)
  223. (photogal-refresh-buffer))
  224. (defun photogal-refresh-buffer (&optional show-filepath)
  225. "Refresh buffer."
  226. (interactive)
  227. (message "refreshing buffer")
  228. (photogal-init (photogal-current-file) show-filepath)
  229. (photogal-generate-tag-commands)
  230. (beginning-of-buffer))
  231. (defun photogal-resize-photo ()
  232. (interactive)
  233. (setq *photogal/--resize-photo* (not *photogal/--resize-photo*))
  234. (photogal-refresh-buffer))
  235. (defun photogal-show-filepath ()
  236. (interactive)
  237. (photogal-refresh-buffer t))
  238. (defun photogal-index-tracker ()
  239. ;; this is a little expensive, running photogal-all-photos
  240. ;; on every paint, but i'd like to have the file count
  241. ;; be very accurate.
  242. (let ((current-index
  243. (+ 1 (seq-position
  244. (photogal-all-photos *photogal/photos-origin-directory*)
  245. (photogal-current-file))))
  246. (total-photos
  247. (length (photogal-all-photos *photogal/photos-origin-directory*))))
  248. (insert "ur lookin at photo ")
  249. (photogal--insert-print-color current-index "red")
  250. (insert " of ")
  251. (photogal--insert-print-color total-photos "red")))
  252. ;;;; -- ---- -- - LO-LEVEL DISPLAY - -- ---- -- ;;;;
  253. ;; this stuff paints the words on the screen, changing ;;
  254. ;; color, etc, pprinting stuff at a pretty granular and ;;
  255. ;; tediously technical level. ;;
  256. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  257. (defun photogal-insert-tags-to-buffer (tags)
  258. "Pretty print the tags with their toggle key."
  259. (mapcar (lambda (tag)
  260. (let* ((key-command (car tag))
  261. (tag-name (cdr tag))
  262. (activated (photogal-file-has-tag? (photogal-current-file) tag-name)))
  263. (photogal--pprint-key-command key-command tag-name activated)))
  264. (seq-sort (lambda (t1 t2) (string< (car t1) (car t2))) tags)))
  265. (defun photogal-insert-commands-to-buffer (commands)
  266. "Pretty print the commands with their invoke key."
  267. (mapcar (lambda (command)
  268. (let ((key-command (car command))
  269. (command-name (cdr command)))
  270. (photogal--pprint-key-command key-command command-name)))
  271. commands))
  272. (defun photogal--pprint-key-command (key-to-type name-of-command &optional activated)
  273. "Make the low-level insertions to the buffer to render a key-command."
  274. (let ((length-of-unit (+ (length key-to-type) (length name-of-command) 3)))
  275. (when (> (+ (+ (current-column) length-of-unit)
  276. 10)
  277. (window-width))
  278. (insert "\n"))
  279. (insert "[")
  280. (if activated
  281. (photogal--insert-print-color key-to-type "SeaGreen2")
  282. (photogal--insert-print-color key-to-type "dark gray"))
  283. (insert "] ")
  284. (photogal--insert-print-color name-of-command "blue" (- 16 (length key-to-type)))
  285. (insert " ")))
  286. (defun photogal--insert-print-color (string-to-insert-to-buffer color &optional padding)
  287. "Insert some text in this color."
  288. (let ((beg (point))
  289. (padding
  290. (if padding
  291. (format "%s" padding)
  292. "0")))
  293. (insert (format (concat "%-" padding "s") string-to-insert-to-buffer))
  294. (put-text-property beg (point) 'font-lock-face `(:foreground ,color))))
  295. ;;;; -- ---- -- - META SHIT - -- ---- -- ;;;;
  296. ;; this is the coolest damn thing here. ;;
  297. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  298. (defmacro photogal-generate-tagger (name)
  299. "Generate function to toggle a tag which is itself on the current file.
  300. One of these is needed per tag. For instance if you want to create the tag
  301. 'cool', you can run and evaluate (photogal-generate-tagger cool) to create a new
  302. function `photogal-toggle-tag-cool` that will toggle the tag 'cool' for
  303. the current file."
  304. (let ((my-funcname (intern (format "photogal-toggle-tag-%s" name))))
  305. `(defun ,my-funcname ()
  306. (interactive)
  307. (photogal-for-file-toggle-tag (photogal-current-file) ,(format "%s" name))
  308. (photogal-refresh-buffer))))
  309. ;;;; -- ---- -- -MOVING FILES AROUND- -- ---- -- ;;;;
  310. (defun photogal-files--get-extension (filepath)
  311. (file-name-extension filepath))
  312. (defun photogal-files--generate-unique-identifier (filepath)
  313. "Not GUARANTEED unique, but probably unique enough for my purposes."
  314. (seq-take (md5 (concat (current-time-string) filepath))
  315. 6))
  316. (defun photogal-files--new-file-name-for-photo (filepath tags name)
  317. (cons
  318. filepath
  319. (file-name-with-extension
  320. (file-name-concat
  321. *photogal/operating-photo-dir*
  322. (concat
  323. (photogal-files--generate-unique-identifier filepath)
  324. "-"
  325. (format-time-string "%M%H,%d%m%y")
  326. "-"
  327. name
  328. "-_"
  329. (string-join tags "_")
  330. "_"
  331. ))
  332. (file-name-extension filepath))))
  333. (defun photogal-files--new-filenames-for-photos ()
  334. (mapcar
  335. (lambda (photo)
  336. (let ((filepath (photogal--get-filepath photo))
  337. (tags (photogal--get-tags photo))
  338. (name (photogal--get-name photo)))
  339. (photogal-files--new-file-name-for-photo filepath tags name)))
  340. *photogal/operating-table*))
  341. (defun photogal-restart ()
  342. (setq *photogal/all-photos* (photogal-all-photos *photogal/photos-origin-directory*))
  343. (photogal-init-operating-table)
  344. (photogal-init (photogal-current-file)))
  345. (defun photogal-compile-and-commit ()
  346. (interactive)
  347. (if (y-or-n-p (format "Are u sure? "))
  348. (progn
  349. (make-directory *photogal/operating-photo-dir* 'parents)
  350. (mapcar
  351. (lambda (file-rename)
  352. (let ((origin (car file-rename))
  353. (destination (cdr file-rename)))
  354. (when (or (photogal-get-tags-for-file origin)
  355. (photogal-get-name-for-file origin))
  356. (rename-file origin destination))))
  357. (photogal-files--new-filenames-for-photos))
  358. (photogal-restart))
  359. (message "whoops")))
  360. ;;;; -- ---- -- - KEY BINDINGS - -- ---- -- ;;;;
  361. (defvar photogal-mode-map nil "Keymap for `photogal-mode`")
  362. (progn
  363. (setq photogal-mode-map (make-sparse-keymap))
  364. (define-key photogal-mode-map (kbd "G") 'photogal-refresh-buffer)
  365. (define-key photogal-mode-map (kbd "RET") 'photogal-next-file)
  366. (define-key photogal-mode-map (kbd "<right>") 'photogal-next-file)
  367. (define-key photogal-mode-map (kbd "SPC") 'photogal-next-file)
  368. (define-key photogal-mode-map (kbd "P") 'photogal-prev-file)
  369. (define-key photogal-mode-map (kbd "<left>") 'photogal-prev-file)
  370. (define-key photogal-mode-map (kbd "A") 'photogal-add-tag)
  371. (define-key photogal-mode-map (kbd "D") 'photogal-delete-tag)
  372. (define-key photogal-mode-map (kbd "F") 'photogal-show-filepath)
  373. (define-key photogal-mode-map (kbd "R") 'photogal-resize-photo)
  374. (define-key photogal-mode-map (kbd "C") 'photogal-compile-and-commit)
  375. (define-key photogal-mode-map (kbd "N") 'photogal-name-the-file))
  376. (define-derived-mode photogal-mode text-mode "photogal"
  377. "Major mode for grouping and labeling images.")