2019-09-28 23:26:02 +00:00
|
|
|
;;; image-converter.el --- Converting images from exotic formats -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2019 Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
|
|
|
|
;; Keywords: images
|
|
|
|
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;; The main interface function here is `image-convert'.
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 'cl-generic)
|
|
|
|
(eval-when-compile (require 'cl-lib))
|
|
|
|
|
|
|
|
(defcustom image-converter nil
|
2019-09-29 07:09:56 +00:00
|
|
|
"Type of the external image converter to use.
|
|
|
|
The value should a symbol, either `imagemagick', `graphicsmagick',
|
|
|
|
or `ffmpeg'.
|
|
|
|
If nil, Emacs will try to find one of the supported converters
|
|
|
|
installed on the system."
|
2019-09-28 23:26:02 +00:00
|
|
|
:group 'image
|
|
|
|
:type 'symbol
|
|
|
|
:version "27.1")
|
|
|
|
|
|
|
|
(defvar image-converter-regexp nil
|
|
|
|
"A regexp that matches the file name suffixes that can be converted.")
|
|
|
|
|
|
|
|
(defvar image-converter--converters
|
|
|
|
'((graphicsmagick :command "gm convert" :probe "-list format")
|
|
|
|
(imagemagick :command "convert" :probe "-list format")
|
|
|
|
(ffmpeg :command "ffmpeg" :probe "-decoders"))
|
|
|
|
"List of supported image converters to try.")
|
|
|
|
|
|
|
|
(defun image-convert-p (file)
|
2019-09-29 07:09:56 +00:00
|
|
|
"Return `image-convert' if FILE is an image file that can be converted."
|
2019-09-28 23:26:02 +00:00
|
|
|
;; Find an installed image converter.
|
|
|
|
(unless image-converter
|
|
|
|
(image-converter--find-converter))
|
|
|
|
(and image-converter
|
|
|
|
(string-match image-converter-regexp file)
|
|
|
|
'image-convert))
|
|
|
|
|
|
|
|
(defun image-convert (image)
|
2019-09-29 07:09:56 +00:00
|
|
|
"Convert IMAGE file to the PNG format.
|
2019-09-28 23:26:02 +00:00
|
|
|
IMAGE can either be a file name, which will make the return value
|
|
|
|
a string with the image data. It can also be an image object as
|
|
|
|
returned by `create-image'. If so, it has to be an image object
|
|
|
|
where created with DATA-P nil (i.e., it has to refer to a file)."
|
|
|
|
;; Find an installed image converter.
|
|
|
|
(unless image-converter
|
|
|
|
(image-converter--find-converter))
|
|
|
|
(unless image-converter
|
2019-09-29 07:09:56 +00:00
|
|
|
(error "No external image converters available"))
|
2019-09-28 23:26:02 +00:00
|
|
|
(when (and (listp image)
|
|
|
|
(not (plist-get (cdr image) :file)))
|
|
|
|
(error "Only images that refer to files can be converted"))
|
|
|
|
(with-temp-buffer
|
|
|
|
(set-buffer-multibyte nil)
|
|
|
|
(when-let ((err (image-converter--convert
|
|
|
|
image-converter
|
|
|
|
(if (listp image)
|
|
|
|
(plist-get (cdr image) :file)
|
|
|
|
image))))
|
|
|
|
(error "%s" err))
|
|
|
|
(if (listp image)
|
|
|
|
;; Return an image object that's the same as we were passed,
|
|
|
|
;; but ignore the :type and :file values.
|
|
|
|
(apply #'create-image (buffer-string) 'png t
|
|
|
|
(cl-loop for (key val) on (cdr image) by #'cddr
|
|
|
|
unless (memq key '(:type :file))
|
|
|
|
append (list key val)))
|
|
|
|
(buffer-string))))
|
|
|
|
|
|
|
|
(defun image-converter--value (type elem)
|
|
|
|
"Return the value of ELEM of image converter TYPE."
|
|
|
|
(plist-get (cdr (assq type image-converter--converters)) elem))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--probe ((type (eql graphicsmagick)))
|
|
|
|
"Check whether the system has GraphicsMagick installed."
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((command (split-string (image-converter--value type :command) " "))
|
|
|
|
formats)
|
|
|
|
(when (zerop (apply #'call-process (car command) nil '(t nil) nil
|
|
|
|
(append (cdr command)
|
|
|
|
(split-string
|
|
|
|
(image-converter--value type :probe) " "))))
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when (re-search-forward "^-" nil t)
|
|
|
|
(forward-line 1)
|
|
|
|
;; Lines look like
|
|
|
|
;; " 8BIM P rw- Photoshop resource format".
|
|
|
|
(while (re-search-forward "^ *\\([A-Z0-9]+\\) +. +r" nil t)
|
|
|
|
(push (downcase (match-string 1)) formats)))
|
|
|
|
(nreverse formats)))))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--probe ((type (eql imagemagick)))
|
|
|
|
"Check whether the system has ImageMagick installed."
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((command (split-string (image-converter--value type :command) " "))
|
|
|
|
formats)
|
|
|
|
;; Can't check return value; ImageMagick convert usually returns
|
|
|
|
;; a non-zero result on "-list format".
|
|
|
|
(apply #'call-process (car command) nil '(t nil) nil
|
|
|
|
(append (cdr command)
|
|
|
|
(split-string (image-converter--value type :probe) " ")))
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when (re-search-forward "^-" nil t)
|
|
|
|
(forward-line 1)
|
|
|
|
;; Lines look like
|
|
|
|
;; " WPG* r-- Word Perfect Graphics".
|
|
|
|
(while (re-search-forward "^ *\\([A-Z0-9]+\\)\\*? +r" nil t)
|
|
|
|
(push (downcase (match-string 1)) formats)))
|
|
|
|
(nreverse formats))))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--probe ((type (eql ffmpeg)))
|
|
|
|
"Check whether the system has ffmpeg installed."
|
|
|
|
(with-temp-buffer
|
|
|
|
(let ((command (image-converter--value type :command))
|
|
|
|
formats)
|
|
|
|
(when (zerop (call-process command nil '(t nil) nil
|
|
|
|
(image-converter--value type :probe)))
|
|
|
|
(goto-char (point-min))
|
|
|
|
(when (re-search-forward "^ *-" nil t)
|
|
|
|
(forward-line 1)
|
|
|
|
;; Lines look like
|
|
|
|
;; " V....D alias_pix Alias/Wavefront PIX image"
|
|
|
|
(while (re-search-forward "^ *V[^ ]+ +\\([a-z0-9_]+\\)" nil t)
|
|
|
|
(push (match-string 1) formats)))
|
|
|
|
(nreverse formats)))))
|
|
|
|
|
|
|
|
(defun image-converter--find-converter ()
|
|
|
|
"Find an installed image converter."
|
|
|
|
(dolist (elem image-converter--converters)
|
|
|
|
(when-let ((formats (image-converter--probe (car elem))))
|
|
|
|
(setq image-converter (car elem)
|
|
|
|
image-converter-regexp (concat "\\." (regexp-opt formats) "\\'")))))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--convert ((type (eql graphicsmagick)) file)
|
|
|
|
"Convert using GraphicsMagick."
|
|
|
|
(image-converter--convert-magick type file))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--convert ((type (eql imagemagick)) file)
|
|
|
|
"Convert using ImageMagick."
|
|
|
|
(image-converter--convert-magick type file))
|
|
|
|
|
|
|
|
(defun image-converter--convert-magick (type file)
|
|
|
|
(let ((command (split-string (image-converter--value type :command) " ")))
|
|
|
|
(unless (zerop (apply #'call-process (car command)
|
|
|
|
nil t nil
|
|
|
|
(append (cdr command)
|
|
|
|
(list (expand-file-name file) "png:-"))))
|
|
|
|
;; If the command failed, hopefully the buffer contains the
|
|
|
|
;; error message.
|
|
|
|
(buffer-string))))
|
|
|
|
|
|
|
|
(cl-defmethod image-converter--convert ((type (eql ffmpeg)) file)
|
|
|
|
"Convert using ffmpeg."
|
|
|
|
(unless (zerop (call-process (image-converter--value type :command)
|
|
|
|
nil '(t nil) nil
|
|
|
|
"-i" (expand-file-name file)
|
|
|
|
"-c:v" "png" "-f" "image2pipe" "-"))
|
|
|
|
"ffmpeg error when converting"))
|
|
|
|
|
|
|
|
(provide 'image-converter)
|
|
|
|
|
|
|
|
;;; image-converter.el ends here
|