import re class StyleException(Exception): pass class EventException(Exception): pass class ScriptException(Exception): pass class ScriptInfo: def __init__(self, f = None): # original case keys used for writing self.ock = {} self.info = {} if f: self.read(f) def __getitem__(self, key): try: return self.info[key.lower()] except ValueError: raise KeyError def read(self, f): s = f.readline().strip() while s: if not s.startswith(';'): k, v = s.split(': ', 1) self.ock[k.lower()] = k self.info[k.lower()] = v s = f.readline().strip() def write(self, f): f.write('[Script Info]\n') f.writelines(['%s: %s\n' % (self.ock[k], self.info[k]) for k in self.info.keys()]) class Style: def __init__(self, format, s = None): self.format = format if s: self.parse(s) def __str__(self): return 'Style: %s' % ','.join([getattr(self, x.lower()) for x in self.format]) def parse(self, s): s = s.split(': ', 1) if s[0] != 'Style': raise StyleException('invalid style line') s = s[1].split(',') for k, v in zip(self.format, s): setattr(self, k.lower(), v) class Styles: def __init__(self, f = None): self.format, self.styles = [], {} if f: self.read(f) def __getitem__(self, key): if self.styles.has_key(key.lower()): return self.styles[key] else: raise KeyError('style "%s" not found' % key) def read(self, f): s = f.readline().strip() s = s.split(': ', 1) if s[0] != 'Format': raise StyleException('format line not found') self.format = s[1].split(', ') s = f.readline().strip() while s: s = Style(self.format, s) self.styles[s.name.lower()] = s s = f.readline().split() def write(self, f): f.write('[V4 Styles]\n') f.write('Format: %s\n' % ', '.join(self.format)) f.writelines(['%s\n' % str(s) for s in self.styles.values()]) class Syllable: def __init__(self, start, end, text): self.start, self.end, self.dur, self.text = start, end, end - start, text def __str__(self): return '{\\k%d}%s' % (self.dur / 10, self.text) class Event: def __init__(self, format, s = None): self.karaoke = [] self.format = format if s: self.parse(s) def __getitem__(self, key): if type(key) is int: return self.karaoke[key] else: raise TypeError('key must be int') def __str__(self): return '%s: %s' % (self.kind.capitalize(), ','.join([getattr(self, x.lower()) for x in self.format])) def parse(self, s): s = s.split(': ', 1) self.kind = s[0].lower() s = s[1].split(',') for k, v in zip(self.format, s): setattr(self, k.lower(), v) if self.kind == 'dialogue' and self.text.find('\\k') > -1: self.parse_karaoke() def parse_karaoke(self): s = self.text i = 0 tot = 0 while i < len(s): i = s.find('\\k', i) # break if no more \k's (end-of-line-text is added at the end of the loop) if i == -1: break i += 2 ms = '' while s[i].isdigit(): ms += s[i] i += 1 ms = int(ms) * 10 tot += ms # unless next is another override, skip one char (past '}') if s[i] != '\\': i += 1 n = s.find('\\k', i) if n > -1: part = s[i:n-1] # get text up to next \k else: part = s[i:] # end of line # go to next if part is empty if not part.strip(): continue self.karaoke.append(Syllable(tot - ms, tot, part)) class Events: def __init__(self, f = None): self.format, self.events = [], [] if f: self.read(f) def __getitem__(self, key): if type(key) is int: return self.events[key] else: raise TypeError('key must be int') def read(self, f): s = f.readline().strip() s = s.split(': ', 1) if s[0] != 'Format': raise EventException('format line not found') self.format = s[1].split(', ') s = f.readline().strip() while s: self.events.append(Event(self.format, s)) s = f.readline().strip() def write(self, f): f.write('[Events]\n') f.write('Format: %s\n' % ', '.join(self.format)) f.writelines(['%s\n' % str(e) for e in self.events]) class Script: def __init__(self, f = None): if f: self.read(f) def read(self, f): f = open(f, 'r') s = f.readline().strip() if s.startswith('\xef\xbb\xbf'): s = s[3:] if s == '[Script Info]': self.info = ScriptInfo(f) else: raise ScriptException('script info not found') s = f.readline().strip() if re.match('\[V4\+? Styles\]', s): self.styles = Styles(f) else: raise ScriptException('script styles not found') s = f.readline().strip() if s == '[Events]': self.events = Events(f) else: raise ScriptException('script events not found') def write(self, f): f = open(f, 'w') self.info.write(f) f.write('\n') self.styles.write(f) f.write('\n') self.events.write(f) f.write('\n')