blob: 93542ac2b9417c15eb7f05b9b47a1a3144b92040 (
plain) (
tree)
|
|
;;; 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 (list (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-msecs-to-timestamp (subed-subtitle-msecs-start))
id)))))
(when (called-interactively-p 'any)
(kill-new result))
result))
(defun emacsconf-subed-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")
emacsconf-captions-directory))
(chapters (with-current-buffer (find-file-noselect caption-file)
(subed-subtitle-list))))
(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 emacsconf-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 emacsconf-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)))))
(provide 'emacsconf-subed)
;;; emacsconf-subed.el ends here
|