2023-08-10

画像形式とimage-converterの設定

前回対応する画像形式を詳しく調べたことやimage-diredを色々いじっていたことを踏まえて、Emacsの画像形式に関する設定を更新しました。

;; (Emacs 29.1で確認)

;; 画像のコンバーターとしてImageMagickを使う。
;; GraphicsMagickは対応形式が少なくmp4やpsdに対応していない。
;; FFmpegは動画中心で色々足りない。
;; see: https://misohena.jp/blog/2023-08-09-imagemagick-vs-graphicsmagick-vs-ffmpeg-for-emacs.html
(setq image-converter 'imagemagick) ;; 注意: 変更時は下の拡張子を修正すること。

;; 変換対象の画像形式を登録する。
(let ((target-extensions
       '(;; ImageMagickが対応する形式のうち問題が無さそうなものを適当に残した。
         ;; セキュリティ的にはもっと絞った方が良いというのはある。
         ;; 他人が作ったファイルを不用意に開くべからず。
         ;; 一覧は (image-converter--probe 'imagemagick) で得られる。
         "3g2" "3gp" "ai" "apng" "art" "avi" "avif"
         "bmp" "cr2" "cr3" "cur" "dcm"
         "dcr" "dds" "dng" "dpx" "dxt1" "dxt5"
         "epdf" "epi" "eps" "epsf" "epsi" "ept" "ept2" "ept3" "erf"
         "fits" "fl32" "flif" "flv"
         "fts" "gif"
         "hdr" "heic" "heif" "hrz"
         "icb" "ico" "icon" "iiq" "ipl" "j2c" "j2k" "jbg" "jbig"
         "jng" "jnx" "jp2" "jpc" "jpe" "jpeg" "jpg" "jpm" "jps" "jpt" "k25"
         "kdc" "m2v" "m4v" "mef" "miff"
         "mkv" "mng" "mono" "mov" "mp4" "mpc" "mpeg" "mpg" "mpo" "mrw"
         "mtv" "mvg" "nef" "nrw" "orf" "otb" "otf"
         "pam" "pbm" "pcd" "pcds" "pcl"
         "pct" "pcx" "pdf" "pdfa" "pef" "pfa" "pfb" "pfm"
         "pgm" "pgx" "phm" "picon" "pict" "pix" "pjpeg" "png"
         "pnm"
         "ppm" "ps" "psb" "psd" "ptif" "pwp" "qoi" "raf" "ras"
         "rgf" "rla" "rle" "rmf" "rw2"
         "sfw"
         "sgi" "six" "sixel" "sr2" "srf"
         "sun"
         "svg" "svgz" "tga" "tiff" "tiff64" "tim"
         "tm2" "ttc" "ttf" "vda" "vicar" "viff" "vips"
         "vst" "wbmp" "webm" "webp" "wmv" "wpg" "x3f" "xbm"
         "xcf" "xpm" "xps" "xv")))

  ;; 対象をimage-file-name-extensionsに追加する。
  ;; おそらく本来はこれだけで良いはず。
  (setq image-file-name-extensions
        (seq-union image-file-name-extensions target-extensions))

  ;; いくつか問題があるので、image-converter.el内の変数を直接変更する。
  ;; (Emacs 29.1時点)
  ;;
  ;; 問題:
  ;;
  ;; - image-file-name-extensionsに指定していない形式もコンバーター
  ;;   を使ってimage-modeやcreate-imageで表示できてしまう。
  ;;
  ;; - コンバーターの初回起動に何秒もかかる。コンバーターの対応形式
  ;;   をリストアップするのに時間がかかるので。
  ;;
  ;; - 一度コンバーターが起動すると、image-file-name-extensionsに指
  ;;   定していない形式もimage-diredでサムネイル表示されるようになっ
  ;;   てしまう。
  ;;
  ;; 対応形式をリストアップする前に手動で設定してしまうことで問題を回避する。
  ;; ここはimage-converter.elの実装が変わると変更が必要になるかもしれない。
  (setq image-converter-file-name-extensions target-extensions)
  (setq image-converter-regexp
        (concat "\\." (regexp-opt target-extensions) "\\'"))

  ;; 変換対象の拡張子を持つファイルをimage-modeで開く。
  ;; auto-mode-alistの初期値には
  ;; 「Image file types probably supported by `image-convert'.」
  ;; として既に含まれているものも多いが、全てが登録されているわけではない。
  ;; psdとか。
  ;; auto-image-file-modeでもいいのかもしれない。
  (dolist (ext target-extensions)
    ;; すでにauto-mode-alistに登録されている拡張子は変更しない。
    (unless (assoc-default (format "a.%s" ext) auto-mode-alist 'string-match)
      (push (cons (format "\\.%s\\'" ext) 'image-mode)
            auto-mode-alist))))

;; create-imageでコンバーターを使う。
(setq image-use-external-converter t)

;; ImageMagickのconvertコマンドをmagick convertに置き換える。
;; convertはWindowsで困るので。
(with-eval-after-load "image-converter" ;;image-converter.elが読み込まれてから
  (setf (plist-get (alist-get 'imagemagick image-converter--converters)
                   :command)
        '("magick" "convert")))

;; 2023-08-12追記
;; MP3等対応
;; image-mode等でMP3等をを表示する方法
(defun my-image-convert-ffmpeg (source format)
  (image-converter--convert 'ffmpeg source format))
(image-converter-add-handler "mp3" #'my-image-convert-ffmpeg)
(image-converter-add-handler "m4a" #'my-image-convert-ffmpeg)
;; image-diredでMP3等をサムネイルを表示する方法
(defun my-image-dired-ffmpeg-options (file-ext)
  (pcase file-ext
    ("mp4" ;;動画のサムネイルもffmpegで生成する。
     '("-stream_loop" "-1" ;;短い動画に備えて無限ループさせる。
       "-ss" "30" ;;開始30秒時点のフレームを使う。
       "-i" "%f"
       "-vf" "scale=%w:%h:force_original_aspect_ratio=decrease"
       "-update" "true"
       "-vframes" "1"
       "%t"))
    ((or "mp3" "m4a")
     '("-i" "%f"
       "-vf" "scale=%w:%h:force_original_aspect_ratio=decrease"
       "-vframes" "1"
       "%t"))))
(defun my-image-dired-create-thumb-1-around (orig-func
                                             original-file thumbnail-file)
  (if-let ((ffmpeg-options (my-image-dired-ffmpeg-options
                            (file-name-extension original-file))))
      (let ((image-dired-cmd-create-thumbnail-program "ffmpeg")
            (image-dired-cmd-create-thumbnail-options ffmpeg-options))
        (funcall orig-func original-file thumbnail-file))
    (funcall orig-func original-file thumbnail-file)))
(advice-add 'image-dired-create-thumb-1 :around #'my-image-dired-create-thumb-1-around)

;; 2023-08-12追記
;; image-converterがImageMagickで動画ファイルを変換するときに長時間
;; 固まるのを避ける。
;; 全フレーム読み込もうとしてしまうのだとか!
;; image-diredの方は対策済み。
(defun my-image-converter--convert-magick (old-fun type source image-format)
  ;; ファイル名の後に[0]をつける。data形式の場合は未対応。
  (unless image-format
    (setq source (concat source "[0]")))
  (funcall old-fun type source image-format))
(advice-add 'image-converter--convert-magick :around
            'my-image-converter--convert-magick)

;; 2023-08-12追記
;; image-diredが生成するサムネイルのファイル名を、元画像の場所が分か
;; るような形式にする。
(defun my-image-dired--encode-thumb-name (path)
  "PATHをファイルのベース名として使える文字列へエンコードします。"
  ;; url-hexify-stringは少し問題があるので使えない。
  (mapconcat (lambda (ch)
               ;; 変換元がファイル名なのでパス区切り文字以外は不要だと思
               ;; うが念のため色々エスケープしておく。
               ;; 拡張子を示す.もエスケープして完全にベース名として認識
               ;; されるようにする。
               (if (or (<= ch 32)
                       (memq ch '(?< ?> ?: ?\" ?/ ?\\ ?| ?? ?* ?. ?%)))
                   (format "%%%02x" ch)
                 (char-to-string ch)))
             path))

(defun my-image-dired-thumb-name (old-func file)
  (if (eq 'image-dired image-dired-thumbnail-storage)
      ;; 独自のファイル名を生成する。
      (let* ((orig-path (expand-file-name file))
             (orig-filename (file-name-nondirectory orig-path))
             (orig-dir (file-name-directory orig-path)) ;;最後のスラッシュは含めてしまっていいかな。c:/とかあるし。
             (thumb-filename
              (concat (my-image-dired--encode-thumb-name orig-filename)
                      ".jpg"))
             (thumb-dir
              (expand-file-name (my-image-dired--encode-thumb-name orig-dir)
                                (image-dired-dir))))
        ;; ここでディレクトリを作ってしまうのはあまり良くないけど……
        (unless (file-directory-p thumb-dir)
          (with-file-modes #o700
            (make-directory thumb-dir t)))
        (file-name-concat thumb-dir thumb-filename))
    ;; 本来の関数を呼び出す。
    (funcall old-func file)))
(advice-add 'image-dired-thumb-name :around 'my-image-dired-thumb-name)

加えて以前書いたimage-dired用の設定を適用。

image-diredの改善 | Misohena Blog

特にWindowsでは、サムネイルを作成する際のconvertをmagick convertに置き換えておいた方が良いです。

というわけで、より多様な形式の画像ファイルをEmacsで扱えるようになりました。

image-diredでフォントファイルを表示した例
図1: image-diredでフォントファイルを表示した例

.ttfファイルってImageMagickで画像ファイルに変換できたんですね。