1 ;;; ffmpeg-utils.el --- FFmpeg command utilities wrappers -*- lexical-binding: t; -*-
3 ;;; Time-stamp: <2020-10-29 11:08:29 stardiviner>
5 ;; Authors: stardiviner <numbchild@gmail.com>
6 ;; Package-Requires: ((emacs "25.1") (alert "1.2") (transient "0.1.0") (osx-lib "0.1"))
8 ;; Keywords: multimedia
9 ;; homepage: https://repo.or.cz/ffmpeg-utils.git
11 ;; ffmpeg-utils.el is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
16 ;; ffmpeg-utils.el is distributed in the hope that it will be useful, but WITHOUT
17 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
19 ;; License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
28 ;;; - [M-x ffmpeg-utils-cut-clip]
32 (require 'notifications
)
36 (defvar ffmpeg-utils--output-filename nil
37 "A variable to store the command output filename which used in notification.")
39 (defcustom ffmpeg-utils-notification-function
'ffmpeg-utils-notification-default
40 "Specify the ffmpeg command process sentinel function."
45 (defun ffmpeg-utils-notification-default (&optional proc event
)
46 "The ffmpeg command process sentinel notification function with args PROC, EVENT."
47 (setq mode-line-process nil
) ; remove mode-line-process indicator.
48 (let ((msg (format "ffmpeg cut file: [%s] finished.\nprocess: [%s], status: [%s]."
49 proc event
(file-name-nondirectory ffmpeg-utils--output-filename
))))
53 ((and (featurep 'dbus
) (fboundp 'notifications-notify
))
54 (notifications-notify :title
"Emacs ffmpeg-utils.el" :body msg
))))
58 (let ((msg "Emacs ffmpeg-utils.el process finished."))
61 (ns-do-applescript (format "say \"%s\"" msg
))))
62 (alert msg
:title
"Emacs ffmpeg-utils.el")))
63 ((and (featurep 'osx-lib
) (bound-and-true-p osx-lib-start-terminal
))
65 (osx-lib-notify3 "Emacs" "ffmpeg-utils.el" msg
))
66 ((fboundp 'ns-do-applescript
)
68 (format "display notification \"%s\" with title \"%s\""
69 msg
"Emacs ffmpeg-utils.el")))
70 ((executable-find "osascript")
72 ;; "emacs-timer-notification" nil
74 ;; (format "-e 'display notification \"%s\" with title \"%s\"'" msg "Emacs ffmpeg-utils.el"))
76 (format "osascript -e 'display notification \"%s\" with title \"%s\"'"
77 "yes" "Emacs ffmpeg-utils.el")))))
78 (_ (message "Emacs ffmpeg-utils.el: %s" msg
)))))
80 (defun ffmpeg-utils--run-command (arglist)
81 "Construct ffmpeg command with ARGLIST, SENTINEL-FUNC and BODY."
84 :command
(append '("ffmpeg") arglist
) ; <------------- problem here
86 :sentinel ffmpeg-utils-notification-function
))
88 (defun ffmpeg-utils-mode-line-running-indicator (msg)
89 "Display a running indicator MSG on mode-line."
90 (setq mode-line-process
92 (propertize (or msg
"ffmpeg running...")
93 'font-lock-face
'mode-line-highlight
)))
94 (force-mode-line-update t
))
96 ;;; ffmpeg command option "-t" accept seconds like 57 as value.
97 (defun ffmpeg-utils--subtract-timestamps (start-timestamp end-timestamp
)
98 "Subtract END-TIMESTAMP with START-TIMESTAMP."
101 (parse-time-string ; (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
102 (concat "2020-01-01" " " end-timestamp
)))
105 (concat "2020-01-01" " " start-timestamp
)))))
107 ;; (ffmpeg-utils--subtract-timestamps "00:11:25" "00:12:12")
109 (defun ffmpeg-utils-cut-clip (input-filename start-timestamp end-timestamp output-filename
)
110 "Clip INPUT-FILENAME on START-TIMESTAMP END-TIMESTAMP, output OUTPUT-FILENAME.
112 Support read timestamp begin/end range in format like this:
113 00:17:23 -- 00:21:45."
115 (if (region-active-p)
116 ;; regexp spec detect "00:17:23 -- 00:21:45"
117 (let* ((time-range (split-string
118 (buffer-substring-no-properties
119 (region-beginning) (region-end))
121 (timestamp-begin (car time-range
))
122 (timestamp-end (cadr time-range
)))
125 (substring-no-properties
126 (read-file-name "FFmpeg input filename: "
127 nil nil
'confirm-after-completion
)))
131 (substring-no-properties
132 (read-file-name "FFmpeg output filename: ")))))
134 (read-file-name "FFmpeg input filename: ")
135 (read-string "FFmpeg start timestamp: ")
136 (read-string "FFmpeg end timestamp: ")
137 (read-file-name "FFmpeg output filename: "))))
138 (when (region-active-p) (deactivate-mark) (forward-line) (newline) (forward-line -
1))
139 (ffmpeg-utils-mode-line-running-indicator "ffmpeg cut video clip")
140 (setq ffmpeg-utils--output-filename output-filename
)
141 (let ((input-type (file-name-extension input-filename
))
142 (output-type (file-name-extension output-filename
)))
144 ;; output filename specified video file extension.
145 (if (string-equal output-type input-type
) ; if output extension is same as input
146 output-filename
; keep output original extension
147 (if (yes-or-no-p "Whether keep output video format extension? ")
149 (setq output-filename
150 (format "%s.%s" (file-name-sans-extension output-filename
) input-type
))))
151 ;; output filename has NOT specific video file extension.
152 (setq output-filename
(format "%s.%s" output-filename input-type
)))
153 ;; "ffmpeg -i input-filename -ss start-timestamp -t time-timestamp -codec copy output-filename"
154 (ffmpeg-utils--run-command
155 `("-i" ,input-filename
156 "-ss" ,start-timestamp
157 "-t" ,(number-to-string (ffmpeg-utils--subtract-timestamps start-timestamp end-timestamp
))
161 ;; (define-transient-command ffmpeg-transient ()
162 ;; "ffmpeg transient commands"
164 ;; ("c" "Cut video clip" ffmpeg-utils-cut-clip)]
166 ;; ("c" "Cut audio clip" ffmpeg-utils-cut-clip)])
170 (provide 'ffmpeg-utils
)
172 ;;; ffmpeg-utils.el ends here