aboutsummaryrefslogtreecommitdiff
path: root/modules/dwim-shell-config.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-23 20:01:25 -0500
committerCraig Jennings <c@cjennings.net>2026-05-23 20:01:25 -0500
commit5de470b1030c932b4e99ce685c0f27078a5db428 (patch)
tree3e9cb37a953a50fe9a81633067dc9c51ef9944c8 /modules/dwim-shell-config.el
parentc0237adec5a92d6a2814f23b40be4d5415bcd9c8 (diff)
downloaddotemacs-5de470b1030c932b4e99ce685c0f27078a5db428.tar.gz
dotemacs-5de470b1030c932b4e99ce685c0f27078a5db428.zip
fix(dwim-shell): build video-concat filelist in elisp
cj/dwim-shell-commands-concatenate-videos built the ffmpeg concat list with echo '<<*>>' | tr ' ' '\n' | sed 's/^/file /'. That splits on spaces, so any video whose name contains a space produced a broken list, and a name with a quote broke the echo outright. I extracted cj/dwim-shell--build-concat-filelist, which renders each path as an escaped file '...' line. I write that list to a temp file and run ffmpeg against the quoted listfile, with a trailing rm to clean up after the process exits. The <<*>> token stays only as an inert shell comment, since dwim-shell needs it to run one command over all marked files instead of once per file.
Diffstat (limited to 'modules/dwim-shell-config.el')
-rw-r--r--modules/dwim-shell-config.el35
1 files changed, 29 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."