1 ;;; org-link-beautify.el --- Beautify Org Links -*- lexical-binding: t; -*-
3 ;; Authors: stardiviner <numbchild@gmail.com>
4 ;; Package-Requires: ((emacs "29.1") (org "9.7.14") (nerd-icons "0.0.1") (qrencode "1.3"))
6 ;; Keywords: hypermedia
7 ;; homepage: https://repo.or.cz/org-link-beautify.git
9 ;; org-link-beautify is free software; you can redistribute it and/or modify it
10 ;; under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; org-link-beautify is distributed in the hope that it will be useful, but WITHOUT
15 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17 ;; License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
27 ;; (use-package org-link-beautify
29 ;; :init (org-link-beautify-mode t))
33 (require 'org
) ; including `ol'
34 (require 'org-element
)
35 (require 'org-element-ast
)
43 ;; (require 'fb2-reader)
44 (declare-function fb2-reader--create-image
"fb2-reader" (data type
&rest props
))
45 (declare-function fb2-reader--extract-image-data
"fb2-reader" (book attributes
&optional tags
))
46 (declare-function fb2-reader--get-cover
"fb2-reader" (book))
47 (declare-function fb2-reader-parse-file-as-html
"fb2-reader" (file))
48 (declare-function fb2-reader-parse-file-as-xml
"fb2-reader" (file))
51 (defgroup org-link-beautify nil
52 "Customize group of `org-link-beautify-mode'."
53 :prefix
"org-link-beautify-"
56 (defcustom org-link-beautify-thumbnails-dir
'current-working-directory
57 "The directory of generated thumbnails.
59 By default option value with symbol 'current-working-directory the
60 thumbnails are generated in source file path’s .thumbnails directory.
61 This is better for avoiding re-generate preview thumbnails. Or you can
62 set this option to 'user-home which represent to ~/.cache/thumbnails/."
65 :group
'org-link-beautify
)
67 (defcustom org-link-beautify-enable-debug-p nil
68 "Whether enable org-link-beautify print debug info."
73 ;;; overlay keymap keybindings
75 (defvar org-link-beautify-keymap
76 (let ((map (make-sparse-keymap)))
77 (set-keymap-parent map image-map
) ; inherit `image-map' keybindings on preview thumbnail image.
80 (defun org-link-beautify-action-goto-file-in-dired ()
81 "Action of opening Dired and goto the link file position."
83 (when (derived-mode-p 'org-mode
)
84 (let* ((file-path (org-element-property :path
(org-element-context)))
85 (file-name (file-name-nondirectory file-path
)))
87 (search-forward file-name
)
88 (dired-move-to-filename) ; move point to beginning of filename.
89 (if (and (featurep 'dwim-shell-command
) (featurep 'dwim-shell-commands
))
91 (message "Jumped to position of link file, now you can execute `dwim-shell-command' commands")
92 (command-execute nil
(read-extended-command-1 nil
"dwim-shell-commands")))
93 (user-error "Jumped to position of link file.
94 Package `dwim-shell-command' is missing, please install it")))))
96 (define-key org-link-beautify-keymap
(kbd "M-o") #'org-link-beautify-action-goto-file-in-dired
)
98 (defun org-link-beautify--copy-file-to-clipboard (file)
99 "Copy the FILE on path to clipboard.
100 The argument FILE must be the absolute path."
104 (format "tell app \"Finder\" to set the clipboard to ( POSIX file \"%s\" )" file
)))
106 ;; - xclip-copyfile :: command copies files into the X clipboard, recursing into directories.
107 ;; - xclip-cutfile :: command Copy the files, but also deletes them afterwards.
108 ;; - xclip-pastefile :: command Paste the files out of the clipboard.
109 ;; - xclip :: command Copy text or files to the clipboard.
110 (if (executable-find "xclip-copyfile")
111 (shell-command (format "xclip-copyfile %s" file
))
112 (user-error "[org-link-beautify] Error: the command-line tool 'xclip-copyfile' is not installed!")))
115 (message "Copied file [%s] to system clipboard." (string-truncate-left file
(/ (window-width) 2))))
117 (defun org-link-beautify-action-copy-file (&optional args
)
118 "Action of copying the Org link file at point with optional ARGS."
120 (when (derived-mode-p 'org-mode
)
121 (if (or (region-active-p) mark-active
)
122 (let ((region-text (buffer-substring-no-properties
123 (region-beginning) (region-end))))
124 (kill-new region-text
)
126 (let ((element (org-element-context)))
127 (if (and (eq (car element
) 'link
)
128 (string-equal (org-element-property :type element
) "file"))
129 (let ((file-path (expand-file-name (org-element-property :path element
))))
130 (org-link-beautify--copy-file-to-clipboard file-path
))
131 (message "[org-link-beautify] No action executed on link."))))))
133 (define-key org-link-beautify-keymap
(kbd "M-w") #'org-link-beautify-action-copy-file
)
135 (defun org-link-beautify-action-qrcode-for-url (&optional args
)
136 "Action of displaying QR code for Org link at point in new buffer in ARGS."
138 (when (derived-mode-p 'org-mode
)
139 (if-let ((url (org-element-property-raw :raw-link
(org-element-context))))
140 (if (require 'qrencode nil t
)
141 (qrencode-string url
)
142 (package-install 'qrencode
)
143 (qrencode-string url
))
144 (if (or (region-active-p) mark-active
)
145 (org-fill-paragraph t t
)
146 (org-fill-paragraph)))))
148 (define-key org-link-beautify-keymap
(kbd "M-q") 'org-link-beautify-action-qrcode-for-url
)
152 (defun org-link-beautify--get-thumbnails-dir-path (file)
153 "Return the FILE thumbnail directory's path."
155 (cl-case org-link-beautify-thumbnails-dir
156 (current-working-directory
157 (concat (file-name-directory file
) ".thumbnails/"))
159 (expand-file-name "~/.cache/thumbnails/")))
160 (user-error "[org-link-beautify] Error: the paramter `file' of function `org-link-beautify--get-thumbnails-dir-path' is nil")))
162 (defun org-link-beautify--ensure-thumbnails-dir (thumbnails-dir)
163 "Ensure THUMBNAILS-DIR exist, if not ,create it."
164 (if (file-exists-p (file-name-parent-directory thumbnails-dir
))
165 (unless (file-directory-p thumbnails-dir
)
166 (make-directory thumbnails-dir
))
167 (if (yes-or-no-p "[org-link-beautify] thumbnails directory parent directory does not exist, create it?")
168 (make-directory thumbnails-dir t
)
169 (warn "[org-link-beautify] thumbnails directory parent directory does not exist"))))
171 (defun org-link-beautify--notify-generate-thumbnail-failed (source-file thumbnail-file
)
172 "Notify that generating THUMBNAIL-FILE for SOURCE-FILE failed."
173 (message "[org-link-beautify] For file %s.\nCreate thumbnail %s failed." source-file thumbnail-file
))
175 (defun org-link-beautify--display-content-block (lines-list)
176 "Display LINES-LIST string as a block with beautified frame border."
183 (make-string (- fill-column
6) ?━
)
189 (make-string (- fill-column
6) ?━
)))
191 (defun org-link-beautify--display-org-content (org-content)
192 "Display ORG-CONTENT in `org-mode'."
194 (let ((org-startup-with-link-previews nil
))
197 ;; (goto-char (line-beginning-position 2))
198 ;; FIXME: how to fix org-attach directory issue.
199 ;; error: "Need absolute ‘org-attach-id-dir’ to attach in buffers without filename"
200 ;; reference `org-attach-dir'
201 ;; (when (org-entry-get nil "DIR" org-attach-use-inheritance)
202 ;; (org-link-preview-region t t (point-min) (point-max)))
203 (buffer-substring (point-min) (point-max)))))
205 ;;; Invoke external Python script file or code.
207 (defcustom org-link-beautify-python-interpreter
(executable-find "python3")
208 "Specify Python interpreter to run python scripts or code."
212 (defun org-link-beautify--python-script-run (python-script-file)
213 "Run PYTHON-SCRIPT-FILE through shell command."
214 (shell-command-to-string
215 (format "%s %s" org-link-beautify-python-interpreter python-script-file
)))
217 (defun org-link-beautify--python-command-to-string (&rest code-lines
)
218 "Run Python CODE-LINES through shell command."
219 (shell-command-to-string
221 ;; solve double quote character issue.
222 "\"" (string-replace "\"" "\\\"" (string-join code-lines
"\n")) "\"")))
225 ;; (org-link-beautify--python-command-to-string
226 ;; "import numpy as np"
227 ;; "print(np.arange(6))"
228 ;; "print(\"blah blah\")"
229 ;; "print('{}'.format(3))")
231 ;;; Invoke external JavaScript script file or code.
233 (defcustom org-link-beautify-javascript-interpreter
(executable-find "node")
234 "Specify JavaScript interpreter to run JavaScript scripts or code."
238 (defun org-link-beautify--javascript-script-run (javascript-script-file)
239 "Run JAVASCRIPT-SCRIPT-FILE through shell command."
240 (shell-command-to-string
241 (format "%s %s" org-link-beautify-javascript-interpreter javascript-script-file
)))
243 (defun org-link-beautify--javascript-command-to-string (&rest code-lines
)
244 "Run JavaScript CODE-LINES through shell command."
245 (shell-command-to-string
246 (concat org-link-beautify-javascript-interpreter
248 ;; solve double quote character issue.
249 "\"" (string-replace "\"" "\\\"" (string-join code-lines
"\n")) "\"")))
252 ;; (org-link-beautify--javascript-command-to-string
253 ;; "console.log(\"hello, world!\");"
254 ;; "console.log(1 + 3);")
258 (defun org-link-beautify--get-link-description (position)
259 "Get the link description at POSITION (fuzzy but faster version)."
263 ((org-element-link-parser)
264 (let ((link (org-element-link-parser)))
265 (if (org-element-property :contents-begin link
)
266 (buffer-substring-no-properties
267 (org-element-property :contents-begin link
)
268 (org-element-property :contents-end link
))
269 (org-element-property :raw-link link
))))
270 ((org-element-context)
271 (let ((link (org-element-link-parser)))
272 (org-element-property :raw-link link
)))
275 (and (org-in-regexp org-link-bracket-re
) (match-string 2)))))))
277 (defun org-link-beautify--return-warning-face (ov path link
)
278 "Return warning face if PATH does not exist on OV overlay at LINK element."
279 (when (string-equal (org-element-property :type link
) "file")
280 (if (and (not (file-remote-p path
))
281 (file-exists-p (expand-file-name path
)))
285 (defun org-link-beautify--return-icon (ov path link
)
286 "Return icon for PATH on OV overlay at LINK element."
287 (let ((type (org-element-property :type link
))
288 (extension (file-name-extension path
)))
290 ("file" (if-let (extension (file-name-extension path
))
291 (nerd-icons-icon-for-extension extension
)
292 (nerd-icons-icon-for-dir path
)))
293 ("attachment" (if-let (extension (file-name-extension path
))
294 (nerd-icons-icon-for-extension extension
)
295 (nerd-icons-icon-for-dir path
)))
296 ("http" (nerd-icons-icon-for-url (concat type
":" path
)))
297 ("https" (nerd-icons-icon-for-url (concat type
":" path
)))
298 ("ftp" (nerd-icons-icon-for-url (concat type
":" path
)))
299 ;; Org mode internal link types
300 ("custom-id" (nerd-icons-mdicon "nf-md-text_box_search_outline" :face
'nerd-icons-blue
))
301 ("id" (nerd-icons-mdicon "nf-md-text_search" :face
'nerd-icons-blue
))
302 ("coderef" (nerd-icons-codicon "nf-cod-references" :face
'nerd-icons-cyan
))
303 ("elisp" (nerd-icons-icon-for-file "file.el"))
304 ("eshell" (nerd-icons-icon-for-mode 'eshell-mode
))
305 ("shell" (nerd-icons-icon-for-mode 'shell-mode
))
306 ("man" (nerd-icons-mdicon "nf-md-file_document_outline" :face
'nerd-icons-lblue
))
307 ("woman" (nerd-icons-mdicon "nf-md-file_document_outline" :face
'nerd-icons-blue-alt
))
308 ("info" (nerd-icons-mdicon "nf-md-information_outline" :face
'nerd-icons-lblue
))
309 ("help" (nerd-icons-mdicon "nf-md-help_circle_outline" :face
'nerd-icons-lblue
))
310 ("shortdoc" (nerd-icons-mdicon "nf-md-file_link" :face
'nerd-icons-blue
))
311 ;; org-ref link types
312 ("cite" (nerd-icons-codicon "nf-cod-references" :face
'nerd-icons-cyan
))
313 ;; Org mode external link types
314 ("eaf" (nerd-icons-mdicon "nf-md-apps" :face
'nerd-icons-blue
)) ; emacs-application-framework
315 ("eww" (nerd-icons-icon-for-mode 'eww-mode
)) ; EWW
316 ("chrome" (nerd-icons-mdicon "nf-md-google_chrome" :face
'nerd-icons-lorange
))
317 ("edge" (nerd-icons-mdicon "nf-md-microsoft_edge" :face
'nerd-icons-green
))
318 ("mu4e" (nerd-icons-mdicon "nf-md-email_search_outline" :face
'nerd-icons-blue
))
319 ("news" (nerd-icons-mdicon "nf-md-newspaper_variant_outline" :face
'nerd-icons-dgreen
))
320 ("git" (nerd-icons-mdicon "nf-md-git" :face
'nerd-icons-lred
))
321 ("orgit" (nerd-icons-faicon "nf-fa-git" :face
'nerd-icons-red
))
322 ("orgit-rev" (nerd-icons-devicon "nf-dev-git_commit" :face
'nerd-icons-silver
))
323 ("orgit-log" (nerd-icons-octicon "nf-oct-diff" :face
'nerd-icons-silver
))
324 ("pdf" (nerd-icons-faicon "nf-fa-file_pdf" :face
'nerd-icons-red
))
325 ("epub" (nerd-icons-mdicon "nf-md-book_open_page_variant_outline" :face
'nerd-icons-blue-alt
))
326 ("nov" (nerd-icons-icon-for-file "file.epub")) ; for Emacs package "nov.el" link type `nov:'
327 ("grep" (nerd-icons-mdicon "nf-md-selection_search" :face
'nerd-icons-green
))
328 ("occur" (nerd-icons-mdicon "nf-md-selection_multiple" :face
'nerd-icons-green
))
329 ("rss" (nerd-icons-mdicon "nf-md-rss" :face
'nerd-icons-lorange
))
330 ("elfeed" (nerd-icons-mdicon "nf-md-rss" :face
'nerd-icons-green
))
331 ("wikipedia" (nerd-icons-mdicon "nf-md-wikipedia" :face
'nerd-icons-dsilver
))
332 ("mailto" (nerd-icons-mdicon "nf-md-email_send_outline" :face
'nerd-icons-lblue
))
333 ("irc" (nerd-icons-mdicon "nf-md-chat" :face
'nerd-icons-blue-alt
))
334 ("wechat" (nerd-icons-mdicon "nf-md-wechat" :face
'nerd-icons-green
))
335 ("magnet" (nerd-icons-mdicon "nf-md-magnet" :face
'nerd-icons-blue-alt
))
336 ("ref" (nerd-icons-codicon "nf-cod-references" :face
'nerd-icons-blue
))
337 ("doi" (nerd-icons-mdicon "nf-md-file_document_plus_outline" :face
'nerd-icons-green
))
338 ("org-contact" (nerd-icons-mdicon "nf-md-contacts_outline" :face
'nerd-icons-purple-alt
))
339 ("org-bookmark" (nerd-icons-mdicon "nf-md-bookmark_check_outline" :face
'nerd-icons-blue-alt
))
340 ("org-ql-search" (nerd-icons-mdicon "nf-md-text_box_search_outline" :face
'nerd-icons-blue-alt
))
341 ;; org-media-note link types
342 ("video" (nerd-icons-faicon "nf-fa-file_video_o" :face
'nerd-icons-blue
))
343 ("audio" (nerd-icons-faicon "nf-fa-file_audio_o" :face
'nerd-icons-blue
))
344 ("videocite" (nerd-icons-faicon "nf-fa-file_video_o" :face
'nerd-icons-blue-alt
))
345 ("audiocite" (nerd-icons-faicon "nf-fa-file_audio_o" :face
'nerd-icons-blue-alt
))
346 ("javascript" (nerd-icons-mdicon "nf-md-language_javascript" :face
'nerd-icons-yellow
))
347 ("js" (nerd-icons-mdicon "nf-md-language_javascript" :face
'nerd-icons-yellow
))
348 ("vscode" (nerd-icons-mdicon "nf-md-microsoft_visual_studio_code" :face
'nerd-icons-blue-alt
)) ; Visual Studio Code
349 ("macappstore" (nerd-icons-mdicon "nf-md-apple" :face
'nerd-icons-blue
)) ; Mac App Store
352 ;; Org internal [[reference][reference]] -> NOT supported by:
353 ;; `(org-link-set-parameters link-type :activate-func ...)'
355 ;; (link (:standard-properties [584419 nil 584470 584517 584519 0 nil nil nil nil nil nil ...] :type "fuzzy" :type-explicit-p nil :path "defcustom org-contacts-identity-properties-list" :format bracket :raw-link "defcustom org-contacts-identity-properties-list" ...))
357 (when-let* ((_ (string-match "\\([^:]*\\):\\(.*\\)" path
))
358 (real-type (match-string 1 path
))) ; extract the "real" link type for "fuzzy" type in :path.
361 (message "[org-link-beautify] link type not supported, add PR for this link type.
362 type: %s, path: %s, extension: %s, link-element: %s" type path extension link
)
363 (nerd-icons-mdicon "nf-md-progress_question" :face
'nerd-icons-lyellow
)))))
365 (message "[org-link-beautify] link type not supported, add PR for this link type.
366 type: %s, path: %s, extension: %s, link-element: %s" type path extension link
)
367 ;; handle when returned link type is `nil'.
368 (nerd-icons-mdicon "nf-md-progress_question" :face
'nerd-icons-lyellow
)))))
370 (defun org-link-beautify-iconify (ov path link
)
371 "Iconify PATH over OV overlay position for LINK element."
372 (when-let* ((begin (org-element-begin link
))
373 (end (org-element-end link
))
374 (description (org-link-beautify--get-link-description begin
))
375 (icon (org-link-beautify--return-icon ov path link
)))
378 (propertize "[" 'face
`( :inherit nil
380 :foreground
,(if (face-foreground 'shadow
)
381 (color-lighten-name (face-foreground 'shadow
) 2)
383 (propertize description
'face
(org-link-beautify--return-warning-face ov path link
))
384 (propertize "]" 'face
`( :inherit nil
386 :foreground
,(if (face-foreground 'shadow
)
387 (color-lighten-name (face-foreground 'shadow
) 2)
390 'after-string
(concat
391 (propertize "[" 'face
'(:inherit nil
:underline nil
:foreground
"orange"))
393 (propertize "]" 'face
'(:inherit nil
:underline nil
:foreground
"orange"))))))
395 ;;; General thumbnail generator.
397 (defvar org-link-beautify-thumbnailer-script
398 (expand-file-name "scripts/thumbnailer.py" (file-name-directory (or load-file-name
(buffer-file-name))))
399 "The path of general thumbnailer script.")
401 (defun org-link-beautify-thumbnailer (path)
402 "Generate thumbnail image for file of PATH over OV overlay position for LINK element."
403 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
404 (let* ((file-path (match-string 1 path
))
405 (input-file (expand-file-name (org-link-unescape file-path
)))
406 (search-option (match-string 2 path
))
407 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path input-file
))
408 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base input-file
))))
410 (proc-name (format "org-link-beautify thumbnailer - %s" (file-name-base input-file
)))
411 (proc-buffer (format " *org-link-beautify thumbnailer - %s*" (file-name-base input-file
)))
412 (proc (get-process proc-name
)))
415 :command
(list org-link-beautify-thumbnailer-script
418 (number-to-string thumbnail-size
))
420 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
421 :sentinel
(lambda (proc event
)
422 (when org-link-beautify-enable-debug-p
423 (message (format "> proc: %s\n> event: %s" proc event
)))
424 ;; (when (string= event "finished\n")
425 ;; (kill-buffer (process-buffer proc))
426 ;; (kill-process proc))
428 ;; return the thumbnail file as result.
431 (defun org-link-beautify-preview-thumbnail (ov path link
)
432 "Display thumbnail on overlay OV from PATH at element LINK."
433 (if-let* (( (display-graphic-p))
434 (thumbnail-file (expand-file-name path
))
435 ;; ((string-match-p (image-file-name-regexp) thumbnail-file))
436 ((file-exists-p thumbnail-file
)))
437 (let* ((width (or (org-display-inline-image--width link
) 300))
438 (align (org-image--align link
))
439 (image (org--create-inline-image thumbnail-file width
)))
440 (when image
; Add image to overlay
441 ;; See bug#59902. We cannot rely on Emacs to update image if the file has changed.
442 (image-flush image
) ; refresh image in cache if file changed.
443 (overlay-put ov
'display image
)
444 (overlay-put ov
'face
'default
)
445 (overlay-put ov
'keymap org-link-beautify-keymap
)
448 'before-string
(propertize " "
450 'display
(pcase align
451 ("center" `(space :align-to
(- center
(0.5 .
,image
))))
452 ("right" `(space :align-to
(- right
,image
)))))))
455 ;;; Preview file: link type
457 (defun org-link-beautify-preview-file (ov path link
)
458 "Preview file of PATH over OV overlay position for LINK element.
459 This function will apply file type function based on file extension."
460 (let ((extension (file-name-extension path
)))
462 ((null extension
) ; no file extension, it's directory.
463 (org-link-beautify-iconify ov path link
))
464 ((string-match-p (image-file-name-regexp) path
) ; `org-link-beautify-image-preview-list'
465 (org-link-beautify-preview-file-image ov path link
))
466 ((string-equal extension
"pdf")
467 (org-link-beautify-preview-file-pdf ov path link
))
468 ((string-equal extension
"epub")
469 (org-link-beautify-preview-file-epub ov path link
))
470 ((string-match-p "\\(mobi\\|azw3\\)" extension
)
471 (org-link-beautify-preview-file-kindle ov path link
))
472 ((string-match-p "\\.fb2\\(\\.zip\\)?" path
)
473 (org-link-beautify-preview-file-fictionbook2 ov path link
))
474 ((member extension org-link-beautify-comic-preview-list
)
475 (org-link-beautify-preview-file-comic ov path link
))
476 ((member extension org-link-beautify-video-preview-list
)
477 (org-link-beautify-preview-file-video ov path link
))
478 ((member extension org-link-beautify-audio-preview-list
)
479 (org-link-beautify-preview-file-audio ov path link
))
480 ((member extension org-link-beautify-subtitle-preview-list
)
481 (org-link-beautify-preview-file-subtitle ov path link
))
482 ((member extension org-link-beautify-archive-preview-list
)
483 (org-link-beautify-preview-file-archive ov path link
))
484 ((member extension org-link-beautify-source-code-preview-list
)
485 (org-link-beautify-preview-file-source-code ov path link
))
486 (t (let ((thumbnail-file (org-link-beautify-thumbnailer path
)))
487 (if (file-exists-p thumbnail-file
)
488 (org-link-beautify-preview-thumbnail ov path link
)
489 (org-link-beautify-iconify ov path link
)))))))
491 (defun org-link-beautify-preview-attachment (ov path link
)
492 "Preview attachment file of PATH over OV overlay position for LINK element.
493 This function will apply file type function based on file extension."
494 (org-with-point-at (org-element-begin link
)
495 (org-link-beautify-preview-file ov
(org-attach-expand path
) link
)))
499 (defcustom org-link-beautify-image-preview-list
500 '("jpg" "jpeg" "png" "gif" "webp")
501 "A list of image file types be supported with thumbnails."
504 :group
'org-link-beautify
)
506 (defun org-link-beautify-preview-file-image (ov path link
)
507 "Preview image file of PATH over OV overlay position for LINK element."
508 (if-let* (( (display-graphic-p))
509 (file (expand-file-name path
))
510 ;; ((string-match-p (image-file-name-regexp) file))
511 ((file-exists-p file
)))
512 (let* ((align (org-image--align link
))
513 (width (org-display-inline-image--width link
))
514 (image (org--create-inline-image file width
)))
515 (when image
; Add image to overlay
516 ;; See bug#59902. We cannot rely on Emacs to update image if the file has changed.
517 (image-flush image
) ; refresh image in cache if file changed.
518 (overlay-put ov
'display image
)
519 (overlay-put ov
'face
'default
)
520 (overlay-put ov
'keymap org-link-beautify-keymap
)
523 'before-string
(propertize " "
525 'display
(pcase align
526 ("center" `(space :align-to
(- center
(0.5 .
,image
))))
527 ("right" `(space :align-to
(- right
,image
)))))))
532 (defcustom org-link-beautify-pdf-preview-command
534 ((executable-find "pdftocairo") "pdftocairo")
535 ((executable-find "pdf2svg") "pdf2svg"))
536 "The command used to preview PDF file cover."
539 :group
'org-link-beautify
)
541 (defcustom org-link-beautify-pdf-preview-size
300
542 "The PDF preview image size."
545 :group
'org-link-beautify
)
547 (defcustom org-link-beautify-pdf-preview-default-page-number
1
548 "The default PDF preview page number."
551 :group
'org-link-beautify
)
553 (defcustom org-link-beautify-pdf-preview-image-format
'png
554 "The format of PDF file preview image."
556 :tag
"The format of PDF file preview image."
557 (const :tag
"PNG" png
)
558 (const :tag
"JPEG" jpeg
)
559 (const :tag
"SVG" svg
))
561 :group
'org-link-beautify
)
563 (defun org-link-beautify--generate-preview-for-file-pdf (path)
564 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for .pdf file of PATH."
565 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
566 (let* ((file-path (match-string 1 path
))
567 (search-option (match-string 2 path
))
568 (pdf-page-number (if search-option
571 ((string-prefix-p "P" search-option
) ; "P42"
572 (substring search-option
1 nil
))
573 ((string-match "\\([[:digit:]]+\\)\\+\\+\\(.*\\)" search-option
) ; "40++0.00"
574 (match-string 1 search-option
))
576 (if-let ((search-option (match-string 2 path
)))
579 ((string-prefix-p "P" search-option
) ; "P42"
580 (substring search-option
1 nil
))
581 ((string-match "\\([[:digit:]]+\\)\\+\\+\\(.*\\)" search-option
) ; "40++0.00"
582 (match-string 1 search-option
))
584 org-link-beautify-pdf-preview-default-page-number
)))
585 (pdf-file (expand-file-name (org-link-unescape file-path
)))
586 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path pdf-file
))
587 (thumbnail-file (expand-file-name
588 (if (= pdf-page-number
1) ; if have page number ::N specified.
590 thumbnails-dir
(file-name-base pdf-file
)
591 (symbol-name org-link-beautify-pdf-preview-image-format
))
592 (format "%s%s-P%s.%s"
593 thumbnails-dir
(file-name-base pdf-file
) pdf-page-number
594 (symbol-name org-link-beautify-pdf-preview-image-format
)))))
596 (proc-name (format "org-link-beautify pdf preview - %s" pdf-file
))
597 (proc-buffer (format " *org-link-beautify pdf preview - %s*" pdf-file
))
598 (proc (get-process proc-name
)))
599 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
600 (unless (file-exists-p thumbnail-file
)
601 (pcase (file-name-nondirectory org-link-beautify-pdf-preview-command
)
604 proc-name proc-buffer
606 (pcase org-link-beautify-pdf-preview-image-format
611 "-f" (number-to-string pdf-page-number
)
612 pdf-file
(file-name-sans-extension thumbnail-file
)))
614 (unless (eq org-link-beautify-pdf-preview-image-format
'svg
)
615 (warn "The pdf2svg only supports convert PDF to SVG format.
616 Please adjust `org-link-beautify-pdf-preview-command' to `pdftocairo' or
617 Set `org-link-beautify-pdf-preview-image-format' to `svg'."))
619 proc-name proc-buffer
620 "pdf2svg" pdf-file thumbnail-file
(number-to-string pdf-page-number
)))))
621 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
622 (org-link-beautify--notify-generate-thumbnail-failed path thumbnail-file
))
623 ;; return the thumbnail file as result.
626 (defun org-link-beautify-preview-file-pdf (ov path link
)
627 "Preview pdf file of PATH over OV overlay position for LINK element."
628 (if-let* (( (display-graphic-p))
629 (org-link-beautify-pdf-preview-command)
630 (thumbnail-file (org-link-beautify--generate-preview-for-file-pdf path
))
631 ((file-exists-p thumbnail-file
))
632 ;; NOTE: limite thumbnail image inline display width to hardcoded 300.
633 (width (or 300 (org-display-inline-image--width link
) org-link-beautify-pdf-preview-size
))
634 (image (org--create-inline-image thumbnail-file width
)))
636 (overlay-put ov
'display image
)
637 (overlay-put ov
'face
'default
)
638 (overlay-put ov
'keymap org-link-beautify-keymap
))
639 (org-link-beautify-iconify ov path link
)))
643 (defcustom org-link-beautify-epub-preview-command
644 (let ((script (expand-file-name "scripts/thumbnailer-ebook.py" (file-name-directory (or load-file-name
(buffer-file-name))))))
646 (gnu/linux
(if (executable-find "gnome-epub-thumbnailer")
647 "gnome-epub-thumbnailer"
649 (darwin (if (executable-find "epub-thumbnailer")
653 "Whether enable EPUB files cover preview?
654 If command \"gnome-epub-thumbnailer\" is available, enable EPUB
655 preview by default. You can set this option to nil to disable
659 :group
'org-link-beautify
)
661 (defcustom org-link-beautify-ebook-preview-size
600
662 "The EPUB cover preview image size."
665 :group
'org-link-beautify
)
667 (defun org-link-beautify--generate-preview-for-file-epub (path)
668 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for .epub file of PATH."
669 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
670 (let* ((file-path (match-string 1 path
))
671 (search-option (match-string 2 path
))
672 ;; TODO: currently epub file page number thumbnail is not supported by `org-link-beautify-epub-preview-command'.
673 (epub-page-number (if search-option
(string-to-number search-option
) 1))
674 (epub-file (expand-file-name (org-link-unescape file-path
)))
675 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path epub-file
))
676 (thumbnail-file (expand-file-name
677 (if (or (null epub-page-number
) (= epub-page-number
1)) ; if have page number ::N specified.
678 (format "%s%s.png" thumbnails-dir
(file-name-base epub-file
))
679 (format "%s%s-P%s.png" thumbnails-dir
(file-name-base epub-file
) epub-page-number
))))
680 (thumbnail-size (or org-link-beautify-ebook-preview-size
600))
681 (proc-name (format "org-link-beautify epub preview - %s" epub-file
))
682 (proc-buffer (format " *org-link-beautify epub preview - %s*" epub-file
))
683 (proc (get-process proc-name
)))
684 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
685 (unless (file-exists-p thumbnail-file
)
686 (pcase (file-name-nondirectory org-link-beautify-epub-preview-command
)
687 ("epub-thumbnailer" ; for macOS "epub-thumbnailer" command
690 :command
(list org-link-beautify-epub-preview-command
693 (number-to-string thumbnail-size
))
695 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
696 :sentinel
(lambda (proc event
)
697 (if org-link-beautify-enable-debug-p
698 (message (format "> proc: %s\n> event: %s" proc event
))
699 ;; (when (string= event "finished\n")
700 ;; (kill-buffer (process-buffer proc))
701 ;; (kill-process proc))
703 ("gnome-epub-thumbnailer" ; for Linux "gnome-epub-thumbnailer"
705 proc-name proc-buffer
706 org-link-beautify-epub-preview-command
707 epub-file thumbnail-file
708 (when org-link-beautify-ebook-preview-size
"--size")
709 (when org-link-beautify-ebook-preview-size
(number-to-string thumbnail-size
))))
710 (_ (user-error "This system platform currently not supported by org-link-beautify.\n Please contribute code to support"))))
711 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
712 (org-link-beautify--notify-generate-thumbnail-failed epub-file thumbnail-file
))
713 ;; return the thumbnail file as result.
716 (defun org-link-beautify-preview-file-epub (ov path link
)
717 "Preview epub file of PATH over OV overlay position for LINK element."
718 (if-let* (( (display-graphic-p))
719 (org-link-beautify-epub-preview-command)
720 (thumbnail-file (org-link-beautify--generate-preview-for-file-epub path
))
721 ((file-exists-p thumbnail-file
))
722 ;; NOTE: limite thumbnail image inline display width to hardcoded 300.
723 (width (or 300 (org-display-inline-image--width link
) org-link-beautify-ebook-preview-size
))
724 (image (org--create-inline-image thumbnail-file width
)))
726 (overlay-put ov
'display image
)
727 (overlay-put ov
'face
'default
)
728 (overlay-put ov
'keymap org-link-beautify-keymap
))
729 (org-link-beautify-iconify ov path link
)))
731 ;;; file: .mobi, .azw3
733 (defcustom org-link-beautify-kindle-preview-command
735 (gnu/linux
(executable-find "mobitool"))
736 (darwin (executable-find "mobitool")))
737 "Whether enable Kindle ebook files cover preview?
739 Enable Kindle ebook preview by default.
741 You can set this option to nil to disable EPUB preview.
743 You can install software `libmobi' to get command `mobitool'."
746 :group
'org-link-beautify
)
748 (defcustom org-link-beautify-kindle-preview-size
300
749 "The Kindle cover preview image size."
752 :group
'org-link-beautify
)
754 (defun org-link-beautify--generate-preview-for-file-kindle (path)
755 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for .mobi & .azw3 file of PATH."
756 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
757 (let* ((file-path (match-string 1 path
))
758 (kindle-page-number (or (match-string 2 path
) 1))
759 (kindle-file (expand-file-name (org-link-unescape file-path
)))
760 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path kindle-file
))
761 (thumbnail-file (expand-file-name
762 (if (or (null kindle-page-number
) (= kindle-page-number
1)) ; if have page number ::N specified.
763 (format "%s%s.jpg" thumbnails-dir
(file-name-base kindle-file
))
764 (format "%s%s-P%s.jpg" thumbnails-dir
(file-name-base kindle-file
) kindle-page-number
))))
765 (thumbnail-size (or org-link-beautify-ebook-preview-size
600))
766 (proc-name (format "org-link-beautify kindle preview - %s" kindle-file
))
767 (proc-buffer (format " *org-link-beautify kindle preview - %s*" kindle-file
))
768 (proc (get-process proc-name
)))
769 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
770 (unless (file-exists-p thumbnail-file
)
771 (pcase (file-name-nondirectory org-link-beautify-kindle-preview-command
)
773 ;; mobitool dumped cover image thumbnail filename can't be specified in command-line argument.
774 (let ((mobitool-cover-file (concat thumbnails-dir
(file-name-base kindle-file
) "_cover.jpg")))
775 (unless (file-exists-p mobitool-cover-file
)
776 (message "[org-link-beautify] preview kindle ebook file %s" kindle-file
)
778 proc-name proc-buffer
779 "mobitool" "-c" "-o" thumbnails-dir kindle-file
))
780 ;; then rename [file.extension.jpg] to [file.jpg]
781 (when (file-exists-p mobitool-cover-file
)
782 (rename-file mobitool-cover-file thumbnail-file
))))
783 (_ (user-error "[org-link-beautify] Error: Can't find command tool to dump kindle ebook file cover"))))
784 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
785 (org-link-beautify--notify-generate-thumbnail-failed kindle-file thumbnail-file
))
786 ;; return the thumbnail file as result.
789 (defun org-link-beautify-preview-file-kindle (ov path link
)
790 "Preview kindle .mobi or .azw3 file of PATH over OV overlay position for LINK element."
791 (if-let* (( (display-graphic-p))
792 (org-link-beautify-kindle-preview-command)
793 (thumbnail-file (org-link-beautify--generate-preview-for-file-kindle path
))
794 ((file-exists-p thumbnail-file
))
795 ;; NOTE: limite epub file thumbnail image inline display width to hardcoded 300.
796 (width (or 300 (org-display-inline-image--width link
)))
797 (image (org--create-inline-image thumbnail-file width
)))
799 (overlay-put ov
'display image
)
800 (overlay-put ov
'face
'default
)
801 (overlay-put ov
'keymap org-link-beautify-keymap
))
802 (org-link-beautify-iconify ov path link
)))
804 ;;; FictionBook2 (.fb2, .fb2.zip) file cover preview
806 (defcustom org-link-beautify-fictionbook2-preview
(featurep 'fb2-reader
)
807 "Whether enable FictionBook2 ebook files covert preview?"
810 :group
'org-link-beautify
)
812 (defcustom org-link-beautify-fictionbook2-preview-size
300
813 "The FictionBook2 cover preview image size."
816 :group
'org-link-beautify
)
818 (defun org-link-beautify-fictionbook2--extract-cover (file-path)
819 "Extract cover image data for FictionBook2 at FILE-PATH."
820 (if-let* (;; reference `fb2-reader-mode'
821 (book (or (fb2-reader-parse-file-as-xml file-path
)
822 (fb2-reader-parse-file-as-html file-path
)))
823 ;; reference `fb2-reader-splash-screen'
824 (cover-item (fb2-reader--get-cover book
))
825 ;; reference `fb2-reader-splash-cover': (fb2-reader-splash-cover book cover-item)
826 (attrs (cl-second (cl-third cover-item
)))
827 (img-data (fb2-reader--extract-image-data book attrs
))
828 (type (cl-first img-data
))
829 (data (cl-second img-data
))
830 ;; reference `fb2-reader--insert-image': (fb2-reader--insert-image data-str type-str nil t)
831 (type-symbol (alist-get type
'(("image/jpeg" . jpeg
) ("image/png" . png
)) nil nil
'equal
))
832 (data-decoded (base64-decode-string data
))
833 (img-raw (fb2-reader--create-image data-decoded type-symbol
))
834 (image (create-image data-decoded type-symbol
't
)))
838 (defun org-link-beautify-fictionbook2--save-cover (image thumbnail-file
)
839 "Save FictionBook2 cover IMAGE to THUMBNAIL-FILE."
840 ;; `image-save': This writes the original image data to a file.
842 (insert (plist-get (cdr image
) :data
))
843 (write-region (point-min) (point-max) thumbnail-file
)))
845 (defun org-link-beautify--generate-preview-for-file-fictionbook2 (path)
846 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for .fb2 & .fb2.zip file of PATH."
847 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
848 (let* ((file-path (match-string 1 path
))
849 (search-option (match-string 2 path
))
850 (fictionbook2-file (expand-file-name (org-link-unescape file-path
)))
851 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path fictionbook2-file
))
852 (thumbnail-file (cond
853 ((string-match-p "\\.fb2.zip$" path
)
855 (format "%s%s.png" thumbnails-dir
(string-replace ".fb2" "" (file-name-base fictionbook2-file
)))))
856 ((string-match-p "\\.fb2$" path
)
857 (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base fictionbook2-file
))))))
858 (thumbnail-size (or org-link-beautify-fictionbook2-preview-size
600))
859 (proc-name (format "org-link-beautify fictionbook preview - %s" fictionbook2-file
))
860 (proc-buffer (format " *org-link-beautify fictionbook preview - %s*" fictionbook2-file
))
861 (proc (get-process proc-name
)))
862 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
863 (unless (file-exists-p thumbnail-file
)
864 (let ((cover-image (org-link-beautify-fictionbook2--extract-cover fictionbook2-file
)))
865 (if (eq cover-image
'no-cover
)
866 (message "[org-link-beautify] FictionBook2 preview failed to extract cover image.")
867 (org-link-beautify-fictionbook2--save-cover cover-image thumbnail-file
))))
868 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
869 (org-link-beautify--notify-generate-thumbnail-failed fictionbook2-file thumbnail-file
))
870 ;; return the thumbnail file as result.
873 (defun org-link-beautify-preview-file-fictionbook2 (ov path link
)
874 "Preview FictionBook2 .fb2, .fb2.zip file of PATH over OV overlay position for LINK element."
875 (if-let* ((org-link-beautify-fictionbook2-preview)
876 ( (display-graphic-p))
877 (thumbnail-file (org-link-beautify--generate-preview-for-file-fictionbook2 path
))
878 ((file-exists-p thumbnail-file
))
879 ;; NOTE: limite thumbnail image inline display width to hardcoded 300.
880 (width (or 300 (org-display-inline-image--width link
)))
881 (image (org--create-inline-image thumbnail-file width
)))
883 (overlay-put ov
'display image
)
884 (overlay-put ov
'face
'default
)
885 (overlay-put ov
'keymap org-link-beautify-keymap
))
886 (org-link-beautify-iconify ov path link
)))
890 (defcustom org-link-beautify-source-code-preview-command
892 ((executable-find "silicon") "silicon")
893 ((executable-find "germanium") "germanium"))
894 "The command used to preview source code file."
897 :group
'org-link-beautify
)
899 (defcustom org-link-beautify-source-code-preview-list
900 '(; "org" "txt" "markdown" "md"
901 "lisp" "scm" "clj" "cljs"
903 "c" "cpp" "h" "hpp" "cs" "java"
906 "A list of link types supports source code file preview below the link."
909 :group
'org-link-beautify
)
911 (defcustom org-link-beautify-source-code-preview-max-lines
30
912 "The maximum lines number of file for previewing."
915 :group
'org-link-beautify
)
917 (defun org-link-beautify--preview-source-code-file (file)
918 "Return first 10 lines of FILE."
922 ;; I originally use `insert-file-contents-literally', so Emacs doesn't
923 ;; decode the non-ASCII characters it reads from the file, i.e. it
924 ;; doesn't interpret the byte sequences as Chinese characters. Use
925 ;; `insert-file-contents' instead. In addition, this function decodes
926 ;; the inserted text from known formats by calling format-decode,
928 (insert-file-contents file
)
929 (org-link-beautify--display-content-block
930 ;; This `cl-loop' extract a LIST of string lines from the file content.
933 collect
(prog1 (buffer-substring-no-properties
934 (line-beginning-position)
938 (message "Unable to read file %S" file
)
941 (defun org-link-beautify--generate-preview-for-file-source-code (path)
942 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for source code file of PATH."
943 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
944 (let* ((file-path (match-string 1 path
))
945 (search-option (match-string 2 path
))
946 (source-code-file (expand-file-name (org-link-unescape file-path
)))
947 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path source-code-file
))
948 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base source-code-file
))))
950 (proc-name (format "org-link-beautify code preview - %s" source-code-file
))
951 (proc-buffer (format " *org-link-beautify code preview - %s*" source-code-file
))
952 (proc (get-process proc-name
)))
953 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
954 (unless (and (file-exists-p thumbnail-file
)
955 ;; limit to maximum 30 lines of file.
956 (> (string-to-number (shell-command-to-string (format "cat %s | wc -l" source-code-file
)))
957 org-link-beautify-source-code-preview-max-lines
))
959 (pcase (file-name-nondirectory org-link-beautify-source-code-preview-command
)
962 proc-name proc-buffer
963 "silicon" source-code-file
"-o" thumbnail-file
965 "--no-window-controls" "--shadow-blur-radius" "30" "--shadow-color" "#555"
966 "--window-title" (file-name-nondirectory file-path
)))
969 proc-name proc-buffer
970 "germanium" source-code-file
"-o" thumbnail-file
971 "--no-line-number" "--no-window-access-bar")))))
972 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
973 (org-link-beautify--notify-generate-thumbnail-failed fictionbook2-file thumbnail-file
))
974 ;; return the thumbnail file as result.
977 (defun org-link-beautify-preview-file-source-code (ov path link
)
978 "Preview source code file of PATH over OV overlay position for LINK element."
979 (if-let* (( (display-graphic-p))
980 (org-link-beautify-source-code-preview-command)
981 (thumbnail-file (org-link-beautify--generate-preview-for-file-source-code path
))
982 ((file-exists-p thumbnail-file
))
983 (image (create-image thumbnail-file nil nil
:width
800)))
985 (overlay-put ov
'display image
)
986 (overlay-put ov
'face
'default
)
987 (overlay-put ov
'keymap org-link-beautify-keymap
))
988 (if-let* ((source-code (org-link-beautify--preview-source-code-file path
)))
990 (overlay-put ov
'after-string source-code
)
991 (overlay-put ov
'face
'org-block
))
992 (org-link-beautify-iconify ov path link
))))
996 (defcustom org-link-beautify-comic-preview-command
998 (darwin (or (executable-find "qlmanage") org-link-beautify-thumbnailer-script
))
999 (gnu/linux org-link-beautify-thumbnailer-script
))
1000 "Whether enable CDisplay Archived Comic Book Formats cover preview.
1001 File extensions like (.cbr, .cbz, .cb7, .cba, .cbt etc)."
1004 :group
'org-link-beautify
)
1006 (defcustom org-link-beautify-comic-preview-list
1007 '("cbr" "cbz" "cb7" "cba" "cbt")
1008 "A list of comic file types be supported with thumbnails."
1011 :group
'org-link-beautify
)
1013 (defcustom org-link-beautify-comic-preview-size
500
1014 "The CDisplay Archived Comic Book Formats cover preview image size."
1017 :group
'org-link-beautify
)
1019 (defun org-link-beautify--generate-preview-for-file-comic (path)
1020 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for CDisplay Archived Comic Book: .cbz, .cbr etc file of PATH."
1021 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1022 (let* ((file-path (match-string 1 path
))
1023 (comic-file (expand-file-name (org-link-unescape file-path
)))
1024 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path comic-file
))
1025 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base comic-file
))))
1026 (thumbnail-size (or org-link-beautify-comic-preview-size
1080))
1027 (proc-name (format "org-link-beautify comic preview - %s" (file-name-base file-path
)))
1028 (proc-buffer (format " *org-link-beautify comic preview - %s*" (file-name-base file-path
)))
1029 (proc (get-process proc-name
)))
1030 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
1031 (unless (file-exists-p thumbnail-file
)
1033 (cl-case system-type
1035 (pcase (file-name-nondirectory org-link-beautify-comic-preview-command
)
1036 ("cbconvert" ; https://github.com/gen2brain/cbconvert
1040 org-link-beautify-comic-preview-command
1041 "cover" comic-file
"--output" thumbnails-dir
1042 (if org-link-beautify-comic-preview-size
1044 (if org-link-beautify-comic-preview-size
1045 (number-to-string thumbnail-size
))))
1046 ("thumbnailer.py" (org-link-beautify-thumbnailer file-path
))))
1048 ;; for macOS "qlmanage" command
1049 ;; $ qlmanage -t "ラセン恐怖閣-マリコとニジロー1-DL版.cbz" - 2.0 -s 1080 -o ".thumbnails"
1050 (pcase (file-name-nondirectory org-link-beautify-comic-preview-command
)
1052 (let ((qlmanage-thumbnail-file (concat thumbnails-dir
(file-name-nondirectory comic-file
) ".png")))
1055 :command
(list org-link-beautify-comic-preview-command
1059 "-s" (number-to-string thumbnail-size
))
1061 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
1062 :sentinel
(lambda (proc event
)
1063 (if org-link-beautify-enable-debug-p
1064 (message (format "> proc: %s\n> event: %s" proc event
))
1065 ;; (when (string= event "finished\n")
1066 ;; (kill-buffer (process-buffer proc))
1067 ;; (kill-process proc))
1069 ;; then rename [file.extension.png] to [file.png]
1070 (when (file-exists-p qlmanage-thumbnail-file
)
1071 (rename-file qlmanage-thumbnail-file thumbnail-file
))))
1072 ("thumbnailer.py" (org-link-beautify-thumbnailer file-path
))))
1073 (t (user-error "This system platform currently not supported by org-link-beautify.\n Please contribute code to support")))))
1074 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
1075 (org-link-beautify--notify-generate-thumbnail-failed comic-file thumbnail-file
))
1076 ;; return the thumbnail file as result.
1079 (defun org-link-beautify-preview-file-comic (ov path link
)
1080 "Preview comic .cbz or .cbr file of PATH over OV overlay position for LINK element."
1081 (if-let* (( (display-graphic-p))
1082 (org-link-beautify-comic-preview-command)
1083 (thumbnail-file (org-link-beautify--generate-preview-for-file-comic path
))
1084 ((file-exists-p thumbnail-file
))
1085 (image (create-image thumbnail-file nil nil
:width
(or org-link-beautify-comic-preview-size
300))))
1087 (overlay-put ov
'display image
)
1088 (overlay-put ov
'face
'default
)
1089 (overlay-put ov
'keymap org-link-beautify-keymap
))
1090 (org-link-beautify-iconify ov path link
)))
1094 (defvar org-link-beautify-video-thumbnailer-script
1095 (expand-file-name "scripts/thumbnailer-video.py" (file-name-directory (or load-file-name
(buffer-file-name))))
1096 "The path of video thumbnailer script.")
1098 (defcustom org-link-beautify-video-preview-command
1099 (cl-case system-type
1100 ;; for macOS, use `qlmanage' on priority
1101 (darwin (or (executable-find "qlmanage") (executable-find "ffmpeg")))
1102 ;; for Linux, use `ffmpegthumbnailer' on priority
1103 (gnu/linux
(or (executable-find "ffmpegthumbnailer") (executable-find "ffmpeg")))
1104 ;; for general, use `ffmpeg'
1105 ;; $ ffmpeg -i video.mp4 -ss 00:01:00.000 -vframes 1 -vcodec png -an -f rawvideo -s 119x64 out.png
1106 (t (or (executable-find "ffmpeg")
1107 org-link-beautify-video-thumbnailer-script
1108 org-link-beautify-thumbnailer-script
)))
1109 "The available command to preview video."
1112 :group
'org-link-beautify
)
1114 (defcustom org-link-beautify-video-preview-list
1115 '("mp4" "webm" "mkv" "mov" "mpeg" "ogg" "ogv" "rmvb" "rm" "avi" "m4v" "flv")
1116 "A list of video file types be supported with thumbnails."
1119 :group
'org-link-beautify
)
1121 (defcustom org-link-beautify-video-preview-size
600
1122 "The video thumbnail image size."
1125 :group
'org-link-beautify
)
1127 (defun org-link-beautify--generate-preview-for-file-video (path)
1128 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for video .mp4 etc file of PATH."
1129 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1130 (let* ((file-path (match-string 1 path
))
1131 (search-option (match-string 2 path
))
1132 (video-file (expand-file-name (org-link-unescape file-path
)))
1133 (video-filename (file-name-nondirectory video-file
))
1134 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path video-file
))
1135 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base video-file
))))
1136 (thumbnail-size (or org-link-beautify-video-preview-size
600))
1137 (proc-name (format "org-link-beautify video preview - %s" video-filename
))
1138 (proc-buffer (format " *org-link-beautify video preview - %s*" video-filename
))
1139 (proc (get-process proc-name
)))
1140 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
1141 (unless (file-exists-p thumbnail-file
)
1142 ;; detect process already running?
1144 (pcase (file-name-nondirectory org-link-beautify-video-preview-command
)
1146 ;; $ ffmpeg -i video.mp4 -ss 00:00:00.001 -vframes 1 -vcodec png -an -f rawvideo -s 119x64 out.png
1147 (let ((thumbnail-width thumbnail-size
)
1148 (thumbnail-height 300))
1150 proc-name proc-buffer
1153 "-ss" "00:00:00.001"
1158 "-s" (format "%sx%s" thumbnail-width thumbnail-height
)
1161 (let ((qlmanage-thumbnail-file (concat thumbnails-dir
(file-name-nondirectory video-file
) ".png")))
1162 (unless (file-exists-p qlmanage-thumbnail-file
)
1163 (let ((proc (start-process
1164 proc-name proc-buffer
1165 "qlmanage" "-x" "-t" "-s" (number-to-string thumbnail-size
) video-file
"-o" thumbnails-dir
))
1166 (proc-filter (lambda (proc output
)
1167 ;; * No thumbnail created for [FILE PATH]
1168 (when (string-match "\\* No thumbnail created for.*" output
)
1170 "[org-link-beautify] video preview FAILED on macOS QuickLook generating thumbnail for %s"
1172 (set-process-filter proc proc-filter
)))
1173 ;; then rename [file.extension.png] to [file.png]
1174 (when (file-exists-p qlmanage-thumbnail-file
)
1175 (rename-file qlmanage-thumbnail-file thumbnail-file
))))
1176 ("ffmpegthumbnailer"
1178 proc-name proc-buffer
1179 "ffmpegthumbnailer" "-f" "-i" video-file
"-s" (number-to-string thumbnail-size
) "-o" thumbnail-file
))
1180 ("thumbnailer-video.py"
1183 :command
(list org-link-beautify-video-thumbnailer-script
1186 (number-to-string thumbnail-size
))
1188 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
1189 :sentinel
(lambda (proc event
)
1190 (when org-link-beautify-enable-debug-p
1191 (message (format "> proc: %s\n> event: %s" proc event
)))
1192 ;; (when (string= event "finished\n")
1193 ;; (kill-buffer (process-buffer proc))
1194 ;; (kill-process proc))
1196 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
1197 (org-link-beautify--notify-generate-thumbnail-failed video-file thumbnail-file
))
1198 ;; return the thumbnail file as result.
1201 (defun org-link-beautify-preview-file-video (ov path link
)
1202 "Preview video file of PATH over OV overlay position for LINK element."
1203 (if-let* (( (display-graphic-p))
1204 (org-link-beautify-video-preview-command)
1205 (thumbnail-file (org-link-beautify--generate-preview-for-file-video path
))
1206 ((file-exists-p thumbnail-file
))
1207 (image (create-image thumbnail-file nil nil
:width
400)))
1209 (overlay-put ov
'display image
)
1210 (overlay-put ov
'face
'default
)
1211 (overlay-put ov
'keymap org-link-beautify-keymap
))
1212 (org-link-beautify-iconify ov path link
)))
1216 (defcustom org-link-beautify-audio-preview-command
1217 (cl-case system-type
1218 (darwin (or (executable-find "ffmpeg") (executable-find "qlmanage")))
1219 (gnu/linux
(or (executable-find "ffmpeg") (executable-find "audiowaveform"))))
1220 "Find available audio preview command."
1223 :group
'org-link-beautify
)
1225 (defcustom org-link-beautify-audio-preview-list
'("mp3" "wav" "flac" "ogg" "m4a" "opus" "dat")
1226 "A list of audio file types be supported generating audio wave form image."
1229 :group
'org-link-beautify
)
1231 (defcustom org-link-beautify-audio-preview-size
600
1232 "The audio wave form image size."
1235 :group
'org-link-beautify
)
1237 (defun org-link-beautify--generate-preview-for-file-audio (path)
1238 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for audio .mp3 etc file of PATH."
1239 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1240 (let* ((file-path (match-string 1 path
))
1241 (search-option (match-string 2 path
))
1242 (audio-file (expand-file-name (org-link-unescape file-path
)))
1243 (audio-filename (file-name-nondirectory audio-file
))
1244 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path audio-file
))
1245 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base audio-file
))))
1246 (thumbnail-size (or org-link-beautify-audio-preview-size
300))
1247 (proc-name (format "org-link-beautify audio preview - %s" audio-filename
))
1248 (proc-buffer (format " *org-link-beautify audio preview - %s*" audio-filename
))
1249 (proc (get-process proc-name
)))
1250 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
1251 (unless (file-exists-p thumbnail-file
)
1253 (pcase (file-name-nondirectory org-link-beautify-audio-preview-command
)
1256 proc-name proc-buffer
1257 "ffmpeg" "-i" audio-file
1258 "-filter_complex" "[0:a]aformat=channel_layouts=mono,compand=gain=-6,showwavespic=s=600x120:colors=#9cf42f[fg];color=s=600x120:color=#44582c,drawgrid=width=iw/10:height=ih/5:color=#9cf42f@0.1[bg];[bg][fg]overlay=format=auto,drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color=#9cf42f"
1262 (let ((qlmanage-thumbnail-file (concat thumbnails-dir
(file-name-nondirectory audio-file
) ".png")))
1263 (unless (file-exists-p qlmanage-thumbnail-file
)
1265 proc-name proc-buffer
1266 "qlmanage" "-x" "-t" "-s" (number-to-string thumbnail-size
) audio-file
"-o" thumbnails-dir
))
1267 ;; then rename [file.extension.png] to [file.png]
1268 (when (file-exists-p qlmanage-thumbnail-file
)
1269 (rename-file qlmanage-thumbnail-file thumbnail-file
))))
1272 proc-name proc-buffer
1273 "audiowaveform" "-i" audio-file
"-o" thumbnail-file
)))))
1274 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
1275 (org-link-beautify--notify-generate-thumbnail-failed audio-file thumbnail-file
))
1276 ;; return the thumbnail file as result.
1279 (defun org-link-beautify-preview-file-audio (ov path link
)
1280 "Preview audio file of PATH over OV overlay position for LINK element."
1281 (if-let* (( (display-graphic-p))
1282 (org-link-beautify-audio-preview-command)
1283 (thumbnail-file (org-link-beautify--generate-preview-for-file-audio path
))
1284 ((file-exists-p thumbnail-file
))
1285 (image (create-image thumbnail-file nil nil
:width
(or org-link-beautify-audio-preview-size
300))))
1287 (overlay-put ov
'display image
)
1288 (overlay-put ov
'face
'default
)
1289 (overlay-put ov
'keymap org-link-beautify-keymap
))
1290 (org-link-beautify-iconify ov path link
)))
1292 ;;; file: [subtitle]
1294 (defcustom org-link-beautify-subtitle-preview-command org-link-beautify-thumbnailer-script
1295 "The command to preview subtitle file."
1298 :group
'org-link-beautify
)
1300 ;; https://en.wikipedia.org/wiki/Subtitles
1301 (defcustom org-link-beautify-subtitle-preview-list
1302 '("ass" "srt" "sub" "vtt" "ssf")
1303 "A list of subtitle file types support previewing."
1306 :group
'org-link-beautify
)
1308 (defcustom org-link-beautify-subtitle-preview-size
300
1309 "The subtitle preview image size."
1312 :group
'org-link-beautify
)
1314 (defun org-link-beautify--generate-preview-for-file-subtitle (path)
1315 "Generate THUMBNAIL-FILE with THUMBNAIL-SIZE for subtitle file of PATH."
1316 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1317 (let* ((file-path (match-string 1 path
))
1318 (search-option (match-string 2 path
))
1319 (subtitle-file (expand-file-name (org-link-unescape file-path
))))
1320 (if-let* ((org-link-beautify-subtitle-preview-command)
1321 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path subtitle-file
))
1322 (thumbnail-file (expand-file-name (format "%s%s.png" thumbnails-dir
(file-name-base subtitle-file
))))
1323 (thumbnail-size (or org-link-beautify-subtitle-preview-size
200))
1324 (proc-name (format "org-link-beautify subtitle preview - %s" subtitle-file
))
1325 (proc-buffer (format " *org-link-beautify subtile preview - %s*" subtitle-file
))
1326 (proc (get-process proc-name
)))
1327 (prog1 thumbnail-file
; return the thumbnail file as result.
1328 (unless (file-exists-p thumbnail-file
)
1329 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
1330 (pcase (file-name-nondirectory org-link-beautify-subtitle-preview-command
)
1331 ("thumbnailer.py" (org-link-beautify-thumbnailer file-path
)))))
1332 (let* ((subtitle-file-context (split-string (shell-command-to-string (format "head -n 20 '%s'" subtitle-file
)) "\n"))
1333 (text (concat "\n" (org-link-beautify--display-content-block subtitle-file-context
))))
1334 ;; return the subtitle file context as result.
1337 (defun org-link-beautify-preview-file-subtitle (ov path link
)
1338 "Preview subtitle file of PATH over OV overlay position for LINK element."
1339 (if-let* (( (display-graphic-p))
1340 (org-link-beautify-subtitle-preview-command)
1341 (thumbnail-file (org-link-beautify--generate-preview-for-file-subtitle path
))
1342 ((file-exists-p thumbnail-file
))
1343 (image (create-image thumbnail-file nil nil
:width
(or org-link-beautify-subtitle-preview-size
300))))
1345 (overlay-put ov
'display image
)
1346 (overlay-put ov
'face
'default
)
1347 (overlay-put ov
'keymap org-link-beautify-keymap
))
1348 (if-let* ((text (org-link-beautify--generate-preview-for-file-subtitle path
)))
1350 (overlay-put ov
'after-string text
)
1351 (overlay-put ov
'face
'default
))
1352 (org-link-beautify-iconify ov path link
))))
1354 ;;; file: [archive file]
1356 (defcustom org-link-beautify-archive-preview-list
1357 '("zip" "rar" "7z" "gz" "tar" "tar.gz" "tar.bz2" "xz" "zst")
1358 "A list of archive file types support previewing."
1361 :group
'org-link-beautify
)
1363 (defcustom org-link-beautify-archive-preview-command-alist
1364 '(("zip" .
"unzip -l")
1366 ("7z" .
"7z l -ba") ; -ba - suppress headers; undocumented.
1367 ("gz" .
"gzip --list")
1369 ("tar" .
"tar --list")
1370 ("tar.gz" .
"tar --gzip --list")
1371 ("tar.bz2" .
"tar --bzip2 --list")
1372 ("xz" .
"xz --list")
1373 ("zst" .
"zstd --list"))
1374 "An alist of archive types supported archive preview inside files list.
1375 Each element has form (ARCHIVE-FILE-EXTENSION COMMAND)."
1376 :type
'(alist :value-type
(group string
))
1377 :group
'org-link-beautify
)
1379 (defun org-link-beautify--generate-preview-for-file-archive (path)
1380 "Get the files list preview of archive file at PATH."
1381 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1382 (let* ((file-path (match-string 1 path
))
1383 (search-option (match-string 2 path
))
1384 (archive-file (expand-file-name (org-link-unescape file-path
)))
1385 (archive-extension (file-name-extension archive-file
))
1386 (command (cdr (assoc archive-extension org-link-beautify-archive-preview-command-alist
)))
1387 (execute-command (format "%s '%s'" command archive-file
))
1388 (archive-files-list (split-string (shell-command-to-string execute-command
) "\n"))
1389 (text (concat "\n" (org-link-beautify--display-content-block archive-files-list
))))
1390 ;; return the files list in archive file as result.
1393 (defun org-link-beautify-preview-file-archive (ov path link
)
1394 "Preview archive file link of PATH over OV overlay position for LINK element."
1395 (if-let* ((text (org-link-beautify--generate-preview-for-file-archive path
)))
1397 (overlay-put ov
'after-string text
)
1398 (overlay-put ov
'face
'default
)
1399 (overlay-put ov
'keymap org-link-beautify-keymap
))
1400 (org-link-beautify-iconify ov path link
)))
1402 ;;; pdf: & docview: link type
1404 (defun org-link-beautify-preview-pdf (ov path link
)
1405 "Preview pdf: link of PATH over OV overlay position for LINK element."
1406 (if-let* (( (display-graphic-p))
1407 (thumbnail-file (org-link-beautify--generate-preview-for-file-pdf path
))
1408 ((file-exists-p thumbnail-file
))
1409 ;; reference `org--create-inline-image'
1410 (width (org-display-inline-image--width link
))
1411 (image (org--create-inline-image thumbnail-file width
)))
1413 (overlay-put ov
'display image
)
1414 (overlay-put ov
'face
'default
)
1415 (overlay-put ov
'keymap org-link-beautify-keymap
))
1416 (org-link-beautify-iconify ov path link
)))
1420 (defalias 'org-link-beautify-preview-epub
'org-link-beautify-preview-file-epub
1421 "Preview epub: link of PATH over OV overlay position for LINK element.")
1425 (defalias 'org-link-beautify-preview-nov
'org-link-beautify-preview-epub
1426 "Preview nov: link of PATH over OV overlay position for LINK element.")
1428 ;;; video: link type
1430 (defalias 'org-link-beautify-preview-video
'org-link-beautify-preview-file-video
1431 "Preview video: link of PATH over OV overlay position for LINK element.")
1433 ;;; audio: link type
1435 (defalias 'org-link-beautify-preview-audio
'org-link-beautify-preview-file-audio
1436 "Preview audio: link of PATH over OV overlay position for LINK element.")
1438 ;;; org-contact: link type
1440 (defun org-link-beautify--generate-preview-for-org-contacts (name)
1441 "Get the avatar of org-contact in NAME."
1442 (let* ((epom (org-contacts-search-contact name
)))
1443 (org-contacts-get-avatar-icon epom
)))
1446 ;; (org-link-beautify--generate-preview-for-org-contacts "stardiviner")
1448 (defun org-link-beautify-preview-org-contact (ov path link
)
1449 "Preview org-contct: link of PATH over OV overlay position for LINK element."
1450 (if-let* ((name path
)
1451 ( (display-graphic-p))
1452 (image (org-link-beautify--generate-preview-for-org-contacts name
)))
1454 (overlay-put ov
'display image
)
1455 (overlay-put ov
'after-string
(concat
1456 (propertize "{" 'face
'(:foreground
"purple2"))
1457 (propertize name
'face
'org-verbatim
)
1458 (propertize "}" 'face
'(:foreground
"purple2")))))
1459 (if-let* ((text (org-element-property :title
(org-contacts-search-contact name
))))
1460 (overlay-put ov
'after-string
(concat
1461 (propertize "{" 'face
'(:foreground
"purple2"))
1462 (propertize text
'face
'org-verbatim
)
1463 (propertize "}" 'face
'(:foreground
"purple2"))))
1464 (org-link-beautify-iconify ov path link
))))
1466 ;;; org-bookmark: link type
1468 (require 'org-bookmarks nil t
)
1470 (defun org-link-beautify--generate-preview-for-org-bookmarks (path)
1471 "Get the bookmark content at title PATH."
1472 (when (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?\\'" path
)
1473 (let* ((bookmark-title (match-string 1 path
))
1474 (search-option (match-string 2 path
))
1475 ;; reference `org-bookmarks-link-open'
1476 (bookmark-content (org-bookmarks--get-bookmark-content path
))
1477 (cwd (file-name-directory org-bookmarks-file
))
1478 (text (concat "\n" (org-link-beautify--display-org-content bookmark-content
))))
1479 ;; return the files list in archive file as result.
1482 (defun org-link-beautify-preview-org-bookmark (ov path link
)
1483 "Preview org-bookmark: link of PATH over OV overlay position for LINK element."
1484 (if-let* (((require 'org-bookmarks nil t
))
1485 (text (org-link-beautify--generate-preview-for-org-bookmarks path
)))
1487 (overlay-put ov
'after-string text
)
1488 (overlay-put ov
'face
'org-link
))
1489 (org-link-beautify-iconify ov path link
)))
1491 ;;; excalidraw: link type
1493 (defun org-link-beautify-preview-excalidraw (ov path link
)
1494 "Preview excalidraw file of PATH over OV overlay position for LINK element."
1495 ;; TODO: reference `org-excalidraw--shell-cmd-to-svg'
1500 (defun org-link-beautify-preview-geography (ov path link
)
1501 "Preview geo: link of PATH over OV overlay position for LINK element."
1507 (defun org-link-beautify-preview-git (ov path link
)
1508 "Preview git: link of PATH over OV overlay position for LINK element."
1509 (org-link-beautify-iconify ov path link
))
1511 ;;; http[s]: url link type
1513 (defcustom org-link-beautify-url-preview-command
1514 (cl-case system-type
1517 ;; Google Chrome headless screenshot
1518 ((file-exists-p "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
1519 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
1521 ((executable-find "webkit2png") "webkit2png")
1523 ((executable-find "monolith") "monolith")))
1526 ;; Google Chrome headless screenshot
1527 ((executable-find "chrome") (executable-find "google-chrome"))
1529 ((executable-find "webkit2png") "webkit2png")
1531 ((executable-find "monolith") "monolith"))))
1532 "Find available URL web page screenshot archive command."
1535 :group
'org-link-beautify
)
1537 (defcustom org-link-beautify-url-preview-size
800
1538 "The url web page thumbnail image size."
1541 :group
'org-link-beautify
)
1543 (defun org-link-beautify--generate-preview-for-url (ov path link
)
1544 "Generate screenshot archive for the URL PATH web page on OV overlay at LINK element."
1545 (let* ((type (org-element-property :type link
))
1546 (url (concat type
":" path
))
1547 (thumbnails-dir (org-link-beautify--get-thumbnails-dir-path (buffer-file-name)))
1548 (thumbnail-filename (format "org-link-beautify screenshot of sha1 URL %s.png" (sha1 url
)))
1549 (thumbnail-file (expand-file-name thumbnail-filename thumbnails-dir
))
1550 (html-archive-file (concat (file-name-sans-extension thumbnail-file
) ".html"))
1551 (thumbnail-size (or org-link-beautify-url-preview-size
1000))
1552 (proc-name (format "org-link-beautify url screenshot - %s" url
))
1553 (proc-buffer (format " *org-link-beautify url screenshot - %s*" url
))
1554 (proc (get-process proc-name
)))
1555 (org-link-beautify--ensure-thumbnails-dir thumbnails-dir
)
1556 (unless (or (file-exists-p thumbnail-file
)
1557 (file-exists-p html-archive-file
))
1558 (when org-link-beautify-url-preview-command
1560 (pcase org-link-beautify-url-preview-command
1561 ((or "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" "google-chrome")
1562 ;; $ google-chrome --headless --screenshot=screenshot.png "https://www.chromestatus.com/"
1564 proc-name proc-buffer
1565 org-link-beautify-url-preview-command
1567 (format "--screenshot=%s" thumbnail-file
)
1572 :command
(list "webkit2png" url
"-o" thumbnail-file
)
1574 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
1575 :sentinel
(lambda (proc event
)
1576 (when (string= event
"finished\n")
1577 (kill-buffer (process-buffer proc
))
1578 (kill-process proc
)))))
1580 (let* ((html-archive-file (concat (file-name-sans-extension thumbnail-file
) ".html")))
1583 :command
(list "monolith" "--no-audio" "--no-video" url
"--output" html-archive-file
)
1585 :stderr nil
; If STDERR is nil, standard error is mixed with standard output and sent to BUFFER or FILTER.
1586 :sentinel
(lambda (proc event
)
1587 (when (string= event
"finished\n")
1588 (kill-buffer (process-buffer proc
))
1589 (kill-process proc
))))))))))
1590 (when (and org-link-beautify-enable-debug-p
(not (file-exists-p thumbnail-file
)))
1591 (org-link-beautify--notify-generate-thumbnail-failed url thumbnail-file
))))
1593 (defun org-link-beautify-preview-url (ov path link
)
1594 "Preview http[s]: URL link of PATH over OV overlay position for LINK element."
1595 (if-let* (( (display-graphic-p))
1596 (org-link-beautify-url-preview-command)
1597 (thumbnail-file (org-link-beautify--generate-preview-for-url ov path link
))
1598 ((file-exists-p thumbnail-file
))
1599 (image (create-image thumbnail-file nil nil
:width
(or org-link-beautify-url-preview-size
600))))
1601 (overlay-put ov
'display image
)
1602 (overlay-put ov
'face
'default
)
1603 (overlay-put ov
'keymap org-link-beautify-keymap
))
1604 (org-link-beautify-iconify ov path link
)))
1606 ;;; Insert Org link without description based on smart detecting file extension.
1608 (defun org-link-beautify-remove-description (orig-func link-raw
&optional link-description
)
1609 "Advice function to remove LINK-DESCRIPTION from LINK-RAW around ORIG-FUNC.
1611 This is for link image previewing to get around function `org-link-preview'
1612 \(original named `org-toggle-inline-images'\) parameter `include-linked'."
1613 (let ((link-type (when (string-match org-link-types-re link-raw
) (match-string 1 link-raw
)))
1614 (extension (file-name-extension link-raw
)))
1615 (when (or (member extension image-file-name-extensions
) ; image files
1616 (member extension
'("pdf" "epub" "mobi" "azw3" "lit" "fb2" "fb2.zip")) ; ebook files
1617 (member extension
'("avi" "rmvb" "ogg" "ogv" "mp4" "mkv" "mov" "mpeg" "webm" "flv" "ts" "mpg")) ; video files
1618 (member extension
'("mp3" "wav" "flac" "ogg" "m4a" "opus" "dat")) ; audio files
1619 (member extension
'("cbr" "cbz" "cb7" "cba" "cbt")) ; comic files
1620 (member extension
'("zip" "rar" "7z" "gz" "tar" "tar.gz" "tar.bz2" "xz" "zst")) ; archive files
1621 (member extension
'("ass" "srt" "sub" "vtt" "ssf")) ; subtitle files
1622 (member link-type
'("info" "help" "shortdoc" "man" "woman" "id" "custom-id" "coderef"))
1623 (member link-type
'("elisp" "shell" "js" "javascript" "grep" "occur" "git"))
1624 (member link-type
'("mailto" "rss" "news" "wikipedia" "irc" "magnet" "wechat" "web-browser" "eww" "chrome" "edge"))
1625 (member link-type
'("org-ql-search" "org-contact" "org-bookmark" "orgit" "orgit-rev" "orgit-log"))
1626 ;; Emacs package special link types
1627 ;; NOTE: "epub" "nov" page-number thumbnail generating not supported.
1628 (member link-type
'("pdf" "pdfview" "docview")) ; "epub" "nov"
1629 (member link-type
'("video" "videocite" "audio" "audiocite" "excalidraw"))
1630 ;; speail meaning link types
1631 (member link-type
'("geo"))
1632 ;; special application link types
1633 (member link-type
'("vscode" "macappstore")))
1634 (setq link-description nil
)))
1635 (funcall orig-func link-raw link-description
))
1638 ;;; minor mode `org-link-beautify-mode'
1641 (defun org-link-beautify-enable ()
1642 "Enable `org-link-beautify'."
1643 (dolist (link-type (mapcar #'car org-link-parameters
))
1645 ("file" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-file
)) ; `org-link-preview-file',
1646 ("attachment" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-attachment
)) ; `org-attach-preview-file'
1647 ("docview" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-pdf
)) ; extension `doc-view'
1648 ("pdfview" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-pdf
)) ; extension `pdf-tools'
1649 ("pdf" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-pdf
))
1650 ("epub" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-epub
))
1651 ("nov" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-nov
)) ; extension `nov'
1652 ("geo" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-geography
))
1653 ("http" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-url
))
1654 ("https" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-url
))
1655 ("ftp" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-url
))
1657 ;; Org mode internal link types
1658 ("custom-id" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1659 ("id" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1660 ("coderef" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1661 ("elisp" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1662 ("eshell" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1663 ("shell" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1664 ("man" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1665 ("woman" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1666 ("info" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1667 ("help" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1668 ("shortdoc" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1670 ;; Org mode external link types
1671 ("grep" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1672 ("occur" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1673 ("mailto" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1674 ("news" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1675 ("rss" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1676 ("elfeed" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; extension `elfeed'
1677 ("wikipedia" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1678 ("irc" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1679 ("wechat" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1680 ("magnet" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1681 ("git" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-git
))
1682 ("eww" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; EWW
1683 ("chrome" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1684 ("edge" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1685 ("mu4e" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1686 ("web-browser" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-git
))
1688 ;; org-ref link types
1689 ("cite" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1690 ("ref" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1691 ("doi" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1692 ("bibtex" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1693 ("bibliography" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))
1695 ;; org-mode extensions link types
1696 ("org-ql-search" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; extension `org-ql'
1697 ("org-contact" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-org-contact
)) ; extension `org-contacts'
1698 ("org-bookmark" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-org-bookmark
)) ; extension `org-bookmarks'
1699 ("orgit-rev" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-git
)) ; extension `orgit'
1700 ("orgit-log" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-git
)) ; extension `orgit'
1701 ("orgit" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-git
)) ; extension `orgit'
1702 ("excalidraw" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-excalidraw
)) ; extension `org-excalidraw'
1704 ;; org-media-note link types
1705 ("video" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-video
)) ; `org-media-note'
1706 ("audio" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-audio
)) ; `org-media-note'
1707 ("videocite" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-video
)) ; `org-media-note'
1708 ("audiocite" (org-link-set-parameters link-type
:preview
#'org-link-beautify-preview-audio
)) ; `org-media-note'
1711 ("eaf" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; extension `emacs-application-framework'
1712 ("javascript" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; Org mode inline source code link
1713 ("js" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; Org mode inline source code link
1714 ("vscode" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; Visual Studio Code
1715 ("macappstore" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; Mac App Store
1716 ("fuzzy" (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
)) ; org-mode internal raw link type
1717 (_ (org-link-set-parameters link-type
:preview
#'org-link-beautify-iconify
))))
1718 ;; remove link description
1719 (advice-add 'org-link-make-string
:around
#'org-link-beautify-remove-description
))
1722 (defun org-link-beautify-disable ()
1723 "Disable `org-link-beautify'."
1724 (dolist (link-type (mapcar #'car org-link-parameters
))
1726 ("file" (org-link-set-parameters "file" :preview
#'org-link-preview-file
))
1727 ("attachment" (org-link-set-parameters "attachment" :preview
#'org-attach-preview-file
))
1728 (_ (org-link-set-parameters link-type
:preview nil
))))
1729 (advice-remove 'org-link-make-string
#'org-link-beautify-remove-description
))
1731 (defvar org-link-beautify-mode-map
1732 (let ((map (make-sparse-keymap)))
1734 "The `org-link-beautify-mode' minor mode map.")
1737 (define-minor-mode org-link-beautify-mode
1738 "A minor mode to beautify Org Mode links with icons, and inline preview etc."
1739 :group
'org-link-beautify
1743 :keymap org-link-beautify-mode-map
; avoid to enable `org-link-beautify-keymap' globally everywhere.
1744 (if org-link-beautify-mode
1745 (org-link-beautify-enable)
1746 (org-link-beautify-disable)))
1750 (provide 'org-link-beautify
)
1752 ;;; org-link-beautify.el ends here