2021-01-15 19:08:06 +01:00
|
|
|
(uiop:define-package ambrevar/ffprobe
|
2021-01-16 01:11:16 +01:00
|
|
|
(:nicknames #:ffprobe)
|
2021-01-15 19:08:06 +01:00
|
|
|
(:documentation "FFprobe abstraction.")
|
|
|
|
(:use #:common-lisp)
|
|
|
|
(:use #:trivia)
|
|
|
|
(:import-from #:hu.dwim.defclass-star #:defclass*)
|
|
|
|
(:import-from #:serapeum #:export-always))
|
|
|
|
(in-package ambrevar/ffprobe)
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
|
|
(trivial-package-local-nicknames:add-package-local-nickname :alex :alexandria)
|
|
|
|
(trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum))
|
|
|
|
|
|
|
|
(sera:eval-always
|
|
|
|
(defun name-identity (name definition)
|
|
|
|
(declare (ignore definition))
|
|
|
|
name))
|
|
|
|
|
2021-01-15 19:41:50 +01:00
|
|
|
;; TODO: Should leave unspecified fields unbound?
|
|
|
|
|
2021-01-15 19:08:06 +01:00
|
|
|
(defclass* disposition ()
|
|
|
|
((default 0)
|
|
|
|
(dub 0)
|
|
|
|
(original 0)
|
|
|
|
(comment 0)
|
|
|
|
(lyrics 0)
|
|
|
|
(karaoke 0)
|
|
|
|
(forced 0)
|
|
|
|
(hearing-impaired 0)
|
|
|
|
(visual-impaired 0)
|
|
|
|
(clean-effects 0)
|
|
|
|
(attached-pic 0)
|
|
|
|
(timed-thumbnails 0))
|
2021-01-15 19:41:50 +01:00
|
|
|
(:accessor-name-transformer #'name-identity)
|
2021-01-16 01:11:16 +01:00
|
|
|
(:export-accessor-names-p t)
|
2021-01-15 19:41:50 +01:00
|
|
|
(:export-class-name-p t))
|
2021-01-15 19:08:06 +01:00
|
|
|
|
|
|
|
(defclass* media-stream () ; REVIEW: `stream' is reserved by CL.
|
|
|
|
((index 0)
|
|
|
|
(codec-name "")
|
|
|
|
(codec-long-name "")
|
|
|
|
(profile "")
|
|
|
|
(codec-type "")
|
|
|
|
(width 0)
|
|
|
|
(height 0)
|
|
|
|
(coded-width 0)
|
|
|
|
(coded-height 0)
|
|
|
|
(closed-captions 0)
|
|
|
|
(has-b-frames 0)
|
|
|
|
(pix-fmt "")
|
|
|
|
(level 0)
|
|
|
|
(color-range "")
|
|
|
|
(color-space "")
|
2021-01-15 19:41:50 +01:00
|
|
|
(color-transfer "")
|
|
|
|
(color-primaries "")
|
|
|
|
(chroma-location "")
|
|
|
|
(field-order "")
|
2021-01-15 19:08:06 +01:00
|
|
|
(refs 0)
|
2021-01-15 19:41:50 +01:00
|
|
|
(id "")
|
|
|
|
(quarter-sample nil
|
|
|
|
:type boolean)
|
|
|
|
(divx-packed nil
|
|
|
|
:type boolean)
|
2021-01-15 19:08:06 +01:00
|
|
|
(sample-aspect-ratio "")
|
|
|
|
(display-aspect-ratio "")
|
|
|
|
(codec-time-base "") ; TODO: Ratio?
|
|
|
|
(codec-tag-string "")
|
|
|
|
(codec-tag "") ; TODO: Hex?
|
|
|
|
(sample-fmt "")
|
|
|
|
(sample-rate 0)
|
|
|
|
(channels 2)
|
|
|
|
(channel-layout "")
|
|
|
|
(bits-per-sample 0)
|
2021-01-15 19:41:50 +01:00
|
|
|
(dmix-mode 0)
|
|
|
|
(ltrt-cmixlev 0.0)
|
|
|
|
(ltrt-surmixlev 0.0)
|
|
|
|
(loro-cmixlev 0.0)
|
|
|
|
(loro-surmixlev 0.0)
|
|
|
|
(is-avc nil
|
|
|
|
:type boolean)
|
|
|
|
(nal-length-size 0)
|
2021-01-15 19:08:06 +01:00
|
|
|
(r-frame-rate "") ; TODO: Ratio?
|
|
|
|
(avg-frame-rate "") ; TODO: Ratio?
|
|
|
|
(time-base "")
|
|
|
|
(start-pts 0)
|
|
|
|
(start-time 0.0)
|
|
|
|
(duration-ts 0.0)
|
|
|
|
(duration 0.0)
|
|
|
|
(bit-rate 0)
|
2021-01-15 19:41:50 +01:00
|
|
|
(bits-per-raw-sample 0)
|
|
|
|
(nb-frames 0)
|
|
|
|
(max-bit-rate 0)
|
2021-01-15 19:08:06 +01:00
|
|
|
(disposition nil
|
|
|
|
:type (or null disposition))
|
2021-01-15 19:41:50 +01:00
|
|
|
(side-data-list '())
|
2021-01-15 19:08:06 +01:00
|
|
|
(tags '()))
|
2021-01-15 19:41:50 +01:00
|
|
|
(:accessor-name-transformer #'name-identity)
|
2021-01-16 01:11:16 +01:00
|
|
|
(:export-accessor-names-p t)
|
2021-01-15 19:41:50 +01:00
|
|
|
(:export-class-name-p t))
|
2021-01-15 19:08:06 +01:00
|
|
|
|
|
|
|
(defclass* media-format () ; REVIEW: `format' is reserved by CL.
|
|
|
|
((filename "")
|
|
|
|
(nb-streams 0)
|
|
|
|
(nb-programs 0)
|
|
|
|
(format-name "")
|
|
|
|
(format-long-name "")
|
|
|
|
(start-time 0.0)
|
|
|
|
(duration 0.0)
|
|
|
|
(size 0)
|
|
|
|
(bit-rate 0)
|
|
|
|
(probe-score 0)
|
|
|
|
(tags '()))
|
2021-01-15 19:41:50 +01:00
|
|
|
(:accessor-name-transformer #'name-identity)
|
2021-01-16 01:11:16 +01:00
|
|
|
(:export-accessor-names-p t)
|
2021-01-15 19:41:50 +01:00
|
|
|
(:export-class-name-p t))
|
2021-01-15 19:08:06 +01:00
|
|
|
|
|
|
|
(defun normalize-cl-json-keywords (sym)
|
|
|
|
"Turn '--' to '-' and remove '+' from keywords."
|
|
|
|
(if (keywordp sym)
|
|
|
|
(intern (str:replace-all
|
|
|
|
"+" ""
|
|
|
|
(str:replace-all "--" "-" (symbol-name sym)))
|
|
|
|
"KEYWORD")
|
|
|
|
sym))
|
|
|
|
|
|
|
|
(defun normalize-cl-json-scalar (value)
|
|
|
|
"Turn non-ratio number string to numbers."
|
|
|
|
(if (stringp value)
|
2021-01-15 19:41:50 +01:00
|
|
|
(match value
|
|
|
|
("true" t)
|
|
|
|
("false" nil)
|
|
|
|
(_ (let ((result (ignore-errors (parse-number:parse-number value))))
|
|
|
|
(if (and result
|
|
|
|
(not (typep result 'ratio)))
|
|
|
|
result
|
|
|
|
value))))
|
2021-01-15 19:08:06 +01:00
|
|
|
value))
|
|
|
|
|
|
|
|
(defun json->media-args (json)
|
|
|
|
(alex:mappend (lambda-match
|
|
|
|
((cons key value)
|
|
|
|
(list (normalize-cl-json-keywords key)
|
|
|
|
(if (listp value)
|
2021-01-15 19:41:50 +01:00
|
|
|
(json->media-args value)
|
2021-01-15 19:08:06 +01:00
|
|
|
(normalize-cl-json-scalar value)))))
|
|
|
|
json))
|
|
|
|
|
2021-01-15 19:41:50 +01:00
|
|
|
(export-always 'ffprobe)
|
2021-01-15 19:08:06 +01:00
|
|
|
(defun ffprobe (path)
|
|
|
|
"Return a list of (MEDIA-FORMAT MEDIA-STREAMS...)."
|
|
|
|
(let* ((json-string
|
2021-01-15 19:41:50 +01:00
|
|
|
(ignore-errors
|
|
|
|
(cmd:$cmd "ffprobe -v quiet -print_format json -show_format -show_streams -- "
|
|
|
|
(write-to-string path)))))
|
|
|
|
(when json-string
|
|
|
|
(let* ((json (cl-json:decode-json-from-string json-string))
|
|
|
|
(format-args (json->media-args (alex:assoc-value json :format)))
|
|
|
|
(format (apply #'make-instance 'media-format format-args)))
|
|
|
|
(cons format
|
|
|
|
(mapcar (lambda (s)
|
|
|
|
(let ((stream-args (json->media-args s)))
|
|
|
|
(apply #'make-instance 'media-stream stream-args)))
|
|
|
|
(alex:assoc-value json :streams)))))))
|