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
def from_model( _ctx: TTMLElement.WritingContext, model_doc: model.ContentDocument ) -> typing.Optional[et.Element]: '''Returns a TTML `styling` element using the information in the ContentDocument `model_doc`. `ctx` contains state information used in the process. ''' styling_element = None for style_prop, style_value in model_doc.iter_initial_values(): imsc_style_prop = imsc_styles.StyleProperties.BY_MODEL_PROP.get(style_prop) if imsc_style_prop is None: LOGGER.error("Unknown property") continue initial_element = InitialElement.from_model(imsc_style_prop, style_value) if initial_element is not None: if styling_element is None: styling_element = et.Element(StylingElement.qn) styling_element.append(initial_element) return styling_element
def from_model(model_doc: model.ContentDocument): layout_element = et.Element(LayoutElement.qn) for r in model_doc.iter_regions(): region_element = RegionElement.from_model(r) if region_element is not None: layout_element.append(region_element) return layout_element
def test_to_paragraph(self): caption_paragraph = SccCaptionParagraph() doc = ContentDocument() self.assertRaisesRegex(TypeError, "Element id must be a valid xml:id string", caption_paragraph.to_paragraph, doc) caption_paragraph.set_id("test-id") origin = caption_paragraph.get_origin() self.assertEqual(0, origin.x.value) self.assertEqual(0, origin.y.value) extent = caption_paragraph.get_extent() self.assertEqual(0, extent.width.value) self.assertEqual(0, extent.height.value) paragraph = caption_paragraph.to_paragraph(doc) self.assertEqual("test-id", paragraph.get_id()) self.assertEqual(doc, paragraph.get_doc()) self.assertIsNone(paragraph.get_begin()) self.assertIsNone(paragraph.get_end()) children = list(paragraph) self.assertEqual(0, len(children)) caption_paragraph.set_begin(SmpteTimeCode.parse("00:01:02:03", FPS_30)) caption_paragraph.set_end(SmpteTimeCode.parse("00:02:03:04", FPS_30)) caption_paragraph.set_cursor_at(0) caption_paragraph.new_caption_text() caption_paragraph.append_text("Hello") caption_paragraph.set_cursor_at(1) caption_paragraph.new_caption_text() caption_paragraph.append_text("World") paragraph = caption_paragraph.to_paragraph(doc) self.assertEqual("test-id", paragraph.get_id()) self.assertEqual(doc, paragraph.get_doc()) self.assertEqual(Fraction(1863, 30), paragraph.get_begin()) self.assertEqual(Fraction(3694, 30), paragraph.get_end()) children = list(paragraph) self.assertEqual(3, len(children)) self.assertIsInstance(children[0], Span) self.assertEqual("Hello", list(children[0])[0].get_text()) self.assertIsInstance(children[1], Br) self.assertIsInstance(children[2], Span) self.assertEqual("World", list(children[2])[0].get_text())
def to_model(scc_content: str): """Converts a SCC document to the data model""" context = _SccContext() document = ContentDocument() # Safe area must be a 32x15 grid, that represents 80% of the root area root_cell_resolution = CellResolutionType(rows=19, columns=40) document.set_cell_resolution(root_cell_resolution) context.set_safe_area(int((root_cell_resolution.columns - 32) / 2), int((root_cell_resolution.rows - 15) / 2)) body = Body() body.set_doc(document) document.set_body(body) context.div = Div() context.div.set_doc(document) body.push_child(context.div) time_code = None for line in scc_content.splitlines(): LOGGER.debug(line) scc_line = SccLine.from_str(line) if scc_line is None: continue time_code = scc_line.to_model(context) context.flush(time_code) return document
def _has_document_paragraphs(self, doc: ContentDocument) -> bool: body = doc.get_body() if body is None: return False paragraphs = False for child in body: if self._has_child_paragraphs(child): return True return paragraphs
def __init__(self, paragraph: SccCaptionParagraph, doc: ContentDocument): self._paragraph = paragraph self._doc = doc cell_resolution = doc.get_cell_resolution() x_offset = self._paragraph.get_safe_area_x_offset() y_offset = self._paragraph.get_safe_area_y_offset() self._left = x_offset self._top = y_offset self._right = cell_resolution.columns - x_offset # Add 1 cell to bottom since the cursor height is 1 self._bottom = cell_resolution.rows - y_offset + 1
def from_model( ctx: TTMLElement.WritingContext, model_doc: model.ContentDocument, ) -> typing.Optional[et.Element]: '''Returns a TTML `layout` element (an XML element) using the information in the ContentDocument `model_doc`. `ctx` contains state information used in the process. ''' layout_element = None for r in model_doc.iter_regions(): region_element = RegionElement.from_model(ctx, r) if region_element is not None: if layout_element is None: layout_element = et.Element(LayoutElement.qn) layout_element.append(region_element) return layout_element
def test_region_prefix(self): doc = ContentDocument() caption_paragraph = SccCaptionParagraph() paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertEqual("region", paragraph_region._get_region_prefix()) caption_paragraph = SccCaptionParagraph( caption_style=SccCaptionStyle.PaintOn) paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertEqual("paint", paragraph_region._get_region_prefix()) caption_paragraph = SccCaptionParagraph( caption_style=SccCaptionStyle.PopOn) paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertEqual("pop", paragraph_region._get_region_prefix()) caption_paragraph = SccCaptionParagraph( caption_style=SccCaptionStyle.RollUp) paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertEqual("rollup", paragraph_region._get_region_prefix())
def from_model( model_doc: model.ContentDocument) -> typing.Optional[et.Element]: styling_element = None for style_prop, style_value in model_doc.iter_initial_values(): if styling_element is None: styling_element = et.Element(StylingElement.qn) imsc_style_prop = imsc_styles.StyleProperties.BY_MODEL_PROP.get( style_prop) if imsc_style_prop is None: LOGGER.error("Unknown property") continue initial_element = InitialElement.from_model( imsc_style_prop, style_value) if initial_element is not None: styling_element.append(initial_element) return styling_element
def to_model(scc_content: str, config: Optional[SccReaderConfiguration] = None, progress_callback=lambda _: None): """Converts a SCC document to the data model""" context = _SccContext(config) document = ContentDocument() # Safe area must be a 32x15 grid, that represents 80% of the root area root_cell_resolution = CellResolutionType( rows=SCC_ROOT_CELL_RESOLUTION_ROWS, columns=SCC_ROOT_CELL_RESOLUTION_COLUMNS) document.set_cell_resolution(root_cell_resolution) context.set_safe_area( int((root_cell_resolution.columns - SCC_SAFE_AREA_CELL_RESOLUTION_COLUMNS) / 2), int((root_cell_resolution.rows - SCC_SAFE_AREA_CELL_RESOLUTION_ROWS) / 2)) # The active area is equivalent to the safe area active_area = ActiveAreaType( left_offset=context.safe_area_x_offset / root_cell_resolution.columns, top_offset=context.safe_area_y_offset / root_cell_resolution.rows, width=(root_cell_resolution.columns - (context.safe_area_x_offset * 2)) / root_cell_resolution.columns, height=(root_cell_resolution.rows - (context.safe_area_y_offset * 2)) / root_cell_resolution.rows, ) document.set_active_area(active_area) body = Body() body.set_doc(document) document.set_body(body) # the default value of LineHeight ("normal") typically translates to 125% of the font size, which causes regions to overflow. body.set_style(StyleProperties.LineHeight, LengthType(value=100, units=LengthType.Units.pct)) # use a more readable font than the default Courier body.set_style(StyleProperties.FontFamily, ("Consolas", "Monaco", GenericFontFamilyType.monospace)) # add line padding body.set_style(StyleProperties.LinePadding, LengthType(value=0.25, units=LengthType.Units.c)) context.div = Div() context.div.set_doc(document) body.push_child(context.div) lines = scc_content.splitlines() nb_lines = len(lines) for (index, line) in enumerate(lines): LOGGER.debug(line) scc_line = SccLine.from_str(line) progress_callback((index + 1) / nb_lines) if scc_line is None: continue context.process_line(scc_line) context.flush() return document
def test_srt_writer(self): doc = ContentDocument() r1 = Region("r1", doc) doc.put_region(r1) r2 = Region("r2", doc) r2.set_begin(Fraction(2)) r2.set_end(Fraction(4)) doc.put_region(r2) body = Body(doc) doc.set_body(body) div = Div(doc) body.push_child(div) p = P(doc) p.set_region(r1) p.set_end(Fraction(2)) div.push_child(p) span = Span(doc) span.push_child(Text(doc, "Lorem ipsum dolor sit amet,")) p.push_child(span) p = P(doc) p.set_region(r2) div.push_child(p) span = Span(doc) span.push_child(Text(doc, "consectetur adipiscing elit.")) p.push_child(span) p = P(doc) p.set_region(r1) p.set_begin(Fraction(4)) p.set_end(Fraction(6)) div.push_child(p) span = Span(doc) span.push_child( Text(doc, "Pellentesque interdum lacinia sollicitudin.")) p.push_child(span) expected_srt = """1 00:00:00,000 --> 00:00:02,000 Lorem ipsum dolor sit amet, 2 00:00:02,000 --> 00:00:04,000 consectetur adipiscing elit. 3 00:00:04,000 --> 00:00:06,000 Pellentesque interdum lacinia sollicitudin. """ srt_from_model = srt_writer.from_model(doc) self.assertEqual(expected_srt, srt_from_model)
def test_process_isd(self): supported_style_properties = SupportedStylePropertiesFilter({ StyleProperties.BackgroundColor: [NamedColors.red.value], StyleProperties.Extent: [] }) doc = ContentDocument() r1 = Region("r1", doc) r1.set_style(StyleProperties.BackgroundColor, NamedColors.red.value) r1.set_style(StyleProperties.LuminanceGain, 2.0) doc.put_region(r1) b = Body(doc) b.set_begin(Fraction(1)) b.set_end(Fraction(10)) doc.set_body(b) div1 = Div(doc) div1.set_region(r1) b.push_child(div1) p1 = P(doc) p1.set_style(StyleProperties.BackgroundColor, NamedColors.white.value) p1.set_style(StyleProperties.Direction, DirectionType.rtl) div1.push_child(p1) span1 = Span(doc) span1.set_style(StyleProperties.BackgroundColor, NamedColors.red.value) span1.set_style(StyleProperties.FontStyle, FontStyleType.italic) span1.set_style(StyleProperties.Direction, DirectionType.ltr) p1.push_child(span1) t1 = Text(doc, "hello") span1.push_child(t1) significant_times = sorted(ISD.significant_times(doc)) self.assertEqual(3, len(significant_times)) isd = ISD.from_model(doc, significant_times[1]) r1 = isd.get_region("r1") self.assertEqual(len(Region._applicableStyles), len(r1._styles)) self.assertEqual(NamedColors.red.value, r1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(2.0, r1.get_style(StyleProperties.LuminanceGain)) body1 = list(r1)[0] div1 = list(body1)[0] p1 = list(div1)[0] span1 = list(p1)[0] self.assertEqual(len(P._applicableStyles), len(p1._styles)) self.assertEqual(NamedColors.white.value, p1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(DirectionType.rtl, p1.get_style(StyleProperties.Direction)) self.assertEqual(len(Span._applicableStyles), len(span1._styles)) self.assertEqual(NamedColors.red.value, span1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(FontStyleType.italic, span1.get_style(StyleProperties.FontStyle)) self.assertEqual(DirectionType.ltr, span1.get_style(StyleProperties.Direction)) supported_style_properties.process(isd) self.assertEqual(2, len(r1._styles)) self.assertEqual(NamedColors.red.value, r1.get_style(StyleProperties.BackgroundColor)) self.assertEqual( ExtentType(height=LengthType(value=0.0, units=LengthType.Units.rh), width=LengthType(value=0.0, units=LengthType.Units.rw)), r1.get_style(StyleProperties.Extent)) self.assertEqual(0, len(p1._styles)) self.assertIsNone(p1.get_style(StyleProperties.BackgroundColor)) self.assertIsNone(p1.get_style(StyleProperties.Direction)) self.assertEqual(1, len(span1._styles)) self.assertEqual(NamedColors.red.value, span1.get_style(StyleProperties.BackgroundColor)) self.assertIsNone(span1.get_style(StyleProperties.FontStyle)) self.assertIsNone(span1.get_style(StyleProperties.Direction))
def test_matching_region(self): doc = ContentDocument() doc_columns = 40 doc_rows = 19 doc.set_cell_resolution( CellResolutionType(rows=doc_rows, columns=doc_columns)) safe_area_x_offset = 4 safe_area_y_offset = 2 caption_paragraph = SccCaptionParagraph(safe_area_x_offset, safe_area_y_offset) caption_paragraph.set_cursor_at(4, 7) caption_paragraph.new_caption_text() caption_paragraph.get_current_text().append("A 20-char long line.") origin = caption_paragraph.get_origin() self.assertEqual(11, origin.x.value) self.assertEqual(5, origin.y.value) extent = caption_paragraph.get_extent() self.assertEqual(20, extent.width.value) self.assertEqual(1, extent.height.value) paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertIsNone(paragraph_region._find_matching_region()) region = paragraph_region._create_matching_region() self.assertEqual("region1", region.get_id()) self.assertTrue(paragraph_region._has_same_origin_as_region(region)) self.assertEqual(region, paragraph_region._find_matching_region()) self.assertEqual(ShowBackgroundType.whenActive, region.get_style(StyleProperties.ShowBackground)) region_extent = region.get_style(StyleProperties.Extent) self.assertEqual(50, region_extent.width.value) self.assertEqual(63, region_extent.height.value) caption_paragraph.set_cursor_at(5, 7) caption_paragraph.new_caption_text() caption_paragraph.get_current_text().append( "This is another 34-char long line.") origin = caption_paragraph.get_origin() self.assertEqual(11, origin.x.value) self.assertEqual(5, origin.y.value) extent = caption_paragraph.get_extent() self.assertEqual(34, extent.width.value) self.assertEqual(2, extent.height.value) paragraph_region = _SccParagraphRegion(caption_paragraph, doc) self.assertEqual(region, paragraph_region._find_matching_region()) paragraph_region._extend_region_to_paragraph(region) self.assertTrue(paragraph_region._has_same_origin_as_region(region)) region_extent = region.get_style(StyleProperties.Extent) self.assertEqual(62, region_extent.width.value) self.assertEqual(63, region_extent.height.value)
def from_model(model_doc: model.ContentDocument) -> et.Element: tt_element = et.Element(TTElement.qn) imsc_attr.XMLLangAttribute.set(tt_element, model_doc.get_lang()) if model_doc.get_cell_resolution() != model.CellResolutionType( rows=15, columns=32): imsc_attr.CellResolutionAttribute.set( tt_element, model_doc.get_cell_resolution()) has_px = False all_elements = list(model_doc.iter_regions()) if model_doc.get_body() is not None: all_elements.extend(model_doc.get_body().dfs_iterator()) for element in all_elements: for model_style_prop in element.iter_styles(): if StyleProperties.BY_MODEL_PROP[model_style_prop].has_px( element.get_style(model_style_prop)): has_px = True break if has_px: break if model_doc.get_px_resolution() is not None and has_px: imsc_attr.ExtentAttribute.set(tt_element, model_doc.get_px_resolution()) if model_doc.get_active_area() is not None: imsc_attr.ActiveAreaAttribute.set(tt_element, model_doc.get_active_area()) # Write the <head> section first head_element = HeadElement.from_model(model_doc) if head_element is not None: tt_element.append(head_element) model_body = model_doc.get_body() if model_body is not None: body_element = BodyElement.from_model(model_body) if body_element is not None: tt_element.append(body_element) return tt_element
def from_model( model_doc: model.ContentDocument, frame_rate: typing.Optional[Fraction], time_expression_syntax: imsc_attr.TimeExpressionSyntaxEnum, progress_callback: typing.Callable[[numbers.Real], typing.NoReturn] ) -> et.Element: '''Converts the data model to an IMSC document contained in an ElementTree Element''' ctx = TTMLElement.WritingContext(frame_rate, time_expression_syntax) tt_element = et.Element(TTElement.qn) imsc_attr.XMLLangAttribute.set(tt_element, model_doc.get_lang()) if model_doc.get_cell_resolution() != model.CellResolutionType(rows=15, columns=32): imsc_attr.CellResolutionAttribute.set(tt_element, model_doc.get_cell_resolution()) has_px = False all_elements = list(model_doc.iter_regions()) if model_doc.get_body() is not None: all_elements.extend(model_doc.get_body().dfs_iterator()) for element in all_elements: for model_style_prop in element.iter_styles(): if StyleProperties.BY_MODEL_PROP[model_style_prop].has_px(element.get_style(model_style_prop)): has_px = True break for animation_step in element.iter_animation_steps(): if StyleProperties.BY_MODEL_PROP[animation_step.style_property].has_px(animation_step.value): has_px = True break if has_px: break if model_doc.get_px_resolution() is not None and has_px: imsc_attr.ExtentAttribute.set(tt_element, model_doc.get_px_resolution()) if model_doc.get_active_area() is not None: imsc_attr.ActiveAreaAttribute.set(tt_element, model_doc.get_active_area()) if model_doc.get_display_aspect_ratio() is not None: imsc_attr.DisplayAspectRatioAttribute.set(tt_element, model_doc.get_display_aspect_ratio()) if frame_rate is not None: imsc_attr.FrameRateAttribute.set(tt_element, frame_rate) # Write the <head> section first head_element = HeadElement.from_model(ctx, model_doc) progress_callback(0.5) if head_element is not None: tt_element.append(head_element) model_body = model_doc.get_body() if model_body is not None: body_element = BodyElement.from_model(ctx, model_body) if body_element is not None: tt_element.append(body_element) progress_callback(1.0) return tt_element
def test_process_isd(self): default_style_value_filter = DefaultStylePropertyValuesFilter({ StyleProperties.BackgroundColor: NamedColors.red.value, StyleProperties.Direction: DirectionType.ltr }) doc = ContentDocument() r1 = Region("r1", doc) r1.set_style(StyleProperties.BackgroundColor, NamedColors.red.value) r1.set_style(StyleProperties.LuminanceGain, 2.0) doc.put_region(r1) b = Body(doc) b.set_begin(Fraction(1)) b.set_end(Fraction(10)) doc.set_body(b) div1 = Div(doc) div1.set_region(r1) b.push_child(div1) p1 = P(doc) p1.set_style(StyleProperties.BackgroundColor, NamedColors.white.value) p1.set_style(StyleProperties.Direction, DirectionType.rtl) div1.push_child(p1) span1 = Span(doc) span1.set_style(StyleProperties.BackgroundColor, NamedColors.red.value) span1.set_style(StyleProperties.FontStyle, FontStyleType.italic) span1.set_style(StyleProperties.Direction, DirectionType.ltr) p1.push_child(span1) t1 = Text(doc, "hello") span1.push_child(t1) significant_times = sorted(ISD.significant_times(doc)) self.assertEqual(3, len(significant_times)) isd = ISD.from_model(doc, significant_times[1]) r1 = isd.get_region("r1") self.assertEqual(len(Region._applicableStyles), len(r1._styles)) self.assertEqual(NamedColors.red.value, r1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(2.0, r1.get_style(StyleProperties.LuminanceGain)) body1 = list(r1)[0] div1 = list(body1)[0] p1 = list(div1)[0] span1 = list(p1)[0] self.assertEqual(len(P._applicableStyles), len(p1._styles)) self.assertEqual(NamedColors.white.value, p1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(DirectionType.rtl, p1.get_style(StyleProperties.Direction)) self.assertEqual(len(Span._applicableStyles), len(span1._styles)) self.assertEqual(NamedColors.red.value, span1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(FontStyleType.italic, span1.get_style(StyleProperties.FontStyle)) self.assertEqual(DirectionType.ltr, span1.get_style(StyleProperties.Direction)) default_style_value_filter.process(isd) self.assertEqual(len(Region._applicableStyles) - 1, len(r1._styles)) self.assertIsNone(r1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(len(P._applicableStyles), len(p1._styles)) self.assertEqual(NamedColors.white.value, p1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(DirectionType.rtl, p1.get_style(StyleProperties.Direction)) self.assertEqual(len(Span._applicableStyles) - 1, len(span1._styles)) self.assertIsNone(span1.get_style(StyleProperties.BackgroundColor)) self.assertEqual(FontStyleType.italic, span1.get_style(StyleProperties.FontStyle)) self.assertEqual(DirectionType.ltr, span1.get_style(StyleProperties.Direction))