File: image-dired-external.el

package info (click to toggle)
emacs 1%3A30.1%2B1-8
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 187,300 kB
  • sloc: lisp: 1,367,529; ansic: 466,479; objc: 19,117; cpp: 8,817; java: 8,780; sh: 8,119; makefile: 7,288; python: 3,788; perl: 1,788; xml: 1,720; yacc: 1,566; asm: 1,150; php: 1,035; pascal: 1,011; awk: 937; cs: 880; ada: 725; ruby: 658; javascript: 187; erlang: 153; tcl: 16
file content (647 lines) | stat: -rw-r--r-- 29,509 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
;;; image-dired-external.el --- External process support for Image-Dired  -*- lexical-binding: t -*-

;; Copyright (C) 2005-2025 Free Software Foundation, Inc.

;; Author: Mathias Dahl <mathias.rem0veth1s.dahl@gmail.com>
;; Maintainer: Stefan Kangas <stefankangas@gmail.com>
;; Keywords: multimedia
;; Package: image-dired

;; 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:

;; See the description of the `image-dired' package.

;;; Code:

(require 'dired)
(require 'exif)

(require 'image-dired-util)

(declare-function image-dired-display-image "image-dired")
(declare-function clear-image-cache "image.c" (&optional filter))
(declare-function w32image-create-thumbnail "w32image.c")

(defvar image-dired-dir)
(defvar image-dired-thumb-size)
(defvar image-dired-main-image-directory)
(defvar image-dired-rotate-original-ask-before-overwrite)
(defvar image-dired-thumbnail-storage)

(defgroup image-dired-external nil
  "External process support for Image-Dired."
  :prefix "image-dired-"
  :link '(info-link "(emacs) Image-Dired")
  :group 'image-dired)

(defcustom image-dired-cmd-create-thumbnail-program
  (if (executable-find "gm") "gm" "convert")
  "File name of the executable used to create thumbnails.
Used together with `image-dired-cmd-create-thumbnail-options'.
On MS-Windows, if such an executable is not available, Emacs
will use `w32image-create-thumbnail' to create thumbnails."
  :type 'file
  :version "29.1")

(defcustom image-dired-cmd-create-thumbnail-options
  (let ((opts '("-size" "%wx%h" "%f[0]"
                "-resize" "%wx%h>"
                "-strip" "jpeg:%t")))
    (if (executable-find "gm") (cons "convert" opts) opts))
  "Options of command used to create thumbnail image.
Used with `image-dired-cmd-create-thumbnail-program', if that is
available.
Available format specifiers are:
    %s, %w and %h, which are replaced by `image-dired-thumb-size'
    %f which is replaced by the file name of the original image and
    %t which is replaced by the file name of the thumbnail file."
  :type '(repeat (string :tag "Argument"))
  :version "29.1")

(defcustom image-dired-cmd-pngnq-program
  ;; Prefer pngquant to pngnq-s9 as it is faster on my machine.
  ;;   The project also seems more active than the alternatives.
  ;; Prefer pngnq-s9 to pngnq as it fixes bugs in pngnq.
  ;; The pngnq project seems dead (?) since 2011 or so.
  (or (executable-find "pngquant")
      (executable-find "pngnq-s9")
      (executable-find "pngnq"))
  "The executable file name of the `pngquant' or `pngnq' program.
It quantizes colors of PNG images down to 256 colors or fewer
using the NeuQuant algorithm."
  :version "29.1"
  :type '(choice (const :tag "Not Set" nil) file))

(defcustom image-dired-cmd-pngnq-options
  (if (executable-find "pngquant")
      '("--ext" "-nq8.png" "%t") ; same extension as "pngnq"
    '("-f" "%t"))
  "Arguments to pass to `image-dired-cmd-pngnq-program'.
Value can use the same format specifiers as in
`image-dired-cmd-create-thumbnail-options'."
  :type '(repeat (string :tag "Argument"))
  :version "29.1")

(defcustom image-dired-cmd-pngcrush-program (executable-find "pngcrush")
  "The executable file name of the `pngcrush' program.
It optimizes the compression of PNG images.  It also adds PNG textual chunks
with the information required by the Thumbnail Managing Standard."
  :type '(choice (const :tag "Not Set" nil) file))

;; Note: the "-text" arguments below are according to specification in
;; https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#CREATION
(defcustom image-dired-cmd-pngcrush-options
  `("-q"
    "-text" "b" "Description" "Thumbnail of file://%u"
    "-text" "b" "Software" ,(string-replace "\n" " " (emacs-version))
    ;; "-text b \"Thumb::Image::Height\" \"%oh\" "
    ;; "-text b \"Thumb::Image::Mimetype\" \"%mime\" "
    ;; "-text b \"Thumb::Image::Width\" \"%ow\" "
    "-text" "b" "Thumb::MTime" "%m"
    ;; "-text b \"Thumb::Size\" \"%b\" "
    "-text" "b" "Thumb::URI" "file://%u"
    "%q" "%t")
  "Arguments for `image-dired-cmd-pngcrush-program'.
The value can use the same %-format specifiers as in
`image-dired-cmd-create-thumbnail-options', with \"%q\" standing for a
temporary file name (typically generated by pnqnq),
and \"%u\" standing for a file URI corresponding to file in \"%f\"."
  :version "26.1"
  :type '(repeat (string :tag "Argument")))

(defcustom image-dired-cmd-optipng-program (executable-find "optipng")
  "The executable file name of the `optipng' program."
  :version "26.1"
  :type '(choice (const :tag "Not Set" nil) file))

(defcustom image-dired-cmd-optipng-options '("-o5" "%t")
  "Arguments passed to `image-dired-cmd-optipng-program'.
The value can use the same %-format specifiers as in
`image-dired-cmd-create-thumbnail-options'."
  :version "26.1"
  :type '(repeat (string :tag "Argument"))
  :link '(url-link "man:optipng(1)"))

;; Note: the "-set" arguments below are according to specification in
;; https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#CREATION
(defcustom image-dired-cmd-create-standard-thumbnail-options
  (let ((opts (list
               "-size" "%wx%h" "%f[0]"
               "-set" "Thumb::MTime" "%m"
               "-set" "Thumb::URI" "file://%u"
               "-set" "Description" "Thumbnail of file://%u"
               "-set" "Software" (string-replace "\n" " " (emacs-version))
               "-thumbnail" "%wx%h>" "png:%t")))
    (if (executable-find "gm") (cons "convert" opts) opts))
  "Options for creating thumbnails according to the Thumbnail Managing Standard.
Used with `image-dired-cmd-create-thumbnail-program', if that is available.
The value can use the same %-format specifiers as in
`image-dired-cmd-create-thumbnail-options', with \"%m\" for file
modification time and \"%u\" for the URI of the file in \"%f\".
On MS-Windows, if the `convert' command is not available, and
`w32image-create-thumbnail' is used instead, the textual chunks
specified by the \"-set\" options will not be injected, and instead
they are added by `pngcrush' if that is available."
  :type '(repeat (string :tag "Argument"))
  :version "29.1")

(defcustom image-dired-cmd-rotate-original-program "jpegtran"
  "Executable file of a program used to rotate original image.
Used together with `image-dired-cmd-rotate-original-options'."
  :type 'file)

(defcustom image-dired-cmd-rotate-original-options
  '("-rotate" "%d" "-copy" "all" "-outfile" "%t" "%o")
  "Arguments of command used to rotate original image.
Used with `image-dired-cmd-rotate-original-program'.
The value can use the following format specifiers:
%d which is replaced by the number of (positive) degrees
to rotate the image, normally 90 or 270 (for 90 degrees right and left),
%o which is replaced by the original image file name
and %t which is replaced by `image-dired-temp-image-file'."
  :version "26.1"
  :type '(repeat (string :tag "Argument")))

(defcustom image-dired-temp-rotate-image-file
  (expand-file-name ".image-dired_rotate_temp"
                    (locate-user-emacs-file "image-dired/"))
  "Temporary file for image rotation operations."
  :type 'file)

(defcustom image-dired-cmd-write-exif-data-program "exiftool"
  "Executable file of a program used to write EXIF data to images.
Used together with `image-dired-cmd-write-exif-data-options'."
  :type 'file)

(defcustom image-dired-cmd-write-exif-data-options '("-%t=%v" "%f")
  "Arguments of the command used to write EXIF data.
Used with `image-dired-cmd-write-exif-data-program'.
The value can use the following format specifiers are:
%f which is replaced by the image file name,
%t which is replaced by the tag name
and %v which is replaced by the tag value."
  :version "26.1"
  :type '(repeat (string :tag "Argument")))


;;; Util functions

(defun image-dired--file-URI (file)
  ;; https://en.wikipedia.org/wiki/File_URI_scheme
  (if (memq system-type '(windows-nt ms-dos))
      (concat "/" file)
    file))

(defun image-dired--probe-thumbnail-cmd (cmd)
  "Check whether CMD is usable for thumbnail creation."
  (cond
   ;; MS-Windows has an incompatible 'convert' command.  Make sure this
   ;; is the one we expect, from ImageMagick.  FIXME: Should we do this
   ;; also on systems other than MS-Windows?
   ((and (memq system-type '(windows-nt cygwin ms-dos))
         (member (downcase (file-name-nondirectory cmd))
                 '("convert" "convert.exe")))
    (with-temp-buffer
      (let (process-file-side-effects)
        (and (equal (condition-case nil
                        ;; Implementation note: 'process-file' below
                        ;; returns non-zero status when convert.exe is
                        ;; the Windows command, because we quote the
                        ;; "/?" argument, and Windows is not smart
                        ;; enough to process quoted options correctly.
		        (apply #'process-file cmd nil t nil '("/?"))
		      (error nil))
		    0)
	     (progn
	       (goto-char (point-min))
	       (looking-at-p "Version: ImageMagick"))))))
   (t t)))

(defun image-dired--check-executable-exists (executable &optional func)
  "If program EXECUTABLE does not exist or cannot be used, signal an error.
But if optional argument FUNC (which must be a symbol) names a known
function, consider that function to be an alternative to running EXECUTABLE."
  (let ((cmd (symbol-value executable)))
    (or (and (executable-find cmd)
             (image-dired--probe-thumbnail-cmd cmd))
        (and func (fboundp func) 'function)
        (error "Executable named by `%S' (%s) not found or not pertinent"
               executable (symbol-value executable)))))


;;; Creating thumbnails

(defun image-dired--thumb-size (&optional _)
  "Return thumb size depending on `image-dired-thumbnail-storage'."
  (declare (advertised-calling-convention () "29.1"))
  (pcase image-dired-thumbnail-storage
    ('standard 128)
    ('standard-large 256)
    ('standard-x-large 512)
    ('standard-xx-large 1024)
    (_ image-dired-thumb-size)))

(defvar image-dired--generate-thumbs-start nil
  "Time when `display-thumbs' was called.")

(defvar image-dired-queue nil
  "List of items in the Image-Dired queue.
Each item has the form (ORIGINAL-FILE TARGET-FILE).")

(defvar image-dired-queue-active-jobs 0
  "Number of active jobs in `image-dired-queue'.")

(defvar image-dired-queue-active-limit (min 4 (max 2 (/ (num-processors) 2)))
  "Maximum number of concurrent jobs permitted for generating images.
Increase at your own risk.  If you want to experiment with this,
consider setting `image-dired-debug' to a non-nil value to see
the time spent on generating thumbnails.  Run `clear-image-cache'
and remove the cached thumbnail files between each trial run.
This is unused on MS-Windows when `w32image-create-thumbnail' is
used instead of ImageMagick or GraphicsMagick commands.
In addition, even if those commands are available, the actual number
of concurrent jobs will be limited by 30 from above, since Emacs
on MS-Windows cannot have too many concurrent sub-processes.")

(defun image-dired-pngnq-thumb (spec)
  "Quantize thumbnail described by format SPEC with command `pngnq'."
  (let* ((snt
          (lambda (process status)
            (if (or (and (processp process) ; async case
                         (eq (process-status process) 'exit)
                         (zerop (process-exit-status process)))
                    (zerop status))     ; sync case
                ;; Pass off to pngcrush, or just rename the
                ;; THUMB-nq8.png file back to THUMB.png
                (if (and image-dired-cmd-pngcrush-program
                         (executable-find image-dired-cmd-pngcrush-program))
                    (image-dired-pngcrush-thumb spec)
                  (let ((nq8 (cdr (assq ?q spec)))
                        (thumb (cdr (assq ?t spec))))
                    (rename-file nq8 thumb t)))
              (if (processp process)
                  (message "command %S %s" (process-command process)
                           (string-replace "\n" "" status))))))
         (proc
          (let ((args (mapcar (lambda (arg) (format-spec arg spec))
                              image-dired-cmd-pngnq-options)))
            (if (eq system-type 'windows-nt)
                ;; Cannot safely use 'start-process' here, since awe
                ;; could be called to produce thumbnails for many
                ;; images, and we have a hard limitation of 32
                ;; simultaneous sub-processes on MS-Windows.
                (apply #'call-process
                       image-dired-cmd-pngnq-program nil nil nil args)
              (apply #'start-process
                     "image-dired-pngnq" nil
                     image-dired-cmd-pngnq-program args)))))
    (if (processp proc)
        (setf (process-sentinel proc) snt)
      (unless (zerop proc)
        (message "command %S failed" image-dired-cmd-pngnq-program))
      (funcall snt image-dired-cmd-pngnq-program proc))
    proc))

(defun image-dired-pngcrush-thumb (spec)
  "Optimize thumbnail described by format SPEC with command `pngcrush'."
  ;; If pngnq wasn't run, then the THUMB-nq8.png file does not exist.
  ;; pngcrush needs an infile and outfile, so we just copy THUMB to
  ;; THUMB-nq8.png and use the latter as a temp file.
  (when (not image-dired-cmd-pngnq-program)
    (let ((temp (cdr (assq ?q spec)))
          (thumb (cdr (assq ?t spec))))
      (copy-file thumb temp)))
  (let* ((args (mapcar
                (lambda (arg)
                  (format-spec arg spec))
                image-dired-cmd-pngcrush-options))
         (snt (lambda (process status)
                (unless (or (and (processp process)
                                 (eq (process-status process) 'exit)
                                 (zerop (process-exit-status process)))
                            (zerop status))
                  (if (processp process)
                      (message "command %S %s" (process-command process)
                               (string-replace "\n" "" status))
                    (message "command %S failed with status %s"
                             process status)))
                (when (or (not (processp process))
                          (memq (process-status process) '(exit signal)))
                  (let ((temp (cdr (assq ?q spec))))
                    (delete-file temp)))))
         (proc
          (if (eq system-type 'windows-nt)
              ;; See above for the reasons we don't use 'start-process'
              ;; on MS-Windows.
              (apply #'call-process
                     image-dired-cmd-pngcrush-program nil nil nil args)
            (apply #'start-process "image-dired-pngcrush" nil
                   image-dired-cmd-pngcrush-program args))))
    (if (processp proc)
        (setf (process-sentinel proc) snt)
      (funcall snt image-dired-cmd-pngcrush-program proc))
    proc))

(defun image-dired-optipng-thumb (spec)
  "Optimize thumbnail described by format SPEC with command `optipng'."
  (let* ((args (mapcar
                (lambda (arg)
                  (format-spec arg spec))
                image-dired-cmd-optipng-options))
         (snt (lambda (process status)
                (unless (or (and (processp process)
                                 (eq (process-status process) 'exit)
                                 (zerop (process-exit-status process)))
                            (zerop status))
                  (if (processp process)
                      (message "command %S %s" (process-command process)
                               (string-replace "\n" "" status))
                    (message "command %S failed with status %s"
                             process status)))))
         (proc
          (if (eq system-type 'windows-nt)
              ;; See above for the reasons we don't use 'start-process'
              ;; on MS-Windows.
              (apply #'call-process
                     image-dired-cmd-optipng-program nil nil nil args)
            (apply #'start-process "image-dired-optipng" nil
                   image-dired-cmd-optipng-program args))))
    (if (processp proc)
        (setf (process-sentinel proc) snt)
      (funcall snt image-dired-cmd-optipng-program proc))
    proc))

(defun image-dired-create-thumb-1 (original-file thumbnail-file)
  "For ORIGINAL-FILE, create thumbnail image named THUMBNAIL-FILE."
  (let* ((size (number-to-string (image-dired--thumb-size)))
         (modif-time (format-time-string
                      "%s" (file-attribute-modification-time
                            (file-attributes original-file))))
         (thumbnail-nq8-file (replace-regexp-in-string ".png\\'" "-nq8.png"
                                                       thumbnail-file))
         (spec `((?s . ,size) (?w . ,size) (?h . ,size)
                 (?m . ,modif-time)
                 (?f . ,original-file)
                 (?u . ,(image-dired--file-URI original-file))
                 (?q . ,thumbnail-nq8-file)
                 (?t . ,thumbnail-file)))
         (thumbnail-dir (file-name-directory thumbnail-file))
         process)
    (when (not (file-exists-p thumbnail-dir))
      (with-file-modes #o700
        (make-directory thumbnail-dir t))
      (message "Thumbnail directory created: %s" thumbnail-dir))

    ;; Thumbnail file creation processes begin here and are marshaled
    ;; in a queue by `image-dired-create-thumb'.
    (let ((cmd image-dired-cmd-create-thumbnail-program)
          (args (mapcar
                 (lambda (arg) (format-spec arg spec))
                 (if (memq image-dired-thumbnail-storage
                           image-dired--thumbnail-standard-sizes)
                     image-dired-cmd-create-standard-thumbnail-options
                   image-dired-cmd-create-thumbnail-options))))
      (image-dired-debug "Running %s %s" cmd (string-join args " "))
      (setq process
            (apply #'start-process "image-dired-create-thumbnail" nil
                   cmd args)))

    (setf (process-sentinel process)
          (lambda (process status)
            ;; Trigger next in queue once a thumbnail has been created
            (cl-decf image-dired-queue-active-jobs)
            (image-dired-thumb-queue-run)
            (when (= image-dired-queue-active-jobs 0)
              (image-dired-debug
               (format-time-string
                "Generated thumbnails in %s.%3N seconds"
                (time-subtract nil
                               image-dired--generate-thumbs-start))))
            (if (not (and (eq (process-status process) 'exit)
                          (zerop (process-exit-status process))))
                (message "Thumb could not be created for %s: %s"
                         (abbreviate-file-name original-file)
                         (string-replace "\n" "" status))
              (set-file-modes thumbnail-file #o600)
              (clear-image-cache thumbnail-file)
              ;; PNG thumbnail has been created since we are
              ;; following the XDG thumbnail spec, so try to optimize
              (when (memq image-dired-thumbnail-storage
                          image-dired--thumbnail-standard-sizes)
                (cond
                 ((and image-dired-cmd-pngnq-program
                       (executable-find image-dired-cmd-pngnq-program))
                  (image-dired-pngnq-thumb spec))
                 ((and image-dired-cmd-pngcrush-program
                       (executable-find image-dired-cmd-pngcrush-program))
                  (image-dired-pngcrush-thumb spec))
                 ((and image-dired-cmd-optipng-program
                       (executable-find image-dired-cmd-optipng-program))
                  (image-dired-optipng-thumb spec)))))))
    process))

(defun image-dired-create-thumb-2 (original-file thumbnail-file)
  "For ORIGINAL-FILE, create thumbnail image named THUMBNAIL-FILE.
This is like `image-dired-create-thumb-1', but used when the thumbnail
file is created by Emacs itself."
  (let ((size (image-dired--thumb-size))
        (thumbnail-dir (file-name-directory thumbnail-file)))
    (when (not (file-exists-p thumbnail-dir))
      (with-file-modes #o700
        (make-directory thumbnail-dir t))
      (message "Thumbnail directory created: %s" thumbnail-dir))
    (image-dired-debug "Creating thumbnail for %s" original-file)
    (if (null (w32image-create-thumbnail original-file thumbnail-file
                                         (file-name-extension thumbnail-file)
                                         size size))
        (message "Failed to create a thumbnail for %s"
                 (abbreviate-file-name original-file))
      (clear-image-cache thumbnail-file)
      ;; PNG thumbnail has been created since we are following the XDG
      ;; thumbnail spec, so try to optimize.
      (when (memq image-dired-thumbnail-storage
                  image-dired--thumbnail-standard-sizes)
        (let* ((modif-time (format-time-string
                            "%s" (file-attribute-modification-time
                                  (file-attributes original-file))))
               (thumbnail-nq8-file (replace-regexp-in-string
                                    ".png\\'" "-nq8.png" thumbnail-file))
               (spec `((?s . ,size) (?w . ,size) (?h . ,size)
                       (?m . ,modif-time)
                       (?f . ,original-file)
                       (?u . ,(image-dired--file-URI original-file))
                       (?q . ,thumbnail-nq8-file)
                       (?t . ,thumbnail-file))))
          (cond
           ((and image-dired-cmd-pngnq-program
                 (executable-find image-dired-cmd-pngnq-program))
            (image-dired-pngnq-thumb spec))
           ((and image-dired-cmd-pngcrush-program
                 (executable-find image-dired-cmd-pngcrush-program))
            (image-dired-pngcrush-thumb spec))
           ((and image-dired-cmd-optipng-program
                 (executable-find image-dired-cmd-optipng-program))
            (image-dired-optipng-thumb spec))))))
    ;; Trigger next in queue once a thumbnail has been created.
    (image-dired-thumb-queue-run)))

(defun image-dired-thumb-queue-run ()
  "Run a queued job if one exists and not too many jobs are running.
Queued items live in `image-dired-queue'.
Number of simultaneous jobs is limited by `image-dired-queue-active-limit'."
  (if (not (eq (image-dired--check-executable-exists
                'image-dired-cmd-create-thumbnail-program
                'w32image-create-thumbnail)
               'function))
      ;; We have a usable gm/convert command; queue thethumbnail jobs.
      (let ((max-jobs
             (if (eq system-type 'windows-nt)
                 ;; Can't have more than 32 concurrent sub-processes on
                 ;; MS-Windows.
                 (min 30 image-dired-queue-active-limit)
               image-dired-queue-active-limit)))
        (while (and image-dired-queue
                    (< image-dired-queue-active-jobs max-jobs))
          (cl-incf image-dired-queue-active-jobs)
          (apply #'image-dired-create-thumb-1 (pop image-dired-queue))))
    ;; We are on MS-Windows with ImageMagick/GraphicsMagick, and need to
    ;; generate thumbnails by our lonesome selves.
    (if image-dired-queue
        (let* ((job (pop image-dired-queue))
               (orig-file (car job))
               (thumb-file (cadr job)))
          (run-with-timer 0.05 nil
                          #'image-dired-create-thumb-2
                          orig-file thumb-file)))))

(defun image-dired-create-thumb (original-file thumbnail-file)
  "Add a job for generating ORIGINAL-FILE thumbnail to `image-dired-queue'.
The new file will be named THUMBNAIL-FILE."
  (setq image-dired-queue
        (nconc image-dired-queue
               (list (list original-file thumbnail-file))))
  (run-at-time 0 nil #'image-dired-thumb-queue-run))

(defun image-dired-refresh-thumb ()
  "Force creation of new image for current thumbnail."
  (interactive nil image-dired-thumbnail-mode)
  (let* ((file (image-dired-original-file-name))
         (thumb (expand-file-name (image-dired-thumb-name file))))
    (clear-image-cache (expand-file-name thumb))
    (image-dired-create-thumb file thumb)))

(defun image-dired-rotate-original (degrees)
  "Rotate original image DEGREES degrees."
  (image-dired--check-executable-exists
   'image-dired-cmd-rotate-original-program)
  (if (not (image-dired-image-at-point-p))
      (message "No image at point")
    (let* ((file (image-dired-original-file-name))
           (spec
            (list
             (cons ?d degrees)
             (cons ?o (expand-file-name file))
             (cons ?t image-dired-temp-rotate-image-file))))
      (unless (eq 'jpeg (image-type file))
        (user-error "Only JPEG images can be rotated"))
      (if (not (= 0 (apply #'call-process image-dired-cmd-rotate-original-program
                           nil nil nil
                           (mapcar (lambda (arg) (format-spec arg spec))
                                   image-dired-cmd-rotate-original-options))))
          (error "Could not rotate image")
        (image-dired-display-image image-dired-temp-rotate-image-file)
        (if (or (and image-dired-rotate-original-ask-before-overwrite
                     (y-or-n-p
                      "Rotate to temp file OK.  Overwrite original image? "))
                (not image-dired-rotate-original-ask-before-overwrite))
            (progn
              (copy-file image-dired-temp-rotate-image-file file t)
              (image-dired-refresh-thumb)
              (image-dired-update-thumbnail-at-point))
          (image-dired-display-image file))))))


;;; EXIF support

(defun image-dired-get-exif-file-name (file)
  "Use the image's EXIF information to return a unique file name.
The file name should be unique as long as you do not take more than
one picture per second.  The original file name is suffixed at the end
for traceability.  The format of the returned file name is
YYYY_MM_DD_HH_MM_ss_ORIG_FILE_NAME.jpg.  Used from
`image-dired-copy-with-exif-file-name'."
  (let (data no-exif-data-found)
    (if (not (eq 'jpeg (image-type (expand-file-name file))))
        (setq no-exif-data-found t
              data (format-time-string
                    "%Y:%m:%d %H:%M:%S"
                    (file-attribute-modification-time
                     (file-attributes (expand-file-name file)))))
      (setq data (exif-field 'date-time (exif-parse-file
                                         (expand-file-name file)))))
    (while (string-match "[ :]" data)
      (setq data (replace-match "_" nil nil data)))
    (format "%s%s%s" data
            (if no-exif-data-found
                "_noexif_"
              "_")
            (file-name-nondirectory file))))

(defun image-dired-thumbnail-set-image-description ()
  "Set the ImageDescription EXIF tag for the original image at point.
If the image already has a value for this tag, it is used as the
default value at the prompt."
  (interactive nil image-dired-thumbnail-mode)
  (if (not (image-dired-image-at-point-p))
      (message "No thumbnail at point")
    (let* ((file (image-dired-original-file-name))
           (old-value (or (exif-field 'description (exif-parse-file file)) ""))
           (defdir default-directory))
      (with-temp-buffer
        (setq default-directory defdir)
        (if (eq 0
                (image-dired-set-exif-data file "ImageDescription"
                                           (read-string
                                            "Value of ImageDescription: "
                                            old-value)))
            (message "Successfully wrote ImageDescription tag")
          (error "Could not write ImageDescription tag: %s"
                 (string-replace "\n" "" (buffer-string))))))))

(defun image-dired-set-exif-data (file tag-name tag-value)
  "In FILE, set EXIF tag TAG-NAME to value TAG-VALUE.
This function inserts any output from `exiftool' program into
the current buffer, so it could be shown to the user.  If that
is not desirable, wrap the call in `with-temp-buffer'."
  (image-dired--check-executable-exists
   'image-dired-cmd-write-exif-data-program)
  (let ((spec
         (list
          (cons ?f (expand-file-name file))
          (cons ?u (image-dired--file-URI (expand-file-name file)))
          (cons ?t tag-name)
          (cons ?v tag-value))))
    (apply #'call-process image-dired-cmd-write-exif-data-program nil t nil
           (mapcar (lambda (arg) (format-spec arg spec))
                   image-dired-cmd-write-exif-data-options))))

(define-obsolete-function-alias 'image-dired-thumb-size #'image-dired--thumb-size "29.1")

(provide 'image-dired-external)

;;; image-dired-external.el ends here