def test_parse_drop_frame_time_code(self):
        time_code = SmpteTimeCode.parse("01:02:03;04", FPS_29_97)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual(Fraction(111582, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("01:02:03;04", str(time_code))

        time_code = SmpteTimeCode.parse("01;02;03;04", FPS_29_97)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual(Fraction(111582, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("01:02:03;04", str(time_code))

        time_code = SmpteTimeCode.parse("01:02:03;04", FPS_30)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual(Fraction(111582, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("01:02:03;04", str(time_code))

        time_code = SmpteTimeCode.parse("01:02:03.04", FPS_29_97)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual(Fraction(111582, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("01:02:03;04", str(time_code))

        time_code = SmpteTimeCode.parse("01.02.03.04", FPS_29_97)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual(Fraction(111582, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("01:02:03;04", str(time_code))
Exemple #2
0
    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())
Exemple #3
0
 def test_irt_requirement_0062_002_modified(self):
   '''Testing Time Code Out with value media 25 frames upper bound'''
   with open("src/test/resources/stl/irt/requirement-0062-002_modified.stl", "rb") as f:
     doc = ttconv.stl.reader.to_model(f)
     p = doc.get_body().first_child().first_child()
     self.assertEqual(p.get_end(),
                      SmpteTimeCode.parse("23:59:59:24",FPS_25).to_temporal_offset())
Exemple #4
0
 def test_irt_requirement_0061_001(self):
   '''Testing Time Code In with value media 25 frames lowerbounds'''  
   with open("src/test/resources/stl/irt/requirement-0061-001.stl", "rb") as f:
     doc = ttconv.stl.reader.to_model(f)
     p = doc.get_body().first_child().first_child()
     self.assertEqual(p.get_begin(),
                     SmpteTimeCode.parse("00:00:00:00",FPS_25).to_temporal_offset())
Exemple #5
0
 def test_scc_line_from_str_with_paint_on_style(self):
     line_str = "01:03:27:29	9429 9429 94f2 94f2 c845 d92c 2054 c845 5245 ae80"
     scc_line = SccLine.from_str(line_str)
     self.assertEqual(10, len(scc_line.scc_words))
     self.assertEqual(
         SmpteTimeCode(1, 3, 27, 29, FPS_30).to_temporal_offset(),
         scc_line.time_code.to_temporal_offset())
     self.assertEqual("01:03:27:29	{RDC}{RDC}{1504}{1504}HEY, THERE.",
                      scc_line.to_disassembly())
     self.assertEqual(SccCaptionStyle.PaintOn, scc_line.get_style())
Exemple #6
0
 def test_scc_line_from_str_with_roll_up_style(self):
     line_str = "01:03:27:29	9425 9425 94ad 94ad 94c8 94c8 c845 d92c 2054 c845 5245 ae80"
     scc_line = SccLine.from_str(line_str)
     self.assertEqual(12, len(scc_line.scc_words))
     self.assertEqual(
         SmpteTimeCode(1, 3, 27, 29, FPS_30).to_temporal_offset(),
         scc_line.time_code.to_temporal_offset())
     self.assertEqual("01:03:27:29	{RU2}{RU2}{CR}{CR}{14R}{14R}HEY, THERE.",
                      scc_line.to_disassembly())
     self.assertEqual(SccCaptionStyle.RollUp, scc_line.get_style())
Exemple #7
0
    def test_scc_line_from_str_with_unknown_style(self):
        line_str = "01:03:27:29	"
        scc_line = SccLine.from_str(line_str)
        self.assertEqual(0, len(scc_line.scc_words))
        self.assertEqual(
            SmpteTimeCode(1, 3, 27, 29, FPS_30).to_temporal_offset(),
            scc_line.time_code.to_temporal_offset())
        self.assertEqual("01:03:27:29	", scc_line.to_disassembly())
        self.assertEqual(SccCaptionStyle.Unknown, scc_line.get_style())

        line_str = "01:03:27:29	9024 c845 d92c 2054 c845 5245 ae80 9f9f"
        scc_line = SccLine.from_str(line_str)
        self.assertEqual(8, len(scc_line.scc_words))
        self.assertEqual(
            SmpteTimeCode(1, 3, 27, 29, FPS_30).to_temporal_offset(),
            scc_line.time_code.to_temporal_offset())
        self.assertEqual("01:03:27:29	{BBl}HEY, THERE.{??}",
                         scc_line.to_disassembly())
        self.assertEqual(SccCaptionStyle.Unknown, scc_line.get_style())
Exemple #8
0
 def test_scc_line_from_str_with_pop_on_style(self):
     line_str = "01:03:27:29	94ae 94ae 9420 9420 94f2 94f2 c845 d92c 2054 c845 5245 ae80 942c 942c 8080 8080 942f 942f"
     scc_line = SccLine.from_str(line_str)
     self.assertEqual(18, len(scc_line.scc_words))
     self.assertEqual(
         SmpteTimeCode(1, 3, 27, 29, FPS_30).to_temporal_offset(),
         scc_line.time_code.to_temporal_offset())
     self.assertEqual(
         "01:03:27:29	{ENM}{ENM}{RCL}{RCL}{1504}{1504}HEY, THERE.{EDM}{EDM}{}{}{EOC}{EOC}",
         scc_line.to_disassembly())
     self.assertEqual(SccCaptionStyle.PopOn, scc_line.get_style())
    def test_from_frames(self):
        time_code = SmpteTimeCode.from_frames(111694, FPS_30)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_30, time_code.get_frame_rate())
        self.assertFalse(time_code.is_drop_frame())
        self.assertEqual(111694, time_code.to_frames())
        self.assertEqual("01:02:03:04", str(time_code))

        time_code = SmpteTimeCode.from_frames(111582, FPS_29_97)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(3, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(111582, time_code.to_frames())
        self.assertEqual("01:02:03;04", str(time_code))
 def test_parse_non_drop_frame_time_code(self):
     time_code = SmpteTimeCode.parse("01:02:03:04", FPS_30)
     self.assertEqual(1, time_code.get_hours())
     self.assertEqual(2, time_code.get_minutes())
     self.assertEqual(3, time_code.get_seconds())
     self.assertEqual(4, time_code.get_frames())
     self.assertEqual(FPS_30, time_code.get_frame_rate())
     self.assertFalse(time_code.is_drop_frame())
     self.assertEqual(111694, time_code.to_frames())
     self.assertAlmostEqual(3723.133, time_code.to_seconds(), delta=0.001)
     self.assertEqual("01:02:03:04", str(time_code))
Exemple #11
0
    def check_caption(self, paragraph: P, caption_id: str, begin: str,
                      end: Optional[str], *children):
        self.assertEqual(caption_id, paragraph.get_id())
        self.assertEqual(
            SmpteTimeCode.parse(begin, FPS_30).to_temporal_offset(),
            paragraph.get_begin())

        if end is not None:
            self.assertEqual(
                SmpteTimeCode.parse(end, FPS_30).to_temporal_offset(),
                paragraph.get_end())

        p_children = list(paragraph)
        self.assertEqual(len(children), len(p_children))

        for (index, child) in enumerate(p_children):
            expected_child = children[index]
            if isinstance(expected_child, str):
                texts = list(child)
                self.assertEqual(expected_child, texts[0].get_text())
            else:
                self.assertEqual(expected_child, Br)
    def test_add_frames(self):
        time_code = SmpteTimeCode.parse("01:02:03:04", FPS_30)
        time_code.add_frames(30)
        self.assertEqual(1, time_code.get_hours())
        self.assertEqual(2, time_code.get_minutes())
        self.assertEqual(4, time_code.get_seconds())
        self.assertEqual(4, time_code.get_frames())
        self.assertEqual(FPS_30, time_code.get_frame_rate())
        self.assertFalse(time_code.is_drop_frame())
        self.assertEqual(111724, time_code.to_frames())
        self.assertEqual(Fraction(111724, 30), time_code.to_temporal_offset())
        self.assertEqual("01:02:04:04", str(time_code))

        time_code = SmpteTimeCode.parse("00:00:59;29", FPS_29_97)
        time_code.add_frames()
        self.assertEqual(0, time_code.get_hours())
        self.assertEqual(1, time_code.get_minutes())
        self.assertEqual(0, time_code.get_seconds())
        self.assertEqual(2, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(1_800, time_code.to_frames())
        self.assertEqual(Fraction(1800, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("00:01:00;02", str(time_code))

        time_code = SmpteTimeCode.parse("00:19:59;29", FPS_29_97)
        time_code.add_frames()
        self.assertEqual(0, time_code.get_hours())
        self.assertEqual(20, time_code.get_minutes())
        self.assertEqual(0, time_code.get_seconds())
        self.assertEqual(0, time_code.get_frames())
        self.assertEqual(FPS_29_97, time_code.get_frame_rate())
        self.assertTrue(time_code.is_drop_frame())
        self.assertEqual(35_964, time_code.to_frames())
        self.assertEqual(Fraction(35964, Fraction(30000, 1001)),
                         time_code.to_temporal_offset())
        self.assertEqual("00:20:00;00", str(time_code))
Exemple #13
0
  def from_str(line: str) -> Optional[SccLine]:
    """Creates a SCC line instance from the specified string"""
    if not line:
      return None

    regex = re.compile(SCC_LINE_PATTERN)
    match = regex.match(line)

    if match is None:
      return None

    time_code = match.group(1)
    time_offset = SmpteTimeCode.parse(time_code, FPS_30)

    hex_words = line.split('\t')[1].split(' ')
    scc_words = [SccWord.from_str(hex_word) for hex_word in hex_words if hex_word]
    return SccLine(time_offset, scc_words)
    def test_time_code_frames_conversion(self):
        time_code = SmpteTimeCode.from_frames(1795, FPS_30)
        self.assertEqual("00:00:59:25", str(time_code))
        self.assertEqual(1795, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1796, FPS_30)
        self.assertEqual("00:00:59:26", str(time_code))
        self.assertEqual(1796, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1797, FPS_30)
        self.assertEqual("00:00:59:27", str(time_code))
        self.assertEqual(1797, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1798, FPS_30)
        self.assertEqual("00:00:59:28", str(time_code))
        self.assertEqual(1798, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1799, FPS_30)
        self.assertEqual("00:00:59:29", str(time_code))
        self.assertEqual(1799, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1800, FPS_30)
        self.assertEqual("00:01:00:00", str(time_code))
        self.assertEqual(1800, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1801, FPS_30)
        self.assertEqual("00:01:00:01", str(time_code))
        self.assertEqual(1801, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1802, FPS_30)
        self.assertEqual("00:01:00:02", str(time_code))
        self.assertEqual(1802, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1803, FPS_30)
        self.assertEqual("00:01:00:03", str(time_code))
        self.assertEqual(1803, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1804, FPS_30)
        self.assertEqual("00:01:00:04", str(time_code))
        self.assertEqual(1804, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1805, FPS_30)
        self.assertEqual("00:01:00:05", str(time_code))
        self.assertEqual(1805, time_code.to_frames())

        time_code = SmpteTimeCode.from_frames(17977, FPS_30)
        self.assertEqual("00:09:59:07", str(time_code))
        self.assertEqual(17977, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17978, FPS_30)
        self.assertEqual("00:09:59:08", str(time_code))
        self.assertEqual(17978, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17979, FPS_30)
        self.assertEqual("00:09:59:09", str(time_code))
        self.assertEqual(17979, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17980, FPS_30)
        self.assertEqual("00:09:59:10", str(time_code))
        self.assertEqual(17980, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17981, FPS_30)
        self.assertEqual("00:09:59:11", str(time_code))
        self.assertEqual(17981, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17982, FPS_30)
        self.assertEqual("00:09:59:12", str(time_code))
        self.assertEqual(17982, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17983, FPS_30)
        self.assertEqual("00:09:59:13", str(time_code))
        self.assertEqual(17983, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17984, FPS_30)
        self.assertEqual("00:09:59:14", str(time_code))
        self.assertEqual(17984, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17985, FPS_30)
        self.assertEqual("00:09:59:15", str(time_code))
        self.assertEqual(17985, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17986, FPS_30)
        self.assertEqual("00:09:59:16", str(time_code))
        self.assertEqual(17986, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17987, FPS_30)
        self.assertEqual("00:09:59:17", str(time_code))
        self.assertEqual(17987, time_code.to_frames())
Exemple #15
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
Exemple #16
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))
    def test_drop_frame_time_code_frames_conversion(self):
        time_code = SmpteTimeCode.from_frames(1795, FPS_29_97)
        self.assertEqual("00:00:59;25", str(time_code))
        self.assertEqual(1795, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1796, FPS_29_97)
        self.assertEqual("00:00:59;26", str(time_code))
        self.assertEqual(1796, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1797, FPS_29_97)
        self.assertEqual("00:00:59;27", str(time_code))
        self.assertEqual(1797, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1798, FPS_29_97)
        self.assertEqual("00:00:59;28", str(time_code))
        self.assertEqual(1798, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1799, FPS_29_97)
        self.assertEqual("00:00:59;29", str(time_code))
        self.assertEqual(1799, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1800, FPS_29_97)
        self.assertEqual("00:01:00;02", str(time_code))
        self.assertEqual(1800, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1801, FPS_29_97)
        self.assertEqual("00:01:00;03", str(time_code))
        self.assertEqual(1801, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1802, FPS_29_97)
        self.assertEqual("00:01:00;04", str(time_code))
        self.assertEqual(1802, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1803, FPS_29_97)
        self.assertEqual("00:01:00;05", str(time_code))
        self.assertEqual(1803, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1804, FPS_29_97)
        self.assertEqual("00:01:00;06", str(time_code))
        self.assertEqual(1804, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(1805, FPS_29_97)
        self.assertEqual("00:01:00;07", str(time_code))
        self.assertEqual(1805, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17977, FPS_29_97)

        self.assertEqual("00:09:59;25", str(time_code))
        self.assertEqual(17977, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17978, FPS_29_97)
        self.assertEqual("00:09:59;26", str(time_code))
        self.assertEqual(17978, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17979, FPS_29_97)
        self.assertEqual("00:09:59;27", str(time_code))
        self.assertEqual(17979, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17980, FPS_29_97)
        self.assertEqual("00:09:59;28", str(time_code))
        self.assertEqual(17980, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17981, FPS_29_97)
        self.assertEqual("00:09:59;29", str(time_code))
        self.assertEqual(17981, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17982, FPS_29_97)
        self.assertEqual("00:10:00;00", str(time_code))
        self.assertEqual(17982, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17983, FPS_29_97)
        self.assertEqual("00:10:00;01", str(time_code))
        self.assertEqual(17983, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17984, FPS_29_97)
        self.assertEqual("00:10:00;02", str(time_code))
        self.assertEqual(17984, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17985, FPS_29_97)
        self.assertEqual("00:10:00;03", str(time_code))
        self.assertEqual(17985, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17986, FPS_29_97)
        self.assertEqual("00:10:00;04", str(time_code))
        self.assertEqual(17986, time_code.to_frames())
        time_code = SmpteTimeCode.from_frames(17987, FPS_29_97)
        self.assertEqual("00:10:00;05", str(time_code))
        self.assertEqual(17987, time_code.to_frames())
    def test_drop_frame_time_code_seconds_conversion(self):
        time_code = SmpteTimeCode.from_frames(1795, FPS_29_97)
        self.assertEqual("00:00:59;25", str(time_code))
        self.assertAlmostEqual(59.893, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1796, FPS_29_97)
        self.assertEqual("00:00:59;26", str(time_code))
        self.assertAlmostEqual(59.926, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1797, FPS_29_97)
        self.assertEqual("00:00:59;27", str(time_code))
        self.assertAlmostEqual(59.959, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1798, FPS_29_97)
        self.assertEqual("00:00:59;28", str(time_code))
        self.assertAlmostEqual(59.993, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1799, FPS_29_97)
        self.assertEqual("00:00:59;29", str(time_code))
        self.assertAlmostEqual(60.026, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1800, FPS_29_97)
        self.assertEqual("00:01:00;02", str(time_code))
        self.assertAlmostEqual(60.06, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1801, FPS_29_97)
        self.assertEqual("00:01:00;03", str(time_code))
        self.assertAlmostEqual(60.093, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1802, FPS_29_97)
        self.assertEqual("00:01:00;04", str(time_code))
        self.assertAlmostEqual(60.126, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1803, FPS_29_97)
        self.assertEqual("00:01:00;05", str(time_code))
        self.assertAlmostEqual(60.160, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1804, FPS_29_97)
        self.assertEqual("00:01:00;06", str(time_code))
        self.assertAlmostEqual(60.193, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1805, FPS_29_97)
        self.assertEqual("00:01:00;07", str(time_code))
        self.assertAlmostEqual(60.226, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17977, FPS_29_97)

        self.assertEqual("00:09:59;25", str(time_code))
        self.assertAlmostEqual(599.832, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17978, FPS_29_97)
        self.assertEqual("00:09:59;26", str(time_code))
        self.assertAlmostEqual(599.865, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17979, FPS_29_97)
        self.assertEqual("00:09:59;27", str(time_code))
        self.assertAlmostEqual(599.899, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17980, FPS_29_97)
        self.assertEqual("00:09:59;28", str(time_code))
        self.assertAlmostEqual(599.932, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17981, FPS_29_97)
        self.assertEqual("00:09:59;29", str(time_code))
        self.assertAlmostEqual(599.966, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17982, FPS_29_97)
        self.assertEqual("00:10:00;00", str(time_code))
        self.assertAlmostEqual(599.999, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17983, FPS_29_97)
        self.assertEqual("00:10:00;01", str(time_code))
        self.assertAlmostEqual(600.032, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17984, FPS_29_97)
        self.assertEqual("00:10:00;02", str(time_code))
        self.assertAlmostEqual(600.066, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17985, FPS_29_97)
        self.assertEqual("00:10:00;03", str(time_code))
        self.assertAlmostEqual(600.099, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17986, FPS_29_97)
        self.assertEqual("00:10:00;04", str(time_code))
        self.assertAlmostEqual(600.132, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17987, FPS_29_97)
        self.assertEqual("00:10:00;05", str(time_code))
        self.assertAlmostEqual(600.166, time_code.to_seconds(), delta=0.001)
    def test_time_code_seconds_conversion(self):
        time_code = SmpteTimeCode.from_frames(1795, FPS_30)
        self.assertEqual("00:00:59:25", str(time_code))
        self.assertAlmostEqual(59.833, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1796, FPS_30)
        self.assertEqual("00:00:59:26", str(time_code))
        self.assertAlmostEqual(59.866, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1797, FPS_30)
        self.assertEqual("00:00:59:27", str(time_code))
        self.assertAlmostEqual(59.9, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1798, FPS_30)
        self.assertEqual("00:00:59:28", str(time_code))
        self.assertAlmostEqual(59.933, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1799, FPS_30)
        self.assertEqual("00:00:59:29", str(time_code))
        self.assertAlmostEqual(59.966, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1800, FPS_30)
        self.assertEqual("00:01:00:00", str(time_code))
        self.assertAlmostEqual(60.0, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1801, FPS_30)
        self.assertEqual("00:01:00:01", str(time_code))
        self.assertAlmostEqual(60.033, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1802, FPS_30)
        self.assertEqual("00:01:00:02", str(time_code))
        self.assertAlmostEqual(60.066, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1803, FPS_30)
        self.assertEqual("00:01:00:03", str(time_code))
        self.assertAlmostEqual(60.1, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1804, FPS_30)
        self.assertEqual("00:01:00:04", str(time_code))
        self.assertAlmostEqual(60.133, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(1805, FPS_30)
        self.assertEqual("00:01:00:05", str(time_code))
        self.assertAlmostEqual(60.166, time_code.to_seconds(), delta=0.001)

        time_code = SmpteTimeCode.from_frames(17977, FPS_30)
        self.assertEqual("00:09:59:07", str(time_code))
        self.assertAlmostEqual(599.233, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17978, FPS_30)
        self.assertEqual("00:09:59:08", str(time_code))
        self.assertAlmostEqual(599.266, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17979, FPS_30)
        self.assertEqual("00:09:59:09", str(time_code))
        self.assertAlmostEqual(599.3, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17980, FPS_30)
        self.assertEqual("00:09:59:10", str(time_code))
        self.assertAlmostEqual(599.333, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17981, FPS_30)
        self.assertEqual("00:09:59:11", str(time_code))
        self.assertAlmostEqual(599.366, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17982, FPS_30)
        self.assertEqual("00:09:59:12", str(time_code))
        self.assertAlmostEqual(599.4, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17983, FPS_30)
        self.assertEqual("00:09:59:13", str(time_code))
        self.assertAlmostEqual(599.433, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17984, FPS_30)
        self.assertEqual("00:09:59:14", str(time_code))
        self.assertAlmostEqual(599.466, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17985, FPS_30)
        self.assertEqual("00:09:59:15", str(time_code))
        self.assertAlmostEqual(599.5, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17986, FPS_30)
        self.assertEqual("00:09:59:16", str(time_code))
        self.assertAlmostEqual(599.533, time_code.to_seconds(), delta=0.001)
        time_code = SmpteTimeCode.from_frames(17987, FPS_30)
        self.assertEqual("00:09:59:17", str(time_code))
        self.assertAlmostEqual(599.566, time_code.to_seconds(), delta=0.001)