diff options
| -rw-r--r-- | modules/dwim-shell-config.el | 35 | ||||
| -rw-r--r-- | tests/test-dwim-shell-config-concat.el | 29 |
2 files changed, 58 insertions, 6 deletions
diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el index 41b9231f..a68f0a22 100644 --- a/modules/dwim-shell-config.el +++ b/modules/dwim-shell-config.el @@ -169,6 +169,18 @@ single-quoted destination it is interpolated into." (and (stringp prefix) (string-match-p "\\`[[:alnum:] ._-]*\\'" prefix))) +(defun cj/dwim-shell--build-concat-filelist (files) + "Return ffmpeg concat-demuxer filelist text for FILES. +Each path becomes a single-quoted `file' line with embedded single quotes +escaped as \\='\\\\\\='\\=', so paths with spaces, quotes, or shell +metacharacters survive intact — unlike an echo/tr/sed pipeline over the raw +file list." + (mapconcat + (lambda (f) + (format "file '%s'" + (replace-regexp-in-string "'" "'\\\\''" (expand-file-name f)))) + files "\n")) + ;; ----------------------------- Dwim Shell Command ---------------------------- (use-package dwim-shell-command @@ -578,12 +590,23 @@ clipboard contents cannot inject shell commands." :utils "ffmpeg"))) (defun cj/dwim-shell-commands-concatenate-videos () - "Concatenate multiple videos into one." - (interactive) - (dwim-shell-command-on-marked-files - "Concatenate videos" - "echo '<<*>>' | tr ' ' '\n' | sed 's/^/file /' > '<<td>>/filelist.txt' && ffmpeg -f concat -safe 0 -i '<<td>>/filelist.txt' -c copy '<<concatenated.mp4(u)>>'" - :utils "ffmpeg")) + "Concatenate multiple videos into one. +Builds the ffmpeg concat filelist in Elisp so paths with spaces or quotes are +handled, instead of reconstructing it with echo/tr/sed over the raw file list. +The temp filelist is removed after ffmpeg finishes. The trailing =<<*>>= is an +inert shell comment whose only job is to make dwim-shell run one command over +all marked files rather than once per file." + (interactive) + (let ((listfile (make-temp-file "dwim-concat-" nil ".txt"))) + (with-temp-file listfile + (insert (cj/dwim-shell--build-concat-filelist (dwim-shell-command--files)) + "\n")) + (dwim-shell-command-on-marked-files + "Concatenate videos" + (format "ffmpeg -f concat -safe 0 -i %s -c copy '<<concatenated.mp4(u)>>'; rm -f %s # <<*>>" + (shell-quote-argument listfile) + (shell-quote-argument listfile)) + :utils "ffmpeg"))) (defun cj/dwim-shell-commands-create-video-thumbnail () "Extract thumbnail from video at specific time." diff --git a/tests/test-dwim-shell-config-concat.el b/tests/test-dwim-shell-config-concat.el new file mode 100644 index 00000000..acbbbb2b --- /dev/null +++ b/tests/test-dwim-shell-config-concat.el @@ -0,0 +1,29 @@ +;;; test-dwim-shell-config-concat.el --- Tests for concat filelist builder -*- lexical-binding: t; -*- + +;;; Commentary: +;; Covers cj/dwim-shell--build-concat-filelist, which renders an ffmpeg +;; concat-demuxer filelist from a list of paths. Building it in Elisp (rather +;; than echo/tr/sed over <<*>>) keeps paths with spaces and quotes intact. + +;;; Code: + +(require 'ert) +(require 'dwim-shell-config) + +(ert-deftest test-dwim-concat-filelist-plain-paths () + "Normal: plain paths become single-quoted file lines, newline-separated." + (should (equal (cj/dwim-shell--build-concat-filelist '("/v/a.mp4" "/v/b.mp4")) + "file '/v/a.mp4'\nfile '/v/b.mp4'"))) + +(ert-deftest test-dwim-concat-filelist-space-in-path () + "Boundary: a path with spaces stays inside the quotes, unsplit." + (should (equal (cj/dwim-shell--build-concat-filelist '("/v/my movie.mp4")) + "file '/v/my movie.mp4'"))) + +(ert-deftest test-dwim-concat-filelist-quote-in-path () + "Error/edge: a single quote in a path is escaped for the concat list." + (should (equal (cj/dwim-shell--build-concat-filelist '("/v/it's here.mp4")) + "file '/v/it'\\''s here.mp4'"))) + +(provide 'test-dwim-shell-config-concat) +;;; test-dwim-shell-config-concat.el ends here |
