aboutsummaryrefslogtreecommitdiff
path: root/tests/test-transcription-video.el
blob: 8327fa326c03808031acf0b3cff378297c526c6a (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
;;; test-transcription-video.el --- Tests for video transcription dispatch -*- lexical-binding: t; -*-

;;; Commentary:
;; Tests for the video branch of the transcription pipeline.  Audio
;; files keep flowing through `cj/--start-transcription-process'
;; unchanged (covered by sibling test files).  Video files go through
;; ffmpeg audio extraction first, then into the same transcription
;; pipeline with the extracted file marked for cleanup once
;; transcription completes.

;;; Code:

(require 'ert)
(require 'cl-lib)

(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'transcription-config)

;;; cj/--video-file-p

(ert-deftest test-tx-video-file-p-recognizes-common-video-extensions ()
  "Normal: common video extensions are recognized."
  (dolist (path '("clip.mp4" "talk.mkv" "demo.mov" "ad.webm" "old.avi"
                  "screencast.m4v" "promo.mpg"))
    (should (cj/--video-file-p path))))

(ert-deftest test-tx-video-file-p-rejects-audio-and-non-media-extensions ()
  "Boundary: audio and unrelated extensions return nil."
  (dolist (path '("song.mp3" "notes.txt" "image.png" "archive.tar.gz"))
    (should-not (cj/--video-file-p path))))

(ert-deftest test-tx-video-file-p-case-insensitive ()
  "Boundary: uppercase extensions count too."
  (should (cj/--video-file-p "Clip.MP4"))
  (should (cj/--video-file-p "TALK.MKV")))

(ert-deftest test-tx-video-file-p-handles-no-extension ()
  "Boundary: extensionless and nil/empty input returns nil."
  (should-not (cj/--video-file-p "README"))
  (should-not (cj/--video-file-p ""))
  (should-not (cj/--video-file-p nil)))

;;; cj/--media-file-p

(ert-deftest test-tx-media-file-p-accepts-audio ()
  "Normal: audio passes."
  (should (cj/--media-file-p "song.mp3")))

(ert-deftest test-tx-media-file-p-accepts-video ()
  "Normal: video passes."
  (should (cj/--media-file-p "clip.mp4")))

(ert-deftest test-tx-media-file-p-rejects-non-media ()
  "Boundary: text, image, etc. fail."
  (should-not (cj/--media-file-p "notes.txt"))
  (should-not (cj/--media-file-p "image.png")))

;;; cj/--extract-audio-from-video

(ert-deftest test-tx-extract-audio-invokes-ffmpeg-with-expected-args ()
  "Normal: extraction shells ffmpeg with -vn and the chosen MP3 encoder."
  (let* ((video "/clips/demo.mp4")
         (out "/tmp/cj-tx-extract.mp3")
         make-process-kwargs)
    (cl-letf (((symbol-function 'cj/executable-find-or-warn)
               (lambda (&rest _) "/usr/bin/ffmpeg"))
              ((symbol-function 'make-process)
               (lambda (&rest kw) (setq make-process-kwargs kw) 'fake-process)))
      (cj/--extract-audio-from-video video out #'ignore))
    (should make-process-kwargs)
    (let ((cmd (plist-get make-process-kwargs :command)))
      (should (equal (car cmd) "/usr/bin/ffmpeg"))
      (should (member "-vn" cmd))
      (should (member video cmd))
      (should (member out cmd))
      (should (member "libmp3lame" cmd)))))

(ert-deftest test-tx-extract-audio-errors-when-ffmpeg-missing ()
  "Error: ffmpeg not on PATH signals user-error before make-process."
  (cl-letf (((symbol-function 'cj/executable-find-or-warn)
             (lambda (&rest _) nil))
            ((symbol-function 'make-process)
             (lambda (&rest _) (error "make-process must not be called"))))
    (should-error (cj/--extract-audio-from-video "/x.mp4" "/tmp/y.mp3" #'ignore)
                  :type 'user-error)))

;;; cj/transcribe-media dispatcher

(ert-deftest test-tx-transcribe-media-audio-routes-directly ()
  "Normal: audio paths go straight to the transcription worker, no ffmpeg."
  (let* ((tmp (make-temp-file "cj-tx-aud-" nil ".mp3"))
         worker-arg ffmpeg-called)
    (unwind-protect
        (cl-letf (((symbol-function 'cj/--start-transcription-process)
                   (lambda (file &rest _) (setq worker-arg file) 'fake-proc))
                  ((symbol-function 'cj/--extract-audio-from-video)
                   (lambda (&rest _) (setq ffmpeg-called t))))
          (cj/transcribe-media tmp))
      (delete-file tmp))
    (should (equal worker-arg tmp))
    (should-not ffmpeg-called)))

(ert-deftest test-tx-transcribe-media-video-extracts-then-transcribes ()
  "Normal: video paths invoke ffmpeg; on success the extracted audio
goes through `cj/--start-transcription-process' with a cleanup hint."
  (let* ((tmp (make-temp-file "cj-tx-vid-" nil ".mp4"))
         extract-args worker-call)
    (unwind-protect
        (cl-letf (((symbol-function 'cj/--extract-audio-from-video)
                   (lambda (vid out cb)
                     (setq extract-args (list vid out cb))
                     ;; Simulate immediate ffmpeg success.
                     (funcall cb)))
                  ((symbol-function 'cj/--start-transcription-process)
                   (lambda (file &rest rest)
                     (setq worker-call (cons file rest))
                     'fake-proc)))
          (cj/transcribe-media tmp))
      (delete-file tmp))
    ;; ffmpeg was asked to extract from tmp.
    (should extract-args)
    (should (equal (car extract-args) tmp))
    ;; The temp audio path passed to ffmpeg matches the path passed to
    ;; the worker -- in other words the extraction output IS what the
    ;; worker transcribes.
    (should (equal (nth 1 extract-args) (car worker-call)))
    ;; The worker got the temp-audio as cleanup-file (so it gets
    ;; deleted after transcription completes).
    (should (equal (nth 1 extract-args) (cadr worker-call)))))

(ert-deftest test-tx-transcribe-media-rejects-non-media ()
  "Error: non-media paths get rejected up front."
  (should-error (cj/transcribe-media "/notes/readme.txt") :type 'user-error))

;;; Aliases

(ert-deftest test-tx-old-transcribe-audio-aliases-new-media-command ()
  "Backwards compat: `cj/transcribe-audio' still resolves to the new
media dispatcher via defalias."
  (should (eq (symbol-function 'cj/transcribe-audio) 'cj/transcribe-media)))

(ert-deftest test-tx-old-at-point-aliases-new-media-at-point ()
  "Backwards compat: `cj/transcribe-audio-at-point' still resolves."
  (should (eq (symbol-function 'cj/transcribe-audio-at-point)
              'cj/transcribe-media-at-point)))

;;; Keybinding

(ert-deftest test-tx-dired-T-binds-media-at-point ()
  "Normal: T in dired-mode-map invokes `cj/transcribe-media-at-point'."
  (require 'dired)
  (should (eq (lookup-key dired-mode-map (kbd "T"))
              #'cj/transcribe-media-at-point)))

(provide 'test-transcription-video)
;;; test-transcription-video.el ends here