#! /usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8 # # Copyright © 2010 Adrian Perez # # Distributed under terms of the GPLv3 license. PAL_FRAME_WIDTH = 720 PAL_FRAME_HEIGHT = 576 def stamp_to_ms(stamp, mssep=".", hhmmsssep=":"): """Converts a text-timestamp to a milliseconds value. Parses a value in ``HH:MM:ss.mmm`` format, converts it to milliseconds and returns that as an integer. :param stamp: Timestamps, as text. :param mssep: Separator for between the seconds and milliseconds value. :param hhmmsssep: Separator for between hours, minutes and seconds. """ hhmmss, ms = stamp.split(mssep) hh, mm, ss = hhmmss.split(hhmmsssep) hh, mm, ss, ms = map(int, (hh, mm, ss, ms)) mm += hh * 60 ss += mm * 60 ms += ss * 1000 return ms def load_srt(inputfile): while True: num = inputfile.readline() if len(num.strip()) == 0: return tss, sep, tse = inputfile.readline().split() assert sep == "-->" # Do some conversions. Note that SRT subtitles use comma as # delimiter for the milliseconds field. num = int(num) tss = stamp_to_ms(tss, ",") tse = stamp_to_ms(tse, ",") # Dome some sanity-check on timestamps. if tss > tse: raise ValueError(("Subtitle %i ends before starting! " "(start: %i, end: %i)") % (num, tss, tse)) text = "" line = inputfile.readline() while len(line.strip()) > 0: text += line + "\n" line = inputfile.readline() text = text[:-1] yield (num, tss, tse, text, None) _loaders = { "srt" : load_srt, } def load_subs(filename): """Loads a subtitle file into something useable. This will yield one tuple per subtitle line, containing the text and information about about the frame. Each tuple will be:: (number, start_timestamp, end_timestamp, text, options) Subtitle format is determined from the file suffix. :param inputfile: File-like object which provides a `readline()` method. """ parts = filename.split(".") if parts[-1] not in _loaders: raise ValueError("No suitable loader") fd = file(filename, "rU") for item in _loaders[parts[-1]](fd): yield item fd.close() import cairo def render_subs(text, w, h, **kw): """Renders subtitles into an image. This will render a line of subtitles into an image. The result will be a `cairo.ImageSurface`. Rendering is done in white-and-black: white text with a black border, on top of a transparent background. :param text: Text to render. :param w: Width of generated image. :param h: Height of generated image. """ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) ctx = cairo.Context(surface) ctx.set_antialias(cairo.ANTIALIAS_NONE) ctx.set_line_width(6) ctx.set_line_join(cairo.LINE_JOIN_ROUND) ctx.set_line_cap(cairo.LINE_CAP_ROUND) ctx.select_font_face("Nokia Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(28) # FIXME Ignoring everything but width/height is extremely naïve. Works # for now, but definitely must be changed at some point. _, _, wt, ht, _, _ = ctx.text_extents(text) if wt > w: # Split text in multiple lines. # TODO Handle more than two lines. preline = u"" for word in text.split(): _, _, nwt, nht, _, _ = ctx.text_extents(preline + u" " + word) if nwt + 50 > w: preline = preline.strip() text = text.strip() break preline += u" " + word text = text[len(word)+1:] ctx.move_to((w - nwt) / 2 + 40, (h - nht * 2.5 - 10)) ctx.text_path(preline) ctx.set_source_rgb(0, 0, 0) ctx.stroke() ctx.move_to((w - nwt) / 2 + 40, (h - nht * 2.5 - 10)) ctx.text_path(preline) ctx.set_source_rgb(1, 1, 1) ctx.fill() _, _, wt, wh, _, _ = ctx.text_extents(text) ctx.move_to((w - wt) / 2, (h - ht - 10)) ctx.text_path(text) ctx.set_source_rgb(0, 0, 0) ctx.stroke() ctx.move_to((w - wt) / 2, (h - ht - 10)) ctx.text_path(text.strip()) ctx.set_source_rgb(1, 1, 1) ctx.fill() return surface if __name__ == "__main__": import sys num = 0 for num, tss, tse, txt, opt in load_subs(sys.argv[1]): print "out-%05i.png" % num surface = render_subs( txt.decode("utf-8").strip(), PAL_FRAME_WIDTH, PAL_FRAME_HEIGHT) surface.write_to_png("out-%05i.png" % num) num += 1