index rss mastodon twitter github linkedin email
Álvaro Ramírez
sponsor

Álvaro Ramírez

11 May 2020 Enrich Emacs dired's batching toolbox

Update

I now use dwim-shell-command, which reduces the logic to:

(defun dwim-shell-commands-image-to-jpg ()
  "Convert all marked images to jpg(s)."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Convert to jpg"
   "convert -verbose '<<f>>' '<<fne>>.jpg'"
   :utils "convert"))

Original post

Shell one-liners are super handy for batch-processing files. Say you'd like to convert a bunch of images from HEIC to jpg, you could use something like:

for f in *.HEIC ; do convert "$f" "${f%.*}.jpg"; done

Save the one-liner (or memorize it) and pull it from your toolbox next time you need it. This is handy as it is, but Emacs dired is just a file-management powerhouse. Its dired-map-over-marks function is just a few elisp lines away from enabling all sorts of batch processing within your dired buffers.

Dired already enables selecting and deselecting files using all sorts of built-in mechanisms (dired-mark-files-regexp, find-name-dired, etc) or wonderful third-party packages like Matus Goljer's dired-filters.

Regardless of how you selected your files, here's a snippet to run ImageMagick's convert on a bunch of selected files:

;;; -*- lexical-binding: t; -*-

(defun ar/dired-convert-image (&optional arg)
  "Convert image files to other formats."
  (interactive "P")
  (assert (or (executable-find "convert") (executable-find "magick.exe")) nil "Install imagemagick")
  (let* ((dst-fpath)
         (src-fpath)
         (src-ext)
         (last-ext)
         (dst-ext))
    (mapc
     (lambda (fpath)
       (setq src-fpath fpath)
       (setq src-ext (downcase (file-name-extension src-fpath)))
       (when (or (null dst-ext)
                 (not (string-equal dst-ext last-ext)))
         (setq dst-ext (completing-read "to format: "
                                        (seq-remove (lambda (format)
                                                      (string-equal format src-ext))
                                                    '("jpg" "png")))))
       (setq last-ext dst-ext)
       (setq dst-fpath (format "%s.%s" (file-name-sans-extension src-fpath) dst-ext))
       (message "convert %s to %s ..." (file-name-nondirectory dst-fpath) dst-ext)
       (set-process-sentinel
        (if (string-equal system-type "windows-nt")
            (start-process "convert"
                           (generate-new-buffer (format "*convert %s*" (file-name-nondirectory src-fpath)))
                           "magick.exe" "convert" src-fpath dst-fpath)
          (start-process "convert"
                         (generate-new-buffer (format "*convert %s*" (file-name-nondirectory src-fpath)))
                         "convert" src-fpath dst-fpath))
        (lambda (process state)
          (if (= (process-exit-status process) 0)
              (message "convert %s ✔" (file-name-nondirectory dst-fpath))
            (message "convert %s ❌" (file-name-nondirectory dst-fpath))
            (message (with-current-buffer (process-buffer process)
                       (buffer-string))))
          (kill-buffer (process-buffer process)))))
     (dired-map-over-marks (dired-get-filename) arg))))

The snippet can be shorter, but wouldn't be as friendly. We ask users to provide desired image format, spawn separate processes (avoids blocking Emacs), and generate a basic report. Also adds support for Windows.

batch-dired.gif

BEWARE

The snippet isn't currently capping the number of processes, but hey we can revise in the future…

Update

Thanks to Philippe Beliveau for pointing out a bug in snippet (now updated) and changes to make it Windows compatible.