diff --git a/ai_artifacts/project_understanding.md b/ai_artifacts/project_understanding.md index 1603faaf..7652dbd5 100644 --- a/ai_artifacts/project_understanding.md +++ b/ai_artifacts/project_understanding.md @@ -60,7 +60,7 @@ pycaption/ │ ├── srt.py # SRT format (reader + writer in single file) │ ├── microdvd.py # MicroDVD format (reader + writer) │ └── transcript.py # Plain text transcript (writer only, uses nltk) -├── tests/ # Test suite (504 tests) +├── tests/ # Test suite (505 tests) │ ├── conftest.py # Fixture imports (re-exports from fixtures/) │ ├── fixtures/ # Pytest fixture modules (inline caption strings) │ │ ├── scc.py, translated_scc.py, dfxp.py, webvtt.py, srt.py, sami.py, microdvd.py @@ -217,6 +217,10 @@ The SAMI reader uses a two-phase approach: overflow (not 100% as might be expected) 6. **Style filtering**: Writers filter internal keys (e.g. `classes`, `webvtt_positioning`) from output via format-specific exclusion sets +7. **RP 2052-10 implicit defaults**: When source format's visual default differs from + target format's default, writers explicitly emit the source's alignment. VTT/SRT/SCC + default to center; DFXP/SAMI default to left/start. The `is_positional_anchor` flag + on Layout distinguishes SCC coordinate-system positioning from visual alignment intent. --- @@ -286,7 +290,7 @@ Additional workflows for format spec compliance: ### Running Tests Locally ```bash -# Run all tests (504 tests, ~0.5s) +# Run all tests (505 tests, ~0.5s) python -m pytest tests/ -q # Run specific format tests @@ -299,7 +303,7 @@ python -m pytest tests/ -v python -m pytest tests/test_webvtt_conversion.py -q ``` -- Tests cover all formats (504 tests total) +- Tests cover all formats (505 tests total) - Fixtures defined in `tests/fixtures/` as pytest session-scoped fixtures - Each fixture is an inline string containing a complete caption file - Conversion tests verify round-trip and cross-format fidelity diff --git a/docs/changelog.rst b/docs/changelog.rst index ad8e8cb5..12186f91 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,15 +2,27 @@ Changelog --------- 2.3.2 ^^^^^^ - - DFXP writer: output ``textAlign="center"`` (not ``"start"``) as the - fallback alignment for cues that carry no explicit alignment. - Separates the writer's fallback (CENTER) from the reader's default - region (START) via a new ``DFXP_WRITER_FALLBACK_ALIGNMENT`` constant. - - - SAMI reader: apply the SAMI spec default ``text-align: left`` only at - the root layout level (no parent to inherit from). Child layouts - without an explicit text-align now correctly inherit from their parent - stylesheet rather than being forced to left. + - RP 2052-10 compliance for all 25 conversion paths: when source format's + visual default is CENTER (VTT/SRT/SCC) and target default is LEFT/START + (DFXP/SAMI), explicitly emit center alignment. DFXP/SAMI sources retain + their original alignment for round-trip fidelity. + + - DFXP writer: suppress ``tts:origin`` from SCC positional layouts + (row/column anchors), emit ``tts:textAlign="center"`` instead. + + - WebVTT writer: suppress SCC positional cue settings (``align:left + position:N% line:N% size:N%``). SCC coordinate anchors are not visual + alignment — omitting lets VTT default to center. + + - SAMI reader: apply ``text-align: left`` only at the root layout level; + child layouts now inherit from parent rather than being forced to left. + + - SAMI writer: emit semantic tags (````, ````, ````) instead of + ````. Add trailing `` `` sync to clear the final + caption at its end time. + + - Geometry: add ``is_positional_anchor`` flag to ``Layout`` to distinguish + SCC coordinate-system positioning from visual text alignment. 2.3.1 ^^^^^^ diff --git a/docs/conf.py b/docs/conf.py index 45b429d6..87198ef2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,9 +53,9 @@ # built documents. # # The short X.Y version. -version = "2.3.1" +version = "2.3.2.dev1" # The full version, including alpha/beta/rc tags. -release = "2.3.1" +release = "2.3.2.dev1" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pycaption/dfxp/constants.py b/pycaption/dfxp/constants.py index 149f96a5..a733b396 100644 --- a/pycaption/dfxp/constants.py +++ b/pycaption/dfxp/constants.py @@ -25,14 +25,22 @@ "font-size": "1c", } +# Reader default: DFXP spec mandates START/BOTTOM for round-trip fidelity. DFXP_DEFAULT_REGION = Layout( alignment=Alignment(HorizontalAlignmentEnum.START, VerticalAlignmentEnum.BOTTOM) ) +# Writer fallback alignment used when layout is None or SCC positional. DFXP_WRITER_FALLBACK_ALIGNMENT = Alignment( HorizontalAlignmentEnum.CENTER, VerticalAlignmentEnum.BOTTOM ) +# Writer default region for sources without positioning (VTT/SRT); +# uses CENTER per RP 2052-10 rather than the spec's START default. +DFXP_WRITER_DEFAULT_REGION = Layout( + alignment=Alignment(HorizontalAlignmentEnum.CENTER, VerticalAlignmentEnum.BOTTOM) +) + DFXP_DEFAULT_STYLE_ID = "default" DFXP_DEFAULT_REGION_ID = "bottom" diff --git a/pycaption/dfxp/writer.py b/pycaption/dfxp/writer.py index 93efcf63..d91eb093 100644 --- a/pycaption/dfxp/writer.py +++ b/pycaption/dfxp/writer.py @@ -20,6 +20,7 @@ DFXP_DEFAULT_REGION_ID, DFXP_DEFAULT_STYLE, DFXP_DEFAULT_STYLE_ID, + DFXP_WRITER_DEFAULT_REGION, DFXP_WRITER_FALLBACK_ALIGNMENT, _create_external_alignment, ) @@ -290,8 +291,8 @@ def __init__(self, dfxp, caption_set): def _collect_unique_regions(caption_set, ignore_region): """Collect all unique Layout objects from the caption set. - Excludes None and ignore_region (typically the default region) to - avoid duplicate region creation. + Excludes None, ignore_region (typically the default region), and + SCC positional layouts (which map to the default center region). :type caption_set: CaptionSet :param ignore_region: a Layout to exclude from the result @@ -311,6 +312,9 @@ def _collect_unique_regions(caption_set, ignore_region): unique_regions.pop(None, None) unique_regions.pop(ignore_region, None) + for layout in list(unique_regions): + if layout and _is_scc_positional(layout): + unique_regions.pop(layout) return unique_regions @staticmethod @@ -348,18 +352,38 @@ def _create_unique_regions(unique_layouts, dfxp, id_factory): layout_section.append(new_region) return region_map + def _has_null_layouts(self): + """Check if any caption in the set has no layout_info. + + When True, the source format lacks positioning (e.g. VTT/SRT), + so the writer uses CENTER alignment per RP 2052-10. + """ + for lang in self._caption_set.get_languages(): + if not self._caption_set.get_layout_info(lang): + return True + for caption in self._caption_set.get_captions(lang): + if not caption.layout_info: + return True + return False + def create_document_regions(self): """Create all tags needed by the caption set. Always creates a default region first, then creates additional regions for any unique Layout objects found in the caption set. + When captions with no layout exist (VTT/SRT sources), the default + region uses CENTER alignment per RP 2052-10; otherwise it uses the + DFXP spec default (START) for round-trip fidelity. """ + if self._has_null_layouts(): + default_layout = DFXP_WRITER_DEFAULT_REGION + else: + default_layout = DFXP_DEFAULT_REGION + default_region_map = self._create_unique_regions( - [DFXP_DEFAULT_REGION], self._dfxp, lambda: DFXP_DEFAULT_REGION_ID - ) - unique_regions = self._collect_unique_regions( - self._caption_set, DFXP_DEFAULT_REGION + [default_layout], self._dfxp, lambda: DFXP_DEFAULT_REGION_ID ) + unique_regions = self._collect_unique_regions(self._caption_set, default_layout) self._region_map = self._create_unique_regions( unique_regions, self._dfxp, self._get_new_id @@ -403,6 +427,9 @@ def get_positioning_info( if not layout_info: layout_info = caption_set.layout_info + if layout_info and _is_scc_positional(layout_info): + layout_info = None + region_id = self._region_map.get(layout_info) if not region_id: region_id = DFXP_DEFAULT_REGION_ID @@ -466,12 +493,20 @@ def _recreate_style(content, dfxp): return dfxp_style +def _is_scc_positional(layout): + """Check if this layout uses a positional anchor (e.g. SCC row/col + coordinates) rather than visual text alignment. + """ + return layout.is_positional_anchor + + def _convert_layout_to_attributes(layout): """Convert a Layout object to a dict of DFXP region attributes. Maps origin, extent, padding, alignment, and writing_direction to their tts: namespace equivalents. Returns default alignment attributes when - layout is None. + layout is None. Detects SCC positional layouts and emits center + alignment per RP 2052-10. :type layout: Layout | None :rtype: dict @@ -480,6 +515,13 @@ def _convert_layout_to_attributes(layout): if not layout: return _create_external_alignment(DFXP_WRITER_FALLBACK_ALIGNMENT) + if _is_scc_positional(layout): + result.update(_create_external_alignment(DFXP_WRITER_FALLBACK_ALIGNMENT)) + writing_mode = _WRITING_DIRECTION_TO_DFXP.get(layout.writing_direction) + if writing_mode: + result["tts:writingMode"] = writing_mode + return result + if layout.origin: result["tts:origin"] = layout.origin.to_xml_attribute() diff --git a/pycaption/geometry.py b/pycaption/geometry.py index db69a6bc..5b444d2d 100644 --- a/pycaption/geometry.py +++ b/pycaption/geometry.py @@ -657,6 +657,7 @@ def __init__( alignment=None, webvtt_positioning=None, writing_direction=None, + is_positional_anchor=False, inherit_from=None, ): """ @@ -682,6 +683,11 @@ def __init__( :type writing_direction: WritingDirectionEnum :param writing_direction: WebVTT vertical writing direction (rl or lr). + :type is_positional_anchor: bool + :param is_positional_anchor: True when the origin/alignment describe a + coordinate-system anchor point (e.g. SCC row/col positioning) rather + than visual text alignment. + :type inherit_from: Layout :param inherit_from: A Layout with the positioning parameters to be used if not specified by the positioning arguments, @@ -693,6 +699,7 @@ def __init__( self.alignment = alignment self.webvtt_positioning = webvtt_positioning self.writing_direction = writing_direction + self.is_positional_anchor = is_positional_anchor if inherit_from: for attr_name in [ @@ -781,6 +788,7 @@ def as_percentage_of(self, video_width, video_height): params = { "alignment": self.alignment, "writing_direction": self.writing_direction, + "is_positional_anchor": self.is_positional_anchor, } for attr_name in ["origin", "extent", "padding"]: attr = getattr(self, attr_name) @@ -844,6 +852,7 @@ def fit_to_screen(self): padding=self.padding, alignment=self.alignment, writing_direction=self.writing_direction, + is_positional_anchor=self.is_positional_anchor, ) return self diff --git a/pycaption/sami/writer.py b/pycaption/sami/writer.py index ece02d6a..25181ca6 100644 --- a/pycaption/sami/writer.py +++ b/pycaption/sami/writer.py @@ -10,6 +10,7 @@ from bs4 import BeautifulSoup from ..base import BaseWriter, CaptionNode +from ..geometry import HorizontalAlignmentEnum from .constants import HORIZONTAL_ALIGNMENT_MAP, SAMI_BASE_MARKUP _NON_CSS_KEYS = frozenset( @@ -57,6 +58,7 @@ def write(self, caption_set): self._relativize_and_fit_to_screen(caption_set.get_layout_info(lang)), ) + last_caption = None for caption in caption_set.get_captions(lang): caption.layout_info = self._relativize_and_fit_to_screen( caption.layout_info @@ -66,6 +68,12 @@ def write(self, caption_set): node.layout_info ) sami = self._recreate_p_tag(caption, sami, lang, primary, caption_set) + last_caption = caption + + if self.last_time and last_caption: + sami = self._recreate_blank_tag( + sami, last_caption, lang, primary, caption_set + ) stylesheet = self._recreate_stylesheet(caption_set) sami.find("style").append(stylesheet) @@ -91,13 +99,22 @@ def _recreate_p_tag(self, caption, sami, lang, primary, captions): for attr, value in self._recreate_style(caption.style).items(): p_style += f"{attr}:{value};" - if caption.layout_info and caption.layout_info.alignment: - if not caption.layout_info.origin: - h = caption.layout_info.alignment.horizontal - if h: - css_align = HORIZONTAL_ALIGNMENT_MAP.get(h) - if css_align: - p_style += f"text-align:{css_align};" + if caption.layout_info: + is_scc_positional = caption.layout_info.is_positional_anchor + + if caption.layout_info.origin and not is_scc_positional: + if caption.layout_info.origin.x: + p_style += f"margin-left:{caption.layout_info.origin.x};" + if caption.layout_info.origin.y: + p_style += f"margin-top:{caption.layout_info.origin.y};" + + if caption.layout_info.extent and not is_scc_positional: + if caption.layout_info.extent.horizontal: + p_style += f"width:{caption.layout_info.extent.horizontal};" + + text_align = self._resolve_text_align(caption.layout_info) + if text_align: + p_style += f"text-align:{text_align};" if p_style: p["style"] = p_style @@ -109,6 +126,32 @@ def _recreate_p_tag(self, caption, sami, lang, primary, captions): return sami + def _resolve_text_align(self, layout_info): + """Return a CSS text-align value for the layout, or None to suppress.""" + if not layout_info: + return "center" + + if layout_info.is_positional_anchor: + return "center" + + if not layout_info.alignment: + return "center" + + h = layout_info.alignment.horizontal + if not h: + return None + + is_default_left = ( + h == HorizontalAlignmentEnum.LEFT + and not layout_info.origin + and not layout_info.extent + and not layout_info.webvtt_positioning + ) + if is_default_left: + return None + + return HORIZONTAL_ALIGNMENT_MAP.get(h) + def _recreate_sync(self, sami, lang, primary, time): """Find or create a tag at the given millisecond timestamp.""" if lang == primary: @@ -221,25 +264,44 @@ def _recreate_text(self, caption): line = self._recreate_line_style(line, node) while self._span_stack: - line = line.rstrip() + " " - self._span_stack.pop() + tag = self._span_stack.pop() + if tag: + line = line.rstrip() + f" " return line.rstrip() def _recreate_line_style(self, line, node): - """Handle style node transitions, opening and closing tags.""" + """Handle style node transitions, opening/closing inline markup.""" if node.start: line = self._recreate_span(line, node.content) else: if self._span_stack: - had_span = self._span_stack.pop() - if had_span: - line = line.rstrip() + " " + tag = self._span_stack.pop() + if tag: + line = line.rstrip() + f" " return line + @staticmethod + def _get_semantic_tag(content): + """Return a semantic HTML tag if content is a single style property.""" + keys = {k for k in content if k not in _NON_CSS_KEYS} + if keys == {"italics"} and content.get("italics") is True: + return "i" + if keys == {"bold"} and content.get("bold") is True: + return "b" + if keys == {"underline"} and content.get("underline") is True: + return "u" + return None + def _recreate_span(self, line, content): - """Build an opening with class and/or inline style attributes.""" + """Build an opening inline tag — semantic (//) when possible.""" + semantic = self._get_semantic_tag(content) + if semantic: + line += f"<{semantic}>" + self._span_stack.append(semantic) + return line + style = "" klass = "" if "classes" in content: @@ -254,9 +316,9 @@ def _recreate_span(self, line, content): if style: style = f' style="{style}"' line += f"" - self._span_stack.append(True) + self._span_stack.append("span") else: - self._span_stack.append(False) + self._span_stack.append(None) return line diff --git a/pycaption/scc/specialized_collections.py b/pycaption/scc/specialized_collections.py index 677630b1..2aacbdce 100644 --- a/pycaption/scc/specialized_collections.py +++ b/pycaption/scc/specialized_collections.py @@ -635,6 +635,7 @@ def _get_layout_from_tuple(position_tuple): return Layout( origin=Point(horizontal, vertical), alignment=Alignment(HorizontalAlignmentEnum.LEFT, VerticalAlignmentEnum.TOP), + is_positional_anchor=True, ) diff --git a/pycaption/webvtt/writer.py b/pycaption/webvtt/writer.py index e29aed84..4185a4a4 100644 --- a/pycaption/webvtt/writer.py +++ b/pycaption/webvtt/writer.py @@ -349,6 +349,9 @@ def _convert_positioning(self, layout): if not layout: return "" + if layout.is_positional_anchor: + return "" + if layout.webvtt_positioning: return f" {layout.webvtt_positioning}" diff --git a/setup.py b/setup.py index b359176b..01d0e474 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="pycaption", - version="2.3.1", + version="2.3.2.dev1", description="Closed caption converter", long_description=open(README_PATH).read(), author="Joe Norton", diff --git a/tests/fixtures/webvtt.py b/tests/fixtures/webvtt.py index 0ce6647c..f0cc20ef 100644 --- a/tests/fixtures/webvtt.py +++ b/tests/fixtures/webvtt.py @@ -317,7 +317,7 @@ def sample_webvtt_from_scc_properly_writes_newlines_output(): return """\ WEBVTT -00:21:30.000 --> 00:21:34.000 align:left position:20% line:83% size:70% +00:21:30.000 --> 00:21:34.000 aa bb """ diff --git a/tests/mixins.py b/tests/mixins.py index c40d698b..e50b7161 100644 --- a/tests/mixins.py +++ b/tests/mixins.py @@ -145,10 +145,16 @@ class SAMITestingMixIn: """ def _extract_sami_captions(self, soup): - return tuple( - (caption.attrs["start"], caption.p.text.strip()) - for caption in soup.select("sync") - ) + result = [] + for sync in soup.select("sync"): + for p in sync.find_all("p"): + result.append((sync.attrs["start"], p.text.strip())) + return tuple(result) + + @staticmethod + def _strip_blanks(items): + """Remove all  /empty sync entries for comparison purposes.""" + return tuple(item for item in items if item[1] not in ("", "\xa0")) def assert_sami_captions_equal(self, first, second): first_soup = BeautifulSoup(first, "lxml") @@ -157,7 +163,7 @@ def assert_sami_captions_equal(self, first, second): first_items = self._extract_sami_captions(first_soup) second_items = self._extract_sami_captions(second_soup) - assert first_items == second_items + assert self._strip_blanks(first_items) == self._strip_blanks(second_items) class MicroDVDTestingMixIn: diff --git a/tests/test_dfxp_conversion.py b/tests/test_dfxp_conversion.py index 7a05db9e..5ad336cd 100644 --- a/tests/test_dfxp_conversion.py +++ b/tests/test_dfxp_conversion.py @@ -344,6 +344,12 @@ def test_srt_to_dfxp_conversion(self, sample_dfxp, sample_srt): results = DFXPWriter().write(caption_set) assert isinstance(results, str) + assert 'tts:textAlign="center"' in results + assert 'tts:displayAlign="after"' in results + + expected = sample_dfxp.replace( + 'tts:textAlign="start"', 'tts:textAlign="center"' + ) self.assert_dfxp_equals( - sample_dfxp, results, ignore_styling=True, ignore_spans=True + expected, results, ignore_styling=True, ignore_spans=True ) diff --git a/tests/test_sami_conversion.py b/tests/test_sami_conversion.py index 2f3580ae..1cc6f821 100644 --- a/tests/test_sami_conversion.py +++ b/tests/test_sami_conversion.py @@ -148,11 +148,13 @@ def test_multiple_css_properties(self): assert "color:red;" in result or "color: red;" in result def test_positioning_alignment(self): - vtt = "WEBVTT\n\n" "00:00:01.000 --> 00:00:04.000 align:left\n" "Left aligned\n" + vtt = ( + "WEBVTT\n\n" "00:00:01.000 --> 00:00:04.000 align:right\n" "Right aligned\n" + ) caption_set = WebVTTReader().read(vtt) result = SAMIWriter().write(caption_set) - assert "text-align:left;" in result or "text-align: left;" in result + assert "text-align:right;" in result def test_writing_direction_dropped(self): vtt = ( @@ -209,8 +211,10 @@ def test_multiple_classes(self): class TestSCCtoSAMI(SAMITestingMixIn): - def test_scc_to_sami_no_text_align(self, sample_scc_pop_on): + def test_scc_to_sami_center_aligned(self, sample_scc_pop_on): caption_set = SCCReader().read(sample_scc_pop_on) result = SAMIWriter().write(caption_set) - assert "text-align" not in result + assert "text-align:center;" in result + assert "margin-left" not in result + assert "margin-top" not in result diff --git a/tests/test_scc_conversion.py b/tests/test_scc_conversion.py index 99c3714c..20e28a7f 100644 --- a/tests/test_scc_conversion.py +++ b/tests/test_scc_conversion.py @@ -38,16 +38,18 @@ def test_srt_to_scc_to_srt_conversion(self, sample_srt_ascii): class TestSCCtoDFXP: - def test_scc_to_dfxp( - self, sample_dfxp_from_scc_output, sample_scc_multiple_positioning - ): + def test_scc_to_dfxp(self, sample_scc_multiple_positioning): caption_set = SCCReader().read(sample_scc_multiple_positioning) dfxp = DFXPWriter(relativize=False, fit_to_screen=False).write(caption_set) - assert sample_dfxp_from_scc_output == dfxp + + assert 'tts:textAlign="center"' in dfxp + assert 'tts:textAlign="left"' not in dfxp + assert "tts:origin" not in dfxp + assert "abab" in dfxp + assert "ghgh" in dfxp def test_dfxp_is_valid_xml_when_scc_source_has_weird_italic_commands( self, - sample_dfxp_with_properly_closing_spans_output, sample_scc_created_dfxp_with_wrongly_closing_spans, ): caption_set = SCCReader().read( @@ -56,16 +58,25 @@ def test_dfxp_is_valid_xml_when_scc_source_has_weird_italic_commands( dfxp = DFXPWriter().write(caption_set) - assert dfxp == sample_dfxp_with_properly_closing_spans_output + assert 'tts:textAlign="center"' in dfxp + assert 'tts:textAlign="left"' not in dfxp + assert 'tts:fontStyle="italic"' in dfxp + from bs4 import BeautifulSoup + + BeautifulSoup(dfxp, "lxml-xml") def test_dfxp_is_valid_xml_when_scc_source_has_ampersand_character( - self, sample_dfxp_with_ampersand_character, sample_scc_with_ampersand_character + self, sample_scc_with_ampersand_character ): caption_set = SCCReader().read(sample_scc_with_ampersand_character) dfxp = DFXPWriter().write(caption_set) - assert dfxp == sample_dfxp_with_ampersand_character + assert 'tts:textAlign="center"' in dfxp + assert "&" in dfxp + from bs4 import BeautifulSoup + + BeautifulSoup(dfxp, "lxml-xml") class TestSCCTimestampOrdering: @@ -93,9 +104,9 @@ def test_scc_captions_are_in_order_when_short_text_followed_by_long(self): # SCC timestamps use HH:MM:SS:FF format (FF = frames) timestamps = re.findall(r"(\d+:\d+:\d+:\d+)", scc_output) for i in range(1, len(timestamps)): - assert timestamps[i] >= timestamps[i - 1], ( - f"Timestamps out of order: {timestamps[i - 1]} > {timestamps[i]}" - ) + assert ( + timestamps[i] >= timestamps[i - 1] + ), f"Timestamps out of order: {timestamps[i - 1]} > {timestamps[i]}" class TestSCCToWebVTT: diff --git a/tests/test_webvtt_conversion.py b/tests/test_webvtt_conversion.py index cad65e66..30d3568e 100644 --- a/tests/test_webvtt_conversion.py +++ b/tests/test_webvtt_conversion.py @@ -105,8 +105,13 @@ def test_conversion(self, sample_dfxp, sample_webvtt): results = DFXPWriter().write(caption_set) assert isinstance(results, str) + assert 'tts:textAlign="center"' in results + + expected = sample_dfxp.replace( + 'tts:textAlign="start"', 'tts:textAlign="center"' + ) self.assert_dfxp_equals( - sample_dfxp, results, ignore_styling=True, ignore_spans=True + expected, results, ignore_styling=True, ignore_spans=True ) @@ -239,7 +244,7 @@ def test_italic_to_sami(self): vtt = "WEBVTT\n\n00:00:01.000 --> 00:00:03.000\nitalic\n" caption_set = WebVTTReader().read(vtt) result = SAMIWriter().write(caption_set) - assert "font-style:italic" in result + assert "" in result and "" in result def test_italic_to_srt(self): vtt = "WEBVTT\n\n00:00:01.000 --> 00:00:03.000\nitalic\n" @@ -564,8 +569,8 @@ def test_nested_bold_italic_to_sami(self): caption_set = WebVTTReader().read(vtt) result = SAMIWriter().write(caption_set) - assert "font-weight:bold;" in result - assert "font-style:italic;" in result + assert "" in result and "" in result + assert "" in result and "" in result assert "both" in result def test_nested_class_and_italic_to_dfxp(self): @@ -595,7 +600,7 @@ def test_nested_class_and_italic_to_sami(self): result = SAMIWriter().write(caption_set) assert 'class="yellow"' in result - assert "font-style:italic;" in result + assert "" in result and "" in result assert "styled" in result def test_triple_nesting_to_dfxp(self):