summaryrefslogtreecommitdiff
path: root/modules/video-audio-recording.el
blob: e2238949c73c66bbefd09483ad02df86f5da472d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
;;; video-audio-recording.el --- Video and Audio Recording -*- lexical-binding: t; coding: utf-8; -*-
;; author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;; Use ffmpeg to record desktop video or just audio.
;; with audio from mic and audio from default audio sink
;; Also supports audio-only recording in Opus format.
;;
;; Note: video-recordings-dir and audio-recordings-dir are defined
;; (and directory created) in user-constants.el
;;
;;
;; To adjust volumes:
;; - Use =M-x cj/recording-adjust-volumes= (or your keybinding =r l=)
;; - Or customize permanently: =M-x customize-group RET cj-recording RET=
;; - Or in your config:
;;   #+begin_src emacs-lisp
;;   (setq cj/recording-mic-boost 1.5)    ; 50% louder
;;   (setq cj/recording-system-volume 0.7) ; 30% quieter
;;
;;; Code:

(require 'user-constants)

(defgroup cj-recording nil
  "Settings for video and audio recording."
  :group 'multimedia)

(defcustom cj/recording-mic-boost 2.0
  "Volume multiplier for microphone in recordings.

1.0 = normal volume, 2.0 = double volume (+6dB), 0.5 = half volume (-6dB)."
  :type 'number
  :group 'cj-recording)

(defcustom cj/recording-system-volume 0.5
  "Volume multiplier for system audio in recordings.

1.0 = normal volume, 2.0 = double volume (+6dB), 0.5 = half volume (-6dB)."
  :type 'number
  :group 'cj-recording)

(defvar cj/video-recording-ffmpeg-process nil
  "Variable to store the process of the ffmpeg video recording.")

(defvar cj/audio-recording-ffmpeg-process nil
  "Variable to store the process of the ffmpeg audio recording.")

(defun cj/video-recording-start (arg)
  "Starts the ffmpeg video recording.

If called with a prefix arg C-u, choose the location on where to save the
recording, otherwise use the default location in =video-recordings-dir'."
  (interactive "P")
  (let* ((location (if arg
					   (read-directory-name "Enter recording location: ")
					 video-recordings-dir))
		 (directory (file-name-directory location)))
	(unless (file-directory-p directory)
	  (make-directory directory t))
	(cj/ffmpeg-record-video location)))

(defun cj/audio-recording-start (arg)
  "Starts the ffmpeg audio recording.

If called with a prefix arg C-u, choose the location on where to save the
recording, otherwise use the default location in =video-recordings-dir'."
  (interactive "P")
  (let* ((location (if arg
					   (read-directory-name "Enter recording location: ")
					 video-recordings-dir))
		 (directory (file-name-directory location)))
	(unless (file-directory-p directory)
	  (make-directory directory t))
	(cj/ffmpeg-record-audio location)))

(defun cj/ffmpeg-record-video (directory)
  "Start an ffmpeg video recording. Save output to DIRECTORY."
  (unless cj/video-recording-ffmpeg-process
	(let* ((location (expand-file-name directory))
		   (name (format-time-string "%Y-%m-%d-%H-%M-%S"))
		   (filename (expand-file-name (concat name ".mkv") location))
		   (ffmpeg-command
			(format (concat "ffmpeg -framerate 30 -f x11grab -i :0.0+ "
							"-f pulse -i "
							"alsa_input.pci-0000_00_1b.0.analog-stereo "
							"-ac 1 "
							"-f pulse -i "
							"alsa_output.pci-0000_00_1b.0.analog-stereo.monitor "
							"-ac 2 "
							"-filter_complex \"[1:a]volume=%.1f[mic];[2:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2[out]\" "
							"-map 0:v -map \"[out]\" "
							"%s")
					cj/recording-mic-boost
					cj/recording-system-volume
					filename)))
	  ;; start the recording
	  (setq cj/video-recording-ffmpeg-process
			(start-process-shell-command "ffmpeg-video-recording"
										 "*ffmpeg-video-recording*"
										 ffmpeg-command))
	  (set-process-query-on-exit-flag cj/video-recording-ffmpeg-process nil)
	  (message "Started video recording process (mic boost: %.1fx, system volume: %.1fx)."
			   cj/recording-mic-boost cj/recording-system-volume))))

(defun cj/ffmpeg-record-audio (directory)
  "Start an ffmpeg audio recording. Save output to DIRECTORY."
  (unless cj/audio-recording-ffmpeg-process
	(let* ((location (expand-file-name directory))
		   (name (format-time-string "%Y-%m-%d-%H-%M-%S"))
		   (filename (expand-file-name (concat name ".opus") location))
		   (ffmpeg-command
			(format (concat "ffmpeg "
							"-f pulse -i "
							"alsa_input.pci-0000_00_1b.0.analog-stereo "
							"-ac 1 "
							"-f pulse -i "
							"alsa_output.pci-0000_00_1b.0.analog-stereo.monitor "
							"-ac 2 "
							"-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2\" "
							"-c:a libopus "
							"-b:a 96k "
							"%s")
					cj/recording-mic-boost
					cj/recording-system-volume
					filename)))
	  ;; start the recording
	  (setq cj/audio-recording-ffmpeg-process
			(start-process-shell-command "ffmpeg-audio-recording"
										 "*ffmpeg-audio-recording*"
										 ffmpeg-command))
	  (set-process-query-on-exit-flag cj/audio-recording-ffmpeg-process nil)
	  (message "Started audio recording process (mic boost: %.1fx, system volume: %.1fx)."
			   cj/recording-mic-boost cj/recording-system-volume))))

(defun cj/video-recording-stop ()
  "Stop the ffmpeg video recording process."
  (interactive)
  (if cj/video-recording-ffmpeg-process
	  (progn
		;; Use interrupt-process to send SIGINT (graceful termination)
		(interrupt-process cj/video-recording-ffmpeg-process)
		;; Give ffmpeg a moment to finalize the file
		(sit-for 1)
		(setq cj/video-recording-ffmpeg-process nil)
		(message "Stopped video recording."))
	(message "No video recording in progress.")))

(defun cj/audio-recording-stop ()
  "Stop the ffmpeg audio recording process."
  (interactive)
  (if cj/audio-recording-ffmpeg-process
	  (progn
		;; Use interrupt-process to send SIGINT (graceful termination)
		(interrupt-process cj/audio-recording-ffmpeg-process)
		;; Give ffmpeg a moment to finalize the file
		(sit-for 1)
		(setq cj/audio-recording-ffmpeg-process nil)
		(message "Stopped audio recording."))
	(message "No audio recording in progress.")))

(defun cj/recording-adjust-volumes ()
  "Interactively adjust recording volume levels."
  (interactive)
  (let ((mic (read-number "Microphone boost (1.0 = normal, 2.0 = double): "
						  cj/recording-mic-boost))
		(sys (read-number "System audio level (1.0 = normal, 0.5 = half): "
						  cj/recording-system-volume)))
	(customize-set-variable 'cj/recording-mic-boost mic)
	(customize-set-variable 'cj/recording-system-volume sys)
	(message "Recording levels updated - Mic: %.1fx, System: %.1fx" mic sys)))

;; Recording operations prefix and keymap
(define-prefix-command 'cj/record-map nil
					   "Keymap for video/audio recording operations.")
(define-key cj/custom-keymap "r" 'cj/record-map)
(define-key cj/record-map "V" 'cj/video-recording-stop)
(define-key cj/record-map "v" 'cj/video-recording-start)
(define-key cj/record-map "A" 'cj/audio-recording-stop)
(define-key cj/record-map "a" 'cj/audio-recording-start)
(define-key cj/record-map "l" 'cj/recording-adjust-volumes)  ; 'l' for levels

(provide 'video-audio-recording)
;;; video-audio-recording.el ends here.