Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 880e540fd4
Fetching contributors…

Cannot retrieve contributors at this time

file 149 lines (140 sloc) 5.715 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
#!/usr/bin/env python2
# -*- coding: UTF-8 -*-
### XOSD Subtitles ##############
# Version 0.1 by Scott Garrett #
# Wintervenom [(at)] gmail.com #
################################
# Dependencies:
#
# pyosd (Python XOSD bindings)
# http://repose.cx/pyosd/
#
# python-xlib
# http://python-xlib.sourceforge.net
#
#################################
# Changelog:
#
# 0.1
# Initial version.
#
#################################
#
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.

import Xlib
import Xlib.display
import pyosd
import re
import textwrap



ATTRSTAMP = re.compile(r'\[\w:[\w ,-\.]+\]')



class OSD(object):
    """ XOSD subtitle display. """
    def __init__(self):
        self.invert = False
        self.colors = ['gray', 'red', 'green', 'yellow', 'blue', 'magenta',
                       'cyan', 'white']
        self.defaultcolor = 3
        self.xroot = Xlib.display.Display().screen().root
        # Get horizontal sceen resolution.
        self.hres = self.xroot.get_geometry().width
        # How many characters can fit in a line at this resolution?
        self.maxcpl = int(self.hres / 22.4)
        self.osd = []
        # We can display five simutaneous events at a time.
        for n in xrange(0, 5, 1):
            self.osd.append(pyosd.osd(lines=3))
            self.osd[n].set_font('-*-dejavu sans mono-bold-r-*-*-36' \
                                 '-*-*-*-*-*-iso10646-1')
            self.osd[n].set_outline_offset(4)


    def display(self, lines):
        # OSDs will be displayed on the bottom by default, so we want the
        # last lines to be on the bottom.
        if not self.invert:
            lines.reverse()
        # Make lines with position stamps the last to be dealt with so we can
        # make sure that centered lines raise the Y-offset, since they will
        # always be allotted the full witdh of the screen.
        lines.sort(key=lambda item: item.lstrip().startswith('[p:'))
        # Upper-, middle-, and lower-left/right/center offsets.
        offset = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
        # Clear what's already being displayed.
        for n in xrange(0, 5, 1):
            self.osd[n].hide()
            self.osd[n].scroll(2)
            self.osd[n].hide()
        # No point in bothering with blank lines...
        l = len(lines)
        if (lines[0].strip() == '' and l == 1) or l == 0:
            return None
        for index, line in enumerate(lines):
            # ...or trying to dislay more events than we have OSDs for.
            if index > 4:
                break
            # Default formatting of current OSD.
            pos = [0, 1] #-1: left/top, 0: center, 1: right/bottom.
            timeout = 10 # Hide the OSD if idle longer than this.
            color = self.defaultcolor # OSD color.
            attrs = ATTRSTAMP.findall(line)
            for attr in attrs:
                # We just want the attribute character and the values.
                attr = re.sub('[^\w-]', ' ', attr).split()
                # Set [p]osition.
                if attr[0] == 'p':
                    for n in (0, 1):
                        try:
                            # Keep value in bounds.
                            pos[n] = max(-1, min(1, int(attr[1 + n])))
                        except (ValueError, IndexError):
                            pass
                # Set [c]olor.
                if attr[0] == 'c':
                    try:
                        color = max(0, min(7, int(attr[1])))
                    except ValueError:
                        # Maybe it was specified as an initial.
                        try:
                            'wrgybmca'.index(attr[1].lower())
                        except ValueError:
                            pass
                # Set [t]imeout.
                if attr[0] == 't':
                    try:
                        timeout = max(0, min(60, float(attr[1])))
                    except (IndexError, ValueError):
                        pass
            if self.invert:
                pos[1] = pos[1] * -1
            # Left- and right-aligned events get displayed on the same Y-offset,
            # so we'll halve the maximum number of character per line.
            maxcpl = self.maxcpl
            if pos[0] != 0:
                maxcpl /= 2
            # Remove the stamps and wrap the text to fit in the OSD.
            line = ATTRSTAMP.sub('', line.strip())
            line = textwrap.wrap(line, maxcpl)
            l = len(line)
            if l == 0:
                l = 1
            # Apply formatting to OSD.
            self.osd[index].set_colour(self.colors[color])
            self.osd[index].set_align(1 + pos[0])
            self.osd[index].set_pos((0, 2, 1)[1 + pos[1]])
            self.osd[index].set_timeout(timeout)
            self.osd[index].set_vertical_offset(offset[1+pos[1]][1+pos[0]] * 44)
            # Raise the Y-offset up however many lines the text was.
            offset[1 + pos[1]][1 + pos[0]] += l
            # Centered OSDs get the whole screen-width.
            if pos[0] == 0:
                for n in (0, 2):
                    if pos[1] == 1:
                        offset[1 + pos[1]][n] += l
                    else:
                        offset[1 + pos[1]][n] += 3
            # Display the OSD.
            for l in line:
                self.osd[index].scroll(1)
                self.osd[index].display(l, line=2)
Something went wrong with that request. Please try again.