def test_push_child(self): b = model.Body() b.push_child(model.Div()) with self.assertRaises(TypeError): b.push_child(model.P())
def test_default_region(self): doc = model.ContentDocument() b = model.Body(doc) doc.set_body(b) div1 = model.Div(doc) b.push_child(div1) p1 = model.P(doc) div1.push_child(p1) span1 = model.Span(doc) span1.push_child(model.Text(doc, "hello")) p1.push_child(span1) isd = ISD.from_model(doc, 0) self.assertEqual(len(isd), 1) regions = list(isd.iter_regions()) self.assertEqual(regions[0].get_id(), ISD.DEFAULT_REGION_ID) p = regions[0][0][0][0] self.assertEqual(len(p), 1) self.assertEqual(p[0][0].get_text(), "hello")
def setUp(self): self.doc = model.ContentDocument() r1 = model.Region("r1", self.doc) r1.set_style(styles.StyleProperties.ShowBackground, styles.ShowBackgroundType.whenActive) self.doc.put_region(r1) b = model.Body(self.doc) b.set_region(r1) self.doc.set_body(b) div1 = model.Div(self.doc) b.push_child(div1) p1 = model.P(self.doc) p1.set_begin(1) p1.set_end(3) div1.push_child(p1) span1 = model.Span(self.doc) span1.push_child(model.Text(self.doc, "hello")) p1.push_child(span1) span2 = model.Span(self.doc) span2.set_begin(1) span2.push_child(model.Text(self.doc, "bye")) p1.push_child(span2)
def from_xml( parent_ctx: typing.Optional[TTMLElement.ParsingContext], xml_elem: et.Element ) -> typing.Optional[PElement.ParsingContext]: p_ctx = PElement.ParsingContext(PElement, parent_ctx, model.P(parent_ctx.doc)) p_ctx.process(parent_ctx, xml_elem) return p_ctx
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)
def test_tts_writing_extent_when_body_has_extents(self): doc = model.ContentDocument() body = model.Body(doc) div = model.Div(doc) div.set_style( styles.StyleProperties.Extent, get_extent_from_dimensions(123, 456, styles.LengthType.Units.px)) p = model.P(doc) span = model.Span(doc) text = model.Text(doc) text.set_text("asdf") span.push_child(text) p.push_child(span) div.push_child(p) body.push_child(div) doc.set_body(body) r = model.Region("hello", doc) r.set_style( styles.StyleProperties.Extent, get_extent_from_dimensions(123, 456, styles.LengthType.Units.px)) doc.put_region(r) tree_from_model = imsc_writer.from_model(doc) extent = tree_from_model.getroot().attrib.get( f"{{{imsc_styles.StyleProperties.Extent.ns}}}{imsc_styles.StyleProperties.Extent.local_name}" ) self.assertEqual(extent, '1920px 1080px')
def test_push_child(self): s = model.Rbc() s.push_child(model.Rb()) with self.assertRaises(TypeError): s.push_child(model.P())
def test_push_child(self): d1 = model.Div() d1.push_child(model.P()) d1.push_child(model.Div()) with self.assertRaises(TypeError): d1.push_child(model.Text())
def test_push_child(self): p = model.P() p.push_child(model.Br()) p.push_child(model.Span()) p.push_child(model.Ruby()) with self.assertRaises(TypeError): p.push_child(model.Text())
def test_push_child(self): s = model.Span() s.push_child(model.Br()) s.push_child(model.Span()) s.push_child(model.Text()) with self.assertRaises(TypeError): s.push_child(model.P())
def test_text_decoration_inheritance(self): doc = model.ContentDocument() r1 = model.Region("r1", doc) r1.set_style( styles.StyleProperties.TextDecoration, styles.TextDecorationType( line_through=False, underline=True, overline=True ) ) doc.put_region(r1) b = model.Body(doc) b.set_style( styles.StyleProperties.TextDecoration, styles.TextDecorationType( overline=False ) ) 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] self.assertEqual( span.get_style(styles.StyleProperties.TextDecoration), styles.TextDecorationType( line_through=False, underline=True, overline=False ) )
def process(context, inherited_space, inherited_lang, ttml_element): element = model.P(context.doc) # process attributes element.set_space( XMLSpaceAttribute.extract(ttml_element) or inherited_space) element.set_lang( XMLLangAttribute.extract(ttml_element) or inherited_lang) ContentElement.process_region_property(context, ttml_element, element) ContentElement.process_style_properties(context, ttml_element, element) # process children elements if ttml_element.text: element.push_child( SpanElement.make_anonymous_span(context.doc, ttml_element.text)) for ttml_child_element in ttml_element: child_element = ContentElement.process(context, element.get_space(), element.get_lang(), ttml_child_element) if child_element is not None: if not isinstance(child_element, (model.Span, model.Br, model.Ruby)): LOGGER.error( "Children of p must be span, br or ruby instances") else: element.push_child(child_element) if ttml_child_element.tail: element.push_child( SpanElement.make_anonymous_span(context.doc, ttml_child_element.tail)) return element
def test_body_only(self): doc = model.ContentDocument() body = model.Body(doc) div = model.Div(doc) p = model.P(doc) span = model.Span(doc) text = model.Text(doc) text.set_text("asdf") span.push_child(text) p.push_child(span) div.push_child(p) body.push_child(div) doc.set_body(body) # write the document out to a file imsc_writer.from_model(doc).write('build/BodyElement.out.ttml', encoding='utf-8', xml_declaration=True)
def test_tts_writing_no_extent_when_body_has_no_extents(self): doc = model.ContentDocument() body = model.Body(doc) div = model.Div(doc) p = model.P(doc) span = model.Span(doc) text = model.Text(doc) text.set_text("asdf") span.push_child(text) p.push_child(span) div.push_child(p) body.push_child(div) doc.set_body(body) tree_from_model = imsc_writer.from_model(doc) extent = tree_from_model.getroot().attrib.get( f"{{{imsc_styles.StyleProperties.Extent.ns}}}{imsc_styles.StyleProperties.Extent.local_name}" ) self.assertEqual(extent, None)
def setUp(self): self.doc = model.ContentDocument() a1 = model.DiscreteAnimationStep( style_property=styles.StyleProperties.Color, begin=None, end=None, value=styles.NamedColors.red.value ) a2 = model.DiscreteAnimationStep( style_property=styles.StyleProperties.Color, begin=Fraction(1), end=None, value=styles.NamedColors.green.value ) a3 = model.DiscreteAnimationStep( style_property=styles.StyleProperties.Color, begin=Fraction(2), end=None, value=styles.NamedColors.blue.value ) r1 = model.Region("r1", self.doc) self.doc.put_region(r1) # r2: sig times = {2, 9} r2 = model.Region("r2", self.doc) r2.set_begin(Fraction(2)) r2.set_end(Fraction(9)) r2.add_animation_step(a1) self.doc.put_region(r2) # b: sig times = {1, 10} b = model.Body(self.doc) b.set_begin(Fraction(1)) b.set_end(Fraction(10)) self.doc.set_body(b) # div1: offset = 1, sig times = {2, 4} div1 = model.Div(self.doc) div1.add_animation_step(a2) div1.set_begin(Fraction(3)) div1.set_region(r1) b.push_child(div1) # div2: offset = 1, sig times = {10} div2 = model.Div(self.doc) div2.set_end(Fraction(12)) div2.set_region(r2) b.push_child(div2) # p1: offset = 1, sig times = {} p1 = model.P(self.doc) div2.push_child(p1) # span1: offset = 1, sig times = {3} span1 = model.Span(self.doc) span1.add_animation_step(a3) p1.push_child(span1) t1 = model.Text(self.doc, "hello") span1.push_child(t1)
def test_push_child(self): r = model.Rtc() with self.assertRaises(RuntimeError): r.push_child(model.P())
def test_push_child(self): r = model.Region("hello") with self.assertRaises(RuntimeError): r.push_child(model.P())
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
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))