From 5de470b1030c932b4e99ce685c0f27078a5db428 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 23 May 2026 20:01:25 -0500 Subject: 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. --- modules/dwim-shell-config.el | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'modules') 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 /' > '<>/filelist.txt' && ffmpeg -f concat -safe 0 -i '<>/filelist.txt' -c copy '<>'" - :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 '<>'; 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." -- cgit v1.2.3