Skip to content

Instantly share code, notes, and snippets.

@krisbalintona
Last active November 13, 2024 04:06
Show Gist options
  • Save krisbalintona/f4554bb8e53c27c246ae5e3c4ff9b342 to your computer and use it in GitHub Desktop.
Save krisbalintona/f4554bb8e53c27c246ae5e3c4ff9b342 to your computer and use it in GitHub Desktop.
Code snippet from my Emacs config. Only tested on Linux; requires the pdftk binary. To use, call `krisb-pdf-tools-metadata-modify` interactively a pdf-view buffer, or invoke it non-interactively with a single argument that is the pdf file path.
;;; -*- lexical-binding: t -*-
;; UPDATE 2024-11-12: I am currently in development of pdf-meta-edit.el
;; (https://github.com/krisbalintona/pdf-meta-edit) which is meant to be a
;; featureful package that covers the use-case below and more.
;;; Modify PDF metadata
;; Emacs wrapper and convenience functions for changing package metadata using
;; pdftk. Only tested on Linux; requires the pdftk binary. See
;; https://unix.stackexchange.com/a/72457 for more information on the CLI
;; commands involved.
;; NOTE: The version of the code below should be evaluated in a lexically-scoped
;; buffer. If evaluated in a dynamically-scoped buffer,
;; `krisb-pdf-tools-metadata-modify' will not be fully functional because of the
;; way it is written.
;;
;; For more information about using lexical- versus dynamic-binding, see
;; (info "(elisp) Using Lisp Binding"), or if you are using emacs-30,
;; (info "(elisp) Selecting Lisp Dialect").
;; USAGE:
;;
;; Code snippet from my Emacs config. Only tested on Linux; requires the pdftk
;; binary. To use, call `krisb-pdf-tools-metadata-modify` interactively a
;; pdf-view buffer, or invoke it non-interactively with a single argument that
;; is the pdf file path.
;;
;; This creates a new buffer that represents the metadata of the pdf. Modify it
;; to your desire. This includes changing the bookmarks (i.e. pdf outline),
;; labels (i.e. actual pagination, not pdf page numbers), pagination styles
;; (e.g. Roman Numeral), and more.
;;
;; The major mode for this buffer does not do any fontification, but users can
;; easily create their own font locking.
;;
;; There is single local command, `krisb-pdf-tools-metadata-bookmark-section',
;; that creates a bookmark section. It is bound to `C-c C-b'. Creating
;; local commands that suit your needs is trivial.
;;
;; When finished editing the package metadata, press `C-c C-c'. This writes the
;; metadata to the file and kills the buffer. If you decide you do not want to
;; make any changes to the metadata, simply kill the buffer.
;; If there is enough interest in this functionality, I can try formalizing the
;; code packaging it on MELPA. For now, this snippet is sufficient for my
;; needs.
;;;###autoload
(defun krisb-pdf-tools-metadata-bookmark-section ()
"Insert bookmark metadata section."
(interactive)
(save-excursion
(insert "\nBookmarkBegin\nBookmarkTitle: \nBookmarkLevel: 1\nBookmarkPageNumber: "))
(move-end-of-line 2))
(defvar-keymap krisb-pdf-tools-metadata-mode-map
:doc "Mode map for `krisb-pdf-tools-metadata-mode'."
"C-c C-b" #'krisb-pdf-tools-metadata-bookmark-section)
(define-derived-mode krisb-pdf-tools-metadata-mode fundamental-mode "Metadata"
"Major mode for altering and viewing PDF metadata."
:interactive t
(use-local-map krisb-pdf-tools-metadata-mode-map))
;;;###autoload
(defun krisb-pdf-tools-metadata-modify (pdf-file)
"Modify PDF-FILE metadata."
(interactive (list (buffer-file-name)))
(unless (string= "pdf" (file-name-extension pdf-file))
(user-error "File is not a PDF!"))
(unless (executable-find "pdftk")
(error "System executable `pdftk' not found. Please install executable on filesystem to proceed"))
(let* ((pdf-name (file-name-sans-extension (file-name-nondirectory pdf-file)))
(buf-name (concat "*pdf-tools metadata: " pdf-name))
(metadata-file (concat "/tmp/pdf-tools-metadata--" pdf-name))
(temp-pdf (make-temp-file "/tmp/pdf-tools-metadata--temp-pdf"))
(metadata-dump-command (concat "pdftk \"" pdf-file "\" dump_data"))
(metadata-update-command
(concat "pdftk \"" pdf-file "\" update_info \"" metadata-file "\" output \"" temp-pdf "\""))
(commit-func (lambda ()
"Commit the changes to PDF metadata."
(interactive)
(with-current-buffer buf-name
(widen)
(write-region (point-min) (point-max) metadata-file))
(shell-command metadata-update-command "*pdf-tools metadata: CLI output")
(kill-buffer buf-name)
;; Have to do it this way since `pdftk' does not allow
;; having the output file be the input file
(rename-file temp-pdf pdf-file t)
(message "Updated metadata!"))))
(save-buffer)
(with-current-buffer (get-buffer-create buf-name)
(insert (shell-command-to-string metadata-dump-command))
(goto-char (point-min))
(krisb-pdf-tools-metadata-mode))
(pop-to-buffer buf-name)
(define-key krisb-pdf-tools-metadata-mode-map (kbd "C-c C-c") commit-func)
(set-buffer-modified-p nil)
(message (substitute-command-keys "Press `C-c C-c' when finished editing PDF metadata. To see keybinds, press \\[describe-mode]"))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment