Skip to content

Commit 06c140c

Browse files
committed
Improvement to Caption effect, including Caption unit-tests, and the following fixes:
- improved regex, which now detects lines without blank lines separating them, and detects captions that start with numbers - line wrapping fixed for languages that don't use spaces - forced line wrapping of long strings of characters
1 parent 70e86ef commit 06c140c

File tree

3 files changed

+187
-4
lines changed

3 files changed

+187
-4
lines changed

src/effects/Caption.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ void Caption::process_regex() {
8888
caption_prepared.append("\n\n");
8989
}
9090

91-
// Parse regex and find all matches
92-
QRegularExpression allPathsRegex(QStringLiteral("(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})\\s*-->\\s*(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})([\\s\\S]*?)\\n(.*?)(?=\\n\\d{2,3}|\\Z)"), QRegularExpression::MultilineOption);
91+
// Parse regex and find all matches (i.e. 00:00.000 --> 00:10.000\ncaption-text)
92+
QRegularExpression allPathsRegex(QStringLiteral("(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})\\s*-->\\s*(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})([\\s\\S]*?)(.*?)(?=\\d{2}.\\d{2,3}|\\Z)"), QRegularExpression::MultilineOption);
9393
QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(caption_prepared);
9494
while (i.hasNext()) {
9595
QRegularExpressionMatch match = i.next();
@@ -200,7 +200,14 @@ std::shared_ptr<openshot::Frame> Caption::GetFrame(std::shared_ptr<openshot::Fra
200200

201201
// Loop through words, and find word-wrap boundaries
202202
QStringList words = line.split(" ");
203-
int words_remaining = words.length();
203+
204+
// Wrap languages which do not use spaces
205+
bool use_spaces = true;
206+
if (line.length() > 20 && words.length() == 1) {
207+
words = line.split("");
208+
use_spaces = false;
209+
}
210+
int words_remaining = words.length();
204211
while (words_remaining > 0) {
205212
bool words_displayed = false;
206213
for(int word_index = words.length(); word_index > 0; word_index--) {
@@ -215,7 +222,12 @@ std::shared_ptr<openshot::Frame> Caption::GetFrame(std::shared_ptr<openshot::Fra
215222

216223
// Create path and add text to it (for correct border and fill)
217224
QPainterPath path1;
218-
QString fitting_line = words.mid(0, word_index).join(" ");
225+
QString fitting_line;
226+
if (use_spaces) {
227+
fitting_line = words.mid(0, word_index).join(" ");
228+
} else {
229+
fitting_line = words.mid(0, word_index).join("");
230+
}
219231
path1.addText(p, font, fitting_line);
220232
text_paths.push_back(path1);
221233

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(OPENSHOT_TESTS
2424
AudioWaveformer
2525
CacheDisk
2626
CacheMemory
27+
Caption
2728
Clip
2829
Color
2930
Coordinate

tests/Caption.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/**
2+
* @file
3+
* @brief Unit tests for openshot::Caption
4+
* @author Jonathan Thomas <[email protected]>
5+
*
6+
* @ref License
7+
*/
8+
9+
// Copyright (c) 2008-2023 OpenShot Studios, LLC
10+
//
11+
// SPDX-License-Identifier: LGPL-3.0-or-later
12+
13+
14+
#include <vector>
15+
#include "openshot_catch.h"
16+
#include <QApplication>
17+
#include <QFontDatabase>
18+
#include "effects/Caption.h"
19+
#include "Clip.h"
20+
#include "Frame.h"
21+
#include "Timeline.h"
22+
23+
24+
TEST_CASE( "caption effect", "[libopenshot][caption]" )
25+
{
26+
int argc;
27+
char* argv[2];
28+
QApplication app(argc, argv);
29+
QApplication::processEvents();
30+
31+
SECTION("default constructor") {
32+
33+
// Create an empty caption
34+
openshot::Caption c1;
35+
36+
CHECK(c1.color.GetColorHex(1) == "#ffffff");
37+
CHECK(c1.stroke.GetColorHex(1) == "#a9a9a9");
38+
CHECK(c1.background.GetColorHex(1) == "#000000");
39+
CHECK(c1.background_alpha.GetValue(1) == Approx(0.0f).margin(0.00001));
40+
CHECK(c1.left.GetValue(1) == Approx(0.15f).margin(0.00001));
41+
CHECK(c1.right.GetValue(1) == Approx(0.15f).margin(0.00001));
42+
CHECK(c1.top.GetValue(1) == Approx(0.7).margin(0.00001));
43+
CHECK(c1.stroke_width.GetValue(1) == Approx(0.5f).margin(0.00001));
44+
CHECK(c1.font_size.GetValue(1) == Approx(30.0f).margin(0.00001));
45+
CHECK(c1.font_alpha.GetValue(1) == Approx(1.0f).margin(0.00001));
46+
CHECK(c1.font_name == "sans");
47+
CHECK(c1.fade_in.GetValue(1) == Approx(0.35f).margin(0.00001));
48+
CHECK(c1.fade_out.GetValue(1) == Approx(0.35f).margin(0.00001));
49+
CHECK(c1.background_corner.GetValue(1) == Approx(10.0f).margin(0.00001));
50+
CHECK(c1.background_padding.GetValue(1) == Approx(20.0f).margin(0.00001));
51+
CHECK(c1.line_spacing.GetValue(1) == Approx(1.0f).margin(0.00001));
52+
CHECK(c1.CaptionText() == "00:00:00:000 --> 00:10:00:000\nEdit this caption with our caption editor");
53+
54+
// Load clip with video
55+
std::stringstream path;
56+
path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4";
57+
openshot::Clip clip1(path.str());
58+
clip1.Open();
59+
60+
// Add Caption effect
61+
clip1.AddEffect(&c1);
62+
63+
// Get frame
64+
std::shared_ptr<openshot::Frame> f = clip1.GetFrame(10);
65+
66+
// Verify pixel values (black background pixels)
67+
const unsigned char *pixels = f->GetPixels(1);
68+
CHECK((int) pixels[0 * 4] == 0);
69+
70+
// Verify pixel values (white text pixels)
71+
pixels = f->GetPixels(543);
72+
CHECK((int) pixels[238 * 4] == 255);
73+
74+
// Create Timeline
75+
openshot::Timeline t(1280, 720, openshot::Fraction(24, 1), 44100, 2, openshot::LAYOUT_STEREO);
76+
t.AddClip(&clip1);
77+
78+
// Get timeline frame
79+
f = t.GetFrame(10);
80+
81+
// Verify pixel values (black background pixels)
82+
pixels = f->GetPixels(1);
83+
CHECK((int) pixels[0 * 4] == 0);
84+
85+
// Verify pixel values (white text pixels)
86+
pixels = f->GetPixels(543);
87+
CHECK((int) pixels[238 * 4] == 255);
88+
89+
// Close objects
90+
t.Close();
91+
clip1.Close();
92+
}
93+
94+
SECTION("audio captions") {
95+
// Create an empty caption
96+
openshot::Caption c1;
97+
98+
// Load clip with audio file
99+
std::stringstream path;
100+
path << TEST_MEDIA_PATH << "piano.wav";
101+
openshot::Clip clip1(path.str());
102+
clip1.Open();
103+
104+
// Add Caption effect
105+
clip1.AddEffect(&c1);
106+
107+
// Get frame
108+
std::shared_ptr<openshot::Frame> f = clip1.GetFrame(10);
109+
110+
// Verify pixel values (black background pixels)
111+
const unsigned char *pixels = f->GetPixels(1);
112+
CHECK((int) pixels[0 * 4] == 0);
113+
114+
// Verify pixel values (white text pixels)
115+
pixels = f->GetPixels(375);
116+
CHECK((int) pixels[131 * 4] == 255);
117+
118+
// Create Timeline
119+
openshot::Timeline t(720, 480, openshot::Fraction(24, 1), 44100, 2, openshot::LAYOUT_STEREO);
120+
t.AddClip(&clip1);
121+
122+
// Get timeline frame
123+
f = t.GetFrame(10);
124+
f->Save("audio-caption.png", 1.0);
125+
126+
// Verify pixel values (black background pixels)
127+
pixels = f->GetPixels(1);
128+
CHECK((int) pixels[0 * 4] == 0);
129+
130+
// Verify pixel values (white text pixels)
131+
pixels = f->GetPixels(375);
132+
CHECK((int) pixels[131 * 4] == 255);
133+
134+
// Close objects
135+
t.Close();
136+
clip1.Close();
137+
}
138+
139+
SECTION("long single-line caption") {
140+
// Create an single-line long caption
141+
std::string caption_text = "00:00.000 --> 00:10.000\nそれが今のF1レースでは時速300kmですから、すごい進歩です。命知らずのレーザーたちによって車のスピードは更新されていったのです。";
142+
openshot::Caption c1(caption_text);
143+
144+
// Load clip with video file
145+
std::stringstream path;
146+
path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4";
147+
openshot::Clip clip1(path.str());
148+
clip1.Open();
149+
150+
// Add Caption effect
151+
clip1.AddEffect(&c1);
152+
153+
// Get frame
154+
std::shared_ptr<openshot::Frame> f = clip1.GetFrame(10);
155+
156+
// Verify pixel values (black background pixels)
157+
const unsigned char *pixels = f->GetPixels(1);
158+
CHECK((int) pixels[0 * 4] == 0);
159+
160+
// Verify pixel values (white text pixels)
161+
pixels = f->GetPixels(539);
162+
CHECK((int) pixels[344 * 4] == 255);
163+
164+
// Close objects
165+
clip1.Close();
166+
}
167+
168+
// Close QApplication
169+
app.quit();
170+
}

0 commit comments

Comments
 (0)