define alias for 'do-applescript to 'ns-do-applescript
[ffmpeg-utils.git] / ffmpeg-utils.el
blobe23308a9e30b4833034bc2c07a0cfda0a7021200
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"))
7 ;; Version: 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)
14 ;; any later version.
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/>.
25 ;;; Commentary:
27 ;; Usage:
28 ;;; - [M-x ffmpeg-utils-cut-clip]
30 ;;; Code:
32 (require 'notifications)
33 (require 'alert)
34 (require 'transient)
36 (declare-function ns-do-applescript "ext:macos-builtin")
37 (unless (fboundp 'do-applescript)
38 (defalias 'do-applescript 'ns-do-applescript))
39 (declare-function osx-lib-notify3 "osx-lib.el")
41 (defvar ffmpeg-utils--output-filename nil
42 "A variable to store the command output filename which used in notification.")
44 (defcustom ffmpeg-utils-notification-function 'ffmpeg-utils-notification-default
45 "Specify the ffmpeg command process sentinel function."
46 :type 'function
47 :safe #'functionp
48 :group 'ffmpeg)
50 (defun ffmpeg-utils-notification-default (&optional proc event)
51 "The ffmpeg command process sentinel notification function with args PROC, EVENT."
52 (setq mode-line-process nil) ; remove mode-line-process indicator.
53 (let ((msg (format "ffmpeg cut file: [%s] finished.\nprocess: [%s], status: [%s]."
54 proc event (file-name-nondirectory ffmpeg-utils--output-filename))))
55 (pcase system-type
56 ('gnu/linux
57 (cond
58 ((and (featurep 'dbus) (fboundp 'notifications-notify))
59 (notifications-notify :title "Emacs ffmpeg-utils.el" :body msg))))
60 ('darwin
61 (cond
62 ((featurep 'alert)
63 (let ((msg "Emacs ffmpeg-utils.el process finished."))
64 (cl-case system-type
65 (darwin (do-applescript (format "say \"%s\"" msg))))
66 (alert msg :title "Emacs ffmpeg-utils.el")))
67 ((require 'osx-lib nil t)
68 (osx-lib-notify3 "Emacs" "ffmpeg-utils.el" msg))
69 ((fboundp 'do-applescript)
70 (do-applescript
71 (format "display notification \"%s\" with title \"%s\""
72 msg "Emacs ffmpeg-utils.el")))
73 ((executable-find "osascript")
74 ;; (start-process
75 ;; "emacs-timer-notification" nil
76 ;; "osascript"
77 ;; (format "-e 'display notification \"%s\" with title \"%s\"'" msg "Emacs ffmpeg-utils.el"))
78 (shell-command
79 (format "osascript -e 'display notification \"%s\" with title \"%s\"'"
80 "yes" "Emacs ffmpeg-utils.el")))))
81 (_ (message "Emacs ffmpeg-utils.el: %s" msg)))))
83 (defun ffmpeg-utils--run-command (arglist)
84 "Construct ffmpeg command with ARGLIST, SENTINEL-FUNC and BODY."
85 (make-process
86 :name "ffmpeg"
87 :command (append '("ffmpeg") arglist) ; <------------- problem here
88 :buffer "*ffmpeg*"
89 :sentinel ffmpeg-utils-notification-function))
91 (defun ffmpeg-utils-mode-line-running-indicator (msg)
92 "Display a running indicator MSG on mode-line."
93 (setq mode-line-process
94 (concat " "
95 (propertize (or msg "ffmpeg running...")
96 'font-lock-face 'mode-line-highlight)))
97 (force-mode-line-update t))
99 ;;; ffmpeg command option "-t" accept seconds like 57 as value.
100 (defun ffmpeg-utils--subtract-timestamps (start-timestamp end-timestamp)
101 "Subtract END-TIMESTAMP with START-TIMESTAMP."
102 (time-subtract
103 (encode-time
104 (parse-time-string ; (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
105 (concat "2020-01-01" " " end-timestamp)))
106 (encode-time
107 (parse-time-string
108 (concat "2020-01-01" " " start-timestamp)))))
110 ;; (ffmpeg-utils--subtract-timestamps "00:11:25" "00:12:12")
112 (defun ffmpeg-utils-cut-clip (input-filename start-timestamp end-timestamp output-filename)
113 "Clip INPUT-FILENAME on START-TIMESTAMP END-TIMESTAMP, output OUTPUT-FILENAME.
115 Support read timestamp begin/end range in format like this:
116 00:17:23 -- 00:21:45."
117 (interactive
118 (if (region-active-p)
119 ;; regexp spec detect "00:17:23 -- 00:21:45"
120 (let* ((time-range (split-string
121 (buffer-substring-no-properties
122 (region-beginning) (region-end))
123 " -- "))
124 (timestamp-begin (car time-range))
125 (timestamp-end (cadr time-range)))
126 (list
127 (expand-file-name
128 (substring-no-properties
129 (read-file-name "FFmpeg input filename: "
130 nil nil 'confirm-after-completion)))
131 timestamp-begin
132 timestamp-end
133 (expand-file-name
134 (substring-no-properties
135 (read-file-name "FFmpeg output filename: ")))))
136 (list
137 (read-file-name "FFmpeg input filename: ")
138 (read-string "FFmpeg start timestamp: ")
139 (read-string "FFmpeg end timestamp: ")
140 (read-file-name "FFmpeg output filename: "))))
141 (when (region-active-p) (deactivate-mark) (forward-line) (newline) (forward-line -1))
142 (ffmpeg-utils-mode-line-running-indicator "ffmpeg cut video clip")
143 (setq ffmpeg-utils--output-filename output-filename)
144 (let ((input-type (file-name-extension input-filename))
145 (output-type (file-name-extension output-filename)))
146 (if output-type
147 ;; output filename specified video file extension.
148 (if (string-equal output-type input-type) ; if output extension is same as input
149 output-filename ; keep output original extension
150 (if (yes-or-no-p "Whether keep output video format extension? ")
151 output-filename
152 (setq output-filename
153 (format "%s.%s" (file-name-sans-extension output-filename) input-type))))
154 ;; output filename has NOT specific video file extension.
155 (setq output-filename (format "%s.%s" output-filename input-type)))
156 ;; "ffmpeg -i input-filename -ss start-timestamp -t time-timestamp -codec copy output-filename"
157 (ffmpeg-utils--run-command
158 `("-i" ,input-filename
159 "-ss" ,start-timestamp
160 "-t" ,(number-to-string (ffmpeg-utils--subtract-timestamps start-timestamp end-timestamp))
161 "-codec" "copy"
162 ,output-filename))))
164 ;; (define-transient-command ffmpeg-transient ()
165 ;; "ffmpeg transient commands"
166 ;; ["Video"
167 ;; ("c" "Cut video clip" ffmpeg-utils-cut-clip)]
168 ;; ["Audio"
169 ;; ("c" "Cut audio clip" ffmpeg-utils-cut-clip)])
173 (provide 'ffmpeg-utils)
175 ;;; ffmpeg-utils.el ends here