emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/subed 527353843b: Add subed-tsv-mode


From: ELPA Syncer
Subject: [nongnu] elpa/subed 527353843b: Add subed-tsv-mode
Date: Sun, 23 Oct 2022 09:59:22 -0400 (EDT)

branch: elpa/subed
commit 527353843ba1656afc5a5fcf58d235c278729283
Author: Sacha Chua <sacha@sachachua.com>
Commit: Sacha Chua <sacha@sachachua.com>

    Add subed-tsv-mode
---
 NEWS.org                |   4 +
 README.org              |  15 +-
 subed/subed-tsv.el      | 445 ++++++++++++++++++++++++++++++++++++++++++++++++
 subed/subed.el          |   2 +-
 tests/test-subed-tsv.el | 378 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 841 insertions(+), 3 deletions(-)

diff --git a/NEWS.org b/NEWS.org
index 0c152bfd4b..2e74852272 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -1,4 +1,8 @@
 * subed news
+** Version 1.0.11 - 2022-10-23 - Sacha Chua
+
+Added subed-tsv.el for Audacity label exports. Use M-x subed-tsv-mode to load 
it.
+
 ** Version 1.0.10 - 2022-09-20 - Sacha Chua
 
 Use - instead of : in mpv socket names to see if that will make it work better 
on Microsoft Windows.
diff --git a/README.org b/README.org
index bc6581da60..4e1c42e92f 100644
--- a/README.org
+++ b/README.org
@@ -6,10 +6,21 @@ SPDX-License-Identifier: GPL-3.0-or-later
 
 * subed
 subed is an Emacs major mode for editing subtitles while playing the
-corresponding video with [[https://mpv.io/][mpv]].  At the moment, the only 
supported formats are
-SubRip ( ~.srt~), WebVTT ( ~.vtt~ ), and Advanced SubStation Alpha ( ~.ass~, 
experimental ).
+corresponding video with [[https://mpv.io/][mpv]].  At the moment, the only 
supported formats are:
+- SubRip ( ~.srt~)
+- WebVTT ( ~.vtt~ )
+- Advanced SubStation Alpha ( ~.ass~, experimental )
+- Tab-separated values ( ~.tsv~, experimental ) - as exported by
+  Audacity for labels. TSVs are not recognized automatically because
+  it's a common data format, but you can use ~subed-tsv-mode~ to turn
+  it on in a buffer.
 
 [[file:https://raw.githubusercontent.com/rndusr/subed/master/screenshot.jpg]]
+
+Using network sockets to control MPV works on Linux and on Mac OS X,
+but not on Microsoft Windows due to the lack of Unix-style sockets. On
+Microsoft Windows, you will not be able to synchronize with MPV.
+
 ** Important change in v1.0.0
 
 ~subed~ now uses ~subed-srt-mode~, ~subed-vtt-mode~, and
diff --git a/subed/subed-tsv.el b/subed/subed-tsv.el
new file mode 100644
index 0000000000..184204aa30
--- /dev/null
+++ b/subed/subed-tsv.el
@@ -0,0 +1,445 @@
+;;; subed-tsv.el --- Tab-separated subtitles, such as Audacity labels  -*- 
lexical-binding: t; -*-
+
+;;; License:
+;;
+;; This file is not part of GNU Emacs.
+;;
+;; This is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; This is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+
+;;; Commentary:
+
+;; This file supports tab-separated values such as labels exported from 
Audacity.
+;; Example:
+;;
+;; 6.191196    27.488912       This is a test
+;; 44.328966   80.733201       This is another line, a little longer than the 
first.
+
+;;; Code:
+
+(require 'subed)
+(require 'subed-config)
+(require 'subed-debug)
+(require 'subed-common)
+
+;;; Syntax highlighting
+
+(defconst subed-tsv-font-lock-keywords
+  (list
+   '("^\\([0-9]+\\.[0-9]+\\)\t\\([0-9]+\\.[0-9]+\\)" (0 subed-tsv-time-face)))
+  "Highlighting expressions for `subed-mode'.")
+
+;;; Parsing
+
+(defconst subed-tsv--regexp-timestamp "\\([0-9]+\\)\\(\\.\\([0-9]+\\)\\)?")
+(defconst subed-tsv--regexp-separator "\n")
+
+(cl-defmethod subed--timestamp-to-msecs (time-string &context (major-mode 
subed-tsv-mode))
+  "Find SS.MS pattern in TIME-STRING and convert it to milliseconds.
+Return nil if TIME-STRING doesn't match the pattern.
+Use the format-specific function for MAJOR-MODE."
+  (save-match-data
+    (when (string-match subed-tsv--regexp-timestamp time-string)
+      (* 1000 (string-to-number (match-string 0 time-string))))))
+
+(cl-defmethod subed--msecs-to-timestamp (msecs &context (major-mode 
subed-tsv-mode))
+  "Convert MSECS to string in the format H:MM:SS.CS.
+Use the format-specific function for MAJOR-MODE."
+  ;; We need to wrap format-seconds in save-match-data because it does regexp
+  ;; stuff and we need to preserve our own match-data.
+  (format "%f" (/ msecs 1000.0)))
+
+(cl-defmethod subed--subtitle-id (&context (major-mode subed-tsv-mode))
+  "Return the ID of the subtitle at point or nil if there is no ID.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (when (subed-jump-to-subtitle-id)
+      (when (looking-at subed-tsv--regexp-timestamp)
+        (match-string 0)))))
+
+(cl-defmethod subed--subtitle-id-max (&context (major-mode subed-tsv-mode))
+  "Return the ID of the last subtitle or nil if there are no subtitles.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (goto-char (point-max))
+    (subed-subtitle-id)))
+
+(cl-defmethod subed--subtitle-id-at-msecs (msecs &context (major-mode 
subed-tsv-mode))
+  "Return the ID of the subtitle at MSECS milliseconds.
+Return nil if there is no subtitle at MSECS.
+Use the format-specific function for MAJOR-MODE."
+  (save-match-data
+    (save-excursion
+      (goto-char (point-min))
+      ;; Move to first subtitle that starts at or after MSECS
+      (catch 'subtitle-id
+        (while (<= (or (subed-subtitle-msecs-start) -1) msecs)
+          ;; If stop time is >= MSECS, we found a match
+          (let ((cur-sub-end (subed-subtitle-msecs-stop)))
+            (when (and cur-sub-end (>= cur-sub-end msecs))
+              (throw 'subtitle-id (subed-subtitle-id))))
+          (unless (subed-forward-subtitle-id)
+            (throw 'subtitle-id nil)))))))
+
+(cl-defmethod subed--subtitle-msecs-start (&context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Subtitle start time in milliseconds or nil if it can't be found.
+If SUB-ID is not given, use subtitle on point.
+Use the format-specific function for MAJOR-MODE."
+  (let ((timestamp (save-excursion
+                     (when (subed-jump-to-subtitle-time-start sub-id)
+                       (when (looking-at subed-tsv--regexp-timestamp)
+                         (match-string 0))))))
+    (when timestamp
+      (subed-timestamp-to-msecs timestamp))))
+
+(cl-defmethod subed--subtitle-msecs-stop (&context (major-mode subed-tsv-mode) 
&optional sub-id)
+  "Subtitle stop time in milliseconds or nil if it can't be found.
+If SUB-ID is not given, use subtitle on point.
+Use the format-specific function for MAJOR-MODE."
+  (let ((timestamp (save-excursion
+                     (when (subed-jump-to-subtitle-time-stop sub-id)
+                       (when (looking-at subed-tsv--regexp-timestamp)
+                         (match-string 0))))))
+    (when timestamp
+      (subed-timestamp-to-msecs timestamp))))
+
+(cl-defmethod subed--subtitle-text (&context (major-mode subed-tsv-mode) 
&optional sub-id)
+  "Return subtitle's text or an empty string.
+If SUB-ID is not given, use subtitle on point.
+Use the format-specific function for MAJOR-MODE."
+  (or (save-excursion
+        (let ((beg (subed-jump-to-subtitle-text sub-id))
+              (end (subed-jump-to-subtitle-end sub-id)))
+          (when (and beg end)
+            (buffer-substring beg end))))
+      ""))
+
+(cl-defmethod subed--subtitle-relative-point (&context (major-mode 
subed-tsv-mode))
+  "Point relative to subtitle's ID or nil if ID can't be found.
+Use the format-specific function for MAJOR-MODE."
+  (let ((start-point (save-excursion
+                       (when (subed-jump-to-subtitle-id)
+                         (point)))))
+    (when start-point
+      (- (point) start-point))))
+
+;;; Traversing
+
+(cl-defmethod subed--jump-to-subtitle-id (&context (major-mode subed-tsv-mode) 
&optional sub-id)
+  "Move to the ID of a subtitle and return point.
+If SUB-ID is not given, focus the current subtitle's ID.
+Return point or nil if no subtitle ID could be found.
+ASS doesn't use IDs, so we use the starting timestamp instead.
+Use the format-specific function for MAJOR-MODE."
+  (save-match-data
+    (if (stringp sub-id)
+        (let* ((orig-point (point))
+               (find-ms (subed-timestamp-to-msecs sub-id))
+               done)
+          (goto-char (point-min))
+          ;; Find the first timestamp that ends after the time we're looking 
for
+          (catch 'found-ending-after
+            (while (not (eobp))
+              (when (and (looking-at (concat "^[^\t]+\t\\(" 
subed-tsv--regexp-timestamp "\\)\t"))
+                         (> (subed-timestamp-to-msecs (match-string 1)) 
find-ms ))
+                (throw 'found-ending-after "Found ending"))
+              (forward-line 1)))
+          ;; Does the time fit in the current one?
+          (if (>= find-ms (subed-subtitle-msecs-start))
+              (progn
+                (beginning-of-line)
+                (point))
+            (goto-char orig-point)
+            nil))
+      (beginning-of-line)
+      (when (looking-at (concat subed-tsv--regexp-timestamp "\t" 
subed-tsv--regexp-timestamp "\t.*"))
+        (point)))))
+
+(cl-defmethod subed--jump-to-subtitle-id-at-msecs (msecs &context (major-mode 
subed-tsv-mode))
+  "Move point to the ID of the subtitle that is playing at MSECS.
+Return point or nil if point is still on the same subtitle.
+See also `subed-tsv--subtitle-id-at-msecs'.
+Use the format-specific function for MAJOR-MODE."
+  (let ((current-sub-id (subed-subtitle-id))
+        (target-sub-id (subed-subtitle-id-at-msecs msecs)))
+    (when (and target-sub-id current-sub-id (not (equal target-sub-id 
current-sub-id)))
+      (subed-jump-to-subtitle-id target-sub-id))))
+
+(cl-defmethod subed--jump-to-subtitle-text-at-msecs (msecs &context 
(major-mode subed-tsv-mode))
+  "Move point to the text of the subtitle that is playing at MSECS.
+Return point or nil if point is still on the same subtitle.
+See also `subed-tsv--subtitle-id-at-msecs'.
+Use the format-specific function for MAJOR-MODE."
+  (when (subed-jump-to-subtitle-id-at-msecs msecs)
+    (subed-jump-to-subtitle-text)))
+
+(cl-defmethod subed--jump-to-subtitle-time-start (&context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Move point to subtitle's start time.
+If SUB-ID is not given, use subtitle on point.
+Return point or nil if no start time could be found.
+Use the format-specific function for MAJOR-MODE."
+  (save-match-data
+    (when (subed-jump-to-subtitle-id sub-id)
+      (when (re-search-forward subed-tsv--regexp-timestamp (line-end-position) 
t)
+        (goto-char (match-beginning 0))
+        (point)))))
+
+(cl-defmethod subed--jump-to-subtitle-time-stop (&context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Move point to subtitle's stop time.
+If SUB-ID is not given, use subtitle on point.
+Return point or nil if no stop time could be found.
+Use the format-specific function for MAJOR-MODE."
+    (save-match-data
+    (when (subed-jump-to-subtitle-id sub-id)
+      (re-search-forward (concat "\\(?:" subed-tsv--regexp-timestamp "\\)\t")
+                         (point-at-eol) t)
+      (when (looking-at subed-tsv--regexp-timestamp)
+        (point)))))
+
+(cl-defmethod subed--jump-to-subtitle-text (&context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Move point on the first character of subtitle's text.
+If SUB-ID is not given, use subtitle on point.
+Return point or nil if a the subtitle's text can't be found.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-jump-to-subtitle-id sub-id)
+    (beginning-of-line)
+    (when (looking-at ".*?\t.*?\t")
+      (goto-char (match-end 0)))
+    (point)))
+
+(cl-defmethod subed--jump-to-subtitle-end (&context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Move point after the last character of the subtitle's text.
+If SUB-ID is not given, use subtitle on point.
+Return point or nil if point did not change or if no subtitle end
+can be found.
+Use the format-specific function for MAJOR-MODE."
+    (save-match-data
+    (let ((orig-point (point)))
+      (when (subed-jump-to-subtitle-text sub-id)
+        (end-of-line)
+        (unless (= orig-point (point))
+          (point))))))
+
+(cl-defmethod subed--forward-subtitle-id (&context (major-mode subed-tsv-mode))
+  "Move point to next subtitle's ID.
+Return point or nil if there is no next subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (save-match-data
+    (let ((pos (point)))
+      (forward-line 1)
+      (if (eobp)
+          (prog1 nil (goto-char pos))
+        (beginning-of-line)
+        (if (looking-at subed-tsv--regexp-timestamp)
+            (point)
+          (goto-char pos)
+          nil)))))
+
+(cl-defmethod subed--backward-subtitle-id (&context (major-mode 
subed-tsv-mode))
+  "Move point to previous subtitle's ID.
+Return point or nil if there is no previous subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (let ((orig-point (point)))
+      (if (bobp)
+          nil
+        (when (subed-jump-to-subtitle-id)
+          (if (bobp)
+              (progn (goto-char orig-point) nil)
+            (forward-line -1)
+            (while (not (or (bobp) (looking-at subed-tsv--regexp-timestamp)))
+              (forward-line -1))
+            (if (looking-at subed-tsv--regexp-timestamp)
+                (point)
+              (goto-char orig-point)
+              nil))))))
+
+(cl-defmethod subed--forward-subtitle-text (&context (major-mode 
subed-tsv-mode))
+  "Move point to next subtitle's text.
+Return point or nil if there is no next subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-forward-subtitle-id)
+    (subed-jump-to-subtitle-text)))
+
+(cl-defmethod subed--backward-subtitle-text (&context (major-mode 
subed-tsv-mode))
+  "Move point to previous subtitle's text.
+Return point or nil if there is no previous subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-backward-subtitle-id)
+    (subed-jump-to-subtitle-text)))
+
+(cl-defmethod subed--forward-subtitle-end (&context (major-mode 
subed-tsv-mode))
+  "Move point to end of next subtitle.
+Return point or nil if there is no next subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-forward-subtitle-id)
+    (subed-jump-to-subtitle-end)))
+
+(cl-defmethod subed--backward-subtitle-end (&context (major-mode 
subed-tsv-mode))
+  "Move point to end of previous subtitle.
+Return point or nil if there is no previous subtitle.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-backward-subtitle-id)
+    (subed-jump-to-subtitle-end)))
+
+(cl-defmethod subed--forward-subtitle-time-start (&context (major-mode 
subed-tsv-mode))
+  "Move point to next subtitle's start time.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-forward-subtitle-id)
+    (subed-jump-to-subtitle-time-start)))
+
+(cl-defmethod subed--backward-subtitle-time-start (&context (major-mode 
subed-tsv-mode))
+  "Move point to previous subtitle's start time.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-backward-subtitle-id)
+    (subed-jump-to-subtitle-time-start)))
+
+(cl-defmethod subed--forward-subtitle-time-stop (&context (major-mode 
subed-tsv-mode))
+  "Move point to next subtitle's stop time.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-forward-subtitle-id)
+    (subed-jump-to-subtitle-time-stop)))
+
+(cl-defmethod subed--backward-subtitle-time-stop (&context (major-mode 
subed-tsv-mode))
+  "Move point to previous subtitle's stop time.
+Use the format-specific function for MAJOR-MODE."
+    (when (subed-backward-subtitle-id)
+      (subed-jump-to-subtitle-time-stop)))
+
+;;; Manipulation
+
+(cl-defmethod subed--set-subtitle-time-start (msecs &context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Set subtitle start time to MSECS milliseconds.
+
+If SUB-ID is not given, set the start of the current subtitle.
+
+Return the new subtitle start time in milliseconds.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (when (or (not sub-id)
+              (and sub-id (subed-jump-to-subtitle-id sub-id)))
+      (subed-jump-to-subtitle-time-start)
+      (when (looking-at subed-tsv--regexp-timestamp)
+        (replace-match (subed-msecs-to-timestamp msecs))))))
+
+(cl-defmethod subed--set-subtitle-time-stop (msecs &context (major-mode 
subed-tsv-mode) &optional sub-id)
+  "Set subtitle stop time to MSECS milliseconds.
+
+If SUB-ID is not given, set the stop of the current subtitle.
+
+Return the new subtitle stop time in milliseconds.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (when (or (not sub-id)
+              (and sub-id (subed-jump-to-subtitle-id sub-id)))
+      (subed-jump-to-subtitle-time-stop)
+      (when (looking-at subed-tsv--regexp-timestamp)
+        (replace-match (subed-msecs-to-timestamp msecs))))))
+
+(cl-defmethod subed--make-subtitle (&context (major-mode subed-tsv-mode) 
&optional id start stop text)
+  "Generate new subtitle string.
+
+ID, START default to 0.
+STOP defaults to (+ START `subed-subtitle-spacing')
+TEXT defaults to an empty string.
+
+A newline is appended to TEXT, meaning you'll get two trailing
+newlines if TEXT is nil or empty.
+Use the format-specific function for MAJOR-MODE."
+  (format "%s\t%s\t%s\n"
+          (subed-msecs-to-timestamp (or start 0))
+          (subed-msecs-to-timestamp (or stop (+ (or start 0)
+                                                     
subed-default-subtitle-length)))
+          (replace-regexp-in-string "\n" " " (or text ""))))
+
+(cl-defmethod subed--prepend-subtitle (&context (major-mode subed-tsv-mode) 
&optional id start stop text)
+  "Insert new subtitle before the subtitle at point.
+
+ID and START default to 0.
+STOP defaults to (+ START `subed-subtitle-spacing')
+TEXT defaults to an empty string.
+
+Move point to the text of the inserted subtitle.
+Return new point.
+Use the format-specific function for MAJOR-MODE."
+  (subed-jump-to-subtitle-id)
+  (insert (subed-make-subtitle id start stop text))
+  (forward-line -1)
+  (subed-jump-to-subtitle-text))
+
+(cl-defmethod subed--append-subtitle (&context (major-mode subed-tsv-mode) 
&optional id start stop text)
+  "Insert new subtitle after the subtitle at point.
+
+ID, START default to 0.
+STOP defaults to (+ START `subed-subtitle-spacing')
+TEXT defaults to an empty string.
+
+Move point to the text of the inserted subtitle.
+Return new point.
+Use the format-specific function for MAJOR-MODE."
+  (unless (subed-forward-subtitle-id)
+    ;; Point is on last subtitle or buffer is empty
+    (subed-jump-to-subtitle-end)
+    (unless (bolp) (insert "\n")))
+  (insert (subed-make-subtitle id start stop text))
+  (forward-line -1)
+  (subed-jump-to-subtitle-text))
+
+(cl-defmethod subed--kill-subtitle (&context (major-mode subed-tsv-mode))
+  "Remove subtitle at point.
+Use the format-specific function for MAJOR-MODE."
+    (let ((beg (save-excursion (subed-jump-to-subtitle-id)
+                             (point)))
+        (end (save-excursion (subed-jump-to-subtitle-id)
+                             (when (subed-forward-subtitle-id)
+                               (point)))))
+    (if (not end)
+        ;; Removing the last subtitle because forward-subtitle-id returned nil
+        (setq beg (save-excursion (goto-char beg)
+                                  (subed-backward-subtitle-end)
+                                  (1+ (point)))
+              end (save-excursion (goto-char (point-max)))))
+    (delete-region beg end)))
+
+(cl-defmethod subed--merge-with-next (&context (major-mode subed-tsv-mode))
+  "Merge the current subtitle with the next subtitle.
+Update the end timestamp accordingly.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (subed-jump-to-subtitle-end)
+    (let ((pos (point)) new-end)
+      (if (subed-forward-subtitle-time-stop)
+          (progn
+            (when (looking-at subed-tsv--regexp-timestamp)
+              (setq new-end (subed-timestamp-to-msecs (match-string 0))))
+            (subed-jump-to-subtitle-text)
+            (delete-region pos (point))
+            (insert " ")
+            (subed-set-subtitle-time-stop new-end))
+        (error "No subtitle to merge into")))))
+
+
+;;; Initialization
+
+(define-derived-mode subed-tsv-mode subed-mode
+  "Subed-TSV"
+  "Tab-separated subtitles, such as from exporting text labels from Audacity."
+  (setq-local subed--subtitle-format "tsv")
+  (setq-local subed--regexp-timestamp subed-tsv--regexp-timestamp)
+  (setq-local subed--regexp-separator subed-tsv--regexp-separator)
+  (setq-local font-lock-defaults '(subed-tsv-font-lock-keywords)))
+
+(provide 'subed-tsv)
+;;; subed-tsv.el ends here
diff --git a/subed/subed.el b/subed/subed.el
index bb942af0d1..d851553830 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.0.10
+;; Version: 1.0.11
 ;; Maintainer: Sacha Chua <sacha@sachachua.com>
 ;; Author: Random User
 ;; Keywords: convenience, files, hypermedia, multimedia
diff --git a/tests/test-subed-tsv.el b/tests/test-subed-tsv.el
new file mode 100644
index 0000000000..546d39de20
--- /dev/null
+++ b/tests/test-subed-tsv.el
@@ -0,0 +1,378 @@
+;; -*- eval: (buttercup-minor-mode) -*-
+
+(add-to-list 'load-path "./subed")
+(require 'subed)
+(require 'subed-tsv)
+
+(defvar mock-tsv-data
+  "11.120000\t14.000000\tHello, world!
+14.000000\t16.800000\tThis is a test.
+17.000000\t19.800000\tI hope it works.
+")
+
+(defmacro with-temp-tsv-buffer (&rest body)
+  "Call `subed-tsv--init' in temporary buffer before running BODY."
+  `(with-temp-buffer
+     (subed-tsv-mode)
+     (progn ,@body)))
+
+(describe "TSV"
+  (describe "Getting"
+    (describe "the subtitle start/stop time"
+      (it "returns the time in milliseconds."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.000000")
+         (expect (floor (subed-subtitle-msecs-start)) :to-equal 14000)
+         (expect (floor (subed-subtitle-msecs-stop)) :to-equal 16800)))
+      (it "returns nil if time can't be found."
+        (with-temp-tsv-buffer
+         (expect (subed-subtitle-msecs-start) :to-be nil)
+         (expect (subed-subtitle-msecs-stop) :to-be nil)))
+      )
+    (describe "the subtitle text"
+      (describe "when text is empty"
+        (it "and at the beginning with a trailing newline."
+          (with-temp-tsv-buffer
+           (insert mock-tsv-data)
+           (subed-jump-to-subtitle-text "14.000000")
+           (kill-line)
+           (expect (subed-subtitle-text) :to-equal "")))))
+    (describe "when text is not empty"
+      (it "and has no linebreaks."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "14.000000")
+         (expect (subed-subtitle-text) :to-equal "This is a test.")))))
+  (describe "Jumping"
+    (describe "to current subtitle timestamp"
+      (it "can handle different formats of timestamps."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (expect (subed-jump-to-subtitle-id "11.120") :to-equal 1)
+         (expect (floor (subed-subtitle-msecs-start)) :to-equal 11120)))
+      (it "returns timestamp's point when point is already on the timestamp."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (subed-jump-to-subtitle-id "11.120000")
+         (expect (subed-jump-to-subtitle-time-start) :to-equal (point))
+         (expect (looking-at subed-tsv--regexp-timestamp) :to-be t)
+         (expect (match-string 0) :to-equal "11.120000")))
+      (it "returns timestamp's point when point is on the text."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (search-backward "test")
+         (expect (thing-at-point 'word) :to-equal "test")
+         (expect (subed-jump-to-subtitle-time-start) :to-equal 35)
+         (expect (looking-at subed-tsv--regexp-timestamp) :to-be t)
+         (expect (match-string 0) :to-equal "14.000000")))
+      (it "returns nil if buffer is empty."
+        (with-temp-tsv-buffer
+         (expect (buffer-string) :to-equal "")
+         (expect (subed-jump-to-subtitle-time-start) :to-equal nil))))
+    (describe "to specific subtitle by timestamp"
+      (it "returns timestamp's point if wanted time exists."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-max))
+         (expect (subed-jump-to-subtitle-id "11.12") :to-equal 1)
+         (expect (looking-at (regexp-quote "11.120000\t14.000000\tHello, 
world!")) :to-be t)
+         (expect (subed-jump-to-subtitle-id "17.00") :to-equal 71)
+         (expect (looking-at (regexp-quote "17.000000\t19.800000\tI hope it 
works.")) :to-be t)))
+      (it "returns nil and does not move if wanted ID does not exists."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (search-forward "test")
+         (let ((stored-point (point)))
+           (expect (subed-jump-to-subtitle-id "8.00") :to-equal nil)
+           (expect stored-point :to-equal (point))))))
+    (describe "to subtitle start time"
+      (it "returns start time's point if movement was successful."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (re-search-backward "world")
+         (expect (subed-jump-to-subtitle-time-start) :to-equal 1)
+         (expect (looking-at subed-tsv--regexp-timestamp) :to-be t)
+         (expect (match-string 0) :to-equal "11.120000")))
+      (it "returns nil if movement failed."
+        (with-temp-tsv-buffer
+         (expect (subed-jump-to-subtitle-time-start) :to-equal nil))))
+    (describe "to subtitle stop time"
+      (it "returns stop time's point if movement was successful."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (re-search-backward "test")
+         (expect (subed-jump-to-subtitle-time-stop) :to-equal 45)
+         (expect (looking-at subed-tsv--regexp-timestamp) :to-be t)
+         (expect (match-string 0) :to-equal "16.800000")))
+      (it "returns nil if movement failed."
+        (with-temp-tsv-buffer
+         (expect (subed-jump-to-subtitle-time-stop) :to-equal nil))))
+    (describe "to subtitle text"
+      (it "returns subtitle text's point if movement was successful."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (expect (subed-jump-to-subtitle-text) :to-equal 21)
+         (expect (looking-at "Hello, world!") :to-equal t)
+         (forward-line 1)
+         (expect (subed-jump-to-subtitle-text) :to-equal 55)
+         (expect (looking-at "This is a test.") :to-equal t)))
+      (it "returns nil if movement failed."
+        (with-temp-tsv-buffer
+         (expect (subed-jump-to-subtitle-text) :to-equal nil))))
+    (describe "to end of subtitle text"
+      (it "returns point if subtitle end can be found."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (expect (subed-jump-to-subtitle-end) :to-be 34)
+         (expect (looking-back "Hello, world!") :to-be t)
+         (forward-char 2)
+         (expect (subed-jump-to-subtitle-end) :to-be 70)
+         (expect (looking-back "This is a test.") :to-be t)
+         (forward-char 2)
+         (expect (subed-jump-to-subtitle-end) :to-be 107)
+         (expect (looking-back "I hope it works.") :to-be t)))
+      (it "returns nil if subtitle end cannot be found."
+        (with-temp-tsv-buffer
+         (expect (subed-jump-to-subtitle-end) :to-be nil)))
+      (it "returns nil if point did not move."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "11.12")
+         (subed-jump-to-subtitle-end)
+         (expect (subed-jump-to-subtitle-end) :to-be nil)))
+      (it "works if text is empty."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "11.12")
+         (kill-line)
+         (backward-char)
+         (expect (subed-jump-to-subtitle-end) :to-be 21))))
+    (describe "to next subtitle ID"
+      (it "returns point when there is a next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "11.12")
+         (expect (subed-forward-subtitle-id) :to-be 35)
+         (expect (looking-at (regexp-quote "14.00")) :to-be t)))
+      (it "returns nil and doesn't move when there is no next subtitle."
+        (with-temp-tsv-buffer
+         (expect (thing-at-point 'word) :to-equal nil)
+         (expect (subed-forward-subtitle-id) :to-be nil))
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "17.00")
+         (expect (subed-forward-subtitle-id) :to-be nil))))
+    (describe "to previous subtitle ID"
+      (it "returns point when there is a previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "14.00")
+         (expect (subed-backward-subtitle-id) :to-be 1)))
+      (it "returns nil and doesn't move when there is no previous subtitle."
+        (with-temp-tsv-buffer
+         (expect (subed-backward-subtitle-id) :to-be nil))
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "11.12")
+         (expect (subed-backward-subtitle-id) :to-be nil))))
+    (describe "to next subtitle text"
+      (it "returns point when there is a next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-forward-subtitle-text) :to-be 91)
+         (expect (thing-at-point 'word) :to-equal "I")))
+      (it "returns nil and doesn't move when there is no next subtitle."
+        (with-temp-tsv-buffer
+         (goto-char (point-max))
+         (insert (concat mock-tsv-data "\n\n"))
+         (subed-jump-to-subtitle-id "17.00")
+         (expect (subed-forward-subtitle-text) :to-be nil))))
+    (describe "to previous subtitle text"
+      (it "returns point when there is a previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-backward-subtitle-text) :to-be 21)
+         (expect (thing-at-point 'word) :to-equal "Hello")))
+      (it "returns nil and doesn't move when there is no previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (expect (looking-at (regexp-quote "11.12")) :to-be t)
+         (expect (subed-backward-subtitle-text) :to-be nil)
+         (expect (looking-at (regexp-quote "11.12")) :to-be t))))
+    (describe "to next subtitle end"
+      (it "returns point when there is a next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-text "14.00")
+         (expect (thing-at-point 'word) :to-equal "This")
+         (expect (subed-forward-subtitle-end) :to-be 107)))
+      (it "returns nil and doesn't move when there is no next subtitle."
+        (with-temp-tsv-buffer
+         (insert (concat mock-tsv-data "\n\n"))
+         (subed-jump-to-subtitle-text "17.00")
+         (expect (subed-forward-subtitle-end) :to-be nil))))
+    (describe "to previous subtitle end"
+      (it "returns point when there is a previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-backward-subtitle-end) :to-be 34)))
+      (it "returns nil and doesn't move when there is no previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (expect (looking-at (regexp-quote "11.12")) :to-be t)
+         (expect (subed-backward-subtitle-text) :to-be nil)
+         (expect (looking-at (regexp-quote "11.12")) :to-be t))))
+    (describe "to next subtitle start time"
+      (it "returns point when there is a next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-forward-subtitle-time-start) :to-be 71)))
+      (it "returns nil and doesn't move when there is no next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "17.00")
+         (let ((pos (point)))
+           (expect (subed-forward-subtitle-time-start) :to-be nil)
+           (expect (point) :to-be pos)))))
+    (describe "to previous subtitle stop"
+      (it "returns point when there is a previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-backward-subtitle-time-stop) :to-be 11)))
+      (it "returns nil and doesn't move when there is no previous subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (goto-char (point-min))
+         (expect (subed-backward-subtitle-time-stop) :to-be nil)
+         (expect (looking-at (regexp-quote "11.12")) :to-be t))))
+    (describe "to next subtitle stop time"
+      (it "returns point when there is a next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "14.00")
+         (expect (subed-forward-subtitle-time-stop) :to-be 81)))
+      (it "returns nil and doesn't move when there is no next subtitle."
+        (with-temp-tsv-buffer
+         (insert mock-tsv-data)
+         (subed-jump-to-subtitle-id "17.00")
+         (let ((pos (point)))
+           (expect (subed-forward-subtitle-time-stop) :to-be nil)
+           (expect (point) :to-be pos))))))
+
+  (describe "Setting start/stop time"
+    (it "of subtitle should set it."
+      (with-temp-tsv-buffer
+       (insert mock-tsv-data)
+       (subed-jump-to-subtitle-id "14.00")
+       (subed-set-subtitle-time-start (+ (* 15 1000) 400))
+       (expect (floor (subed-subtitle-msecs-start)) :to-be (+ (* 15 1000) 
400)))))
+
+  (describe "Inserting a subtitle"
+    (describe "in an empty buffer"
+      (describe "before the current subtitle"
+        (it "creates an empty subtitle when passed nothing."
+          (with-temp-tsv-buffer
+           (subed-prepend-subtitle)
+           (expect (buffer-string) :to-equal "0.000000\t1.000000\t\n")))
+        (it "creates a subtitle with a start time."
+          (with-temp-tsv-buffer
+           (subed-prepend-subtitle nil 12340)
+           (expect (buffer-string) :to-equal "12.340000\t13.340000\t\n")))
+        (it "creates a subtitle with a start time and stop time."
+          (with-temp-tsv-buffer
+           (subed-prepend-subtitle nil 60000 65000)
+           (expect (buffer-string) :to-equal "60.000000\t65.000000\t\n")))
+        (it "creates a subtitle with start time, stop time and text."
+          (with-temp-tsv-buffer
+           (subed-prepend-subtitle nil 60000 65000 "Hello world")
+           (expect (buffer-string) :to-equal "60.000000\t65.000000\tHello 
world\n"))))
+      (describe "after the current subtitle"
+        (it "creates an empty subtitle when passed nothing."
+          (with-temp-tsv-buffer
+           (subed-append-subtitle)
+           (expect (buffer-string) :to-equal "0.000000\t1.000000\t\n")))
+        (it "creates a subtitle with a start time."
+          (with-temp-tsv-buffer
+           (subed-append-subtitle nil 12340)
+           (expect (buffer-string) :to-equal "12.340000\t13.340000\t\n")))
+        (it "creates a subtitle with a start time and stop time."
+          (with-temp-tsv-buffer
+           (subed-append-subtitle nil 60000 65000)
+           (expect (buffer-string) :to-equal "60.000000\t65.000000\t\n")))
+        (it "creates a subtitle with start time, stop time and text."
+          (with-temp-tsv-buffer
+           (subed-append-subtitle nil 60000 65000 "Hello world")
+           (expect (buffer-string) :to-equal "60.000000\t65.000000\tHello 
world\n"))))))
+  (describe "in a non-empty buffer"
+    (describe "before the current subtitle"
+      (describe "with point on the first subtitle"
+        (it "creates the subtitle before the current one."
+          (with-temp-tsv-buffer
+           (insert mock-tsv-data)
+           (subed-jump-to-subtitle-time-stop)
+           (subed-prepend-subtitle)
+           (expect (buffer-substring (line-beginning-position) 
(line-end-position))
+                   :to-equal "0.000000\t1.000000\t"))))
+      (describe "with point on a middle subtitle"
+        (it "creates the subtitle before the current one."
+          (with-temp-tsv-buffer
+           (insert mock-tsv-data)
+           (subed-jump-to-subtitle-time-stop "14.00")
+           (subed-prepend-subtitle)
+           (expect (buffer-substring (line-beginning-position) 
(line-end-position))
+                   :to-equal "0.000000\t1.000000\t")
+           (forward-line 1)
+           (beginning-of-line)
+           (expect (looking-at "14.00"))))))
+    (describe "after the current subtitle"
+      (describe "with point on a subtitle"
+        (it "creates the subtitle after the current one."
+          (with-temp-tsv-buffer
+           (insert mock-tsv-data)
+           (subed-jump-to-subtitle-time-stop "14.00")
+           (subed-append-subtitle)
+           (expect (buffer-substring (line-beginning-position) 
(line-end-position))
+                   :to-equal "0.000000\t1.000000\t")
+           (forward-line -1)
+           (expect (floor (subed-subtitle-msecs-start)) :to-be 14000))))))
+  (describe "Killing a subtitle"
+    (it "removes the first subtitle."
+      (with-temp-tsv-buffer
+       (insert mock-tsv-data)
+       (subed-jump-to-subtitle-text "11.12")
+       (subed-kill-subtitle)
+       (expect (floor (subed-subtitle-msecs-start)) :to-be 14000)
+       (forward-line -1)
+       (beginning-of-line)
+       (expect (looking-at "14\\.00000")))))
+  (it "removes it in between."
+    (with-temp-tsv-buffer
+     (insert mock-tsv-data)
+     (subed-jump-to-subtitle-text "14.00")
+     (subed-kill-subtitle)
+     (expect (floor (subed-subtitle-msecs-start)) :to-be 17000)))
+  (it "removes the last subtitle."
+    (with-temp-tsv-buffer
+     (insert mock-tsv-data)
+     (subed-jump-to-subtitle-text "17.00")
+     (subed-kill-subtitle)
+     (expect (buffer-string) :to-equal
+             "11.120000\t14.000000\tHello, world!
+14.000000\t16.800000\tThis is a test.
+")))
+  (describe "Converting msecs to timestamp"
+    (it "uses the right format"
+      (with-temp-tsv-buffer
+       (expect (subed-msecs-to-timestamp 1410) :to-equal "1.410000")))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]