[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/subed d5e48886f4 1/2: 1.2.14: Bugfix: handle waveform for
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/subed d5e48886f4 1/2: 1.2.14: Bugfix: handle waveform for last cue |
Date: |
Wed, 10 Jul 2024 22:01:37 -0400 (EDT) |
branch: elpa/subed
commit d5e48886f4472a644c1c406dd8994b357abd15d8
Author: Sacha Chua <sacha@sachachua.com>
Commit: Sacha Chua <sacha@sachachua.com>
1.2.14: Bugfix: handle waveform for last cue
subed-waveform should now handle the case where
the stop time + subed-waveform-preview-msecs-after
might extend past the end of the file.
* subed/subed-waveform.el (subed-waveform-ffprobe-executable): New.
(subed-waveform-file-duration-ms-cache): New.
(subed-waveform-ffprobe-duration-ms): New,
calculates duration.
(subed-waveform-file-duration-ms): New function
for caching the duration.
(subed-waveform-clear-file-duration-ms-cache): New.
(subed-mpv): Add advice around
subed-mpv-play-from-file for now;
ideally change this to a hook later on.
(subed-waveform--image-parameters): Move to a separate
function for easier testing.
(subed-waveform--make-overlay): Do the
calculations in subed-waveform--image-parameters.
(subed-waveform--update-bars): Use the actual stop
time if needed.
* tests/test-subed-waveform.el: New.
* Set lexical-binding: t in tests/* files
Thanks to rodrigomorales1 and rndusr for bug
reports and pull requests!
Related:
- https://github.com/sachac/subed/issues/68
- https://github.com/sachac/subed/pull/75
- https://github.com/sachac/subed/issues/74
---
AUTHORS.org | 5 +-
NEWS.org | 9 ++
subed/subed-waveform.el | 197 +++++++++++++++++++++++++++----
subed/subed-waveform.el.license | 2 +-
subed/subed.el | 2 +-
tests/test-subed-mpv.el | 2 +-
tests/test-subed-srt.el | 2 +-
tests/test-subed-tsv.el | 2 +-
tests/test-subed-waveform.el | 217 +++++++++++++++++++++++++++++++++++
tests/test-subed-waveform.el.license | 3 +
10 files changed, 412 insertions(+), 29 deletions(-)
diff --git a/AUTHORS.org b/AUTHORS.org
index 443709a4e2..55ec1e20f4 100644
--- a/AUTHORS.org
+++ b/AUTHORS.org
@@ -1,5 +1,5 @@
#+BEGIN_COMMENT
-SPDX-FileCopyrightText: 2021 The subed Authors
+SPDX-FileCopyrightText: 2021-2024 The subed Authors
SPDX-License-Identifier: CC0-1.0
#+END_COMMENT
@@ -12,4 +12,7 @@ Please note this shouldn't be taken as a list of copyright
holders,
nor is it necessarily complete.
- Random User <rndusr@posteo.de> (original creator)
+- Sacha Chua <sacha@sachachua.com>
- Sebastian 'seabass' Crane <seabass-labrax@gmx.com>
+- Marcin Borkowski
+- Rodrigo Morales
diff --git a/NEWS.org b/NEWS.org
index fd434de8bc..78c0e30cef 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,6 +1,15 @@
#+OPTIONS: toc:nil
* subed news
+** Version 1.2.14 - 2024-07-05 - Sacha Chua
+
+- Bugfix: subed-waveform should now handle the
+ case where the stop time +
+ subed-waveform-preview-msecs-after might extend
+ past the end of the file.
+
+ Thanks to rodrigomorales1 and rnduser for the bug reports and pull requests!
+
** Version 1.2.13 - 2024-07-05 - Sacha Chua
- Bugfix: Fix the requires in subed-waveform to load subed-common.
diff --git a/subed/subed-waveform.el b/subed/subed-waveform.el
index 8b78810267..66fce8b7a5 100644
--- a/subed/subed-waveform.el
+++ b/subed/subed-waveform.el
@@ -1,6 +1,6 @@
;;; subed-waveform.el --- display waveforms in subed buffers -*-
lexical-binding: t; -*-
-;; Copyright (C) 2023 Sacha Chua, Marcin Borkowski
+;; Copyright (C) 2023-2024 Sacha Chua, Marcin Borkowski, Rodrigo Morales
;; Author: Sacha Chua <sacha@sachachua.com>, Marcin Borkowski <mbork@mbork.pl>
;; Keywords: multimedia
@@ -140,6 +140,11 @@ SVG parameters of the displayed bars. Every bar must have
a unique
:value-type (plist :key-type symbol :value-type string))
:group 'subed-waveform)
+(defcustom subed-waveform-ffprobe-executable "ffprobe"
+ "Path to the FFprobe executable used for measuring file duration."
+ :type 'file
+ :group 'subed-waveform)
+
(defcustom subed-waveform-preview-msecs-before 2000
"Prelude in milliseconds displaying subtitle waveform."
:type 'integer
@@ -244,6 +249,123 @@ WIDTH and HEIGHT are given in pixels."
width height)
"[bg][fg]overlay=format=auto,drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color=#9cf42f"))
+(defvar-local subed-waveform-file-duration-ms-cache nil "If non-nil, duration
of current file in milliseconds.")
+
+(defun subed-waveform-convert-ffprobe-tags-duration-to-ms (duration)
+ "Return milliseconds as an integer for DURATION.
+
+DURATION must be a string of the format HH:MM:SS.MMMM.
+
+Example:
+
+00:00:03.003000000 -> 3003
+00:00:03.00370000 -> 3004"
+ (unless (string-match
"\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)\\.\\([0-9]+\\)"
duration)
+ (error "The duration is not well formatted."))
+ (let ((hour (match-string 1 duration))
+ (minute (match-string 2 duration))
+ (seconds (match-string 3 duration))
+ (milliseconds (match-string 4 duration)))
+ (+
+ (* (string-to-number hour) 3600000)
+ (* (string-to-number minute) 60000)
+ (* (string-to-number seconds) 1000)
+ (* (string-to-number (concat "0." milliseconds)) 1000))))
+
+(defun subed-waveform-ffprobe-duration-ms (filename)
+ "Use ffprobe to get duration of audio stream in milliseconds of FILENAME."
+ (let ((json
+ (json-read-from-string
+ (with-temp-buffer
+ (call-process
+ subed-waveform-ffprobe-executable nil t nil
+ "-v" "error"
+ "-print_format" "json"
+ "-show_streams"
+ "-show_format"
+ filename)
+ (buffer-string)))))
+ ;; Check that the file has at least one audio stream.
+ (when (eq (seq-find
+ (lambda (stream)
+ (equal (alist-get 'codec_type stream) "audio"))
+ (alist-get 'streams json))
+ 0)
+ (error "The provided file doesn't have an audio stream."))
+ (cond
+ ;; If the file has one stream and it is an audio stream, we can
+ ;; get the duration from format=duration
+ ;;
+ ;; nb_streams equals the number of streams in the media file.
+ ((and (eq (alist-get 'nb_streams (alist-get 'format json)) 1)
+ (equal (alist-get
+ 'codec_type
+ (seq-first (alist-get 'streams json)))
+ "audio"))
+ (* 1000 (string-to-number
+ (alist-get 'duration (alist-get 'format json)))))
+ ;; If the file has more than one stream and only one audio
+ ;; stream, return the duration of the audio stream.
+ ((and (> (alist-get 'nb_streams (alist-get 'format json)) 1)
+ (eq (length (seq-filter
+ (lambda (stream)
+ (equal (alist-get 'codec_type stream) "audio"))
+ (alist-get 'streams json)))
+ 1))
+ (cond
+ ((or
+ (string-match "\\.mkv\\'" filename)
+ (string-match "\\.webm\\'" filename))
+ (subed-waveform-convert-ffprobe-tags-duration-to-ms
+ (alist-get
+ 'DURATION
+ (alist-get
+ 'tags
+ (seq-find
+ (lambda (stream)
+ (equal (alist-get 'codec_type stream) "audio"))
+ (alist-get 'streams json))))))
+ (t
+ (* 1000
+ (string-to-number
+ (alist-get
+ 'duration
+ (seq-find
+ (lambda (stream)
+ (equal (alist-get 'codec_type stream) "audio"))
+ (alist-get 'streams json))))))))
+ ;; TODO: Some media files might have multiple audio streams
+ ;; (e.g. multiple languages). When the media file has multiple
+ ;; audio streams, prompt the user for the audio stream. The audio
+ ;; stream selected by the user must be stored in a buffer-local
+ ;; variable so that ffmpeg knows the audio stream from which the
+ ;; waveforms are created.
+ )))
+
+(defun subed-waveform-file-duration-ms (&optional filename)
+ "Return the duration of FILENAME in milliseconds."
+ (cond
+ (subed-waveform-file-duration-ms-cache
+ (when (> subed-waveform-file-duration-ms-cache 0)
+ subed-waveform-file-duration-ms-cache))
+ (subed-waveform-ffprobe-executable
+ (setq subed-waveform-file-duration-ms-cache
+ (subed-waveform-ffprobe-duration-ms
+ (or filename (subed-media-file))))
+ (if (> subed-waveform-file-duration-ms-cache 0)
+ subed-waveform-file-duration-ms-cache
+ ;; mark as invalid
+ (setq subed-waveform-file-duration-ms-cache -1)
+ nil))))
+
+(defun subed-waveform-clear-file-duration-ms-cache (&rest _)
+ "Clear `subed-waveform-file-duration-ms-cache'."
+ (setq subed-waveform-file-duration-ms-cache nil))
+
+;; This should eventually be replaced with a hook.
+(with-eval-after-load 'subed-mpv
+ (advice-add 'subed-mpv-play-from-file :after
'subed-waveform-clear-file-duration-ms-cache))
+
(defun subed-waveform--from-file (filename from to width height)
"Returns a string representing the image data in PNG format.
FILENAME is the input file, FROM and TO are time positions, WIDTH
@@ -284,34 +406,60 @@ and HEIGHT are dimensions in pixels."
(when pos
(format "%.2f%%" (/ (* 100.0 (- pos start)) (- stop start)))))
+(defun subed-waveform--image-parameters (&optional width height)
+ "Return a plist of media-file, start, stop, width, height.
+Use WIDTH and HEIGHT if specified."
+ (let* ((duration (subed-waveform-file-duration-ms (subed-media-file)))
+ (start (floor (max 0 (- (subed-subtitle-msecs-start)
subed-waveform-preview-msecs-before))))
+ (stop
+ (min
+ (floor (+ (subed-subtitle-msecs-stop)
subed-waveform-preview-msecs-after))
+ (or duration most-positive-fixnum)))
+ (width-ratio
+ (/
+ (* 100.0 (- stop start))
+ (- (+ (subed-subtitle-msecs-stop)
subed-waveform-preview-msecs-after) start)))
+ (width (or width (/ (* width-ratio (string-pixel-width (make-string
fill-column ?*)))
+ (face-attribute 'default :height))))
+ (height (or height (save-excursion
+ ;; don't count the current waveform towards the
+ ;; line height
+ (forward-line -1)
+ (* 2 (line-pixel-height))))))
+ (list
+ :file
+ (or (subed-media-file)
+ (error "No media file found"))
+ :start
+ start
+ :stop
+ stop
+ :width
+ width
+ :height
+ height)))
+
(defun subed-waveform--make-overlay (&optional width height)
"Make an overlay at point for the current subtitle."
(let* ((overlay (make-overlay (point) (point)))
- (start (floor (max 0 (- (subed-subtitle-msecs-start)
subed-waveform-preview-msecs-before))))
- (stop (floor (+ (subed-subtitle-msecs-stop)
subed-waveform-preview-msecs-after)))
- (width (/ (* 100.0 (string-pixel-width (make-string fill-column ?*)))
- (face-attribute 'default :height)))
- (height (save-excursion
- ;; don't count the current waveform towards the
- ;; line height
- (forward-line -1)
- (* 2 (line-pixel-height))))
+ (params (subed-waveform--image-parameters width height))
(image (subed-waveform--from-file
- (or (subed-media-file)
- (error "No media file found"))
- (subed-waveform--msecs-to-ffmpeg start)
- (subed-waveform--msecs-to-ffmpeg stop)
- width
- height))
- (svg (svg-create width height)))
+ (plist-get params :file)
+ (subed-waveform--msecs-to-ffmpeg (plist-get params :start))
+ (subed-waveform--msecs-to-ffmpeg (plist-get params :stop))
+ (plist-get params :width)
+ (plist-get params :height)))
+ (svg (svg-create
+ (plist-get params :width)
+ (plist-get params :height))))
(svg-embed svg image "image/png" t
:x 0 :y 0
:width "100%" :height "100%"
:preserveAspectRatio "none")
(overlay-put overlay 'subed-waveform t)
(overlay-put overlay 'after-string "\n")
- (overlay-put overlay 'waveform-start start)
- (overlay-put overlay 'waveform-stop stop)
+ (overlay-put overlay 'waveform-start (plist-get params :start))
+ (overlay-put overlay 'waveform-stop (plist-get params :stop))
(overlay-put overlay 'before-string
(propertize
" "
@@ -319,9 +467,11 @@ and HEIGHT are dimensions in pixels."
'svg svg
'pointer 'arrow
'keymap subed-waveform-svg-map
- 'waveform-start start
- 'waveform-stop stop
- 'waveform-pixels-per-second (/ width (* 0.001 (- stop
start)))))
+ 'waveform-start (plist-get params :start)
+ 'waveform-stop (plist-get params :stop)
+ 'waveform-pixels-per-second (/ (plist-get params :width)
+ (* 0.001 (- (plist-get params
:stop)
+ (plist-get params
:start))))))
(unless subed-waveform-show-all
(setq subed-waveform--overlay overlay)
(setq subed-waveform--svg svg))
@@ -365,7 +515,8 @@ If POSITION is nil, remove the bar."
"Update the bars in OVERLAY."
(setq overlay (or overlay (subed-waveform--get-current-overlay)))
(let* ((start (subed-subtitle-msecs-start))
- (stop (subed-subtitle-msecs-stop))
+ (stop (min (subed-subtitle-msecs-stop)
+ (or (subed-waveform-file-duration-ms)
most-positive-fixnum)))
(start-pos (subed-waveform--position-to-percent
start
(overlay-get overlay 'waveform-start)
diff --git a/subed/subed-waveform.el.license b/subed/subed-waveform.el.license
index 8de80387df..d930de3cb7 100644
--- a/subed/subed-waveform.el.license
+++ b/subed/subed-waveform.el.license
@@ -1,3 +1,3 @@
-;;;; SPDX-FileCopyrightText: 2023 Sacha Chua, Marcin Borkowski
+;;;; SPDX-FileCopyrightText: 2023, 2024 Sacha Chua, Marcin Borkowski, Rodrigo
Morales
;;;;
;;;; SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/subed/subed.el b/subed/subed.el
index 284bc24053..c16522fa8b 100644
--- a/subed/subed.el
+++ b/subed/subed.el
@@ -1,6 +1,6 @@
;;; subed.el --- A major mode for editing subtitles -*- lexical-binding: t;
-*-
-;; Version: 1.2.13
+;; Version: 1.2.14
;; Maintainer: Sacha Chua <sacha@sachachua.com>
;; Author: Random User
;; Keywords: convenience, files, hypermedia, multimedia
diff --git a/tests/test-subed-mpv.el b/tests/test-subed-mpv.el
index 3c292a28b8..bf04fd5c73 100644
--- a/tests/test-subed-mpv.el
+++ b/tests/test-subed-mpv.el
@@ -1,4 +1,4 @@
-;; -*- eval: (buttercup-minor-mode) -*-
+;; -*- lexical-binding: t; eval: (buttercup-minor-mode) -*-
(load-file "./tests/undercover-init.el")
(require 'subed-mpv)
diff --git a/tests/test-subed-srt.el b/tests/test-subed-srt.el
index 564db390a7..717901c62f 100644
--- a/tests/test-subed-srt.el
+++ b/tests/test-subed-srt.el
@@ -1,4 +1,4 @@
-;; -*- eval: (buttercup-minor-mode) -*-
+;; -*- lexical-binding: t; eval: (buttercup-minor-mode) -*-
(load-file "./tests/undercover-init.el")
(require 'subed-srt)
diff --git a/tests/test-subed-tsv.el b/tests/test-subed-tsv.el
index 50c99d28f5..24adfb9f00 100644
--- a/tests/test-subed-tsv.el
+++ b/tests/test-subed-tsv.el
@@ -1,4 +1,4 @@
-;; -*- eval: (buttercup-minor-mode) -*-
+;; -*- lexical-binding: t; eval: (buttercup-minor-mode) -*-
(add-to-list 'load-path "./subed")
(require 'subed)
diff --git a/tests/test-subed-waveform.el b/tests/test-subed-waveform.el
new file mode 100644
index 0000000000..5948532e21
--- /dev/null
+++ b/tests/test-subed-waveform.el
@@ -0,0 +1,217 @@
+;; -*- eval: (buttercup-minor-mode); lexical-binding: t -*-
+
+(require 'subed-waveform)
+
+(cl-defun create-sample-media-file-1-audio-stream (&key
+ path
+ duration-audio-stream)
+ "Create a sample media file with one audio stream
+
+PATH is the absolute path for the output file. It must be a
+string.
+
+DURATION is the number of seconds for the media file. It must be
+a string."
+ (call-process
+ ;; The ffmpeg command shown below can create files with the
+ ;; extensions shown below (tested using ffmpeg version
+ ;; 4.4.2-0ubuntu0.22.04.1)
+ ;; + audio extensions: wav ogg mp3 opus m4a
+ ;; + video extensions: mkv mp4 webm avi ts ogv"
+ "ffmpeg"
+ nil
+ nil
+ nil
+ "-v" "error"
+ "-y"
+ ;; We use lavfi to create the audio stream
+ "-f" "lavfi" "-i" (concat "sine=frequency=1000:duration="
duration-audio-stream)
+ path))
+
+(cl-defun create-sample-media-file-1-audio-stream-1-video-stream (&key
+ path
+
duration-video-stream
+
duration-audio-stream)
+ "Create a sample media file with 1 audio stream and 1 video stream.
+
+PATH is the absolute path for the output file. It must be a
+string.
+
+AUDIO-DURATION is the duration in seconds for the audio
+stream. It must be a string.
+
+VIDEO-DURATION is the duration in seconds for the video stream. It
+must be a string."
+ (call-process
+ ;; The ffmpeg command shown below can create files with the
+ ;; extensions shown below (tested using ffmpeg version
+ ;; 4.4.2-0ubuntu0.22.04.1)
+ ;; + audio extensions: wav ogg mp3 opus m4a
+ ;; + video extensions: mkv mp4 webm avi ts ogv"
+ "ffmpeg"
+ nil
+ nil
+ nil
+ "-v" "error"
+ "-y"
+ ;; Create the video stream
+ "-f" "lavfi" "-i" (concat "testsrc=size=100x100:duration="
duration-video-stream)
+ ;; Create the audio stream
+ "-f" "lavfi" "-i" (concat "sine=frequency=1000:duration="
duration-audio-stream)
+ path))
+
+(describe "waveform"
+ (describe "Get duration in milliseconds of a file with a single audio stream"
+ (let (;; `duration-audio-stream' is the duration in seconds for
+ ;; the media file that is used inside the tests. When
+ ;; `duration-audio-stream' is an integer, ffprobe might
+ ;; report a duration that is slightly greater, so we can't
+ ;; expect the duration reported by ffprobe to be equal to
+ ;; the duration that we passed to ffmpeg when creating the
+ ;; sample media file. For this reason, we define the
+ ;; variables `duration-lower-boundary' and
+ ;; `duration-upper-boundary' to set a tolerance to the
+ ;; reported value by ffprobe.
+ ;;
+ ;; When `duration-audio-stream' changes, the variables
+ ;; `duration-lower-boundary' and
+ ;; `duration-upper-boundary' should be set accordingly."
+ (duration-audio-stream "3")
+ (duration-lower-boundary 3000)
+ (duration-upper-boundary 4000))
+ (describe "audio file"
+ (it "extension .wav"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.wav"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.wav")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .ogg"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.ogg"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.ogg")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .mp3"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.mp3"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.mp3")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .opus"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.opus"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.opus")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .m4a"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.m4a"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.m4a")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary))))
+ (describe "video file"
+ (it "extension .mkv"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.mkv"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.mkv")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .mp4"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.mp4"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.mp4")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .webm"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.webm"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.webm")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .avi"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.avi"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.avi")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .ts"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.ts"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.ts")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary)))
+ (it "extension .ogv"
+ (create-sample-media-file-1-audio-stream
+ :path "/tmp/a.ogv"
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms
"/tmp/a.ogv")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-lower-boundary)
+ (expect duration-ms :to-be-less-than duration-upper-boundary))))))
+ (describe "Get duration in milliseconds of a file with 1 video and 1 audio
stream"
+ ;; In this group of test cases, we want the duration of the audio
+ ;; stream to be shorter than the duration of the video stream, so
+ ;; that we can make sure that subed-waveform-ffprobe-duration-ms
+ ;; specifically gets the duration of the audio stream.
+ (let ((duration-video-stream "5")
+ (duration-audio-stream "3")
+ (duration-audio-stream-lower-boundary 3000)
+ (duration-audio-stream-upper-boundary 4000))
+ (it "extension .mkv"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.mkv"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.mkv")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary)))
+ (it "extension .mp4"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.mp4"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.mp4")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary)))
+ (it "extension .webm"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.webm"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.webm")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary)))
+ (it "extension .avi"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.avi"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.avi")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary)))
+ (it "extension .ts"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.ts"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.ts")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary)))
+ (it "extension .ogv"
+ (create-sample-media-file-1-audio-stream-1-video-stream
+ :path "/tmp/a.ogv"
+ :duration-video-stream duration-video-stream
+ :duration-audio-stream duration-audio-stream)
+ (let ((duration-ms (subed-waveform-ffprobe-duration-ms "/tmp/a.ogv")))
+ (expect duration-ms :to-be-weakly-greater-than
duration-audio-stream-lower-boundary)
+ (expect duration-ms :to-be-less-than
duration-audio-stream-upper-boundary))))))
diff --git a/tests/test-subed-waveform.el.license
b/tests/test-subed-waveform.el.license
new file mode 100644
index 0000000000..b71ceecb7f
--- /dev/null
+++ b/tests/test-subed-waveform.el.license
@@ -0,0 +1,3 @@
+SPDX-FileCopyrightText: 2024 Rodrigo Morales
+
+SPDX-License-Identifier: GPL-3.0-or-later
\ No newline at end of file