Source code for three_play.utils.parse.models

from dataclasses import dataclass, field
from textwrap import wrap
from typing import List, Union, Type

from ...utils.parse import as_int
from ..types import R


# Default max line width for text wrapping
DEFAULT_LINE_WIDTH = 35


# Extend from List[T] so we can get type hinting when looping over,
# for example
[docs]class ListOfSRTLine(List['SRTLine']): def __init__(self, srt_contents: str): """ Initialize a new :class:`ListOfSRTLine` object. :param srt_contents: SRT file contents, as a string """ seq = (SRTLine.parse(lines) for lines in srt_contents.split('\n\n')) super().__init__(seq) @property def contents(self) -> str: """ Return the SRT file contents, as a string """ return '\n\n'.join(str(lines) for lines in self)
[docs] def insert(self, __index: int, __object: 'SRTLine') -> None: """ Insert a :class:`SRTLine` into the list at a specified index, and increment the line numbers for all subsequent lines as needed. """ for rem_lines in self[__index:]: # Added because type hints don't automatically work here, for some reason rem_lines: SRTLine if not rem_lines.num: # Line number cannot be parsed to int rem_lines.num = __index + 1 rem_lines.num += 1 super(ListOfSRTLine, self).insert(__index, __object)
[docs]@dataclass(init=False) class SRTLine: """ Represents a sequence of dialogue lines in an SRT file. For each element in the sequence: * the first line will be the line number (ex. 12) * the second line will be the timeframe, which is formatted like so with an arrow denoting range: [First Timestamp] --> [Second Timestamp] * the remaining lines (can be empty) will be the dialogue """ # The line number (as a string) num: int # The time range where the line should appear in the video time_range: str # The text (lines of dialogue) that should appear on the line dialogue: List[str] # The line width to wrap when `dialogue` is passed as a string width: int = field(repr=False) def __init__(self, num: Union[int, str] = 0, time_range: str = '', dialogue: Union[List[str], str, None] = None, width: int = DEFAULT_LINE_WIDTH): self.num = num self.time_range = time_range self.width = width self.dialogue = dialogue
[docs] @classmethod def parse(cls: Type[R], lines: Union[str, List[str]]) -> R: """ Parse and return a new :class:`SRTLine` object. :param lines: The lines from an SRT file, which should adhere to the sequence as specified in the class docs. """ # Convert to a list if needed if isinstance(lines, str): lines = lines.strip().split('\n') # Unpack the list, save the remaining lines into `dialogue` line_num, time_range, *dialogue = lines # Returns the new object return cls(line_num, time_range, dialogue)
@property def start_ts(self) -> str: """Return the start timestamp associated with the line.""" return self.time_range.split('-->', 1)[0].strip() @property def end_ts(self) -> str: """Return the end timestamp associated with the line.""" return self.time_range.rsplit('-->', 1)[-1].strip() @property def text(self) -> str: """Returns the dialogue as a string.""" return ' '.join(line.strip() for line in self.dialogue) @property def num(self) -> int: return self._num @num.setter def num(self, n: Union[str, int], default=0): """Set the :attr:`num` attribute.""" self._num = as_int(n, default, raise_=False) @property def dialogue(self) -> List[str]: return self._dialogue @dialogue.setter def dialogue(self, lines: Union[List[str], str, None]): """Set the dialogue for the line.""" dialogue = lines or [] if not isinstance(dialogue, list): dialogue = wrap(dialogue, width=self.width) self._dialogue = dialogue
[docs] def set_time_range_if_empty(self, time_range: str): """Set the :attr:`time_range` attribute if it's empty or null.""" if not self.time_range: self.time_range = time_range
def __str__(self): """ Returns the string representation of the line, as it would appear in an SRT file. """ return '\n'.join([str(self.num), self.time_range, *self.dialogue])