1from functools import partial 2import re 3from .exceptions import UnknownFPSError 4from .ssaevent import SSAEvent 5from .ssastyle import SSAStyle 6from .formatbase import FormatBase 7from .substation import parse_tags 8from .time import ms_to_frames, frames_to_ms 9 10#: Matches a MicroDVD line. 11MICRODVD_LINE = re.compile(r" *\{ *(\d+) *\} *\{ *(\d+) *\}(.+)") 12 13 14class MicroDVDFormat(FormatBase): 15 """MicroDVD subtitle format implementation""" 16 @classmethod 17 def guess_format(cls, text): 18 """See :meth:`pysubs2.formats.FormatBase.guess_format()`""" 19 if any(map(MICRODVD_LINE.match, text.splitlines())): 20 return "microdvd" 21 22 @classmethod 23 def from_file(cls, subs, fp, format_, fps=None, **kwargs): 24 """See :meth:`pysubs2.formats.FormatBase.from_file()`""" 25 for line in fp: 26 match = MICRODVD_LINE.match(line) 27 if not match: 28 continue 29 30 fstart, fend, text = match.groups() 31 fstart, fend = map(int, (fstart, fend)) 32 33 if fps is None: 34 # We don't know the framerate, but it is customary to include 35 # it as text of the first subtitle. In that case, we skip 36 # this auxiliary subtitle and proceed with reading. 37 try: 38 fps = float(text) 39 subs.fps = fps 40 continue 41 except ValueError: 42 raise UnknownFPSError("Framerate was not specified and " 43 "cannot be read from " 44 "the MicroDVD file.") 45 46 start, end = map(partial(frames_to_ms, fps=fps), (fstart, fend)) 47 48 def prepare_text(text): 49 text = text.replace("|", r"\N") 50 51 def style_replacer(match): 52 tags = [c for c in "biu" if c in match.group(0)] 53 return "{%s}" % "".join(r"\%s1" % c for c in tags) 54 55 text = re.sub(r"\{[Yy]:[^}]+\}", style_replacer, text) 56 text = re.sub(r"\{[Ff]:([^}]+)\}", r"{\\fn\1}", text) 57 text = re.sub(r"\{[Ss]:([^}]+)\}", r"{\\fs\1}", text) 58 text = re.sub(r"\{P:(\d+),(\d+)\}", r"{\\pos(\1,\2)}", text) 59 60 return text.strip() 61 62 ev = SSAEvent(start=start, end=end, text=prepare_text(text)) 63 subs.append(ev) 64 65 @classmethod 66 def to_file(cls, subs, fp, format_, fps=None, write_fps_declaration=True, apply_styles=True, **kwargs): 67 """ 68 See :meth:`pysubs2.formats.FormatBase.to_file()` 69 70 The only supported styling is marking whole lines italic. 71 72 Keyword args: 73 write_fps_declaration: If True, create a zero-duration first subtitle which will contain 74 the fps. 75 apply_styles: If False, do not write any styling. 76 77 """ 78 if fps is None: 79 fps = subs.fps 80 81 if fps is None: 82 raise UnknownFPSError("Framerate must be specified when writing MicroDVD.") 83 to_frames = partial(ms_to_frames, fps=fps) 84 85 def is_entirely_italic(line): 86 style = subs.styles.get(line.style, SSAStyle.DEFAULT_STYLE) 87 for fragment, sty in parse_tags(line.text, style, subs.styles): 88 fragment = fragment.replace(r"\h", " ") 89 fragment = fragment.replace(r"\n", "\n") 90 fragment = fragment.replace(r"\N", "\n") 91 if not sty.italic and fragment and not fragment.isspace(): 92 return False 93 return True 94 95 # insert an artificial first line telling the framerate 96 if write_fps_declaration: 97 subs.insert(0, SSAEvent(start=0, end=0, text=str(fps))) 98 99 for line in subs: 100 if line.is_comment or line.is_drawing: 101 continue 102 103 text = "|".join(line.plaintext.splitlines()) 104 if apply_styles and is_entirely_italic(line): 105 text = "{Y:i}" + text 106 107 start, end = map(to_frames, (line.start, line.end)) 108 109 # XXX warn on underflow? 110 if start < 0: start = 0 111 if end < 0: end = 0 112 113 print("{%d}{%d}%s" % (start, end, text), file=fp) 114 115 # remove the artificial framerate-telling line 116 if write_fps_declaration: 117 subs.pop(0) 118