2023-12-01 17:32:19 +00:00
|
|
|
// Copyright 2017 Google LLC. All rights reserved.
|
2017-05-31 19:23:44 +00:00
|
|
|
//
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file or at
|
|
|
|
// https://developers.google.com/open-source/licenses/bsd
|
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <packager/media/formats/webvtt/webvtt_utils.h>
|
2017-05-31 19:23:44 +00:00
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <gtest/gtest.h>
|
2017-05-31 19:23:44 +00:00
|
|
|
|
|
|
|
namespace shaka {
|
|
|
|
namespace media {
|
|
|
|
|
2020-08-26 20:47:14 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
const TextFragmentStyle kNoStyle{};
|
|
|
|
|
|
|
|
TextFragmentStyle GetItalicStyle() {
|
|
|
|
TextFragmentStyle style;
|
|
|
|
style.italic = true;
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
TextFragmentStyle GetBoldStyle() {
|
|
|
|
TextFragmentStyle style;
|
|
|
|
style.bold = true;
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2017-05-31 19:23:44 +00:00
|
|
|
TEST(WebVttTimestampTest, TooShort) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, RightLengthButMeaningless) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("ABCDEFGHI", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseHours) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("12:00:00.000", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(43200000, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseLongHours) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("120:00:00.000", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(432000000, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseMinutes) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("00:12:00.000", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(720000, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseSeconds) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("00:00:12.000", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(12000, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseMs) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("00:00:00.123", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(123, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, ParseNoHours) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_TRUE(WebVttTimestampToMs("12:00.000", &ms));
|
2021-08-04 18:56:44 +00:00
|
|
|
EXPECT_EQ(720000, ms);
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithShortHours) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("1:00:00.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithShortMinutes) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:1:00.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithShortSeconds) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:1.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithShortMs) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:00.01", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithNonDigit) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:0A:00.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithInvalidMinutes) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:79:00.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, FailWithInvalidSeconds) {
|
2021-08-04 18:56:44 +00:00
|
|
|
int64_t ms;
|
2017-08-17 18:09:34 +00:00
|
|
|
EXPECT_FALSE(WebVttTimestampToMs("00:00:79.000", &ms));
|
2017-05-31 19:23:44 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 18:09:34 +00:00
|
|
|
TEST(WebVttTimestampTest, CreatesMilliseconds) {
|
|
|
|
EXPECT_EQ("00:00:00.123", MsToWebVttTimestamp(123));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreatesMillisecondsShort) {
|
|
|
|
EXPECT_EQ("00:00:00.012", MsToWebVttTimestamp(12));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateSeconds) {
|
|
|
|
EXPECT_EQ("00:00:12.000", MsToWebVttTimestamp(12000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateSecondsShort) {
|
|
|
|
EXPECT_EQ("00:00:01.000", MsToWebVttTimestamp(1000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateMinutes) {
|
|
|
|
EXPECT_EQ("00:12:00.000", MsToWebVttTimestamp(720000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateMinutesShort) {
|
|
|
|
EXPECT_EQ("00:01:00.000", MsToWebVttTimestamp(60000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateHours) {
|
|
|
|
EXPECT_EQ("12:00:00.000", MsToWebVttTimestamp(43200000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateHoursShort) {
|
|
|
|
EXPECT_EQ("01:00:00.000", MsToWebVttTimestamp(3600000));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttTimestampTest, CreateHoursLong) {
|
|
|
|
EXPECT_EQ("123:00:00.000", MsToWebVttTimestamp(442800000));
|
|
|
|
}
|
2020-08-26 19:31:58 +00:00
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, SettingsToString) {
|
|
|
|
TextSettings settings;
|
|
|
|
settings.region = "foo";
|
|
|
|
settings.line = TextNumber(27, TextUnitType::kPercent);
|
|
|
|
settings.position = TextNumber(42, TextUnitType::kPercent);
|
2020-12-01 19:32:39 +00:00
|
|
|
settings.width = TextNumber(54, TextUnitType::kPercent);
|
2020-08-26 19:31:58 +00:00
|
|
|
settings.writing_direction = WritingDirection::kVerticalGrowingLeft;
|
|
|
|
settings.text_alignment = TextAlignment::kEnd;
|
|
|
|
|
|
|
|
const auto actual = WebVttSettingsToString(settings);
|
|
|
|
EXPECT_EQ(actual,
|
|
|
|
"region:foo line:27% position:42% size:54% direction:rl align:end");
|
|
|
|
}
|
|
|
|
|
feat: teletext formatting (#1384)
This PR adds parsing of teletext styling, and rendering of the styling
in output TTML and WebVTT subtitle tracks.
Beyond unit tests, I've used the sample
https://drive.google.com/file/d/19ZYsoeUfH85gEilQkaAdLbPhC4CxhDEh/view?usp=sharing
which has rather advanced subtitling with two separate rows at the same
time, where one is left aligned and another is right aligned. This
necessitates two parallel cues to be rendered. It also has some colored
text.
Solve #1335.
## parse teletext styling and formatting
Extend the teletext parser to parse the teletext styling and formatting.
This includes translating rows into regions, calculating alignment
from start and stop position of the text, and extracting text and
background colors.
The colors are limited to full lines.
Both lines and regions are propagated in the TextSample structures.
This is because the number of lines may differ from different sources.
For teletext, there are 24 rows, but they are essentially always
used with double height, so the number of output lines is 12
from 0 to 11.
There are also corresponding regions are denoted "ttx_R",
where R is an integer row number. A renderer can use either
the line number or the region ID to render the text.
## ttml generation for teletext to EBU-TT-D
Add support to render teletext input in EBU-TT-D (IMSC-1) format.
This includes appropriate regions ttx_0 to ttx_11 signalled
in the TextSamples, alignment and text and background colors.
The general TTML output has been changed to always include
metadata, layout, and styling nodes, even if they are empty.
EBU-TT-D is detected by the presence of "ttx_?" regions in the
samples. If detected, extra TTML elements will be added and
the EBU-TT-D linePadding used as well.
Appropriate styles for background and text colors are generated
depending on the color and backgroundColor attributes in the
text fragments.
## adapt WebVTT output to teletext TextSample.
Teletext input generates both a region with prefix ttx_
and a floating point line number (e.g. 9.5) in the
range 0 to 11.5 (due to input 0-23 as double lines).
The output is adopted to drop such regions
and convert the line number to an integer
since the standard only used floats for percent
values but not for plain line numbers.
2024-04-29 17:33:03 +00:00
|
|
|
TEST(WebVttUtilsTest, TeletextSettingsToStringRemovesRegionOutputsIntegerLine) {
|
|
|
|
TextSettings settings;
|
|
|
|
settings.region = "ttx_9";
|
|
|
|
settings.line = TextNumber(9.5, TextUnitType::kLines);
|
|
|
|
settings.text_alignment = TextAlignment::kCenter;
|
|
|
|
|
|
|
|
const auto actual = WebVttSettingsToString(settings);
|
|
|
|
EXPECT_EQ(actual, "line:10 align:center");
|
|
|
|
}
|
|
|
|
|
2020-08-26 19:31:58 +00:00
|
|
|
TEST(WebVttUtilsTest, SettingsToString_IgnoresDefaults) {
|
|
|
|
TextSettings settings;
|
|
|
|
settings.region = "foo";
|
2021-05-05 05:57:43 +00:00
|
|
|
settings.text_alignment = TextAlignment::kCenter;
|
2020-08-26 19:31:58 +00:00
|
|
|
|
|
|
|
const auto actual = WebVttSettingsToString(settings);
|
2021-05-05 05:57:43 +00:00
|
|
|
EXPECT_EQ(actual, "region:foo align:center");
|
2020-08-26 19:31:58 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 20:47:14 +00:00
|
|
|
TEST(WebVttUtilsTest, FragmentToString) {
|
|
|
|
TextFragment frag(GetBoldStyle(), "Foobar");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "<b>Foobar</b>");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_PreservesTags) {
|
|
|
|
TextFragment frag(kNoStyle, "<i>Foobar</i>");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "<i>Foobar</i>");
|
|
|
|
}
|
|
|
|
|
2021-05-05 17:58:17 +00:00
|
|
|
TEST(WebVttUtilsTest, FragmentToString_ConsecutiveLeadingWhitespaces) {
|
|
|
|
TextFragment frag(kNoStyle, "\r\n\t \r\nFoobar");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), " Foobar");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_ConsecutiveTrailingWhitespaces) {
|
|
|
|
TextFragment frag(kNoStyle, "Foobar\r\n\t \r\n");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "Foobar ");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_ConsecutiveInternalWhitespaces) {
|
|
|
|
TextFragment frag(kNoStyle, "Hello\r\n\t \r\nWorld");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "Hello World");
|
|
|
|
}
|
|
|
|
|
2020-08-26 20:47:14 +00:00
|
|
|
TEST(WebVttUtilsTest, FragmentToString_HandlesNestedFragments) {
|
|
|
|
TextFragment frag;
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "Hello ");
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "World");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "Hello World");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_HandlesNestedFragmentsWithStyle) {
|
|
|
|
TextFragment frag;
|
|
|
|
frag.style.bold = true;
|
|
|
|
frag.sub_fragments.emplace_back(GetItalicStyle(), "Hello");
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, " World");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "<b><i>Hello</i> World</b>");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_HandlesNewlines) {
|
|
|
|
TextFragment frag;
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "Hello");
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, true);
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "World");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "Hello\nWorld");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_HandlesNewlinesWithStyle) {
|
|
|
|
TextFragment frag;
|
|
|
|
frag.style.bold = true;
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "Hello");
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, true);
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, "World");
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "<b>Hello</b>\n<b>World</b>");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, FragmentToString_HandlesNestedNewlinesWithStyle) {
|
|
|
|
TextFragment nested;
|
|
|
|
nested.sub_fragments.emplace_back(kNoStyle, "Hello");
|
|
|
|
nested.sub_fragments.emplace_back(kNoStyle, true);
|
|
|
|
nested.sub_fragments.emplace_back(kNoStyle, "World");
|
|
|
|
|
|
|
|
TextFragment frag;
|
|
|
|
frag.style.bold = true;
|
|
|
|
frag.sub_fragments.emplace_back(nested);
|
|
|
|
frag.sub_fragments.emplace_back(kNoStyle, " Now");
|
|
|
|
|
|
|
|
EXPECT_EQ(WebVttFragmentToString(frag), "<b>Hello</b>\n<b>World Now</b>");
|
|
|
|
}
|
|
|
|
|
2020-08-26 21:21:09 +00:00
|
|
|
TEST(WebVttUtilsTest, GetPreamble_BasicFlow) {
|
|
|
|
TextStreamInfo info(0, 0, 0, kCodecWebVtt, "", "", 0, 0, "");
|
|
|
|
info.set_css_styles("::cue { color: red; }");
|
|
|
|
|
|
|
|
TextRegion region;
|
|
|
|
region.width.value = 34;
|
|
|
|
region.height = TextNumber(56, TextUnitType::kLines);
|
|
|
|
region.window_anchor_x.value = 99;
|
|
|
|
region.window_anchor_y.value = 12;
|
|
|
|
region.region_anchor_x.value = 41;
|
|
|
|
region.region_anchor_y.value = 29;
|
|
|
|
info.AddRegion("foo", region);
|
|
|
|
|
|
|
|
EXPECT_EQ(WebVttGetPreamble(info),
|
|
|
|
"REGION\n"
|
|
|
|
"id:foo\n"
|
|
|
|
"width:34.000000%\n"
|
|
|
|
"lines:56\n"
|
|
|
|
"viewportanchor:99.000000%,12.000000%\n"
|
|
|
|
"regionanchor:41.000000%,29.000000%\n"
|
|
|
|
"\n"
|
|
|
|
"STYLE\n"
|
|
|
|
"::cue { color: red; }");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, GetPreamble_MultipleRegions) {
|
|
|
|
TextStreamInfo info(0, 0, 0, kCodecWebVtt, "", "", 0, 0, "");
|
|
|
|
|
|
|
|
TextRegion region1;
|
|
|
|
region1.width.value = 34;
|
|
|
|
region1.height = TextNumber(56, TextUnitType::kLines);
|
|
|
|
region1.window_anchor_x.value = 99;
|
|
|
|
region1.window_anchor_y.value = 12;
|
|
|
|
region1.region_anchor_x.value = 41;
|
|
|
|
region1.region_anchor_y.value = 29;
|
|
|
|
info.AddRegion("r1", region1);
|
|
|
|
|
|
|
|
TextRegion region2;
|
|
|
|
region2.width.value = 82;
|
|
|
|
region2.height = TextNumber(61, TextUnitType::kLines);
|
|
|
|
region2.window_anchor_x.value = 51;
|
|
|
|
region2.window_anchor_y.value = 62;
|
|
|
|
region2.region_anchor_x.value = 92;
|
|
|
|
region2.region_anchor_y.value = 78;
|
|
|
|
info.AddRegion("r2", region2);
|
|
|
|
|
|
|
|
EXPECT_EQ(WebVttGetPreamble(info),
|
|
|
|
"REGION\n"
|
|
|
|
"id:r1\n"
|
|
|
|
"width:34.000000%\n"
|
|
|
|
"lines:56\n"
|
|
|
|
"viewportanchor:99.000000%,12.000000%\n"
|
|
|
|
"regionanchor:41.000000%,29.000000%\n"
|
|
|
|
"\n"
|
|
|
|
"REGION\n"
|
|
|
|
"id:r2\n"
|
|
|
|
"width:82.000000%\n"
|
|
|
|
"lines:61\n"
|
|
|
|
"viewportanchor:51.000000%,62.000000%\n"
|
|
|
|
"regionanchor:92.000000%,78.000000%");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, GetPreamble_Scroll) {
|
|
|
|
TextStreamInfo info(0, 0, 0, kCodecWebVtt, "", "", 0, 0, "");
|
|
|
|
|
|
|
|
TextRegion region;
|
|
|
|
region.width.value = 37;
|
|
|
|
region.height = TextNumber(82, TextUnitType::kLines);
|
|
|
|
region.window_anchor_x.value = 32;
|
|
|
|
region.window_anchor_y.value = 66;
|
|
|
|
region.region_anchor_x.value = 95;
|
|
|
|
region.region_anchor_y.value = 72;
|
|
|
|
region.scroll = true;
|
|
|
|
info.AddRegion("foo", region);
|
|
|
|
|
|
|
|
EXPECT_EQ(WebVttGetPreamble(info),
|
|
|
|
"REGION\n"
|
|
|
|
"id:foo\n"
|
|
|
|
"width:37.000000%\n"
|
|
|
|
"lines:82\n"
|
|
|
|
"viewportanchor:32.000000%,66.000000%\n"
|
|
|
|
"regionanchor:95.000000%,72.000000%\n"
|
|
|
|
"scroll:up");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(WebVttUtilsTest, GetPreamble_OnlyStyles) {
|
|
|
|
TextStreamInfo info(0, 0, 0, kCodecWebVtt, "", "", 0, 0, "");
|
|
|
|
info.set_css_styles("::cue { color: red; }");
|
|
|
|
|
|
|
|
EXPECT_EQ(WebVttGetPreamble(info),
|
|
|
|
"STYLE\n"
|
|
|
|
"::cue { color: red; }");
|
|
|
|
}
|
|
|
|
|
2017-05-31 19:23:44 +00:00
|
|
|
} // namespace media
|
|
|
|
} // namespace shaka
|