diff options
| author | Sacha Chua <sacha@sachachua.com> | 2021-12-13 00:04:19 -0500 | 
|---|---|---|
| committer | Sacha Chua <sacha@sachachua.com> | 2021-12-13 00:04:19 -0500 | 
| commit | 17781f0097fd8a0cf59942c6426ecfac78a2a90a (patch) | |
| tree | e8f5ca067e53dd89a5b314cb7f201b4c725f717b | |
| parent | 560ed7b45dc1261cc36068d0189a59e7e58684f0 (diff) | |
| download | emacsconf-el-17781f0097fd8a0cf59942c6426ecfac78a2a90a.tar.xz emacsconf-el-17781f0097fd8a0cf59942c6426ecfac78a2a90a.zip  | |
Initial
| -rw-r--r-- | emacsconf-subed.el | 219 | 
1 files changed, 219 insertions, 0 deletions
diff --git a/emacsconf-subed.el b/emacsconf-subed.el new file mode 100644 index 0000000..cfb81b4 --- /dev/null +++ b/emacsconf-subed.el @@ -0,0 +1,219 @@ +;;; emacsconf-subed.el --- Utilities for working with subtitle files  -*- lexical-binding: t; -*- + +;; Copyright (C) 2021  Sacha Chua + +;; Author: Sacha Chua <sacha@sachachua.com> +;; Keywords: multimedia + +;; This program 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 of the License, or +;; (at your option) any later version. + +;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; You will need the subed package, which is available from NonGNU +;; ELPA. For creating new caption files with +;; emacsconf-subed-find-captions, you will also need compile-media.el +;; from https://github.com/sachac/compile-media . + +;;; Code: + +(require 'subed) + +(defcustom emacsconf-subed-subtitle-max-length 50 +  "Target number of characters." +  :group 'emacsconf +  :type 'integer) + +(defcustom emacsconf-subed-subtitle-minimum-duration-ms 600 +  "Minimum length in milliseconds." +  :group 'emacsconf +  :type 'integer) + + +(defun emacsconf-subed-fix-timestamps () +  "Change overlapping timestamps to the start of the next subtitle." +  (interactive) +  (goto-char (point-max)) +  (let ((timestamp (subed-subtitle-msecs-start))) +    (while (subed-backward-subtitle-time-start) +      (when (> (subed-subtitle-msecs-stop) timestamp) +        (subed-set-subtitle-time-stop timestamp)) +      (setq timestamp (subed-subtitle-msecs-start))))) + +(defun emacsconf-subed-mark-chapter (chapter-name) +  (interactive "MChapter: ") +  (let ((start (subed-subtitle-msecs-start))) +    (save-excursion +      (when (and (subed-backward-subtitle-time-stop) +                 (= (subed-subtitle-msecs-stop) start)) +        (subed-set-subtitle-time-stop (1- start)))) +    (with-current-buffer (find-file-noselect +                          (if (string-match "--main" (buffer-file-name)) +                              (replace-match "--chapters" nil t (buffer-file-name)) +                            (concat (file-name-sans-extension (buffer-file-name)) "--chapters.vtt"))) +      (goto-char (point-max)) +      (if (bobp) +          (insert "WEBVTT\n\n" +                  (subed-make-subtitle nil start nil chapter-name)) +        (insert "\n" (subed-make-subtitle nil start nil chapter-name))) +      (when (subed-backward-subtitle-time-stop) +        (subed-set-subtitle-time-stop (1- start))) +      (save-buffer)))) + +(defun emacsconf-subed-convert-transcript-to-directives (id &optional chapters) +  (interactive (read-string "ID: " "mainVideo")) +  (goto-char (point-min)) +  (let* ((chapter-starts (mapcar 'car chapters)) +         (result (concat +                  "<a name=\"transcript\"></a>\n# Transcript\n\n" +                  (cl-loop while (subed-forward-subtitle-text) +                           concat (format +                                   "[[!template %stext=\"%s\" start=\"%s\" video=\"%s\" id=subtitle]]\n" +                                   (if (member (subed-subtitle-msecs-start) chapter-starts) +                                       "new=\"1\" " +                                     "") +                                   (replace-regexp-in-string +                                    "\n" " "  +                                    (replace-regexp-in-string +                                     "\"" "" ;" +     (replace-regexp-in-string "[][]" "" (subed-subtitle-text)))) +                                   (subed-vtt--msecs-to-timestamp (subed-subtitle-msecs-start)) +                                   id))))) +    (when (called-interactively-p 'any) +      (kill-new result)) +    result)) + +(defun conf-prepare-transcript-directives () +  (interactive) +  (let* ((info (emacsconf-get-talk-info-for-subtree)) +         (wiki-file (plist-get info :wiki-file-path)) +         (caption-file (expand-file-name (concat (plist-get info :video-slug) "--main.vtt") +                                         conf-captions-directory)) +         (chapters (emacsconf-subed-chapters-as-list info))) +    (with-temp-file wiki-file +      (insert +       (with-current-buffer (find-file-noselect caption-file) +         (emacsconf-subed-convert-transcript-to-directives "mainVideo" chapters)))) +    (find-file wiki-file))) + +(defun emacsconf-subed-download-captions (&optional youtube-url video-slug) +  (interactive (list (org-entry-get (point) "YOUTUBE_URL") (org-entry-get (point) "VIDEO_SLUG"))) +  (shell-command +   (mapconcat +    (lambda (f) +      (format "youtube-dl --write-sub --write-auto-sub --no-warnings --sub-lang en --skip-download --sub-format %s %s -o %s" +              f +              youtube-url +              (expand-file-name video-slug conf-captions-directory))) +    '("vtt" "srv2") +    ";"))) + +(defun emacsconf-subed--copy-downloaded-captions-base (video-slug url type) +  (let ((new-file (expand-file-name (concat video-slug "--" type ".ass") emacsconf-captions-directory))) +    (call-process "ffmpeg" nil nil nil "-y" "-i" (emacsconf-latest-file emacsconf-download-directory "srt$") +                  new-file) +    (emacsconf-subed-download-captions url new-file) +    (with-current-buffer (find-file new-file) +      (goto-char (point-min)) +      (emacsconf-subed-fix-timestamps) +      (save-buffer)))) + +(defun emacsconf-subed-copy-downloaded-captions () +  "Copy the most recently downloaded captions for this entry's main talk." +  (interactive) +  (emacsconf-subed--copy-downloaded-captions-base +   (org-entry-get (point) "VIDEO_SLUG") +   (org-entry-get (point) "YOUTUBE_URL") +   "main")) + +(defun emacsconf-subed-copy-downloaded-qa-captions () +  "Copy the most recently downloaded captions for this entry's Q&A" +  (interactive) +  (emacsconf-subed--copy-downloaded-captions-base +   (org-entry-get (point) "VIDEO_SLUG") +   (org-entry-get (point) "QA_YOUTUBE") +   "answers")) + +(defun emacsconf-subed-find-captions () +  "Open the caption file for this talk. +Create it if necessary." +  (interactive) +  (require 'compile-media) +  (let ((video-slug (org-entry-get (point) "VIDEO_SLUG"))) +    (find-file +     (or (car (directory-files conf-captions-directory +                               t +                               (concat (regexp-quote video-slug) +                                       "--main\\.\\(srt\\|vtt\\)"))) +         (expand-file-name (concat video-slug "--main.vtt") "captions"))) +    (when (eobp) +      (insert "WEBVTT\n\n0:00:00.000 --> " +              (compile-media-msecs-to-timestamp +               (compile-media-get-file-duration-ms (subed-guess-video-file))) +              "\n")))) + +(defun emacsconf-subed-check-subtitles () +  "Do some simple validation of subtitles." +  (interactive) +  (while (not (eobp)) +    (if (> (length (subed-subtitle-text)) emacsconf-subed-subtitle-max-length) +        (error "Length %d exceeds maximum length" (length (subed-subtitle-text)))) +    (if (< (- (subed-subtitle-msecs-stop) (subed-subtitle-msecs-start)) emacsconf-subed-subtitle-minimum-duration-ms) +        (error "Duration %d is less than minimum" (- (subed-subtitle-msecs-stop) (subed-subtitle-msecs-start)))) +    (or (subed-forward-subtitle-text) (goto-char (point-max))))) + +(defun emacsconf-subed-chapters-as-list (info) +  (when (file-exists-p (expand-file-name (concat (plist-get info :video-slug) "--main--chapters.vtt") +                                         conf-captions-directory)) +    (with-current-buffer (find-file-noselect (expand-file-name (concat (plist-get info :video-slug) "--main--chapters.vtt") +                                                               conf-captions-directory)) +      (let (result) +        (subed-for-each-subtitle (point-min) (point-max) nil +          (setq result +                (cons +                 (cons (subed-subtitle-msecs-start) +                       (subed-subtitle-text))  +                 result))) +        (nreverse result))))) + +(defun emacsconf-subed-chapters-buffer-as-list () +  (let (result) +    (subed-for-each-subtitle (point-min) (point-max) nil +      (setq result +            (cons +             (list +              :text +              (subed-subtitle-text) +              :start-ms +              (subed-subtitle-msecs-start) +              :stop-ms +              (subed-subtitle-msecs-stop))  +             result))) +    (nreverse result))) + +(defun emacsconf-subed-chapters-as-description () +  (interactive) +  (let ((result +         (mapconcat +          (lambda (o) +            (concat (format-seconds "%.2m:%.2s" (/ (plist-get o :start-ms) 1000)) +                    " " +                    (plist-get o :text))) +          (emacsconf-subed-chapters-buffer-as-list) +          "\n"))) +    (when (called-interactively-p 'any) +      (kill-new result)) +    result)) + +(provide 'emacsconf-subed) +;;; emacsconf-subed.el ends here  | 
