Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Changelog
The buffer lead-time calculation could
push a caption's start before its predecessor;
now clamped to preserve chronological order.
- Fix DFXPReader producing None entries in CaptionList when
empty <p> elements yield zero parsed nodes, causing
AttributeError in merge_concurrent_captions() and SRTWriter.

2.2.22
^^^^^^
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
# built documents.
#
# The short X.Y version.
version = "2.2.22"
version = "2.2.23.dev1"
# The full version, including alpha/beta/rc tags.
release = "2.2.22"
release = "2.2.23.dev1"

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
10 changes: 7 additions & 3 deletions pycaption/dfxp/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,13 @@ def _get_dfxp_parser_class():
def _convert_div_to_caption_list(self, div):
return CaptionList(
[
self._convert_p_tag_to_caption(p_tag)
for p_tag in div.find_all("p")
if p_tag.get_text().strip()
caption
for caption in (
self._convert_p_tag_to_caption(p_tag)
for p_tag in div.find_all("p")
if p_tag.get_text().strip()
)
if caption is not None
],
div.layout_info,
)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

setup(
name="pycaption",
version="2.2.22",
version="2.2.23.dev1",
description="Closed caption converter",
long_description=open(README_PATH).read(),
author="Joe Norton",
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
sample_dfxp_with_relativized_positioning,
sample_dfxp_with_templated_style,
sample_dfxp_without_region_and_style,
sample_dfxp_concurrent_with_empty_p,
)
from tests.fixtures.microdvd import missing_fps_sample_microdvd # noqa: F401
from tests.fixtures.microdvd import (
Expand Down
28 changes: 28 additions & 0 deletions tests/fixtures/dfxp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1521,3 +1521,31 @@ def sample_dfxp_default_styling_p_tags():
</div>
</body>
</tt>"""


@pytest.fixture(scope="session")
def sample_dfxp_concurrent_with_empty_p():
return """\
<?xml version="1.0" encoding="utf-8"?>
<tt xml:lang="en" xmlns="http://www.w3.org/ns/ttml"
xmlns:tts="http://www.w3.org/ns/ttml#styling">
<head>
<styling>
<style xml:id="basic" tts:color="white"/>
</styling>
<layout>
<region xml:id="pop1" tts:origin="17.5% 84.66%" tts:extent="62.5% 5.33%"/>
<region xml:id="pop2" tts:origin="50% 84.66%" tts:extent="0% 5.33%"/>
</layout>
</head>
<body>
<div xml:lang="en">
<p region="pop1" style="basic" begin="01:55:05:08" end="01:55:11:14"
tts:origin="17.5% 84.66%" tts:extent="62.5% 5.33%">Subtitle End
</p>
<p region="pop2" style="basic" begin="01:55:05:08" end="01:55:11:14"
tts:origin="50% 84.66%" tts:extent="0% 5.33%">
</p>
</div>
</body>
</tt>"""
44 changes: 43 additions & 1 deletion tests/test_dfxp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from pycaption import CaptionReadNoCaptions, DFXPReader
from pycaption import CaptionReadNoCaptions, DFXPReader, SRTWriter
from pycaption.base import merge_concurrent_captions
from pycaption.exceptions import (
CaptionReadError,
CaptionReadSyntaxError,
Expand Down Expand Up @@ -230,3 +231,44 @@ def test_empty_cue(self, sample_dfxp_empty_cue):
caps = caption_set.get_captions("en-US")

assert len(caps) == 1

def test_concurrent_captions_with_empty_p_no_none_in_list(
self, sample_dfxp_concurrent_with_empty_p
):
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
captions = caption_set.get_captions("en")
assert all(c is not None for c in captions)
assert len(captions) == 1

def test_concurrent_captions_with_empty_p_merge_does_not_crash(
self, sample_dfxp_concurrent_with_empty_p
):
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
merged = merge_concurrent_captions(caption_set)
captions = merged.get_captions("en")
assert len(captions) >= 1
assert all(c is not None for c in captions)

def test_concurrent_captions_with_empty_p_srt_writer_does_not_crash(
self, sample_dfxp_concurrent_with_empty_p
):
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
output = SRTWriter().write(caption_set)
assert "Subtitle End" in output

def test_none_from_convert_p_tag_is_filtered(self, sample_dfxp):
reader = DFXPReader()
original = reader._convert_p_tag_to_caption
call_count = [0]

def patched(p_tag):
call_count[0] += 1
if call_count[0] == 2:
return None
return original(p_tag)

reader._convert_p_tag_to_caption = patched
caption_set = reader.read(sample_dfxp)
captions = caption_set.get_captions("en-US")
assert all(c is not None for c in captions)
assert len(captions) == 6
Loading