16 #include "../Timeline.h"
24 #include <QPainterPath>
29 Caption::Caption() : color(
"#ffffff"), stroke(
"#a9a9a9"), background(
"#ff000000"), background_alpha(0.0), left(0.15), top(0.7), right(0.15),
30 stroke_width(0.5), font_size(30.0), font_alpha(1.0), is_dirty(true), font_name(
"sans"), font(NULL), metrics(NULL),
31 fade_in(0.35), fade_out(0.35), background_corner(10.0), background_padding(20.0), line_spacing(1.0)
34 init_effect_details();
39 color(
"#ffffff"), caption_text(captions), stroke(
"#a9a9a9"), background(
"#ff000000"), background_alpha(0.0),
40 left(0.15), top(0.7), right(0.15), stroke_width(0.5), font_size(30.0), font_alpha(1.0), is_dirty(true), font_name(
"sans"),
41 font(NULL), metrics(NULL), fade_in(0.35), fade_out(0.35), background_corner(10.0), background_padding(20.0), line_spacing(1.0)
44 init_effect_details();
48 void Caption::init_effect_details()
61 if (caption_text.length() == 0) {
62 caption_text =
"00:00:00:000 --> 00:10:00:000\nEdit this caption with our caption editor";
73 caption_text = new_caption_text;
78 void Caption::process_regex() {
83 matchedCaptions.clear();
85 QString caption_prepared = QString(caption_text.c_str());
86 if (caption_prepared.endsWith(
"\n\n") ==
false) {
88 caption_prepared.append(
"\n\n");
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);
93 QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(caption_prepared);
95 QRegularExpressionMatch match = i.next();
96 if (match.hasMatch()) {
98 matchedCaptions.push_back(match);
106 std::shared_ptr<openshot::Frame>
Caption::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
115 double scale_factor = 1.0;
123 if (timeline != NULL) {
128 }
else if (
clip != NULL &&
clip->Reader() != NULL) {
129 fps.
num =
clip->Reader()->info.fps.num;
130 fps.
den =
clip->Reader()->info.fps.den;
135 std::shared_ptr<QImage> frame_image = frame->GetImage();
138 QPainter painter(frame_image.get());
139 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
true);
142 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
146 QFont font(QString(
font_name.c_str()),
int(font_size_value));
147 font.setPointSizeF(std::max(font_size_value, 1.0));
148 QFontMetricsF metrics = QFontMetricsF(font);
160 double metrics_line_spacing = metrics.lineSpacing();
163 double left_margin_x = frame_image->width() * left_value;
164 double starting_y = (frame_image->height() * top_value) + metrics_line_spacing;
165 double current_y = starting_y;
166 double bottom_y = starting_y;
167 double top_y = starting_y;
168 double max_text_width = 0.0;
169 double right_margin_x = frame_image->width() - (frame_image->width() * right_value);
170 double caption_area_width = right_margin_x - left_margin_x;
171 QRectF caption_area = QRectF(left_margin_x, starting_y, caption_area_width, frame_image->height());
174 std::vector<QPainterPath> text_paths;
175 double fade_in_percentage = 0.0;
176 double fade_out_percentage = 0.0;
177 double line_height = metrics_line_spacing * line_spacing_value;
180 for (
auto match = matchedCaptions.begin(); match != matchedCaptions.end(); match++) {
183 int64_t start_frame = ((match->captured(1).toFloat() * 60.0 * 60.0 ) + (match->captured(2).toFloat() * 60.0 ) +
184 match->captured(3).toFloat() + (match->captured(4).toFloat() / 1000.0)) * fps.
ToFloat();
185 int64_t end_frame = ((match->captured(5).toFloat() * 60.0 * 60.0 ) + (match->captured(6).toFloat() * 60.0 ) +
186 match->captured(7).toFloat() + (match->captured(8).toFloat() / 1000.0)) * fps.
ToFloat();
189 QStringList lines = match->captured(9).split(
"\n");
190 for(
int index = 0; index < lines.length(); index++) {
192 QString line = lines[index];
194 if (!line.startsWith(QStringLiteral(
"NOTE")) &&
195 !line.isEmpty() && frame_number >= start_frame && frame_number <= end_frame && line.length() > 1) {
198 fade_in_percentage = ((float) frame_number - (
float) start_frame) / fade_in_value;
199 fade_out_percentage = 1.0 - (((float) frame_number - ((
float) end_frame - fade_out_value)) / fade_out_value);
202 QStringList words = line.split(
" ");
203 int words_remaining = words.length();
204 while (words_remaining > 0) {
205 bool words_displayed =
false;
206 for(
int word_index = words.length(); word_index > 0; word_index--) {
208 QString fitting_line = words.mid(0, word_index).join(
" ");
211 QRectF textRect = metrics.boundingRect(caption_area, Qt::TextSingleLine, fitting_line);
212 if (textRect.width() <= caption_area.width()) {
214 QPoint p(left_margin_x, current_y);
218 QString fitting_line = words.mid(0, word_index).join(
" ");
219 path1.addText(p, font, fitting_line);
220 text_paths.push_back(path1);
223 words = words.mid(word_index, words.length());
224 words_remaining = words.length();
225 words_displayed =
true;
228 current_y += line_height;
231 if (path1.boundingRect().width() > max_text_width) {
232 max_text_width = path1.boundingRect().width();
235 if (path1.boundingRect().top() < top_y) {
236 top_y = path1.boundingRect().top();
239 if (path1.boundingRect().bottom() > bottom_y) {
240 bottom_y = path1.boundingRect().bottom();
246 if (!words_displayed) {
257 QRectF caption_area_with_padding = QRectF(left_margin_x - (padding_value / 2.0),
258 top_y - (padding_value / 2.0),
259 max_text_width + padding_value,
260 (bottom_y - top_y) + padding_value);
263 double alignment_offset = std::max((caption_area_width - max_text_width) / 2.0, 0.0);
266 QBrush background_brush;
269 caption_area_with_padding.translate(alignment_offset, 0.0);
270 if (fade_in_percentage < 1.0) {
273 }
else if (fade_out_percentage >= 0.0 && fade_out_percentage <= 1.0) {
279 background_brush.setColor(background_qcolor);
280 background_brush.setStyle(Qt::SolidPattern);
281 painter.setBrush(background_brush);
282 painter.setPen(Qt::NoPen);
283 painter.drawRoundedRect(caption_area_with_padding, background_corner_value, background_corner_value);
287 QColor font_qcolor = QColor(QString(
color.
GetColorHex(frame_number).c_str()));
289 font_brush.setStyle(Qt::SolidPattern);
293 QColor stroke_qcolor;
296 pen.setColor(stroke_qcolor);
297 pen.setWidthF(std::max(stroke_width_value, 0.0));
301 for(QPainterPath
path : text_paths) {
303 path.translate(alignment_offset, 0.0);
304 if (fade_in_percentage < 1.0) {
308 }
else if (fade_out_percentage >= 0.0 && fade_out_percentage <= 1.0) {
313 pen.setColor(stroke_qcolor);
314 font_brush.setColor(font_qcolor);
317 if (stroke_width_value <= 0.0) {
318 painter.setPen(Qt::NoPen);
323 painter.setBrush(font_brush);
324 painter.drawPath(
path);
362 root[
"caption_text"] = caption_text;
379 catch (
const std::exception& e)
382 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
393 if (!root[
"color"].isNull())
395 if (!root[
"stroke"].isNull())
397 if (!root[
"background"].isNull())
399 if (!root[
"background_alpha"].isNull())
401 if (!root[
"background_corner"].isNull())
403 if (!root[
"background_padding"].isNull())
405 if (!root[
"stroke_width"].isNull())
407 if (!root[
"font_size"].isNull())
409 if (!root[
"font_alpha"].isNull())
411 if (!root[
"fade_in"].isNull())
413 if (!root[
"fade_out"].isNull())
415 if (!root[
"line_spacing"].isNull())
417 if (!root[
"left"].isNull())
419 if (!root[
"top"].isNull())
421 if (!root[
"right"].isNull())
423 if (!root[
"caption_text"].isNull())
424 caption_text = root[
"caption_text"].asString();
425 if (!root[
"caption_font"].isNull())
426 font_name = root[
"caption_font"].asString();
437 root[
"id"] =
add_property_json(
"ID", 0.0,
"string",
Id(), NULL, -1, -1,
true, requested_frame);
438 root[
"position"] =
add_property_json(
"Position",
Position(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
440 root[
"start"] =
add_property_json(
"Start",
Start(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
441 root[
"end"] =
add_property_json(
"End",
End(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
442 root[
"duration"] =
add_property_json(
"Duration",
Duration(),
"float",
"", NULL, 0, 1000 * 60 * 30,
true, requested_frame);
469 root[
"caption_text"] =
add_property_json(
"Captions", 0.0,
"caption", caption_text, NULL, -1, -1,
false, requested_frame);
476 return root.toStyledString();
Header file for Caption effect class.
Header file for all Exception classes.
std::string PropertiesJSON(int64_t requested_frame) const override
Keyframe background_padding
Background padding.
Caption()
Blank constructor, useful when using Json to load the effect properties.
Keyframe stroke_width
Width of text border / stroke.
Json::Value JsonValue() const override
Generate Json::Value for this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
std::string Json() const override
Generate JSON string of this object.
void SetJson(const std::string value) override
Load JSON string into this object.
std::string font_name
Font string.
Color background
Color of caption area background.
Keyframe font_size
Font size in points.
Keyframe font_alpha
Font color alpha.
Keyframe background_alpha
Background color alpha.
Keyframe fade_out
Fade in per caption (# of seconds)
Keyframe background_corner
Background cornder radius.
Keyframe fade_in
Fade in per caption (# of seconds)
Keyframe line_spacing
Distance between lines (1.0 default / 100%)
Keyframe top
Size of top bar.
Color stroke
Color of text border / stroke.
std::string CaptionText()
Set the caption string to use (see VTT format)
Keyframe right
Size of right bar.
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
Color color
Color of caption text.
Keyframe left
Size of left bar.
float Start() const
Get start position (in seconds) of clip (trim start of video)
float Duration() const
Get the length of this clip (in seconds)
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
std::string Id() const
Get the Id of this clip object.
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
float Position() const
Get position on timeline (in seconds)
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
This class represents a clip (used to arrange readers on the timeline)
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
openshot::Keyframe blue
Curve representing the red value (0 - 255)
openshot::Keyframe red
Curve representing the red value (0 - 255)
openshot::Keyframe green
Curve representing the green value (0 - 255)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Json::Value JsonValue() const
Generate Json::Value for this object.
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
EffectInfoStruct info
Information about the current effect.
This class represents a fraction.
int num
Numerator for the fraction.
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
int den
Denominator for the fraction.
Exception for invalid JSON.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Json::Value JsonValue() const
Generate Json::Value for this object.
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This class represents a timeline.
This namespace is the default namespace for all code in the openshot library.
const Json::Value stringToJson(const std::string value)
bool has_video
Determines if this effect manipulates the image of a frame.
std::string parent_effect_id
Id of the parent effect (if there is one)
bool has_audio
Determines if this effect manipulates the audio of a frame.
std::string class_name
The class name of the effect.
std::string name
The name of the effect.
std::string description
The description of this effect and what it does.