Coverage report: /home/ellis/comp/core/lib/cli/tools/media.lisp
Kind | Covered | All | % |
expression | 0 | 395 | 0.0 |
branch | 0 | 22 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; media.lisp --- CLI Media Tools
6
(in-package :cli/tools/media)
8
(define-cli-tool :ffmpeg (args &optional (output *standard-output*))
9
(let ((proc (sb-ext:run-program *ffmpeg* args :wait t :output output)))
10
(unless (eq 0 (sb-ext:process-exit-code proc))
11
(ffmpeg-error "FFMPEG command failed: ~A ~A" *ffmpeg* (or args "")))))
14
D..... = Decoding supported
15
.E.... = Encoding supported
18
..S... = Subtitle codec
20
..T... = Attachment codec
21
...I.. = Intra frame-only codec
22
....L. = Lossy compression
23
.....S = Lossless compression
25
(define-bitfield ffmpeg-codec-props
28
(type (member :video :audio :subtitle :data :attachment))
33
(defun list-ffmpeg-codec-props (i)
35
:decode (ffmpeg-codec-props-decode i)
36
:encode (ffmpeg-codec-props-encode i)
37
:type (ffmpeg-codec-props-type i)
38
:intra (ffmpeg-codec-props-intra i)
39
:lossy (ffmpeg-codec-props-lossy i)
40
:lossless (ffmpeg-codec-props-lossless i)))
42
(defun parse-ffmpeg-codec-type (char)
50
(defun parse-ffmpeg-codec-props (str)
52
(make-ffmpeg-codec-props
53
:decode (char= #\D (schar str 0))
54
:encode (char= #\E (schar str 1))
55
:type (parse-ffmpeg-codec-type (schar str 2))
56
:intra (char= #\I (schar str 3))
57
:lossy (char= #\L (schar str 4))
58
:lossless (char= #\S (schar str 5))))
60
(defstruct ffmpeg-codec (props 0 :type ffmpeg-codec-props) name description)
62
(defmethod print-object ((self ffmpeg-codec) stream)
63
(format stream "#S(~A ~A ~{~S~^ ~})"
65
(ffmpeg-codec-name self)
66
(list-ffmpeg-codec-props (ffmpeg-codec-props self))))
68
(defun read-ffmpeg-codec (stream)
69
(when-let ((props (string (read stream nil nil)))
70
(name (read stream nil nil))
71
(description (trim (read-line stream nil nil))))
72
(make-ffmpeg-codec :props (parse-ffmpeg-codec-props props) :name name :description description)))
74
(defun list-ffmpeg-codecs ()
75
(let ((ret (with-output-to-string (s)
76
(run-ffmpeg (list "-v" "0" "-codecs") s))))
77
(when-let ((i (search " -------" ret)))
78
(with-input-from-string (s (subseq ret (+ i 9)))
79
(loop for f = (print (read-ffmpeg-codec s))
83
(defstruct ffmpeg-format props name description)
85
(defmethod print-object ((self ffmpeg-format) stream)
86
(format stream "#S(~A ~A ~{~S~^ ~})"
88
(ffmpeg-format-name self)
89
(list-ffmpeg-format-props (ffmpeg-format-props self))))
91
(define-bitfield ffmpeg-format-props
96
(defun list-ffmpeg-format-props (i)
98
:mux (ffmpeg-format-props-mux i)
99
:demux (ffmpeg-format-props-demux i)
100
:device (ffmpeg-format-props-device i)))
102
(defun parse-ffmpeg-format-props (str)
103
(make-ffmpeg-format-props
104
:demux (find #\D str)
106
:device (find #\d str)))
108
(defun read-ffmpeg-format (stream)
109
(when-let ((props (string (read stream nil nil)))
110
(name (read stream nil nil))
111
(description (trim (read-line stream nil nil))))
112
(make-ffmpeg-format :props (parse-ffmpeg-format-props props) :name name :description description)))
114
(defun list-ffmpeg-formats ()
115
(let ((ret (with-output-to-string (s)
116
(run-ffmpeg (list "-v" "0" "-formats") s))))
117
(when-let ((i (search " ---" ret)))
118
(with-input-from-string (s (subseq ret (+ i 5)))
119
(loop for f = (print (read-ffmpeg-format s))
123
(define-cli-tool :mpv (&rest args)
124
(let ((proc (sb-ext:run-program *mpv* args :wait t :output t)))
125
(unless (eq 0 (sb-ext:process-exit-code proc))
126
(mpv-error "MPV command failed: ~A ~A" *mpv* (or args "")))))
128
(defvar *mpv-config-path* (merge-homedir-pathnames ".config/mpv/mpv.conf"))
130
;; incomplete config description
131
(defconfig mpv-config (cli-tool-config ini-document)
139
(defmethod make-config ((self (eql :mpv)) &rest args &key path &allow-other-keys)
140
(if (remf args :path)
141
(load-ast (apply 'change-class (deserialize path :ini) 'mpv-config args))
142
(apply 'make-instance 'mpv-config args)))
144
(defmethod load-ast ((self mpv-config))
145
(with-slots (ast) self
149
(let ((k (car x)) (v (cdr x)))
150
(when-let ((s (print (find-symbol* (string-upcase k) #.*package* nil)))) ;; needs to be correct package
159
(setf (slot-value self s) v)))))
161
;; invalid ast, signal error
162
(error 'syntax-error)))
163
(unless *keep-ast* (setf (ast self) nil))
166
(defun load-mpv-config (&optional (path *mpv-config-path*))
167
(when (probe-file path)
168
(make-config :mpv :path path)))
170
(define-cli-tool :wireplumber (&rest args)
171
(let ((proc (sb-ext:run-program *wireplumber* args :wait t :output t)))
172
(unless (eq 0 (sb-ext:process-exit-code proc))
173
(wireplumber-error "WIREPLUMBER command failed: ~A ~A" *wireplumber* (or args "")))))
175
(define-cli-tool :picard (args &key (wait t) (output t))
176
(let ((proc (sb-ext:run-program *picard* args :wait wait :output output)))
177
(unless (positive-integer-p #1=(sb-ext:process-exit-code proc))
178
(picard-error "PICARD command failed: ~A ~{~A ~^~}~%exit-code = ~A" *picard* (or args "") #1#))))
180
(defvar *picard-config-path* (merge-homedir-pathnames ".config/MusicBrainz/Picard.ini"))
182
(defconfig picard-config (cli-tool-config ini-document) ())
184
(defmethod make-config ((self (eql :picard)) &rest args &key path &allow-other-keys)
185
(if (remf args :path)
186
(apply 'change-class (deserialize path :ini) 'picard-config args)
187
(apply 'make-instance 'picard-config args)))
189
(defun load-picard-config (&optional (path *picard-config-path*))
190
(when (probe-file path)
191
(make-config :picard :path path)))
193
(defvar *picard-commands*
214
(defun picard-cmd (cmd)
215
(when (member cmd *picard-commands*)
216
(substitute #\_ #\- (string-upcase cmd)))))
219
(defun %do-picard (body)
220
"Execute a sequence of forms where atoms are interpreted by picard as commands
221
or arguments and lists are evaluated interpreted as args."
222
(let ((cmd) (args) (ret))
223
(loop for i below (length body)
228
(push (cons cmd args) ret)
232
(t (push (format nil "~A" a) args)))
235
(when cmd (push (cons cmd args) ret))
236
(return (nreverse ret))))))
238
(defun exec-picard (&rest commands)
239
"Execute a PICARD batch program consisting of COMMANDS."
240
(run-picard (flatten (mapcar (lambda (x) (cons "-e" (rplaca x (string (car x))))) commands))))
242
(defmacro do-picard (&body body)
244
(apply 'exec-picard ',(%do-picard body))))