Example #1
0
  def test_compute_extent_em(self):
    doc = model.ContentDocument()

    r1 = model.Region("r1", doc)
    r1.set_style(styles.StyleProperties.ShowBackground, styles.ShowBackgroundType.always)
    r1.set_style(
      styles.StyleProperties.Extent,
      styles.ExtentType(
        width=styles.LengthType(20, styles.LengthType.Units.em),
        height=styles.LengthType(3, styles.LengthType.Units.em)
      )
    )
    doc.put_region(r1)

    isd = ISD.from_model(doc, 0)

    region = list(isd.iter_regions())[0]

    extent: styles.ExtentType = region.get_style(styles.StyleProperties.Extent)

    self.assertAlmostEqual(extent.width.value, 100*20/doc.get_cell_resolution().rows)
    self.assertEqual(extent.width.units, styles.LengthType.Units.rh)

    self.assertAlmostEqual(extent.height.value, 100*3/doc.get_cell_resolution().rows)
    self.assertEqual(extent.height.units, styles.LengthType.Units.rh)
Example #2
0
        def extract(cls, context: StyleParsingContext, xml_attrib: str):

            (h_edge, h_offset, v_edge,
             v_offset) = utils.parse_position(xml_attrib)

            if h_edge == "right":
                if h_offset.units is styles.LengthType.Units.px:
                    h_offset = styles.LengthType(
                        context.doc.get_px_resolution().width - h_offset.value,
                        h_offset.units)
                elif h_offset.units is styles.LengthType.Units.pct or h_offset.units is styles.LengthType.Units.rw:
                    h_offset = styles.LengthType(100 - h_offset.value,
                                                 h_offset.units)
                else:
                    raise ValueError(
                        "Units other than px, pct, rh, rw used in tts:position"
                    )

            if v_edge == "bottom":
                if v_offset.units is styles.LengthType.Units.px:
                    v_offset = styles.LengthType(
                        context.doc.get_px_resolution().height -
                        v_offset.value, v_offset.units)
                elif v_offset.units is styles.LengthType.Units.pct or v_offset.units is styles.LengthType.Units.rh:
                    v_offset = styles.LengthType(100 - v_offset.value,
                                                 v_offset.units)
                else:
                    raise ValueError(
                        "Units other than px, pct, rh, rw used in tts:position"
                    )

            return styles.PositionType(x=h_offset, y=v_offset)
Example #3
0
  def test_compute_extent_pct(self):
    doc = model.ContentDocument()

    r1 = model.Region("r1", doc)
    r1.set_style(styles.StyleProperties.ShowBackground, styles.ShowBackgroundType.always)
    r1.set_style(
      styles.StyleProperties.Extent,
      styles.ExtentType(
        width=styles.LengthType(50, styles.LengthType.Units.pct),
        height=styles.LengthType(25, styles.LengthType.Units.pct)
      )
    )
    doc.put_region(r1)

    isd = ISD.from_model(doc, 0)

    region = list(isd.iter_regions())[0]

    extent: styles.ExtentType = region.get_style(styles.StyleProperties.Extent)

    self.assertEqual(extent.height.value, 25)
    self.assertEqual(extent.height.units, styles.LengthType.Units.rh)

    self.assertEqual(extent.width.value, 50)
    self.assertEqual(extent.width.units, styles.LengthType.Units.rw)
def position(h_edge, h_offval, h_offunit, v_edge, v_offval, v_offunit):
    return (h_edge,
            styles.LengthType(value=h_offval,
                              units=styles.LengthType.Units(h_offunit)),
            v_edge,
            styles.LengthType(value=v_offval,
                              units=styles.LengthType.Units(v_offunit)))
Example #5
0
  def test_compute_style_property(self):
    doc = model.ContentDocument()

    r1 = model.Region("r1", doc)
    r1.set_style(styles.StyleProperties.FontSize, styles.LengthType(value=50, units=styles.LengthType.Units.pct))
    doc.put_region(r1)

    b = model.Body(doc)
    b.set_style(styles.StyleProperties.FontSize, styles.LengthType(value=50, units=styles.LengthType.Units.pct))
    b.set_region(r1)
    doc.set_body(b)

    div1 = model.Div(doc)
    b.push_child(div1)

    p1 = model.P(doc)
    div1.push_child(p1)

    span1 = model.Span(doc)
    p1.push_child(span1)

    t1 = model.Text(doc, "hello")
    span1.push_child(t1)

    isd = ISD.from_model(doc, 0)

    region = list(isd.iter_regions())[0]

    span = region[0][0][0][0]

    fs: styles.LengthType = span.get_style(styles.StyleProperties.FontSize)

    self.assertAlmostEqual(fs.value, 25 / doc.get_cell_resolution().rows)

    self.assertEqual(fs.units, styles.LengthType.Units.rh)
Example #6
0
    def test_style_property_disparity_has_px(self):
        prop = styles.LengthType(1, styles.LengthType.units.em)
        self.assertEqual(imsc_styles.StyleProperties.Disparity.has_px(prop),
                         False)

        prop = styles.LengthType(1, styles.LengthType.units.px)
        self.assertEqual(imsc_styles.StyleProperties.Disparity.has_px(prop),
                         True)
Example #7
0
    def test_style_property_font_size_has_px(self):
        prop = styles.LengthType(1, styles.LengthType.units.em)
        self.assertEqual(imsc_styles.StyleProperties.FontSize.has_px(prop),
                         False)

        prop = styles.LengthType(1, styles.LengthType.units.px)
        self.assertEqual(imsc_styles.StyleProperties.FontSize.has_px(prop),
                         True)
Example #8
0
    def test_tts_padding(self):
        padding = styles.PaddingType(before=styles.LengthType(10.1),
                                     end=styles.LengthType(20.2),
                                     after=styles.LengthType(30.3),
                                     start=styles.LengthType(40.4))

        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.Padding, padding),
            r"10.1% 20.2% 30.3% 40.4%")
Example #9
0
def _get_region_from_model(doc: model.ContentDocument, x_origin: Number,
                           y_origin: Number, width: Number, height: Number,
                           display_align: styles.DisplayAlignType):
    """Returns a matching region from `doc` or creates one
  """

    found_region = None

    regions = list(doc.iter_regions())

    for r in regions:
        r_origin: styles.CoordinateType = r.get_style(
            styles.StyleProperties.Origin)
        assert r_origin is not None
        assert r_origin.x.units is styles.LengthType.Units.pct
        assert r_origin.y.units is styles.LengthType.Units.pct
        if r_origin.x.value != x_origin or r_origin.y.value != y_origin:
            continue

        r_extent: styles.ExtentType = r.get_style(
            styles.StyleProperties.Extent)
        assert r_extent is not None
        assert r_extent.height.units is styles.LengthType.Units.pct
        assert r_extent.width.units is styles.LengthType.Units.pct
        if r_extent.height.value != height or r_extent.width.value != width:
            continue

        r_display_align: styles.DisplayAlignType = r.get_style(
            styles.StyleProperties.DisplayAlign)
        assert r_display_align is not None
        if r_display_align != display_align:
            continue

        found_region = r
        break

    if found_region is None:
        found_region = model.Region(f"r{len(regions)}", doc)
        found_region.set_style(
            styles.StyleProperties.Extent,
            styles.ExtentType(
                height=styles.LengthType(height, styles.LengthType.Units.pct),
                width=styles.LengthType(width, styles.LengthType.Units.pct),
            ))
        found_region.set_style(
            styles.StyleProperties.Origin,
            styles.CoordinateType(
                x=styles.LengthType(x_origin, styles.LengthType.Units.pct),
                y=styles.LengthType(y_origin, styles.LengthType.Units.pct)))
        found_region.set_style(styles.StyleProperties.DisplayAlign,
                               display_align)
        doc.put_region(found_region)

    return found_region
Example #10
0
    def test_compute_extent_em(self):
        doc = model.ContentDocument()

        r1 = model.Region("r1", doc)
        r1.set_style(styles.StyleProperties.ShowBackground,
                     styles.ShowBackgroundType.always)
        with self.assertRaises(ValueError) as _context:
            r1.set_style(
                styles.StyleProperties.Extent,
                styles.ExtentType(
                    width=styles.LengthType(20, styles.LengthType.Units.em),
                    height=styles.LengthType(3, styles.LengthType.Units.em)))
Example #11
0
    def test_style_property_line_height_has_px(self):
        prop = styles.SpecialValues.normal
        self.assertEqual(imsc_styles.StyleProperties.LineHeight.has_px(prop),
                         False)

        prop = styles.LengthType(1, styles.LengthType.units.em)
        self.assertEqual(imsc_styles.StyleProperties.LineHeight.has_px(prop),
                         False)

        prop = styles.LengthType(1, styles.LengthType.units.px)
        self.assertEqual(imsc_styles.StyleProperties.LineHeight.has_px(prop),
                         True)
Example #12
0
    def test_style_property_ruby_reserve_has_px(self):
        prop = styles.RubyReserveType(
            position=styles.RubyReserveType.Position.both,
            length=styles.LengthType(value=1.2,
                                     units=styles.LengthType.Units.px))
        self.assertEqual(imsc_styles.StyleProperties.RubyReserve.has_px(prop),
                         True)

        prop = styles.RubyReserveType(
            position=styles.RubyReserveType.Position.both,
            length=styles.LengthType(value=1.2,
                                     units=styles.LengthType.Units.em))
        self.assertEqual(imsc_styles.StyleProperties.RubyReserve.has_px(prop),
                         False)
Example #13
0
        def extract(cls, context: StyleParsingContext, xml_attrib: str):
            if xml_attrib == "auto":

                return styles.ExtentType(
                    height=styles.LengthType(1, styles.LengthType.Units.rh),
                    width=styles.LengthType(1, styles.LengthType.Units.rw))

            s = xml_attrib.split(" ")

            if len(s) != 2:
                raise ValueError("Bad tts:extent syntax")

            return styles.ExtentType(
                height=StyleProperties.ttml_length_to_model(context, s[1]),
                width=StyleProperties.ttml_length_to_model(context, s[0]))
Example #14
0
    def test_style_property_text_outline_has_px(self):
        prop = styles.SpecialValues.none
        self.assertEqual(imsc_styles.StyleProperties.TextOutline.has_px(prop),
                         False)

        prop = styles.TextOutlineType(color=styles.NamedColors.red.value,
                                      thickness=styles.LengthType(
                                          value=5,
                                          units=styles.LengthType.Units.em))
        self.assertEqual(imsc_styles.StyleProperties.TextOutline.has_px(prop),
                         False)

        prop = styles.TextOutlineType(color=styles.NamedColors.red.value,
                                      thickness=styles.LengthType(
                                          value=5,
                                          units=styles.LengthType.Units.px))
        self.assertEqual(imsc_styles.StyleProperties.TextOutline.has_px(prop),
                         True)
Example #15
0
        def extract(cls, context: StyleParsingContext, xml_attrib: str):

            if xml_attrib == "auto":

                r = styles.PositionType(
                    x=styles.LengthType(0, styles.LengthType.Units.pct),
                    y=styles.LengthType(0, styles.LengthType.Units.pct))

            else:
                s = xml_attrib.split(" ")

                if len(s) != 2:
                    raise ValueError("tts:origin has not two components")

                r = styles.PositionType(
                    x=StyleProperties.ttml_length_to_model(context, s[0]),
                    y=StyleProperties.ttml_length_to_model(context, s[1]))

            return r
Example #16
0
    def test_tts_text_outline(self):
        to1 = styles.SpecialValues.none
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.TextOutline, to1),
            "none")

        to2 = styles.TextOutlineType(color=styles.NamedColors.red.value,
                                     thickness=styles.LengthType(value=5))
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.TextOutline, to2),
            "#ff0000 5%")
Example #17
0
  def test_compute_padding(self):
    doc = model.ContentDocument()

    r1 = model.Region("r1", doc)
    r1.set_style(styles.StyleProperties.ShowBackground, styles.ShowBackgroundType.always)
    r1.set_style(
      styles.StyleProperties.Extent,
      styles.ExtentType(
        width=styles.LengthType(50, styles.LengthType.Units.pct),
        height=styles.LengthType(25, styles.LengthType.Units.pct)
      )
    )
    r1.set_style(
      styles.StyleProperties.Padding,
      styles.PaddingType(
        before=styles.LengthType(5, styles.LengthType.Units.pct),
        after=styles.LengthType(10, styles.LengthType.Units.pct),
        start=styles.LengthType(15, styles.LengthType.Units.pct),
        end=styles.LengthType(20, styles.LengthType.Units.pct)
      )
    )
    doc.put_region(r1)

    isd = ISD.from_model(doc, 0)

    region = list(isd.iter_regions())[0]

    padding: styles.PaddingType = region.get_style(styles.StyleProperties.Padding)

    self.assertAlmostEqual(padding.before.value, 25 * 0.05)
    self.assertAlmostEqual(padding.after.value, 25 * 0.10)
    self.assertAlmostEqual(padding.start.value, 50 * 0.15)
    self.assertAlmostEqual(padding.end.value, 50 * 0.2)
Example #18
0
    def test_tts_text_shadow(self):
        ts1 = styles.SpecialValues.none
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.TextShadow, ts1),
            "none")

        ts2 = styles.TextShadowType(
            (styles.TextShadowType.Shadow(
                x_offset=styles.LengthType(value=1,
                                           units=styles.LengthType.Units.em),
                y_offset=styles.LengthType(value=1.2,
                                           units=styles.LengthType.Units.em)),
             styles.TextShadowType.Shadow(
                 x_offset=styles.LengthType(value=0.5,
                                            units=styles.LengthType.Units.em),
                 y_offset=styles.LengthType(value=0.7,
                                            units=styles.LengthType.Units.em),
                 blur_radius=styles.LengthType(
                     value=1, units=styles.LengthType.Units.em),
                 color=styles.NamedColors.red.value)))

        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.TextShadow, ts2),
            "1em 1.2em, 0.5em 0.7em 1em #ff0000")
Example #19
0
    def test_style_property_origin_has_px(self):
        prop = styles.CoordinateType(
            styles.LengthType(1, styles.LengthType.units.px),
            styles.LengthType(1, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Origin.has_px(prop), True)

        prop = styles.CoordinateType(
            styles.LengthType(1, styles.LengthType.units.px),
            styles.LengthType(1, styles.LengthType.units.px))
        self.assertEqual(imsc_styles.StyleProperties.Origin.has_px(prop), True)

        prop = styles.CoordinateType(
            styles.LengthType(1, styles.LengthType.units.em),
            styles.LengthType(1, styles.LengthType.units.px))
        self.assertEqual(imsc_styles.StyleProperties.Origin.has_px(prop), True)

        prop = styles.CoordinateType(
            styles.LengthType(1, styles.LengthType.units.em),
            styles.LengthType(1, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Origin.has_px(prop),
                         False)
Example #20
0
    def test_tts_ruby_reserve(self):
        rr1 = styles.SpecialValues.none
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.RubyReserve, rr1),
            "none")

        rr2 = styles.RubyReserveType(
            position=styles.RubyReserveType.Position.both,
            length=styles.LengthType(value=1.2,
                                     units=styles.LengthType.Units.em))
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.RubyReserve, rr2),
            "both 1.2em")

        rr3 = styles.RubyReserveType(
            position=styles.RubyReserveType.Position.outside, )
        self.assertEqual(
            _get_set_style(imsc_styles.StyleProperties.RubyReserve, rr3),
            "outside")
Example #21
0
    def test_style_property_extent_has_px(self):
        prop = styles.ExtentType(
            styles.LengthType(1, styles.LengthType.units.px),
            styles.LengthType(1, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Extent.has_px(prop), True)

        prop = styles.ExtentType(
            styles.LengthType(1, styles.LengthType.units.px),
            styles.LengthType(1, styles.LengthType.units.px))
        self.assertEqual(imsc_styles.StyleProperties.Extent.has_px(prop), True)

        prop = styles.ExtentType(
            styles.LengthType(1, styles.LengthType.units.em),
            styles.LengthType(1, styles.LengthType.units.px))
        self.assertEqual(imsc_styles.StyleProperties.Extent.has_px(prop), True)

        prop = styles.ExtentType(
            styles.LengthType(1, styles.LengthType.units.em),
            styles.LengthType(1, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Extent.has_px(prop),
                         False)
Example #22
0
    def ttml_length_to_model(cls, _context: StyleParsingContext,
                             xml_attrib: str):
        (value, units) = utils.parse_length(xml_attrib)

        return styles.LengthType(value, styles.LengthType.Units(units))
Example #23
0
def parse_position(
    attr_value: str
) -> typing.Tuple[str, styles.LengthType, str, styles.LengthType]:
    '''Parse a TTML \\<position\\> value into offsets from a horizontal and vertical edge
  '''

    length_50pct = styles.LengthType(value=50,
                                     units=styles.LengthType.Units.pct)
    length_0pct = styles.LengthType(value=0, units=styles.LengthType.Units.pct)

    h_edges = {"left", "right"}
    v_edges = {"top", "bottom"}

    h_edge: str = None
    h_offset: styles.LengthType = None
    v_edge: str = None
    v_offset: styles.LengthType = None

    items = attr_value.split()

    if len(items) in (1, 2):

        # begin processing 1 and 2 components

        while len(items) > 0:

            cur_item = items.pop(0)

            if cur_item in h_edges:

                h_edge = cur_item
                h_offset = length_0pct

            elif cur_item in v_edges:

                v_edge = cur_item
                v_offset = length_0pct

            elif cur_item == "center":

                if h_edge is None:

                    h_edge = "left"
                    h_offset = length_50pct

                elif v_edge is None:

                    v_edge = "top"
                    v_offset = length_50pct

            else:

                (value, units) = parse_length(cur_item)

                if h_edge is None:

                    h_edge = "left"
                    h_offset = styles.LengthType(
                        value, styles.LengthType.Units(units))

                elif v_edge is None:

                    v_edge = "top"
                    v_offset = styles.LengthType(
                        value, styles.LengthType.Units(units))

        # end processing 1 and 2 components

    else:

        # begin processing 3 and 4 components

        while len(items) > 0:

            cur_item = items.pop(0)

            if cur_item in h_edges:

                h_edge = cur_item

                if v_edge is not None and v_offset is None:
                    v_offset = length_0pct

            elif cur_item in v_edges:

                v_edge = cur_item

                if h_edge is not None and h_offset is None:
                    h_offset = length_0pct

            elif cur_item == "center":

                pass

            else:

                (value, units) = parse_length(cur_item)

                if h_edge is not None and h_offset is None:

                    h_offset = styles.LengthType(
                        value, styles.LengthType.Units(units))

                if v_edge is not None and v_offset is None:

                    v_offset = styles.LengthType(
                        value, styles.LengthType.Units(units))

        # end processing 3 and 4 components

    # fill-in missing components, if any

    if h_offset is None:

        if h_edge is None:
            h_edge = "left"
            h_offset = length_50pct
        else:
            h_offset = length_0pct

    if v_offset is None:

        if v_edge is None:
            v_edge = "top"
            v_offset = length_50pct
        else:
            v_offset = length_0pct

    return (h_edge, h_offset, v_edge, v_offset)
Example #24
0
    def __init__(self,
                 gsi_block: bytes,
                 disable_fill_line_gap: bool = False,
                 disable_line_padding: bool = False,
                 start_tc: typing.Optional[str] = None,
                 font_stack: typing.Tuple[typing.Union[
                     str, styles.GenericFontFamilyType]] = None,
                 max_row_count: typing.Optional[typing.Union[int,
                                                             str]] = None):

        self.gsi = _GSIBlock._make(
            struct.unpack(
                '3s8sc2s2s32s32s32s32s32s32s16s6s6s2s5s5s3s2s2s1s8s8s1s1s3s32s32s32s75x576s',
                gsi_block))

        self.doc = model.ContentDocument()

        self.doc.set_cell_resolution(
            model.CellResolutionType(
                columns=round(100 * DEFAULT_TELETEXT_COLS /
                              (100 - 2 * DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT)),
                rows=round(100 * DEFAULT_TELETEXT_ROWS /
                           (100 - 2 * DEFAULT_VERTICAL_SAFE_MARGIN_PCT))))

        self.doc.set_active_area(
            model.ActiveAreaType(
                left_offset=DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT / 100,
                top_offset=DEFAULT_VERTICAL_SAFE_MARGIN_PCT / 100,
                width=1 - 2 * DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT / 100,
                height=1 - 2 * DEFAULT_VERTICAL_SAFE_MARGIN_PCT / 100))

        self.body = model.Body(self.doc)

        if not disable_fill_line_gap:
            self.body.set_style(styles.StyleProperties.FillLineGap, True)

        if not disable_line_padding:
            self.body.set_style(
                styles.StyleProperties.LinePadding,
                styles.LengthType(LINE_PADDING_LENGTH_C,
                                  styles.LengthType.Units.c))

        if font_stack is not None:
            self.body.set_style(styles.StyleProperties.FontFamily, font_stack)
        else:
            self.body.set_style(styles.StyleProperties.FontFamily,
                                DEFAULT_FONT_STACK)

        self.doc.set_body(self.body)

        self.sgn_to_div_map = {}

        self.last_sn = None

        self.is_in_extension = False

        self.tti_tf = None

        self.fps = _DFC_FRACTION_MAP.get(self.gsi.DFC)
        if self.fps is None:
            LOGGER.error("Unknown GSI DFC value %s, defaulting to 25 fps",
                         self.gsi.DFC)
            self.fps = Fraction(25)
        else:
            LOGGER.debug("GSI DFC: %s", self.gsi.DFC)

        self.cct = self.gsi.CCT
        LOGGER.debug("GSI CCT: %s", self.gsi.CCT)

        try:
            self.tti_count = int(self.gsi.TNB)
            LOGGER.debug("GSI TNB: %s", self.gsi.TNB)
        except ValueError:
            LOGGER.error("Invalid TNB field value: %s", self.gsi.TNB)
            self.tti_count = sys.maxsize

        self.language = _LC_BCP47_MAP.get(self.gsi.LC)
        if self.language is None:
            LOGGER.warning(
                "Unknown LC value: %s, defaulting to 'unspecified''",
                self.gsi.LC)
            self.language = ""
        else:
            LOGGER.debug("GSI LC: %s", self.gsi.LC)

        self.doc.set_lang(self.language)

        if start_tc is None:
            self.start_offset = 0
        elif start_tc == "TCP":
            try:
                self.start_offset = SmpteTimeCode(
                    int(self.gsi.TCP[0:2]), int(self.gsi.TCP[2:4]),
                    int(self.gsi.TCP[4:6]), int(self.gsi.TCP[6:8]),
                    self.get_fps()).to_temporal_offset()
                LOGGER.debug("GSI TCP: %s", self.gsi.TCP)
            except ValueError:
                LOGGER.error("Invalid TCP value: %s", self.gsi.tcp)
                self.start_offset = 0
        else:
            try:
                self.start_offset = SmpteTimeCode.parse(
                    start_tc, self.get_fps()).to_temporal_offset()
            except ValueError:
                LOGGER.error("Invalid start_tc value")
                raise

        if max_row_count is None or self.is_teletext():
            self.max_row_count = DEFAULT_TELETEXT_ROWS
        elif isinstance(max_row_count, str) and max_row_count == "MNR":
            try:
                self.max_row_count = int(self.gsi.MNR)
                LOGGER.debug("GSI MNR: %s", self.gsi.MNR)
            except ValueError:
                LOGGER.error("Invalid MNR value: %s", self.gsi.MNR)
                self.start_offset = DEFAULT_TELETEXT_ROWS
        else:
            self.max_row_count = max_row_count

        # p_element for use across cumulative subtitles
        self.cur_p_element = None
Example #25
0
        def extract(cls, context: StyleParsingContext, xml_attrib: str):

            return styles.PositionType(x=styles.LengthType(),
                                       y=styles.LengthType())
Example #26
0
    def process_tti_block(self, tti_block: bytes):
        """Processes a single TTI block
    """
        if tti_block is None:
            raise ValueError("tti_block should not be None")

        tti = _TTIBlock._make(struct.unpack('<BHBBBBBBBBBBBBB112s', tti_block))

        LOGGER.debug("Subtitle SN: %s", tti.SN)
        LOGGER.debug("  EBN: %s", tti.EBN)
        LOGGER.debug("  CS: %s", tti.CS)
        LOGGER.debug("  SGN: %s", tti.SGN)
        LOGGER.debug("  JC: %s", tti.JC)
        LOGGER.debug("  VP: %s", tti.VP)

        if 0xEF < tti.EBN < 0xFF:
            # skip user data and reserved blocks
            return

        if not self.is_in_extension:
            self.tti_tf = b''

        self.tti_tf += tti.TF.strip(b'\x8f')

        is_double_height_characters = tf.has_double_height_char(self.tti_tf)

        # continue accumulating if we have an extension block

        if tti.EBN != 0xFF:
            self.is_in_extension = True
            return

        self.is_in_extension = False

        # apply program offset

        try:
            tci = SmpteTimeCode(tti.TCIh, tti.TCIm, tti.TCIs, tti.TCIf,
                                self.get_fps())
            tco = SmpteTimeCode(tti.TCOh, tti.TCOm, tti.TCOs, tti.TCOf,
                                self.get_fps())
        except ValueError:
            LOGGER.error("Invalid TTI timecode")
            return

        begin_time = tci.to_temporal_offset() - self.start_offset
        if begin_time < 0:
            LOGGER.debug(
                "Skipping subtitle because TCI is less than start time")
            return
        LOGGER.debug("  Time in: %s", tci)

        end_time = tco.to_temporal_offset() - self.start_offset
        if end_time < begin_time:
            LOGGER.error("Subtitle TCO is less than TCI")
            return
        LOGGER.debug("  Time out: %s", tco)

        # create a new subtitle if SN changes and we are not in cumulative mode

        if tti.SN is not self.last_sn and tti.CS in (0x00, 0x01):

            self.last_sn = tti.SN

            # find the div to which the subtitle belongs, based on SGN

            div_element = self.sgn_to_div_map.get(tti.SGN)

            # create the div if it does not exist

            if div_element is None:
                div_element = model.Div(self.doc)
                self.body.push_child(div_element)
                self.sgn_to_div_map[tti.SGN] = div_element

            # create the p that will hold the subtitle

            self.cur_p_element = model.P(self.doc)

            if tti.JC == 0x01:
                self.cur_p_element.set_style(styles.StyleProperties.TextAlign,
                                             styles.TextAlignType.start)
            elif tti.JC == 0x03:
                self.cur_p_element.set_style(styles.StyleProperties.TextAlign,
                                             styles.TextAlignType.end)
            else:
                self.cur_p_element.set_style(styles.StyleProperties.TextAlign,
                                             styles.TextAlignType.center)

            self.cur_p_element.set_style(
                styles.StyleProperties.LineHeight,
                styles.LengthType(DEFAULT_LINE_HEIGHT_PCT,
                                  styles.LengthType.Units.pct))

            if self.is_teletext() and not is_double_height_characters:
                font_size = DEFAULT_SINGLE_HEIGHT_FONT_SIZE_PCT
            else:
                font_size = DEFAULT_DOUBLE_HEIGHT_FONT_SIZE_PCT

            self.cur_p_element.set_style(
                styles.StyleProperties.FontSize,
                styles.LengthType(font_size, styles.LengthType.Units.pct))

            safe_area_height = round(100 -
                                     DEFAULT_VERTICAL_SAFE_MARGIN_PCT * 2)
            safe_area_width = round(100 -
                                    DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT * 2)

            # assume that VP < max number of rows/2 means bottom-aligned and otherwise top-aligned
            # probably should offer an option to override this

            if tti.VP < self.get_max_row_count() // 2:
                # top-aligned large region

                r_y = DEFAULT_VERTICAL_SAFE_MARGIN_PCT + (
                    (tti.VP - 1) / self.get_max_row_count()) * safe_area_height
                r_height = 100 - DEFAULT_VERTICAL_SAFE_MARGIN_PCT - r_y

                region = _get_region_from_model(
                    self.doc, round(DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT), r_y,
                    safe_area_width, r_height, styles.DisplayAlignType.before)

            else:

                line_count = tf.line_count(self.tti_tf,
                                           is_double_height_characters)
                vp = tti.VP
                line_height = 2 if is_double_height_characters else 1

                r_y = DEFAULT_VERTICAL_SAFE_MARGIN_PCT
                r_height = ((vp + line_count * line_height - 1) /
                            self.get_max_row_count()) * safe_area_height

                region = _get_region_from_model(
                    self.doc, round(DEFAULT_HORIZONTAL_SAFE_MARGIN_PCT), r_y,
                    safe_area_width, r_height, styles.DisplayAlignType.after)

            self.cur_p_element.set_region(region)

            div_element.push_child(self.cur_p_element)

        if tti.CS in (0x01, 0x02, 0x03):
            # create a nested span if we are in cumulative mode
            sub_element = model.Span(self.doc)
            self.cur_p_element.push_child(sub_element)
        else:
            sub_element = self.cur_p_element

        sub_element.set_begin(begin_time)
        sub_element.set_end(end_time)

        LOGGER.debug("  TF: %s", self.tti_tf)

        tf.to_model(sub_element, self.is_teletext(), self.get_cct(),
                    self.tti_tf)

        if tti.CS in (0x01, 0x02):
            sub_element.push_child(model.Br(self.doc))
Example #27
0
def to_model(data_file: typing.IO, _config = None, progress_callback=lambda _: None):
  """Converts an SRT document to the data model"""

  doc = model.ContentDocument()

  region = model.Region(_DEFAULT_REGION_ID, doc)
  region.set_style(
    styles.StyleProperties.Origin,
    styles.CoordinateType(
      x=styles.LengthType(5, styles.LengthType.Units.pct),
      y=styles.LengthType(5, styles.LengthType.Units.pct)
    )
  )
  region.set_style(
    styles.StyleProperties.Extent,
    styles.ExtentType(
      height=styles.LengthType(90, styles.LengthType.Units.pct),
      width=styles.LengthType(90, styles.LengthType.Units.pct)
    )
  )
  region.set_style(
    styles.StyleProperties.DisplayAlign,
    styles.DisplayAlignType.after
  )
  region.set_style(
    styles.StyleProperties.TextAlign,
    styles.TextAlignType.center
  )
  region.set_style(
    styles.StyleProperties.LineHeight,
    _DEFAULT_LINE_HEIGHT
  )
  region.set_style(
    styles.StyleProperties.FontFamily,
    _DEFAULT_FONT_STACK
  )
  region.set_style(
    styles.StyleProperties.FontSize,
    _DEFAULT_FONT_SIZE
  )
  region.set_style(
    styles.StyleProperties.Color,
    _DEFAULT_TEXT_COLOR
  )
  region.set_style(
    styles.StyleProperties.TextOutline,
    styles.TextOutlineType(
      _DEFAULT_OUTLINE_THICKNESS,
      _DEFAULT_OUTLINE_COLOR
    )
  )

  doc.put_region(region)

  body = model.Body(doc)
  body.set_region(region)

  doc.set_body(body)

  div = model.Div(doc)

  body.push_child(div)

  lines : str = data_file.readlines()

  state = _State.COUNTER
  current_p = None
 
  for line_index, line in enumerate(_none_terminated(lines)):

    if state is _State.COUNTER:
      if line is None:
        break

      if _EMPTY_RE.fullmatch(line):
        continue

      if _COUNTER_RE.search(line) is None:
        LOGGER.fatal("Missing subtitle counter at line %s", line_index)
        return None
      
      progress_callback(line_index/len(lines))

      state = _State.TC

      continue

    if state is _State.TC:
      if line is None:
        break

      m = _TIMECODE_RE.search(line)

      if m is None:
        LOGGER.fatal("Missing timecode at line %s", line_index)
        return None

      current_p = model.P(doc)

      current_p.set_begin(
        int(m.group('begin_h')) * 3600 + 
        int(m.group('begin_m')) * 60 + 
        int(m.group('begin_s')) +
        int(m.group('begin_ms')) / 1000
        )
    
      current_p.set_end(
        int(m.group('end_h')) * 3600 + 
        int(m.group('end_m')) * 60 + 
        int(m.group('end_s')) +
        int(m.group('end_ms')) / 1000
        )

      state = _State.TEXT

      continue

    if state in (_State.TEXT, _State.TEXT_MORE):

      if line is None or _EMPTY_RE.fullmatch(line):
        subtitle_text = subtitle_text.strip('\r\n')\
          .replace(r"\n\r", "\n")\
          .replace(r"{bold}", r"<bold>")\
          .replace(r"{/bold}", r"</bold>")\
          .replace(r"{italic}", r"<italic>")\
          .replace(r"{/italic}", r"</italic>")\
          .replace(r"{underline}", r"<underline>")\
          .replace(r"{/underline}", r"</underline>")

        parser = _TextParser(current_p, line_index)
        parser.feed(subtitle_text)
        parser.close()

        state = _State.COUNTER
        continue

      if state is _State.TEXT:
        div.push_child(current_p)
        subtitle_text = ""

      if state is _State.TEXT_MORE:
        current_p.push_child(model.Br(current_p.get_doc()))

      subtitle_text += line

      state = _State.TEXT_MORE

      continue

  return doc
Example #28
0
      span = model.Span(self.parent.get_doc())
      span.push_child(model.Text(self.parent.get_doc(), line))
      self.parent.push_child(span)

class _State(Enum):
  COUNTER = 1
  TC = 2
  TEXT = 3
  TEXT_MORE = 4

_EMPTY_RE = re.compile(r"\s+")
_COUNTER_RE = re.compile(r"\d+")
_TIMECODE_RE = re.compile(r"(?P<begin_h>[0-9]{2,3}):(?P<begin_m>[0-9]{2}):(?P<begin_s>[0-9]{2}),(?P<begin_ms>[0-9]{3})\s+-->\s+(?P<end_h>[0-9]{2,3}):(?P<end_m>[0-9]{2}):(?P<end_s>[0-9]{2}),(?P<end_ms>[0-9]{3})")
_DEFAULT_REGION_ID = "r1"
_DEFAULT_FONT_STACK = ("Verdana", "Arial", "Tiresias", styles.GenericFontFamilyType.sansSerif)
_DEFAULT_FONT_SIZE = styles.LengthType(80, styles.LengthType.Units.pct)
_DEFAULT_OUTLINE_THICKNESS = styles.LengthType(5, styles.LengthType.Units.pct)
_DEFAULT_TEXT_COLOR = styles.NamedColors.white.value
_DEFAULT_OUTLINE_COLOR = styles.NamedColors.black.value
_DEFAULT_LINE_HEIGHT = styles.LengthType(125, styles.LengthType.Units.pct)

def to_model(data_file: typing.IO, _config = None, progress_callback=lambda _: None):
  """Converts an SRT document to the data model"""

  doc = model.ContentDocument()

  region = model.Region(_DEFAULT_REGION_ID, doc)
  region.set_style(
    styles.StyleProperties.Origin,
    styles.CoordinateType(
      x=styles.LengthType(5, styles.LengthType.Units.pct),
Example #29
0
    def test_style_property_text_shadow_has_px(self):
        prop = styles.TextShadowType(
            (styles.TextShadowType.Shadow(
                x_offset=styles.LengthType(value=1,
                                           units=styles.LengthType.Units.em),
                y_offset=styles.LengthType(value=1,
                                           units=styles.LengthType.Units.em)),
             styles.TextShadowType.Shadow(
                 x_offset=styles.LengthType(value=0.5,
                                            units=styles.LengthType.Units.em),
                 y_offset=styles.LengthType(value=0.7,
                                            units=styles.LengthType.Units.em),
                 blur_radius=styles.LengthType(
                     value=1, units=styles.LengthType.Units.em),
                 color=styles.NamedColors.red.value)))
        self.assertEqual(imsc_styles.StyleProperties.TextShadow.has_px(prop),
                         False)

        prop = styles.TextShadowType(
            (styles.TextShadowType.Shadow(
                x_offset=styles.LengthType(value=1,
                                           units=styles.LengthType.Units.px),
                y_offset=styles.LengthType(value=1.2,
                                           units=styles.LengthType.Units.em)),
             styles.TextShadowType.Shadow(
                 x_offset=styles.LengthType(value=0.5,
                                            units=styles.LengthType.Units.em),
                 y_offset=styles.LengthType(value=0.7,
                                            units=styles.LengthType.Units.em),
                 blur_radius=styles.LengthType(
                     value=1, units=styles.LengthType.Units.em),
                 color=styles.NamedColors.red.value)))
        self.assertEqual(imsc_styles.StyleProperties.TextShadow.has_px(prop),
                         True)

        prop = styles.TextShadowType(
            (styles.TextShadowType.Shadow(
                x_offset=styles.LengthType(value=1,
                                           units=styles.LengthType.Units.em),
                y_offset=styles.LengthType(value=1.2,
                                           units=styles.LengthType.Units.em)),
             styles.TextShadowType.Shadow(
                 x_offset=styles.LengthType(value=0.5,
                                            units=styles.LengthType.Units.px),
                 y_offset=styles.LengthType(value=0.7,
                                            units=styles.LengthType.Units.em),
                 blur_radius=styles.LengthType(
                     value=1, units=styles.LengthType.Units.em),
                 color=styles.NamedColors.red.value)))
        self.assertEqual(imsc_styles.StyleProperties.TextShadow.has_px(prop),
                         True)
Example #30
0
    def test_style_property_padding_has_px(self):
        prop = styles.PaddingType(
            before=styles.LengthType(10.1, styles.LengthType.units.px),
            end=styles.LengthType(20.2, styles.LengthType.units.em),
            after=styles.LengthType(30.3, styles.LengthType.units.em),
            start=styles.LengthType(40.4, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Padding.has_px(prop),
                         True)

        prop = styles.PaddingType(
            before=styles.LengthType(10.1, styles.LengthType.units.em),
            end=styles.LengthType(20.2, styles.LengthType.units.px),
            after=styles.LengthType(30.3, styles.LengthType.units.em),
            start=styles.LengthType(40.4, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Padding.has_px(prop),
                         True)

        prop = styles.PaddingType(
            before=styles.LengthType(10.1, styles.LengthType.units.em),
            end=styles.LengthType(20.2, styles.LengthType.units.em),
            after=styles.LengthType(30.3, styles.LengthType.units.px),
            start=styles.LengthType(40.4, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Padding.has_px(prop),
                         True)

        prop = styles.PaddingType(
            before=styles.LengthType(10.1, styles.LengthType.units.em),
            end=styles.LengthType(20.2, styles.LengthType.units.em),
            after=styles.LengthType(30.3, styles.LengthType.units.em),
            start=styles.LengthType(40.4, styles.LengthType.units.px))
        self.assertEqual(imsc_styles.StyleProperties.Padding.has_px(prop),
                         True)

        prop = styles.PaddingType(
            before=styles.LengthType(10.1, styles.LengthType.units.em),
            end=styles.LengthType(20.2, styles.LengthType.units.em),
            after=styles.LengthType(30.3, styles.LengthType.units.em),
            start=styles.LengthType(40.4, styles.LengthType.units.em))
        self.assertEqual(imsc_styles.StyleProperties.Padding.has_px(prop),
                         False)