Skip to content
Open
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
7 changes: 5 additions & 2 deletions src/main/python/ttconv/imsc/style_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -1114,8 +1114,11 @@ def extract(cls, context: StyleParsingContext, xml_attrib: str):
if xml_attrib == "rl":
return styles.WritingModeType.rltb

if xml_attrib == "tb":
return styles.WritingModeType.tbrl
if xml_attrib.startswith("tb"):
if xml_attrib == "tbrl":
return styles.WritingModeType.tbrl
elif xml_attrib == "tbrl"
return styles.WritingModeType.tblr

return styles.WritingModeType[xml_attrib]

Expand Down
3 changes: 2 additions & 1 deletion src/main/python/ttconv/isd.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ def generate_isd_sequence(
styles.StyleProperties.TextOutline,
styles.StyleProperties.TextShadow,
styles.StyleProperties.TextEmphasis,
styles.StyleProperties.Padding
styles.StyleProperties.Padding,
styles.StyleProperties.WritingMode
)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion src/main/python/ttconv/style_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ class WritingMode(StyleProperty):
'''Corresponds to tts:writingMode
'''

is_inherited = False
is_inherited = True
is_animatable = True

@staticmethod
Expand Down
3 changes: 3 additions & 0 deletions src/main/python/ttconv/vtt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ def name(cls):

# outputs cue identifier
cue_id: bool = field(default=True, metadata={"decoder": bool})

# vertical positioning
vertical_position: bool = field(default=False, metadata={"decoder": bool})
45 changes: 45 additions & 0 deletions src/main/python/ttconv/vtt/cue.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ class TextAlignment(Enum):
left = "left"
center = "center"
right = "right"
start = "start"
end = "end"

class Vertical(Enum):
"""WebVTT vertical positioning cue setting"""
lr = "lr" # left to right
rl = "rl" # right to left

_EOL_SEQ_RE = re.compile(r"\n{2,}")

Expand All @@ -55,9 +62,12 @@ def __init__(self, identifier: Optional[int] = None):
self._begin: Optional[ClockTime] = None
self._end: Optional[ClockTime] = None
self._text: str = ""
self._position: int = None
self._size: int = None
self._line: int = None
self._linealign: VttCue.LineAlignment = None
self._textalign: VttCue.TextAlignment = None
self._vertical: VttCue.Vertical = None

def set_begin(self, offset: Fraction):
"""Sets the paragraph begin time code"""
Expand Down Expand Up @@ -95,6 +105,30 @@ def set_align(self, align: LineAlignment):
def get_align(self) -> Optional[LineAlignment]:
"""Return the WebVTT line alignment cue setting"""
return self._linealign

def set_vertical(self, vertical: Vertical):
"""Sets the WebVTT vertical positioning cue setting"""
self._vertical = vertical

def get_vertical(self) -> Optional[Vertical]:
"""Returns the WebVTT vertical positioning cue setting"""
return self._vertical

def set_position(self, position: int):
"""Sets the WebVTT position cue setting (in whole percent)"""
self._position = position

def get_position(self) -> Optional[int]:
"""Returns the WebVTT position cue setting (in whole percent)"""
return self._position

def set_size(self, size: int):
"""Sets the WebVTT size cue setting (in whole percent)"""
self._size = size

def get_size(self) -> Optional[int]:
"""Returns the WebVTT size cue setting (in whole percent)"""
return self._size

def set_textalign(self, textalign: TextAlignment):
"""Sets the WebVTT text alignment cue setting"""
Expand Down Expand Up @@ -134,6 +168,10 @@ def __str__(self) -> str:
# cue timing
t += f"{self._begin} --> {self._end}"

# cue vertical positioning
if self._vertical is not None:
t += f" vertical:{self._vertical.value}"

# cue text position
if self._textalign is not None:
t += f" align:{self._textalign.value}"
Expand All @@ -144,6 +182,13 @@ def __str__(self) -> str:

if self._linealign is not None:
t += f",{self._linealign.value}"

# cue position
if self._position is not None:
t += f" position:{self._position}%"

if self._size is not None:
t += f" size:{self._size}%"

t += "\n"

Expand Down
78 changes: 75 additions & 3 deletions src/main/python/ttconv/vtt/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from ttconv.vtt.cue import VttCue
from ttconv.vtt.css_class import CssClass
from ttconv.style_properties import DirectionType, ExtentType, PositionType, StyleProperties, FontStyleType, NamedColors, \
FontWeightType, TextDecorationType, DisplayAlignType, TextAlignType
FontWeightType, TextDecorationType, DisplayAlignType, TextAlignType, WritingModeType

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -92,6 +92,11 @@ def __init__(self, config: VTTWriterConfiguration):
StyleProperties.Direction: [],
})

if self._config.vertical_position:
supported_styles.update({
StyleProperties.WritingMode: []
})

self._filters.append(SupportedStylePropertiesISDFilter(supported_styles))

self._filters.append(
Expand Down Expand Up @@ -167,7 +172,74 @@ def process_p(self, region: ISD.Region, element: model.P, begin: Fraction, end:
cue.set_begin(begin)
cue.set_end(end)

if self._config.line_position:
# add vertical position config
if self._config.vertical_position:
vertical = region.get_style(StyleProperties.WritingMode)
display_align = region.get_style(StyleProperties.DisplayAlign)
position: PositionType = region.get_style(StyleProperties.Position)
extent: ExtentType = region.get_style(StyleProperties.Extent)
text_align = element.get_style(StyleProperties.TextAlign)

if vertical == WritingModeType.tbrl:
cue.set_vertical(VttCue.Vertical.rl)
if display_align == DisplayAlignType.after:
# vertical subtitle should be on the left side
cue.set_line(round(position.v_offset.value))
if text_align == TextAlignType.start:
# vertical subtitle should be on the top
cue.set_textalign(VttCue.TextAlignment.start)
cue.set_position(round(position.h_offset.value))
elif text_align == TextAlignType.end:
# vertical subtitle should be on the bottom
cue.set_textalign(VttCue.TextAlignment.end)
cue.set_position(round(position.h_offset.value + extent.height.value))
else:
cue.set_position(round(position.h_offset.value))
cue.set_textalign(VttCue.TextAlignment.start)
elif display_align == DisplayAlignType.before:
# vertical subtitle should be on the right side
cue.set_line(round(position.v_offset.value + extent.height.value))
if text_align == TextAlignType.start:
# vertical subtitle should be on the top
cue.set_textalign(VttCue.TextAlignment.start)
cue.set_position(round(position.h_offset.value))
elif text_align == TextAlignType.end:
# vertical subtitle should be on the bottom
cue.set_textalign(VttCue.TextAlignment.end)
cue.set_position(round(position.h_offset.value + extent.height.value))
else:
cue.set_position(round(position.h_offset.value))
cue.set_textalign(VttCue.TextAlignment.start)
cue.set_size(round(extent.width.value - position.v_offset.value - position.h_offset.value))
elif vertical == WritingModeType.tblr:
cue.set_vertical(VttCue.Vertical.lr)
if display_align == DisplayAlignType.after:
# vertical subtitle should be on the right side
cue.set_line(round(position.v_offset.value))
if text_align == TextAlignType.start:
# vertical subtitle should be on the top
cue.set_textalign(VttCue.TextAlignment.start)
cue.set_position(round(position.h_offset.value))
elif text_align == TextAlignType.end:
# vertical subtitle should be on the bottom
cue.set_textalign(VttCue.TextAlignment.end)
cue.set_position(round(position.h_offset.value + extent.height.value))
elif display_align == DisplayAlignType.before:
# vertical subtitle should be on the left side
cue.set_line(round(position.v_offset.value + extent.width.value))
if text_align == TextAlignType.start:
# vertical subtitle should be on the start
cue.set_textalign(VttCue.TextAlignment.start)
cue.set_position(round(position.h_offset.value))
elif text_align == TextAlignType.end:
# vertical subtitle should be on the top
cue.set_textalign(VttCue.TextAlignment.end)
cue.set_position(round(position.h_offset.value + extent.height.value))
cue.set_size(round(extent.width.value - position.v_offset.value - position.h_offset.value))
else:
cue.set_vertical(None)

if self._config.line_position and cue.get_vertical() is None:
display_align = region.get_style(StyleProperties.DisplayAlign)
position: PositionType = region.get_style(StyleProperties.Position)
extent: ExtentType = region.get_style(StyleProperties.Extent)
Expand All @@ -182,7 +254,7 @@ def process_p(self, region: ISD.Region, element: model.P, begin: Fraction, end:
cue.set_line(round(position.v_offset.value + extent.height.value / 2))
cue.set_align(VttCue.LineAlignment.center)

if self._config.text_align:
if self._config.text_align and cue.get_vertical() is None:
direction = element.get_style(StyleProperties.Direction)
text_align = element.get_style(StyleProperties.TextAlign)
if text_align == TextAlignType.center:
Expand Down