2018-10-31 16:08:29 +00:00
|
|
|
from functools import partial
|
|
|
|
import re
|
|
|
|
from .exceptions import UnknownFPSError
|
|
|
|
from .ssaevent import SSAEvent
|
|
|
|
from .ssastyle import SSAStyle
|
|
|
|
from .formatbase import FormatBase
|
|
|
|
from .substation import parse_tags
|
|
|
|
from .time import ms_to_frames, frames_to_ms
|
|
|
|
|
|
|
|
#: Matches a MicroDVD line.
|
|
|
|
MICRODVD_LINE = re.compile(r" *\{ *(\d+) *\} *\{ *(\d+) *\}(.+)")
|
|
|
|
|
|
|
|
|
|
|
|
class MicroDVDFormat(FormatBase):
|
2021-07-14 23:13:28 +00:00
|
|
|
"""MicroDVD subtitle format implementation"""
|
2018-10-31 16:08:29 +00:00
|
|
|
@classmethod
|
|
|
|
def guess_format(cls, text):
|
2021-07-14 23:13:28 +00:00
|
|
|
"""See :meth:`pysubs2.formats.FormatBase.guess_format()`"""
|
2018-10-31 16:08:29 +00:00
|
|
|
if any(map(MICRODVD_LINE.match, text.splitlines())):
|
|
|
|
return "microdvd"
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_file(cls, subs, fp, format_, fps=None, **kwargs):
|
2021-07-14 23:13:28 +00:00
|
|
|
"""See :meth:`pysubs2.formats.FormatBase.from_file()`"""
|
2018-10-31 16:08:29 +00:00
|
|
|
for line in fp:
|
|
|
|
match = MICRODVD_LINE.match(line)
|
|
|
|
if not match:
|
|
|
|
continue
|
|
|
|
|
|
|
|
fstart, fend, text = match.groups()
|
|
|
|
fstart, fend = map(int, (fstart, fend))
|
|
|
|
|
|
|
|
if fps is None:
|
|
|
|
# We don't know the framerate, but it is customary to include
|
|
|
|
# it as text of the first subtitle. In that case, we skip
|
|
|
|
# this auxiliary subtitle and proceed with reading.
|
|
|
|
try:
|
|
|
|
fps = float(text)
|
|
|
|
subs.fps = fps
|
|
|
|
continue
|
|
|
|
except ValueError:
|
|
|
|
raise UnknownFPSError("Framerate was not specified and "
|
|
|
|
"cannot be read from "
|
|
|
|
"the MicroDVD file.")
|
|
|
|
|
|
|
|
start, end = map(partial(frames_to_ms, fps=fps), (fstart, fend))
|
|
|
|
|
|
|
|
def prepare_text(text):
|
|
|
|
text = text.replace("|", r"\N")
|
|
|
|
|
2024-03-03 17:15:23 +00:00
|
|
|
def style_replacer(match: re.Match) -> str:
|
2018-10-31 16:08:29 +00:00
|
|
|
tags = [c for c in "biu" if c in match.group(0)]
|
2024-03-03 17:15:23 +00:00
|
|
|
return "{%s}" % "".join(f"\\{c}1" for c in tags)
|
2018-10-31 16:08:29 +00:00
|
|
|
|
|
|
|
text = re.sub(r"\{[Yy]:[^}]+\}", style_replacer, text)
|
|
|
|
text = re.sub(r"\{[Ff]:([^}]+)\}", r"{\\fn\1}", text)
|
|
|
|
text = re.sub(r"\{[Ss]:([^}]+)\}", r"{\\fs\1}", text)
|
|
|
|
text = re.sub(r"\{P:(\d+),(\d+)\}", r"{\\pos(\1,\2)}", text)
|
|
|
|
|
|
|
|
return text.strip()
|
|
|
|
|
|
|
|
ev = SSAEvent(start=start, end=end, text=prepare_text(text))
|
|
|
|
subs.append(ev)
|
|
|
|
|
|
|
|
@classmethod
|
2021-07-14 23:13:28 +00:00
|
|
|
def to_file(cls, subs, fp, format_, fps=None, write_fps_declaration=True, apply_styles=True, **kwargs):
|
|
|
|
"""
|
|
|
|
See :meth:`pysubs2.formats.FormatBase.to_file()`
|
|
|
|
|
|
|
|
The only supported styling is marking whole lines italic.
|
|
|
|
|
|
|
|
Keyword args:
|
|
|
|
write_fps_declaration: If True, create a zero-duration first subtitle which will contain
|
|
|
|
the fps.
|
|
|
|
apply_styles: If False, do not write any styling.
|
|
|
|
|
|
|
|
"""
|
2018-10-31 16:08:29 +00:00
|
|
|
if fps is None:
|
|
|
|
fps = subs.fps
|
|
|
|
|
|
|
|
if fps is None:
|
|
|
|
raise UnknownFPSError("Framerate must be specified when writing MicroDVD.")
|
|
|
|
to_frames = partial(ms_to_frames, fps=fps)
|
|
|
|
|
2024-03-03 17:15:23 +00:00
|
|
|
def is_entirely_italic(line: SSAEvent) -> bool:
|
2018-10-31 16:08:29 +00:00
|
|
|
style = subs.styles.get(line.style, SSAStyle.DEFAULT_STYLE)
|
|
|
|
for fragment, sty in parse_tags(line.text, style, subs.styles):
|
|
|
|
fragment = fragment.replace(r"\h", " ")
|
|
|
|
fragment = fragment.replace(r"\n", "\n")
|
|
|
|
fragment = fragment.replace(r"\N", "\n")
|
|
|
|
if not sty.italic and fragment and not fragment.isspace():
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
# insert an artificial first line telling the framerate
|
|
|
|
if write_fps_declaration:
|
2021-07-14 23:13:28 +00:00
|
|
|
subs.insert(0, SSAEvent(start=0, end=0, text=str(fps)))
|
|
|
|
|
|
|
|
for line in subs:
|
|
|
|
if line.is_comment or line.is_drawing:
|
|
|
|
continue
|
2018-10-31 16:08:29 +00:00
|
|
|
|
|
|
|
text = "|".join(line.plaintext.splitlines())
|
2021-07-14 23:13:28 +00:00
|
|
|
if apply_styles and is_entirely_italic(line):
|
2018-10-31 16:08:29 +00:00
|
|
|
text = "{Y:i}" + text
|
|
|
|
|
|
|
|
start, end = map(to_frames, (line.start, line.end))
|
|
|
|
|
|
|
|
# XXX warn on underflow?
|
|
|
|
if start < 0: start = 0
|
|
|
|
if end < 0: end = 0
|
|
|
|
|
|
|
|
print("{%d}{%d}%s" % (start, end, text), file=fp)
|
|
|
|
|
|
|
|
# remove the artificial framerate-telling line
|
|
|
|
if write_fps_declaration:
|
|
|
|
subs.pop(0)
|