Collecting images into one directory for self-contained Org mode exports
Published on Jun 17, 2023.
I often embed images into my Org mode documents. Especially attachments via
org-download
are convenient while composing notes or drafts.
To share such documents, I usually export them. While HTML export allows to inline
images, this is not an option for e.g. LaTeX (if I want to share the .tex
file) or even Org mode.
To create a self-contained export including images, I have written an Elisp
routine that extracts the file path from a link, copies the target file into a
common directory and returns a modified link to the new location.
The directory can be specified as a relative path, e.g. ./images/
, which will
result in relative links in the exported document as well. The exported file can
then be easily compressed together with this directory into a zip file and send
off.
This is the code that defines the variable that configures the output path and the filter function:
(defvar hanno/org-copy-linked-files-export-path "images/" "Sets the directory that linked local files are copied to on export. The directory needs to already exist. Used by `hanno/org-link-filter-copy-images-on-export' as target directory.") (defun hanno/org-copy-linked-files-on-export (text backend _info) "Copy linked files in exports into a common location. Returns modified TEXT with path to the copied file for a given export BACKEND. Supported backends are HTML, LaTeX and Org mode. Intended to be added to `org-export-filter-link-functions'." (let (target source) (cond ((org-export-derived-backend-p backend 'html) (let* ((url (url-unhex-string (car (cl-remove-if (lambda (link) (not (string-match-p "^file:/" link))) (split-string-and-unquote text))))) (filename (file-name-nondirectory (car (url-path-and-query (url-generic-parse-url url)))))) (unless (string-empty-p filename) (setq target (concat (file-name-as-directory hanno/org-copy-linked-files-export-path) filename)) (setq source url) (with-demoted-errors "Copy-files-link-filter error: %S" (url-copy-file url target))))) ((or (org-export-derived-backend-p backend 'latex) (org-export-derived-backend-p backend 'org)) (let ((match (if (org-export-derived-backend-p backend 'latex) "\\includegraphics.*?{\\(.*?\\)}" "\\[\\[file:\\(.*?\\)\\]"))) (when (string-match match text) (setq source (match-string 1 text)) (let* ((filename (file-name-nondirectory source))) (setq target (concat (file-name-as-directory hanno/org-copy-linked-files-export-path) filename)) (with-demoted-errors "Copy-files-link-filter error: %S" (copy-file source target))))))) (when (and source target (file-exists-p target)) (string-replace source target text))))
By adding this function to org-export-filter-link-functions
it will be
triggered once for each link when exporting an Org file:
(add-to-list 'org-export-filter-link-functions 'hanno/org-copy-linked-files-on-export)
The filter will handle links such as
[[file:/path/to/some/file]]
and is technically not limited to images but would work with any linked file type.
Note that links with a description result in a \href{...}
instead of an
\includegraphics{...}
in the LaTeX backend and are ignored by the filter to
avoid affecting external links. Similarly, links without the file:
prefix are
not considered in the HTML backend.