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