A movie Subtitle Editor
A few days ago I wanted to create Romanian subtitles for a movie that I want to watch with my friends. After Googling around I found 4 projects that claim to do this in Linux. These are:
- gnome-subtitles
- ksubtile
- subtitleeditor
- gaupol
All of them seemed very disappointing.
- Gnome-subtitles uses gstreamer, which for some reason wasn't able to open my movie.
- Ksubtile seems to be designed only to edit subtitles, but I needed to create them; all the interface is greyed out and basically couldn't do anything with it, except close it.
- Subtitleeditor seemed more reasonable, but video playback is horrible (seek doesn't work properly) and I couldn't figure out how to make it insert current time; keyboard features are very poor (as in all GTK applications after all)—in order to work, focus must be in some bizzare position that I never quite understand.
- Gaupol is just a miserable interface that allows you to edit subtitles (as if I couldn't do this in Emacs).
Wait! EMACS! Isn't there anything like this?
I found GNEVE, an Emacs-based video editor (!). I played with it. It promises to do a lot more than what I need, but for some reason subtitles won't be generated correctly anyway (totally wrong timing). But nevertheless, it was cool, it has some interesting ideas that form the basis on my little project:
SESE: Simple Emacs-based Subtitle Editor
SESE is a major mode that I wrote for Emacs, based on ideas and even some code from GNEVE. It Just Works. It doesn't require a patched MPlayer (unlike GNEVE); I found that the default timings reported by MPlayer are more than enough for subtitles (resolution is 1/10 sec. which is pretty good).
SESE has the following simple features:
- open MPlayer to play movie with a shortcut
- press a shortcut at any time to start a new subtitle
- press the same shortcut a few seconds later to end the subtitle
- at this point, playback is paused and you can type the lines
- you can easily seek to the subtitle prior to the caret position, with a shortcut
- you can easily change the start/end time positions for a subtitle you already wrote (with a shortcut)
- the file format is a “custom” one (see below); that's because it was easier to maintain; you can generate subtitles in SRT any time.
Installation
Download sese.el and put it into some directory in your Emacs's load-path. For example, if you have the following in your ~/.emacs:
(setq load-path (cons (expand-file-name "~/emacs") load-path))
then you can save sese.el in ~/emacs.
Then put the following lines in your ~/.emacs:
(autoload 'sese-mode "sese" "Subtitle Editor major mode" t) (setq auto-mode-alist (cons '("\\.sese\\'" . sese-mode) auto-mode-alist))
and restart Emacs. You're now ready to start writing subtitles.
File format
I decided to implement my own simple format, rather than working directly with SRT, SUB or anything else; that's because it's easier to parse/maintain and in the future I will be able to easily generate other subtitle formats from a central file.
The .sese file format looks like this:
# MOVIE: ~/movies/foo-movie.avi 10.5 - 13.5 { subtitle line 1 subtitle line 2 } 13.7 - 14.7 { OK. }
So each subtitle starts with a line that contains the start time (i.e. 10.5), end time (13.5) and a bracket. SESE is kind of picky about whitespace so—don't put a newline before the bracket. But wait, you don't even need to write this; SESE writes it for you.
Inside the brackets, you write the text that should appear for the subtitle. I recommend (though this is not enforced) that each line starts with a TAB character. Of course, SESE does this for you automagically.
Good subtitles should not have more than 2 lines and no more than 60 characters per line; SESE sets the fill-column to 59. I find longer subtitles hard to read, though sometimes they are necessary...
I recommend you to write this file in UTF8; later, when the subtitles are generated, you can easily convert that to whatever you want using M-x set-buffer-file-coding-system.
Usage
Let's say you want to write subtitles for a movie ~/movies/foo-movie.avi. Start like this:
Press C-x C-f (open file) and create a new file ~/movies/foo-movie.sese. SESE mode will start automatically.
Press C-? (usually it's control shift /) to open a movie. Type the AVI file name with TAB completion. Video playback will start automatically. Note that this inserts a comment containing the video file; next time you'll press this keybinding, it'll just open the existing video without asking for a filename.
Make sure the Emacs window is focused (i.e. press ALT-TAB). MPlayer should stay on top.
If it's a new file, you may need to press C-END (move the caret to end of file).
Now, to start a subtitle press C-ENTER. Start time will be inserted into your Emacs buffer. Video playback continues; don't move the caret.
To end the subtitle press C-ENTER again. End time will be inserted as well as a set of brackets; the caret is positioned so that you only need to type the lines. Video playback is paused.
To resume playback and immediately start a new subtitle, press C-ENTER. To resume playback without starting a new subtitle, press C-P (toggles between play/pause).
To restart playback from the start time of the last subtitle, press C-] (that's right sqare bracket).
While the caret is between the brackets, you can press M-[ to reset the start time of this subtitle to the current video position. Similarly, M-] resets the end time.
To render the subtitles in a format that MPlayer can use, do M-x sese-render-subtitles. It will determine the file name based on the video file name, but it won't save the file by default—it'll just insert the generated subtitles in an Emacs buffer; you can save them with C-x C-s.
To control the video player, you can use C-, (control comma) and C-. (control dot) to seek back/forward 1 second, or M-, (meta comma) and M-. (meta dot) to seek back/forward 10 seconds. These come in handy sometimes, but many times it will be even more convenient to just focus the MPlayer window and use it's controls.
That's all folks. Happy subtitling! If you can think of some improvement, please drop me a note.
Lessons I've learned
I'm not an (E)Lisp hacker so I spent a fair amount of time writing that code.
I noticed that string concatenation is waaaay slower than array join, pretty much like in IE's JavaScript. But wait, there's no “join” function. (And for some reason the ELisp info pages have gone from my Debian installation, and I can't figure out how to bring it back.)
So I was using something like this to generate the final SRT:
(let ((output "")) (while i-still-have-lines (generate-srt-line) (setq output (concat output srt-line))))
That was awfully, awfully slow. I later found mapconcat which is a lot faster. Use it like this:
(let ((output '())) (while i-still-have-lines (generate-srt-line) (setq output (append output `(,srt-line)))) (setq output (mapconcat 'identity output "\n\n")))
I would feel better to have a function named "join" that did that without me having to pass an "identity" function that basically does nothing. I know it's easy to write, but it would be nice to just have it in the core Elisp. Oh well.
Wait, what was that? What's with the "`(," stuff?
Again, I had to spend quite some time to dig this. I had code like this for syntax highlighting:
(defconst sese-fragment-regexp (concat sese-start-regexp sese-number-regexp)) (defvar sese-font-lock-keywords (list `(sese-fragment-regexp (1 font-lock-keyword-face t) (4 font-lock-reference-face t))
It didn't work because sese-fragment-regexp remains quoted; in fact, we should replace it with a string (the regexp). So I needed to put a comma right before it.
That's quite a horrible syntax... but as I went through the manual, it does make sense.
Comments — add your comment
Sounds very interesting and promising. Just what I was looking out for. Must say, I've not tried it yet, but I plan to! Thank you!
I'll inform my user group about this... we're having a discussion there currently. http://groups.yahoo.com/group/ilug-goa Thanks again!
I'm looking forward to trying this out. Your introduction sounds exactly like what I was thinking as I tried to figure out how one might use subtitleeditor :-)
"aptitude install emacs21-common-non-dfsg" will probably bring back the emacs documentation to your debian, by the way.
I didn't have "filladapt", so I downloaded it from http://www.wonderworks.com/download/filladap…
My emacs (21.4.1) doesn't have a "looking-back" function, so I copied the hack described at the end of http://moinmoin.wikiwikiweb.de/EmacsForMoinM… into sese.el to add one.
After that, it's all working nicely, except that my mplayer doesn't support seeking to arbitrary positions for all file formats, so the C-] is off by a few seconds for some files :-/
Anyway, very nice work, thanks for sharing!
Thanks! :-. Damn, that *almost* worked. I still can't find the "Elisp" item in the "info" table of contents, but that line did install some docs that I was missing (such as i.e. cc-mode documentation).
I'm wondering why are they now in "non-free"...?
Debian people believe that the GNU Free Documentation License is Evil (because of the "Invariant Sections" requirement, I think) and therefore the emacs documentation must be hidden away in a cryptically-named package in non-free. In the case of the elisp-manual, it looks like they didn't even move it to non-free, they just removed it from the distro. Bah.
478 subtitles later, I still like your mode. Having a Real Editor to edit subtitles (and their timings!) is very pleasant, especially for long monologues. I had to remove the C-p binding, though... I needed the "real" C-p all the time to go back up and reedit the previous subtitles when I changed my mind about how to translate something.
“I needed the "real" C-p all the time to go back up[...]”
Oh, sorry, I never thought of that... My hands are trained to use the arrow keys for movement :-) (old habit since when I was a Windows freak). I should think of a different default binding, in spirit of Emacs.
BTW, I'm not exactly sure what I did, but I got all the documentation back. I think this package did it: emacs22-common-non-dfsg.
Oh damn, I just noticed that you mentioned the "non-dfsg" package in your first post :). For some reason I missed it. Sorry..