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.