(define parse-time
(let ((regexp (make-regexp "([0-9]+):([0-9]+):([0-9]+),([0-9]+)")))
(lambda (s)
+ "Parse the SubRip formatted timestamp in the string S into a 4
+element list. Valid input looks like '00:00:03.417'."
(let ((match (regexp-exec regexp s)))
(map (cut match:substring match <>) '(1 2 3 4))))))
(define parse-time-span
(let ((regexp (make-regexp "([0-9:,]+) --> ([0-9:,]+)")))
(lambda (s)
+ "Parse the SubRip formatted time span in the string S and return
+two values: the start time and the end time. Valid input looks like
+'00:00:03.417 --> 00:00:04.936'."
(let ((match (regexp-exec regexp s)))
(values (parse-time (match:substring match 1))
(parse-time (match:substring match 2)))))))
(define (read-sub-rip port)
+ "Read a SubRip formatted subtitle from PORT."
(let-values (((id) (string->number (read-line port)))
((start end) (parse-time-span (read-line port)))
((lines) (let loop ((lines '()))
(make-subtitle id start end lines)))
(define (read-sub-rips port)
+ "Read all SubRip formatted subtitles from PORT."
(reverse
(let loop ((subs '()))
(if (eof-object? (peek-char port))
(loop (cons (read-sub-rip port) subs))))))
(define (write-time time port)
+ "Write TIME as a WebVTT formatted timestamp to PORT."
(match time
((h m s ms)
(format port "~a:~a:~a.~a" h m s ms))))
(define (write-web-vtt subtitle port)
+ "Write SUBTITLE as a WebVTT formatted subtitle to PORT."
(match subtitle
(($ <subtitle> id start end text)
(format port "~a~%" id)
(newline port))))
(define (write-web-vtts subtitles port)
+ "Write all SUBTITLES as WebVTT formatted subtitles to PORT."
(format port "WEBVTT~%~%")
(for-each (cut write-web-vtt <> port) subtitles))
(define (convert input-port output-port)
+ "Read the SubRip formatted subtitles from INPUT-PORT and write the
+WebVTT equivalents to OUTPUT-PORT."
(write-web-vtts (read-sub-rips input-port) output-port))
(define (show-help-and-exit)
(alist-cons 'output arg args)))))
(define (make-call-with-port-or-file file-proc)
+ "Create a new procedure that accepts two arguments: a port or file,
+and a procedure. When the returned procedure is called with a port as
+the first argument, the second argument is simply applied with that
+port. If the first argument is a string, the second argument is
+applied with the port corresponding to the file name contained in the
+string as opened by FILE-PROC."
(lambda (port-or-file proc)
(if (port? port-or-file)
(proc port-or-file)
(make-call-with-port-or-file call-with-output-file))
(define (parse-opts args)
+ "Parse the list of ARGS and return a list of option/value pairs.
+When an option isn't specified in ARGS, it defaults to the value in
+'%default-args'."
(args-fold args
%options
(lambda (opt name arg args)
%default-args))
(define (main args)
+ "srt2vtt entry point."
(let ((opts (parse-opts args)))
(call-with-port-or-input-file (assoc-ref opts 'input)
(lambda (input-port)