Change library homepage from GitHUb.com to repo.or.cz
[ffmpeg-utils.git] / ffmpeg-utils.el
blob44754c582827defe11f4597ed677cb3e5e22d99d
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") (transient "0.1.0"))
7 ;; Package-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-cut-clip]
30 ;;; Code:
32 (require 'notifications)
33 (require 'alert)
34 (require 'transient)
36 (defvar ffmpeg--output-filename nil
37 "A variable to store the command output filename which used in notification.")
39 (defcustom ffmpeg-notification-function 'ffmpeg-notification-default
40 "Specify the ffmpeg command process sentinel function."
41 :type 'function
42 :safe #'functionp
43 :group 'ffmpeg)
45 (defun ffmpeg-notification-default (proc event)
46 "The default ffmpeg command process sentinel notification function."
47 (setq mode-line-process nil) ; remove mode-line-process indicator.
48 (let ((msg (format "ffmpeg cut %s finished" (file-name-nondirectory ffmpeg--output-filename))))
49 (pcase system-type
50 ('gnu/linux
51 (cond
52 ((and (featurep 'dbus) (fboundp 'notifications-notify))
53 (notifications-notify :title "Emacs ffmpeg-utils.el" :body msg))))
54 ('darwin
55 (cond
56 ((featurep 'alert)
57 (alert msg :title "Emacs ffmpeg-utils.el"))
58 ((and (featurep 'osx-lib) (bound-and-true-p osx-lib-start-terminal))
59 (osx-lib-notify2 "Emacs ffmpeg-utils.el" msg))
60 ((fboundp 'ns-do-applescript)
61 (ns-do-applescript
62 (format "display notification \"%s\" with title \"%s\""
63 msg "Emacs ffmpeg-utils.el")))
64 ((executable-find "osascript")
65 (start-process
66 "emacs-timer-notification" nil
67 "osascript" "-e"
68 (format "'display notification \"%s\" with title \"title\"'" msg "Emacs ffmpeg-utils.el")))))
69 (t (message (format "Emacs ffmpeg-utils.el: %s" msg))))))
71 (defun ffmpeg--run-command (arglist)
72 "Construct ffmpeg command with ARGLIST, SENTINEL-FUNC and BODY."
73 (make-process
74 :name "ffmpeg"
75 :command (append '("ffmpeg") arglist) ; <------------- problem here
76 :buffer "*ffmpeg*"
77 :sentinel ffmpeg-notification-function))
79 (defun ffmpeg-mode-line-running-indicator (msg)
80 "Display a running indicator on mode-line."
81 (setq mode-line-process
82 (concat " "
83 (propertize (or msg "ffmpeg running...")
84 'font-lock-face 'mode-line-highlight)))
85 (force-mode-line-update t))
87 ;;; ffmpeg command option "-t" accept seconds like 57 as value.
88 (defun ffmpeg--subtract-timestamps (start-timestamp end-timestamp)
89 "Subtract END-TIMESTAMP with START-TIMESTAMP."
90 (time-subtract
91 (encode-time
92 (parse-time-string ; (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
93 (concat "2020-01-01" " " end-timestamp)))
94 (encode-time
95 (parse-time-string
96 (concat "2020-01-01" " " start-timestamp)))))
98 ;; (ffmpeg--subtract-timestamps "00:11:25" "00:12:12")
100 (defun ffmpeg-cut-clip (input-filename start-timestamp end-timestamp output-filename)
101 "Cut clip of media INPUT-FILENAME between START-TIMESTAMP END-TIMESTAMP and output to OUTPUT-FILENAME.
103 Support read timestamp begin/end range in format like this: 00:17:23 -- 00:21:45."
104 (interactive (if (region-active-p)
105 ;; TODO: regexp spec detect
106 (let* ((time-range (split-string
107 (buffer-substring-no-properties (region-beginning) (region-end))
108 " -- "))
109 (timestamp-begin (car time-range))
110 (timestamp-end (cadr time-range)))
111 (list
112 (expand-file-name
113 (substring-no-properties
114 (read-file-name "FFmpeg input filename: " nil nil 'confirm-after-completion)))
115 timestamp-begin
116 timestamp-end
117 (expand-file-name
118 (substring-no-properties
119 (read-file-name "FFmpeg output filename: ")))))
120 (list
121 (read-file-name "FFmpeg input filename: ")
122 (read-string "FFmpeg start timestamp: ")
123 (read-string "FFmpeg end timestamp: ")
124 (read-file-name "FFmpeg output filename: "))))
125 (when (region-active-p) (deactivate-mark) (forward-line) (newline) (forward-line -1))
126 (ffmpeg-mode-line-running-indicator "ffmpeg cut video clip")
127 (setq ffmpeg--output-filename output-filename)
128 (let ((input-type (file-name-extension input-filename))
129 (output-type (file-name-extension output-filename)))
130 (if output-type
131 ;; output filename specified video file extension.
132 (if (string-equal output-type input-type) ; if output extension is same as input
133 output-filename ; keep output original extension
134 (if (yes-or-no-p "Whether keep output video format extension? ")
135 output-filename
136 (setq output-filename (format "%s.%s" (file-name-sans-extension output-filename) input-type))))
137 ;; output filename has NOT specific video file extension.
138 (setq output-filename (format "%s.%s" output-filename input-type)))
139 ;; "ffmpeg -i input-filename -ss start-timestamp -t time-timestamp -codec copy output-filename"
140 (ffmpeg--run-command
141 `("-i" ,input-filename
142 "-ss" ,start-timestamp
143 "-t" ,(number-to-string (ffmpeg--subtract-timestamps start-timestamp end-timestamp))
144 "-codec" "copy"
145 ,output-filename))))
147 (define-transient-command ffmpeg-transient ()
148 "ffmpeg transient commands"
149 ["Video"
150 ("c" "Cut video clip" ffmpeg-cut-clip)]
151 ["Audio"
152 ("c" "Cut audio clip" ffmpeg-cut-clip)])
156 (provide 'ffmpeg-utils)
158 ;;; ffmpeg-utils.el ends here