[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")))))
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [nongnu] elpa/subed 527353843b: Add subed-tsv-mode,
ELPA Syncer <=