def paint_on_active_caption(self, time_code: SmpteTimeCode): """Initialize active caption for paint-on style""" active_style = SccCaptionStyle.PaintOn copied_lines = [] cursor = self.active_cursor if self.has_active_caption(): active_style = self.active_caption.get_caption_style() cursor = self.active_caption.get_cursor() # Copy buffered lines copied_lines = self.active_caption.copy_lines() # Push active to model if there is one self.push_active_caption_to_model(time_code) # Initialize new buffered caption self.active_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, active_style) self.initialize_active_caption(time_code) if len(copied_lines) > 0: # Set remaining lines to the new buffered caption self.active_caption.set_lines(copied_lines) self.active_caption.set_cursor_at(cursor[0], cursor[1])
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 process_preamble_address_code(self, pac: SccPreambleAddressCode, time_code: SccTimeCode): """Processes SCC Preamble Address Code it to the map to model""" if self.current_caption is None: raise ValueError("No current SCC caption initialized") pac_row = pac.get_row() pac_indent = pac.get_indent() if self.current_caption.get_caption_style() is SccCaptionStyle.PaintOn: if self.current_caption.get_current_text() is not None \ and self.safe_area_y_offset + pac_row == self.current_caption.get_row_offset() + 1: # Creates a new Paragraph if the new caption is contiguous (Paint-On) self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PaintOn) self.init_current_caption(time_code) elif len(self.previous_captions) > 0 and self.previous_captions[0].get_current_text() is not None \ and self.safe_area_y_offset + pac_row == self.previous_captions[0].get_row_offset(): # Pushes and erases displayed row so that it can be replaced by current row (Paint-On) self.push_previous_caption(self.current_caption.get_begin()) self.current_caption.new_caption_text() if self.current_caption.get_caption_style() is SccCaptionStyle.RollUp: # Ignore PACs for rows 5-11, but get indent from PACs for rows 1-4 and 12-15. (Roll-Up) if pac_row in range(5, 12): self.current_caption.apply_current_text_offsets() return self.current_caption.set_column_offset(pac_indent) else: self.current_caption.set_row_offset(pac_row) self.current_caption.set_column_offset(pac_indent) self.current_caption.get_current_text().add_style_property(StyleProperties.Color, pac.get_color()) self.current_caption.get_current_text().add_style_property(StyleProperties.FontStyle, pac.get_font_style()) self.current_caption.get_current_text().add_style_property(StyleProperties.TextDecoration, pac.get_text_decoration()) self.current_caption.apply_current_text_offsets()
class _SccContext: def __init__(self, config: Optional[SccReaderConfiguration] = None): # Caption paragraphs container self.div: Optional[Div] = None # Caption paragraphs counter self.count: int = 0 # Screen safe area offsets self.safe_area_x_offset: int = 0 self.safe_area_y_offset: int = 0 # Previously read SCC word value self.previous_code = 0 self.previous_code_type = None # Buffered caption being built self.buffered_caption: Optional[SccCaptionParagraph] = None # Captions being displayed self.active_caption: Optional[SccCaptionParagraph] = None # Caption style (Pop-on, Roll-up, Paint-on) currently processed self.current_style: Optional[SccCaptionStyle] = None # Roll-up caption number of lines self.roll_up_depth: int = 0 # Cursor position in the active area self.active_cursor: (int, int) = (0, 0) self.current_text_decoration = None self.current_color = None self.current_font_style = None # Text alignment self.text_alignment = TextAlignment.AUTO if config is None else config.text_align def set_safe_area(self, safe_area_x_offset: int, safe_area_y_offset: int): """Sets the safe area""" self.safe_area_x_offset = safe_area_x_offset self.safe_area_y_offset = safe_area_y_offset def has_active_caption(self) -> bool: """Returns whether captions are being displayed or not""" return self.active_caption is not None def set_buffered_caption_begin_time(self, time_code: SmpteTimeCode): """Initializes the current buffered caption with begin time""" if self.buffered_caption is not None: self.buffered_caption.set_begin(time_code) def initialize_active_caption(self, begin_time_code: SmpteTimeCode): """Initializes the current active caption with id and begin time""" if self.active_caption is not None: if not self.active_caption.get_id(): self.count += 1 self.active_caption.set_id("caption" + str(self.count)) self.active_caption.set_begin(begin_time_code) def push_buffered_to_active_captions(self): """Send the current buffered caption to the active captions list""" if self.buffered_caption is not None and self.buffered_caption.get_current_text( ): if not self.buffered_caption.get_id(): self.count += 1 self.buffered_caption.set_id("caption" + str(self.count)) self.active_caption = self.buffered_caption self.buffered_caption = None def flip_buffered_to_active_captions( self, time_code: Optional[SmpteTimeCode] = None): """ Flip the current buffered caption with the last active captions list, and push to model if an end time code is specified. """ temporary_caption = None if self.has_active_caption(): temporary_caption = self.active_caption if time_code is not None: # End of display of active captions if self.has_active_caption(): self.push_active_caption_to_model(time_code) self.push_buffered_to_active_captions() if temporary_caption is not None: self.buffered_caption = temporary_caption def push_active_caption_to_model(self, time_code: SmpteTimeCode, clear_active_caption: bool = True): """Sets end time to the last active caption, and pushes it into the data model""" if self.has_active_caption(): self.active_cursor = self.active_caption.get_cursor() previous_caption = self.active_caption previous_caption.set_end(time_code) if clear_active_caption: self.active_caption = None self.div.push_child( previous_caption.to_paragraph(self.div.get_doc())) def paint_on_active_caption(self, time_code: SmpteTimeCode): """Initialize active caption for paint-on style""" active_style = SccCaptionStyle.PaintOn copied_lines = [] cursor = self.active_cursor if self.has_active_caption(): active_style = self.active_caption.get_caption_style() cursor = self.active_caption.get_cursor() # Copy buffered lines copied_lines = self.active_caption.copy_lines() # Push active to model if there is one self.push_active_caption_to_model(time_code) # Initialize new buffered caption self.active_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, active_style) self.initialize_active_caption(time_code) if len(copied_lines) > 0: # Set remaining lines to the new buffered caption self.active_caption.set_lines(copied_lines) self.active_caption.set_cursor_at(cursor[0], cursor[1]) def process_preamble_address_code(self, pac: SccPreambleAddressCode, time_code: SmpteTimeCode): """Processes SCC Preamble Address Code it to the map to model""" pac_row = pac.get_row() pac_indent = pac.get_indent() if self.current_style is SccCaptionStyle.PaintOn: self.paint_on_active_caption(time_code) if self.active_caption.get_caption_style( ) is SccCaptionStyle.PaintOn: # Clear target row on Paint-On style target_row = self.active_caption.get_lines().get(pac_row) if target_row is not None: target_row.clear() self.active_caption.set_cursor_at(pac_row, pac_indent) if self.active_caption.get_current_text() is None: self.active_caption.new_caption_text() elif self.current_style is SccCaptionStyle.RollUp: if not self.has_active_caption(): # If there is no current active caption, initialize an empty new paragraph self.active_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.initialize_active_caption(time_code) # Ignore PACs for rows 5-11, but get indent from PACs for rows 1-4 and 12-15. (Roll-Up) if pac_row in range(5, 12): self.active_caption.set_cursor_at(ROLL_UP_BASE_ROW) self.active_caption.new_caption_text() return # Force roll-up paragraph to belong to the same region self.active_caption.set_cursor_at(ROLL_UP_BASE_ROW, pac_indent) self.active_caption.new_caption_text() else: # Pop-On Style if self.buffered_caption is None: self.buffered_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PopOn) # set cursor in paragraph and create line or text if necessary self.buffered_caption.set_cursor_at(pac_row, pac_indent) self.buffered_caption.new_caption_text() self.current_color = pac.get_color() self.current_font_style = pac.get_font_style() self.current_text_decoration = pac.get_text_decoration() if self.has_active_caption(): self.active_cursor = self.active_caption.get_cursor() def process_mid_row_code(self, mid_row_code: SccMidRowCode, time_code: SmpteTimeCode): """Processes SCC Mid-Row Code to map it to the model""" # If the Paint-On or Roll-Up style is activated, write directly on active caption processed_caption = self.buffered_caption if self.current_style in (SccCaptionStyle.PaintOn, SccCaptionStyle.RollUp): processed_caption = self.active_caption if processed_caption is None: raise ValueError("No current SCC caption initialized") color = mid_row_code.get_color() font_style = mid_row_code.get_font_style() text_decoration = mid_row_code.get_text_decoration() if self.previous_code_type is not SccMidRowCode: # In case of multiple mid-row codes, move right only after the first code # If there is already text on the current line if processed_caption.get_current_text() is not None \ and processed_caption.get_current_text().get_text() != "": # In case of paint-on replacing text if self.current_style is SccCaptionStyle.PaintOn \ and processed_caption.get_current_line().get_cursor() < processed_caption.get_current_line().get_length(): processed_caption.append_text(" ") else: if text_decoration is None: processed_caption.new_caption_text() processed_caption.append_text(" ") else: processed_caption.append_text(" ") processed_caption.new_caption_text() else: processed_caption.append_text(" ") self.current_color = color self.current_font_style = font_style self.current_text_decoration = text_decoration else: if color is not None: self.current_color = color if font_style is not None: self.current_font_style = font_style if text_decoration is not None: self.current_text_decoration = text_decoration processed_caption.append_text(" ") processed_caption.new_caption_text() if processed_caption.get_caption_style() is SccCaptionStyle.PaintOn: processed_caption.get_current_text().set_begin(time_code) def process_attribute_code(self, attribute_code: SccAttributeCode): """Processes SCC Attribute Code to map it to the model""" # If the Paint-On or Roll-Up style is activated, write directly on active caption processed_caption = self.buffered_caption if self.current_style in (SccCaptionStyle.PaintOn, SccCaptionStyle.RollUp): processed_caption = self.active_caption if processed_caption is None or processed_caption.get_current_text( ) is None: raise ValueError("No current SCC caption nor content initialized") if processed_caption.get_current_text( ) is not None and processed_caption.get_current_text().get_text(): processed_caption.new_caption_text() if attribute_code.is_background(): processed_caption.get_current_text().add_style_property( StyleProperties.BackgroundColor, attribute_code.get_color()) else: processed_caption.get_current_text().add_style_property( StyleProperties.Color, attribute_code.get_color()) processed_caption.get_current_text().add_style_property( StyleProperties.TextDecoration, attribute_code.get_text_decoration()) def process_control_code(self, control_code: SccControlCode, time_code: SmpteTimeCode): """Processes SCC Control Code to map it to the model""" processed_caption = self.buffered_caption if control_code is SccControlCode.RCL: # Start a new Pop-On caption self.current_style = SccCaptionStyle.PopOn elif control_code is SccControlCode.RDC: # Start a new Paint-On caption self.current_style = SccCaptionStyle.PaintOn elif control_code in (SccControlCode.RU2, SccControlCode.RU3, SccControlCode.RU4): # Start a new Roll-Up caption self.current_style = SccCaptionStyle.RollUp if control_code is SccControlCode.RU2: self.roll_up_depth = 2 elif control_code is SccControlCode.RU3: self.roll_up_depth = 3 elif control_code is SccControlCode.RU4: self.roll_up_depth = 4 else: # If the Paint-On or Roll-Up style is activated, write directly on active caption if self.current_style in (SccCaptionStyle.PaintOn, SccCaptionStyle.RollUp): processed_caption = self.active_caption if control_code is SccControlCode.EOC: # Display caption (Pop-On) self.set_buffered_caption_begin_time(time_code) self.flip_buffered_to_active_captions(time_code) if self.has_active_caption(): # Set text alignment if self.text_alignment == TextAlignment.AUTO: text_alignment = self.active_caption.guess_text_alignment() else: text_alignment = self.text_alignment.text_align # Apply text alignment self.active_caption.add_style_property( StyleProperties.TextAlign, text_alignment) elif control_code is SccControlCode.EDM: # Erase displayed captions if self.has_active_caption(): if time_code is not None: # End time is exclusive in the model, set it to the next frame end_time_code = copy.copy(time_code) end_time_code.add_frames() else: end_time_code = time_code self.push_active_caption_to_model(end_time_code) elif control_code is SccControlCode.ENM: # Erase buffered caption self.buffered_caption = None elif control_code is SccControlCode.TO1: processed_caption.indent_cursor(1) elif control_code is SccControlCode.TO2: processed_caption.indent_cursor(2) elif control_code is SccControlCode.TO3: processed_caption.indent_cursor(3) elif control_code is SccControlCode.CR: # Roll the displayed caption up one row (Roll-Up) if self.has_active_caption(): # Push active caption to model (but don't erase it) self.push_active_caption_to_model(time_code, False) # Roll the active caption up self.active_caption.roll_up() # Get the remaining lines to initialize the following caption with the expected depth previous_lines = self.active_caption.get_last_caption_lines( self.roll_up_depth - 1) # Initialize the new caption with the previous lines self.active_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.initialize_active_caption(time_code) self.active_caption.set_lines(previous_lines) self.active_caption.set_cursor_at(self.active_cursor[0], self.active_cursor[1]) elif control_code is SccControlCode.DER: # Delete to End of Row (Paint-On) # The DER may be issued from any point on a row to delete all displayable characters, transparent # spaces, and mid-row codes from (and including) the current cell to the end of the row. # Not used in this implementation since this SCC reader does not map the text overlapping into # the model (i.e. a row is erased when a PAC is received, so before a new caption is written onto it). pass elif control_code is SccControlCode.BS: # Backspace # When a Backspace is received, the cursor moves to the left one column position erasing # the character or Mid-Row Code occupying that location, unless the cursor is in Column 1 processed_caption.get_current_text().backspace() def process_text(self, word: str, time_code: SmpteTimeCode): """Processes SCC text words""" if self.current_style is SccCaptionStyle.PaintOn: if word.startswith(" "): if self.active_caption.get_caption_style( ) is not SccCaptionStyle.PaintOn: self.paint_on_active_caption(time_code) self.active_caption.append_text(word) else: self.active_caption.new_caption_text() self.active_caption.append_text(word) self.active_caption.get_current_text().set_begin(time_code) elif word.endswith(" "): self.active_caption.append_text(word) if self.active_caption.get_caption_style( ) is not SccCaptionStyle.PaintOn: self.paint_on_active_caption(time_code) else: self.active_caption.new_caption_text() self.active_caption.get_current_text().set_begin(time_code) else: if not self.has_active_caption(): self.paint_on_active_caption(time_code) self.active_caption.append_text(word) self.active_caption.get_current_text().add_style_property( StyleProperties.Color, self.current_color) self.active_caption.get_current_text().add_style_property( StyleProperties.FontStyle, self.current_font_style) self.active_caption.get_current_text().add_style_property( StyleProperties.TextDecoration, self.current_text_decoration) elif self.current_style is SccCaptionStyle.RollUp: self.active_caption.append_text(word) self.active_caption.get_current_text().add_style_property( StyleProperties.Color, self.current_color) self.active_caption.get_current_text().add_style_property( StyleProperties.FontStyle, self.current_font_style) self.active_caption.get_current_text().add_style_property( StyleProperties.TextDecoration, self.current_text_decoration) else: self.buffered_caption.append_text(word) self.buffered_caption.get_current_text().add_style_property( StyleProperties.Color, self.current_color) self.buffered_caption.get_current_text().add_style_property( StyleProperties.FontStyle, self.current_font_style) self.buffered_caption.get_current_text().add_style_property( StyleProperties.TextDecoration, self.current_text_decoration) if self.has_active_caption(): self.active_cursor = self.active_caption.get_cursor() def flush(self, time_code: Optional[SmpteTimeCode] = None): """Flushes the remaining current caption""" if self.has_active_caption(): self.push_active_caption_to_model(time_code) if self.buffered_caption is not None: # Remove the buffered caption self.buffered_caption = None def process_line(self, line: SccLine) -> SmpteTimeCode: """Converts the SCC line to the data model""" debug = str(line.time_code) + "\t" for scc_word in line.scc_words: if self.previous_code == scc_word.value: continue line.time_code.add_frames() if scc_word.value == 0x0000: continue if scc_word.byte_1 < 0x20: control_code = SccControlCode.find(scc_word.value) if control_code is not None \ and control_code is SccControlCode.find(self.previous_code): # Skip duplicated control code from 'Field 2' line.time_code.add_frames(-1) continue attribute_code = SccAttributeCode.find(scc_word.value) mid_row_code = SccMidRowCode.find(scc_word.value) pac = SccPreambleAddressCode.find(scc_word.byte_1, scc_word.byte_2) spec_char = SccSpecialCharacter.find(scc_word.value) extended_char = SccExtendedCharacter.find(scc_word.value) if pac is not None: debug += "[PAC|" + str(pac.get_row()) + "|" + str( pac.get_indent()) if pac.get_color() is not None: debug += "|" + str(pac.get_color()) if pac.get_font_style() is not None: debug += "|I" if pac.get_text_decoration() is not None: debug += "|U" debug += "/" + hex(scc_word.value) + "]" self.process_preamble_address_code(pac, line.time_code) self.previous_code_type = type(pac) elif attribute_code is not None: debug += "[ATC/" + hex(scc_word.value) + "]" self.process_attribute_code(attribute_code) self.previous_code_type = type(attribute_code) elif mid_row_code is not None: debug += "[MRC|" + mid_row_code.get_name() + "/" + hex( scc_word.value) + "]" self.process_mid_row_code(mid_row_code, line.time_code) self.previous_code_type = type(mid_row_code) elif control_code is not None: debug += "[CC|" + control_code.get_name() + "/" + hex( scc_word.value) + "]" self.process_control_code(control_code, line.time_code) self.previous_code_type = type(control_code) elif spec_char is not None: word = spec_char.get_unicode_value() debug += word self.process_text(word, line.time_code) self.previous_code_type = type(spec_char) elif extended_char is not None: if self.current_style in (SccCaptionStyle.PaintOn, SccCaptionStyle.RollUp): self.active_caption.get_current_text().backspace() else: self.buffered_caption.get_current_text().backspace() word = extended_char.get_unicode_value() debug += word self.process_text(word, line.time_code) self.previous_code_type = type(extended_char) else: debug += "[??/" + hex(scc_word.value) + "]" LOGGER.warning("Unsupported SCC word: %s", hex(scc_word.value)) self.previous_code_type = None else: word = scc_word.to_text() debug += word self.process_text(word, line.time_code) self.previous_code_type = str self.previous_code = scc_word.value LOGGER.debug(debug) return line.time_code
def process_control_code(self, control_code: SccControlCode, time_code: SmpteTimeCode): """Processes SCC Control Code to map it to the model""" processed_caption = self.buffered_caption if control_code is SccControlCode.RCL: # Start a new Pop-On caption self.current_style = SccCaptionStyle.PopOn elif control_code is SccControlCode.RDC: # Start a new Paint-On caption self.current_style = SccCaptionStyle.PaintOn elif control_code in (SccControlCode.RU2, SccControlCode.RU3, SccControlCode.RU4): # Start a new Roll-Up caption self.current_style = SccCaptionStyle.RollUp if control_code is SccControlCode.RU2: self.roll_up_depth = 2 elif control_code is SccControlCode.RU3: self.roll_up_depth = 3 elif control_code is SccControlCode.RU4: self.roll_up_depth = 4 else: # If the Paint-On or Roll-Up style is activated, write directly on active caption if self.current_style in (SccCaptionStyle.PaintOn, SccCaptionStyle.RollUp): processed_caption = self.active_caption if control_code is SccControlCode.EOC: # Display caption (Pop-On) self.set_buffered_caption_begin_time(time_code) self.flip_buffered_to_active_captions(time_code) if self.has_active_caption(): # Set text alignment if self.text_alignment == TextAlignment.AUTO: text_alignment = self.active_caption.guess_text_alignment() else: text_alignment = self.text_alignment.text_align # Apply text alignment self.active_caption.add_style_property( StyleProperties.TextAlign, text_alignment) elif control_code is SccControlCode.EDM: # Erase displayed captions if self.has_active_caption(): if time_code is not None: # End time is exclusive in the model, set it to the next frame end_time_code = copy.copy(time_code) end_time_code.add_frames() else: end_time_code = time_code self.push_active_caption_to_model(end_time_code) elif control_code is SccControlCode.ENM: # Erase buffered caption self.buffered_caption = None elif control_code is SccControlCode.TO1: processed_caption.indent_cursor(1) elif control_code is SccControlCode.TO2: processed_caption.indent_cursor(2) elif control_code is SccControlCode.TO3: processed_caption.indent_cursor(3) elif control_code is SccControlCode.CR: # Roll the displayed caption up one row (Roll-Up) if self.has_active_caption(): # Push active caption to model (but don't erase it) self.push_active_caption_to_model(time_code, False) # Roll the active caption up self.active_caption.roll_up() # Get the remaining lines to initialize the following caption with the expected depth previous_lines = self.active_caption.get_last_caption_lines( self.roll_up_depth - 1) # Initialize the new caption with the previous lines self.active_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.initialize_active_caption(time_code) self.active_caption.set_lines(previous_lines) self.active_caption.set_cursor_at(self.active_cursor[0], self.active_cursor[1]) elif control_code is SccControlCode.DER: # Delete to End of Row (Paint-On) # The DER may be issued from any point on a row to delete all displayable characters, transparent # spaces, and mid-row codes from (and including) the current cell to the end of the row. # Not used in this implementation since this SCC reader does not map the text overlapping into # the model (i.e. a row is erased when a PAC is received, so before a new caption is written onto it). pass elif control_code is SccControlCode.BS: # Backspace # When a Backspace is received, the cursor moves to the left one column position erasing # the character or Mid-Row Code occupying that location, unless the cursor is in Column 1 processed_caption.get_current_text().backspace()
def process_preamble_address_code(self, pac: SccPreambleAddressCode, time_code: SmpteTimeCode): """Processes SCC Preamble Address Code it to the map to model""" pac_row = pac.get_row() pac_indent = pac.get_indent() if self.current_style is SccCaptionStyle.PaintOn: self.paint_on_active_caption(time_code) if self.active_caption.get_caption_style( ) is SccCaptionStyle.PaintOn: # Clear target row on Paint-On style target_row = self.active_caption.get_lines().get(pac_row) if target_row is not None: target_row.clear() self.active_caption.set_cursor_at(pac_row, pac_indent) if self.active_caption.get_current_text() is None: self.active_caption.new_caption_text() elif self.current_style is SccCaptionStyle.RollUp: if not self.has_active_caption(): # If there is no current active caption, initialize an empty new paragraph self.active_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.initialize_active_caption(time_code) # Ignore PACs for rows 5-11, but get indent from PACs for rows 1-4 and 12-15. (Roll-Up) if pac_row in range(5, 12): self.active_caption.set_cursor_at(ROLL_UP_BASE_ROW) self.active_caption.new_caption_text() return # Force roll-up paragraph to belong to the same region self.active_caption.set_cursor_at(ROLL_UP_BASE_ROW, pac_indent) self.active_caption.new_caption_text() else: # Pop-On Style if self.buffered_caption is None: self.buffered_caption = SccCaptionParagraph( self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PopOn) # set cursor in paragraph and create line or text if necessary self.buffered_caption.set_cursor_at(pac_row, pac_indent) self.buffered_caption.new_caption_text() self.current_color = pac.get_color() self.current_font_style = pac.get_font_style() self.current_text_decoration = pac.get_text_decoration() if self.has_active_caption(): self.active_cursor = self.active_caption.get_cursor()
class _SccContext: def __init__(self): self.div: Optional[Div] = None self.count: int = 0 self.safe_area_x_offset: int = 0 self.safe_area_y_offset: int = 0 self.previous_code = 0 self.current_caption: Optional[SccCaptionParagraph] = None self.previous_captions: List[SccCaptionParagraph] = [] def set_safe_area(self, safe_area_x_offset: int, safe_area_y_offset: int): """Sets the safe area""" self.safe_area_x_offset = safe_area_x_offset self.safe_area_y_offset = safe_area_y_offset def set_current_to_previous(self): """Rotates current caption to previous caption""" if self.current_caption is not None and self.current_caption.get_current_text(): if not self.current_caption.get_id(): self.count += 1 self.current_caption.set_id("caption" + str(self.count)) self.previous_captions.append(self.current_caption) self.current_caption = None def init_current_caption(self, time_code: SccTimeCode): """Initializes the current caption with begin time""" if self.current_caption is not None: self.current_caption.set_begin(time_code) def push_previous_caption(self, time_code: SccTimeCode, index: int = 0): """Sets previous caption end time, pushes it into the data model and resets it""" if len(self.previous_captions) > 0: previous_caption = self.previous_captions.pop(index) previous_caption.set_end(time_code) if previous_caption.get_style_property(StyleProperties.BackgroundColor) is None: previous_caption.add_style_property(StyleProperties.BackgroundColor, NamedColors.black.value) self.div.push_child(previous_caption.to_paragraph(self.div.get_doc())) def process_preamble_address_code(self, pac: SccPreambleAddressCode, time_code: SccTimeCode): """Processes SCC Preamble Address Code it to the map to model""" if self.current_caption is None: raise ValueError("No current SCC caption initialized") pac_row = pac.get_row() pac_indent = pac.get_indent() if self.current_caption.get_caption_style() is SccCaptionStyle.PaintOn: if self.current_caption.get_current_text() is not None \ and self.safe_area_y_offset + pac_row == self.current_caption.get_row_offset() + 1: # Creates a new Paragraph if the new caption is contiguous (Paint-On) self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PaintOn) self.init_current_caption(time_code) elif len(self.previous_captions) > 0 and self.previous_captions[0].get_current_text() is not None \ and self.safe_area_y_offset + pac_row == self.previous_captions[0].get_row_offset(): # Pushes and erases displayed row so that it can be replaced by current row (Paint-On) self.push_previous_caption(self.current_caption.get_begin()) self.current_caption.new_caption_text() if self.current_caption.get_caption_style() is SccCaptionStyle.RollUp: # Ignore PACs for rows 5-11, but get indent from PACs for rows 1-4 and 12-15. (Roll-Up) if pac_row in range(5, 12): self.current_caption.apply_current_text_offsets() return self.current_caption.set_column_offset(pac_indent) else: self.current_caption.set_row_offset(pac_row) self.current_caption.set_column_offset(pac_indent) self.current_caption.get_current_text().add_style_property(StyleProperties.Color, pac.get_color()) self.current_caption.get_current_text().add_style_property(StyleProperties.FontStyle, pac.get_font_style()) self.current_caption.get_current_text().add_style_property(StyleProperties.TextDecoration, pac.get_text_decoration()) self.current_caption.apply_current_text_offsets() def process_mid_row_code(self, mid_row_code: SccMidRowCode): """Processes SCC Mid-Row Code to map it to the model""" if self.current_caption is None: raise ValueError("No current SCC caption initialized") if self.current_caption.get_current_text() is not None and self.current_caption.get_current_text().get_text(): self.current_caption.new_caption_text() self.current_caption.apply_current_text_offsets() self.current_caption.get_current_text().add_style_property(StyleProperties.Color, mid_row_code.get_color()) self.current_caption.get_current_text().add_style_property(StyleProperties.FontStyle, mid_row_code.get_font_style()) self.current_caption.get_current_text().add_style_property(StyleProperties.TextDecoration, mid_row_code.get_text_decoration()) def process_attribute_code(self, attribute_code: SccAttributeCode): """Processes SCC Attribute Code to map it to the model""" if self.current_caption is None or self.current_caption.get_current_text() is None: raise ValueError("No current SCC caption nor content initialized") if self.current_caption.get_current_text() is not None and self.current_caption.get_current_text().get_text(): self.current_caption.new_caption_text() self.current_caption.apply_current_text_offsets() if attribute_code.is_background(): self.current_caption.get_current_text().add_style_property(StyleProperties.BackgroundColor, attribute_code.get_color()) else: self.current_caption.get_current_text().add_style_property(StyleProperties.Color, attribute_code.get_color()) self.current_caption.get_current_text().add_style_property(StyleProperties.TextDecoration, attribute_code.get_text_decoration()) def process_control_code(self, control_code: SccControlCode, time_code: SccTimeCode): """Processes SCC Control Code to map it to the model""" if control_code is SccControlCode.RCL: # Start a new Pop-On caption if self.current_caption is not None and self.current_caption.get_current_text() is not None: self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PopOn) elif control_code is SccControlCode.RDC: # Start a new Paint-On caption self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PaintOn) self.init_current_caption(time_code) elif control_code in (SccControlCode.RU2, SccControlCode.RU3, SccControlCode.RU4): # Start a new Roll-Up caption self.set_current_to_previous() previous_last_lines: List[SccCaptionContent] = [] if len(self.previous_captions) > 0: if control_code is SccControlCode.RU2: previous_last_lines = self.previous_captions[0].get_last_caption_lines(1) elif control_code is SccControlCode.RU3: previous_last_lines = self.previous_captions[0].get_last_caption_lines(2) elif control_code is SccControlCode.RU4: previous_last_lines = self.previous_captions[0].get_last_caption_lines(3) previous_last_lines.append(SccCaptionLineBreak()) self.push_previous_caption(time_code) self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.current_caption.set_contents(previous_last_lines) self.init_current_caption(time_code) self.current_caption.apply_roll_up_row_offsets() elif control_code is SccControlCode.EOC: # Display caption (Pop-On) self.init_current_caption(time_code) self.set_current_to_previous() elif control_code in (SccControlCode.EDM, SccControlCode.ENM): # Erase displayed caption (Pop-On) if len(self.previous_captions) > 0: # Set line breaks depending on the position of the content contents = [] initial_length = len(self.previous_captions[0].get_contents()) for index in range(0, initial_length): content = self.previous_captions[0].get_contents()[index - 1] if not isinstance(content, SccCaptionText): continue next_content = self.previous_captions[0].get_contents()[index] if not isinstance(next_content, SccCaptionText): continue if next_content.is_contiguous(content): contents.append(SccCaptionLineBreak()) contents.append(next_content) self.previous_captions[0].set_contents(contents) self.push_previous_caption(time_code) elif control_code is SccControlCode.TO1: self.current_caption.indent(1) elif control_code is SccControlCode.TO2: self.current_caption.indent(2) elif control_code is SccControlCode.TO3: self.current_caption.indent(3) elif control_code is SccControlCode.CR: # Roll the display up one row (Roll-Up) # Not used in this implementation since this SCC reader does not really map the roll-up effect, # but it erases the displayed paragraph and resets a new paragraph with the resulting rows. pass elif control_code is SccControlCode.DER: # Delete to End of Row (Paint-On) # The DER may be issued from any point on a row to delete all displayable characters, transparent # spaces, and mid-row codes from (and including) the current cell to the end of the row. # Not used in this implementation since this SCC reader does not map the text overlapping into # the model (i.e. a row is erased when a PAC is received, so before a new caption is written onto it). pass def process_text(self, word: str, time_code: SccTimeCode): """Processes SCC text words""" if self.current_caption.get_caption_style() is SccCaptionStyle.PaintOn: if word.startswith(" "): self.current_caption.new_caption_text() self.current_caption.get_current_text().append(word) self.current_caption.apply_current_text_offsets() self.current_caption.get_current_text().set_begin(time_code) return if word.endswith(" "): self.current_caption.get_current_text().append(word) self.current_caption.new_caption_text() self.current_caption.apply_current_text_offsets() self.current_caption.get_current_text().set_begin(time_code) return self.current_caption.get_current_text().append(word) def flush(self, time_code: SccTimeCode): """Flushes the remaining current caption""" if self.current_caption is not None: self.set_current_to_previous() while len(self.previous_captions) > 0: self.push_previous_caption(time_code) self.current_caption = None
def process_control_code(self, control_code: SccControlCode, time_code: SccTimeCode): """Processes SCC Control Code to map it to the model""" if control_code is SccControlCode.RCL: # Start a new Pop-On caption if self.current_caption is not None and self.current_caption.get_current_text() is not None: self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PopOn) elif control_code is SccControlCode.RDC: # Start a new Paint-On caption self.set_current_to_previous() self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.PaintOn) self.init_current_caption(time_code) elif control_code in (SccControlCode.RU2, SccControlCode.RU3, SccControlCode.RU4): # Start a new Roll-Up caption self.set_current_to_previous() previous_last_lines: List[SccCaptionContent] = [] if len(self.previous_captions) > 0: if control_code is SccControlCode.RU2: previous_last_lines = self.previous_captions[0].get_last_caption_lines(1) elif control_code is SccControlCode.RU3: previous_last_lines = self.previous_captions[0].get_last_caption_lines(2) elif control_code is SccControlCode.RU4: previous_last_lines = self.previous_captions[0].get_last_caption_lines(3) previous_last_lines.append(SccCaptionLineBreak()) self.push_previous_caption(time_code) self.current_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.current_caption.set_contents(previous_last_lines) self.init_current_caption(time_code) self.current_caption.apply_roll_up_row_offsets() elif control_code is SccControlCode.EOC: # Display caption (Pop-On) self.init_current_caption(time_code) self.set_current_to_previous() elif control_code in (SccControlCode.EDM, SccControlCode.ENM): # Erase displayed caption (Pop-On) if len(self.previous_captions) > 0: # Set line breaks depending on the position of the content contents = [] initial_length = len(self.previous_captions[0].get_contents()) for index in range(0, initial_length): content = self.previous_captions[0].get_contents()[index - 1] if not isinstance(content, SccCaptionText): continue next_content = self.previous_captions[0].get_contents()[index] if not isinstance(next_content, SccCaptionText): continue if next_content.is_contiguous(content): contents.append(SccCaptionLineBreak()) contents.append(next_content) self.previous_captions[0].set_contents(contents) self.push_previous_caption(time_code) elif control_code is SccControlCode.TO1: self.current_caption.indent(1) elif control_code is SccControlCode.TO2: self.current_caption.indent(2) elif control_code is SccControlCode.TO3: self.current_caption.indent(3) elif control_code is SccControlCode.CR: # Roll the display up one row (Roll-Up) # Not used in this implementation since this SCC reader does not really map the roll-up effect, # but it erases the displayed paragraph and resets a new paragraph with the resulting rows. pass elif control_code is SccControlCode.DER: # Delete to End of Row (Paint-On) # The DER may be issued from any point on a row to delete all displayable characters, transparent # spaces, and mid-row codes from (and including) the current cell to the end of the row. # Not used in this implementation since this SCC reader does not map the text overlapping into # the model (i.e. a row is erased when a PAC is received, so before a new caption is written onto it). pass
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 test_content(self): caption_paragraph = SccCaptionParagraph() self.assertEqual(0, caption_paragraph._safe_area_x_offset) self.assertEqual(0, caption_paragraph._safe_area_y_offset) caption_paragraph = SccCaptionParagraph(4, 2) self.assertEqual(4, caption_paragraph._safe_area_x_offset) self.assertEqual(2, caption_paragraph._safe_area_y_offset) self.assertIsNone(caption_paragraph.get_current_text()) self.assertEqual(0, len(caption_paragraph._caption_lines)) caption_paragraph.new_caption_text() self.assertEqual(caption_paragraph.get_current_line(), caption_paragraph.get_lines()[0]) self.assertIsNotNone(caption_paragraph.get_current_text()) self.assertEqual(1, len(caption_paragraph._caption_lines)) caption_paragraph.set_cursor_at(4, 4) caption_paragraph.new_caption_text() self.assertEqual((4, 4), caption_paragraph.get_cursor()) self.assertEqual(caption_paragraph.get_current_line(), caption_paragraph.get_lines()[4]) self.assertEqual(4, caption_paragraph.get_current_line().get_row()) self.assertEqual(4, caption_paragraph.get_current_line().get_indent()) self.assertEqual(0, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(0, caption_paragraph.get_current_text().get_cursor()) caption_paragraph.indent_cursor(3) self.assertEqual((4, 7), caption_paragraph.get_cursor()) self.assertEqual(4, caption_paragraph.get_current_line().get_row()) self.assertEqual(7, caption_paragraph.get_current_line().get_indent()) self.assertEqual(0, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(0, caption_paragraph.get_current_text().get_cursor()) caption_paragraph.append_text("Hello") self.assertEqual(5, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(5, caption_paragraph.get_current_text().get_cursor()) caption_paragraph.set_cursor_at(4, 10) self.assertEqual((4, 10), caption_paragraph.get_cursor()) self.assertEqual(4, caption_paragraph.get_current_line().get_row()) self.assertEqual(7, caption_paragraph.get_current_line().get_indent()) self.assertEqual(3, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(3, caption_paragraph.get_current_text().get_cursor()) caption_paragraph.indent_cursor(2) self.assertEqual((4, 12), caption_paragraph.get_cursor()) self.assertEqual(4, caption_paragraph.get_current_line().get_row()) self.assertEqual(7, caption_paragraph.get_current_line().get_indent()) self.assertEqual(5, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(5, caption_paragraph.get_current_text().get_cursor()) self.assertListEqual([], caption_paragraph.get_last_caption_lines(0)) self.assertListEqual([caption_paragraph.get_current_line()], caption_paragraph.get_last_caption_lines(1)) caption_paragraph.set_cursor_at(2, 4) caption_paragraph.new_caption_text() caption_paragraph.append_text("World") self.assertEqual(5, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(5, caption_paragraph.get_current_text().get_cursor()) self.assertRaisesRegex(RuntimeError, "Cannot roll-Up Unknown-styled caption.", caption_paragraph.roll_up) caption_paragraph._caption_style = SccCaptionStyle.PopOn self.assertRaisesRegex(RuntimeError, "Cannot roll-Up PopOn-styled caption.", caption_paragraph.roll_up) caption_paragraph._caption_style = SccCaptionStyle.RollUp caption_paragraph.roll_up() self.assertEqual(2, len(caption_paragraph.get_lines())) self.assertEqual(caption_paragraph.get_current_line(), caption_paragraph.get_lines().get(1)) self.assertEqual(1, caption_paragraph.get_lines().get(1).get_row()) self.assertEqual( "World", caption_paragraph.get_lines().get(1).get_current_text().get_text()) self.assertTrue( isinstance(caption_paragraph.get_lines().get(3), SccCaptionLine)) self.assertEqual(3, caption_paragraph.get_lines().get(3).get_row()) self.assertEqual( "Hello", caption_paragraph.get_lines().get(3).get_current_text().get_text()) self.assertListEqual([], caption_paragraph.get_last_caption_lines(0)) self.assertListEqual([caption_paragraph.get_lines().get(3)], caption_paragraph.get_last_caption_lines(1)) self.assertListEqual([ caption_paragraph.get_lines().get(1), caption_paragraph.get_lines().get(3) ], caption_paragraph.get_last_caption_lines(2)) caption_paragraph.set_cursor_at(15, 0) caption_paragraph.new_caption_text() caption_paragraph.append_text("!!!") self.assertEqual(3, caption_paragraph.get_current_line().get_cursor()) self.assertEqual(3, caption_paragraph.get_current_text().get_cursor()) caption_paragraph.roll_up() self.assertEqual(3, len(caption_paragraph.get_lines())) self.assertTrue( isinstance(caption_paragraph.get_lines().get(0), SccCaptionLine)) self.assertEqual(0, caption_paragraph.get_lines().get(0).get_row()) self.assertEqual( "World", caption_paragraph.get_lines().get(0).get_current_text().get_text()) self.assertTrue( isinstance(caption_paragraph.get_lines().get(2), SccCaptionLine)) self.assertEqual(2, caption_paragraph.get_lines().get(2).get_row()) self.assertEqual( "Hello", caption_paragraph.get_lines().get(2).get_current_text().get_text()) self.assertEqual(caption_paragraph.get_current_line(), caption_paragraph.get_lines().get(14)) self.assertEqual(14, caption_paragraph.get_lines().get(14).get_row()) self.assertEqual( "!!!", caption_paragraph.get_lines().get( 14).get_current_text().get_text()) self.assertListEqual([], caption_paragraph.get_last_caption_lines(0)) self.assertListEqual([caption_paragraph.get_lines().get(14)], caption_paragraph.get_last_caption_lines(1)) self.assertListEqual([ caption_paragraph.get_lines().get(2), caption_paragraph.get_lines().get(14) ], caption_paragraph.get_last_caption_lines(2)) self.assertListEqual([ caption_paragraph.get_lines().get(0), caption_paragraph.get_lines().get(2), caption_paragraph.get_lines().get(14) ], caption_paragraph.get_last_caption_lines(3))
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 test_paragraph_content_alignment_detection_right(self): caption_paragraph = SccCaptionParagraph() caption_paragraph.set_cursor_at(10, 0) caption_paragraph.append_text("0123456789") caption_paragraph.set_cursor_at(11, 0) caption_paragraph.append_text(" 012345") caption_paragraph.set_cursor_at(12, 0) caption_paragraph.append_text(" 0123") self.assertEqual(TextAlignType.end, caption_paragraph.guess_text_alignment()) caption_paragraph = SccCaptionParagraph() caption_paragraph.set_cursor_at(10, 0) caption_paragraph.append_text("0123456789") caption_paragraph.set_cursor_at(11, 0) caption_paragraph.append_text(" 012345") caption_paragraph.set_cursor_at(12, 4) caption_paragraph.append_text(" 0123") self.assertEqual(TextAlignType.end, caption_paragraph.guess_text_alignment())
def test_paragraph_content_alignment_detection_center(self): caption_paragraph = SccCaptionParagraph() caption_paragraph.set_cursor_at(0, 2) caption_paragraph.append_text("0123456789") caption_paragraph.set_cursor_at(1, 2) caption_paragraph.append_text(" 012345") caption_paragraph.set_cursor_at(2, 2) caption_paragraph.append_text(" 0123") self.assertEqual(TextAlignType.center, caption_paragraph.guess_text_alignment()) caption_paragraph = SccCaptionParagraph() caption_paragraph.set_cursor_at(0, 0) caption_paragraph.append_text(" 0123456789") caption_paragraph.set_cursor_at(1, 2) caption_paragraph.append_text(" 012345") caption_paragraph.set_cursor_at(2, 5) caption_paragraph.append_text("0123") self.assertEqual(TextAlignType.center, caption_paragraph.guess_text_alignment())
def test_paragraph_alignment_detection_left(self): caption_paragraph = SccCaptionParagraph() caption_paragraph.set_cursor_at(0, 0) caption_paragraph.append_text("0123456789") caption_paragraph.set_cursor_at(1, 0) caption_paragraph.append_text("012345") caption_paragraph.set_cursor_at(2, 0) caption_paragraph.append_text("0123") self.assertEqual(TextAlignType.start, caption_paragraph.guess_text_alignment())
def process_control_code(self, control_code: SccControlCode, time_code: SmpteTimeCode): """Processes SCC Control Code to map it to the model""" if control_code is SccControlCode.RCL: # Start a new Pop-On caption self.current_style = SccCaptionStyle.PopOn elif control_code is SccControlCode.RDC: # Start a new Paint-On caption self.current_style = SccCaptionStyle.PaintOn elif control_code in (SccControlCode.RU2, SccControlCode.RU3, SccControlCode.RU4): # Start a new Roll-Up caption self.current_style = SccCaptionStyle.RollUp previous_last_lines: List[SccCaptionLine] = [] if self.has_active_caption(): if control_code is SccControlCode.RU2: previous_last_lines = self.active_caption.get_last_caption_lines( 1) elif control_code is SccControlCode.RU3: previous_last_lines = self.active_caption.get_last_caption_lines( 2) elif control_code is SccControlCode.RU4: previous_last_lines = self.active_caption.get_last_caption_lines( 3) self.push_active_caption_to_model(time_code) self.active_caption = SccCaptionParagraph(self.safe_area_x_offset, self.safe_area_y_offset, SccCaptionStyle.RollUp) self.initialize_active_caption(time_code) self.active_caption.set_lines(previous_last_lines) elif control_code is SccControlCode.EOC: # Display caption (Pop-On) self.set_buffered_caption_begin_time(time_code) self.flip_buffered_to_active_captions(time_code) if self.has_active_caption(): # Set text alignment if self.text_alignment == TextAlignment.AUTO: text_alignment = self.active_caption.guess_text_alignment() else: text_alignment = self.text_alignment.text_align # Apply text alignment self.active_caption.add_style_property( StyleProperties.TextAlign, text_alignment) elif control_code is SccControlCode.EDM: # Erase displayed captions if self.has_active_caption(): self.push_active_caption_to_model(time_code) elif control_code is SccControlCode.ENM: # Erase buffered caption self.buffered_caption = None elif control_code is SccControlCode.TO1: self.buffered_caption.indent_cursor(1) elif control_code is SccControlCode.TO2: self.buffered_caption.indent_cursor(2) elif control_code is SccControlCode.TO3: self.buffered_caption.indent_cursor(3) elif control_code is SccControlCode.CR: # Roll the display up one row (Roll-Up) self.active_caption.roll_up() elif control_code is SccControlCode.DER: # Delete to End of Row (Paint-On) # The DER may be issued from any point on a row to delete all displayable characters, transparent # spaces, and mid-row codes from (and including) the current cell to the end of the row. # Not used in this implementation since this SCC reader does not map the text overlapping into # the model (i.e. a row is erased when a PAC is received, so before a new caption is written onto it). pass elif control_code is SccControlCode.BS: # Backspace # When a Backspace is received, the cursor moves to the left one column position erasing # the character or Mid-Row Code occupying that location, unless the cursor is in Column 1 self.buffered_caption.get_current_text().backspace()