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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Features:
Fixes:

- Support slicing ``Container.streams``, which raised ``TypeError`` despite being typed.
- ``AssSubtitle.dialogue`` now strips override tags such as ``{\b1}``, turns ``\n`` and ``\h`` into spaces, and keeps a ``{`` with no closing ``}`` instead of dropping the rest of the line.
- ``HWConfig.methods`` no longer raises ``ValueError`` for configs that support several methods; ``HWConfigMethod`` is now an ``IntFlag``.
- ``CodecContext.is_encoder``, ``CodecContext.is_decoder``, ``Container.input_was_opened``, and ``VideoFormatComponent.is_chroma`` now return ``bool`` instead of ``int``.
- ``Frame.time_base`` returns ``AVRational(0, 1)`` instead of ``None`` when unset, like other ``AVRational`` properties.
Expand Down
35 changes: 22 additions & 13 deletions av/subtitles/subtitle.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,21 @@ def ass(self):
@property
def dialogue(self):
"""
Extract the dialogue from the ass format. Strip comments.
Extract the plain text from the ASS dialogue line.

Override tags and comments (``{...}`` blocks) are removed, ``\\N``
becomes a newline, and ``\\n`` and ``\\h`` become a space. A ``{``
with no closing ``}`` is kept as literal text.
"""
ass_text: bytes = self.ass
text_len: cython.Py_ssize_t = len(ass_text)
# A '{' only opens a block if a '}' appears somewhere after it.
last_close: cython.Py_ssize_t = ass_text.rfind(b"}")
comma_count: cython.short = 0
i: cython.Py_ssize_t = 0
state: cython.bint = False
ass_text: bytes = self.ass
in_block: cython.bint = False
char, next_char = cython.declare(cython.uchar)
result: bytearray = bytearray()
text_len: cython.Py_ssize_t = len(ass_text)

while comma_count < 8 and i < text_len:
if ass_text[i] == b","[0]:
Expand All @@ -310,18 +316,21 @@ def dialogue(self):
char = ass_text[i]
next_char = 0 if i + 1 >= text_len else ass_text[i + 1]

if char == b"\\"[0] and next_char == b"N"[0]:
result.append(b"\n"[0])
i += 2
continue

if not state:
if char == b"{"[0] and next_char != b"\\"[0]:
state = True
if not in_block:
if char == b"\\"[0] and next_char == b"N"[0]:
result.append(b"\n"[0])
i += 2
continue
if char == b"\\"[0] and (next_char == b"n"[0] or next_char == b"h"[0]):
result.append(b" "[0])
i += 2
continue
if char == b"{"[0] and i < last_close:
in_block = True
else:
result.append(char)
elif char == b"}"[0]:
state = False
in_block = False
i += 1

return bytes(result)
Expand Down
22 changes: 22 additions & 0 deletions tests/test_subtitles.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,28 @@ def test_subtitle_dialogue_extended_chars(self) -> None:

assert sub.dialogue == "♪ Hey, hey, hey ♪".encode()

def test_subtitle_dialogue(self) -> None:
from av.subtitles.subtitle import SubtitleSet

def dialogue(text: bytes) -> bytes:
subtitle = SubtitleSet.create(text=text, start=0, end=2000)
return cast(AssSubtitle, subtitle[0]).dialogue

prefix = b"0,0,Default,,0,0,0,,"
# Override tags and comments are stripped.
assert dialogue(prefix + b"{\\b1}bold{\\b0}") == b"bold"
assert dialogue(prefix + b"{\\an8}top") == b"top"
assert dialogue(prefix + b"{\\fnArial}hi") == b"hi"
assert dialogue(prefix + b"a{comment}b") == b"ab"
# Text escapes: \N -> newline, \n and \h -> space.
assert dialogue(prefix + b"a\\Nb") == b"a\nb"
assert dialogue(prefix + b"a\\nb") == b"a b"
assert dialogue(prefix + b"a\\hb") == b"a b"
# A stray '{' with no closing '}' is literal text.
assert dialogue(prefix + b"cost is {5") == b"cost is {5"
# Commas in the Text field are kept.
assert dialogue(prefix + b"x, y, z") == b"x, y, z"

def test_subtitle_encode_mp4(self) -> None:
"""Test encoding subtitles to MP4 container."""
from av.subtitles.subtitle import SubtitleSet
Expand Down
Loading