34 #include "../Timeline.h"
45 Caption::Caption() : color(
"#ffffff"), stroke(
"#a9a9a9"), background(
"#ff000000"), background_alpha(0.0), left(0.25), top(0.7), right(0.1),
46 stroke_width(0.5), font_size(30.0), font_alpha(1.0), is_dirty(true), font_name(
"sans"), font(NULL), metrics(NULL),
47 fade_in(0.35), fade_out(0.35), background_corner(10.0), background_padding(20.0)
50 init_effect_details();
55 color(
"#ffffff"), caption_text(captions), stroke(
"#a9a9a9"), background(
"#ff000000"), background_alpha(0.0),
56 left(0.25), top(0.7), right(0.1), stroke_width(0.5), font_size(30.0), font_alpha(1.0), is_dirty(true), font_name(
"sans"),
57 font(NULL), metrics(NULL), fade_in(0.35), fade_out(0.35), background_corner(10.0), background_padding(20.0)
60 init_effect_details();
64 void Caption::init_effect_details()
77 if (caption_text.length() == 0) {
78 caption_text =
"00:00:00:000 --> 00:10:00:000\nEdit this caption with our caption editor";
89 caption_text = new_caption_text;
94 void Caption::process_regex() {
99 matchedCaptions.clear();
101 QString caption_prepared = QString(caption_text.c_str());
102 if (caption_prepared.endsWith(
"\n\n") ==
false) {
104 caption_prepared.append(
"\n\n");
108 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);
109 QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(caption_prepared);
110 while (i.hasNext()) {
111 QRegularExpressionMatch match = i.next();
112 if (match.hasMatch()) {
114 matchedCaptions.push_back(match);
122 std::shared_ptr<openshot::Frame>
Caption::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
131 double scale_factor = 1.0;
139 if (timeline != NULL) {
144 }
else if (
clip != NULL &&
clip->Reader() != NULL) {
145 fps.
num =
clip->Reader()->info.fps.num;
146 fps.
den =
clip->Reader()->info.fps.den;
151 std::shared_ptr<QImage> frame_image = frame->GetImage();
154 QPainter painter(frame_image.get());
155 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
true);
158 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
162 QFont font(QString(
font_name.c_str()),
int(font_size_value));
163 font.setPointSizeF(std::max(font_size_value, 1.0));
164 QFontMetricsF metrics = QFontMetricsF(font);
176 double left_margin_x = frame_image->width() * left_value;
177 double starting_y = (frame_image->height() * top_value) + (metrics.lineSpacing() * scale_factor);
178 double right_margin_x = frame_image->width() - (frame_image->width() * right_value);
179 double caption_area_width = right_margin_x - left_margin_x;
180 QRectF caption_area = QRectF(left_margin_x, starting_y, caption_area_width, frame_image->height());
181 QRectF caption_area_with_padding = QRectF(left_margin_x - (padding_value / 2.0), starting_y - (padding_value / 2.0), caption_area_width + padding_value, frame_image->height() + padding_value);
187 brush.setColor(background_qcolor);
188 brush.setStyle(Qt::SolidPattern);
189 painter.setBrush(brush);
190 painter.setPen(Qt::NoPen);
191 painter.drawRoundedRect(caption_area_with_padding, background_corner_value, background_corner_value);
195 QColor stroke_qcolor;
198 painter.setPen(Qt::NoPen);
203 pen.setColor(stroke_qcolor);
208 QColor font_qcolor = QColor(QString(
color.
GetColorHex(frame_number).c_str()));
210 brush.setColor(font_qcolor);
211 painter.setBrush(brush);
214 for (
auto match = matchedCaptions.begin(); match != matchedCaptions.end(); match++) {
217 int64_t start_frame = ((match->captured(1).toFloat() * 60.0 * 60.0 ) + (match->captured(2).toFloat() * 60.0 ) +
218 match->captured(3).toFloat() + (match->captured(4).toFloat() / 1000.0)) * fps.
ToFloat();
219 int64_t end_frame = ((match->captured(5).toFloat() * 60.0 * 60.0 ) + (match->captured(6).toFloat() * 60.0 ) +
220 match->captured(7).toFloat() + (match->captured(8).toFloat() / 1000.0)) * fps.
ToFloat();
223 QStringList lines = match->captured(9).split(
"\n");
224 for(
int index = 0; index < lines.length(); index++) {
226 QString line = lines[index];
228 if (!line.startsWith(QStringLiteral(
"NOTE")) &&
229 !line.isEmpty() && frame_number >= start_frame && frame_number <= end_frame &&
233 double fade_in_percentage = ((float) frame_number - (
float) start_frame) / fade_in_value;
234 double fade_out_percentage = 1.0 - (((float) frame_number - ((
float) end_frame - fade_out_value)) / fade_out_value);
235 if (fade_in_percentage < 1.0) {
239 }
else if (fade_out_percentage >= 0.0 && fade_out_percentage <= 1.0) {
244 pen.setColor(stroke_qcolor);
245 brush.setColor(font_qcolor);
247 painter.setBrush(brush);
250 QStringList words = line.split(
" ");
251 int words_remaining = words.length();
252 while (words_remaining > 0) {
253 bool words_displayed =
false;
254 for(
int word_index = words.length(); word_index > 0; word_index--) {
256 QString fitting_line = words.mid(0, word_index).join(
" ");
259 QRectF textRect = metrics.boundingRect(caption_area, Qt::TextSingleLine, fitting_line);
260 if (textRect.width() <= caption_area.width()) {
262 QPoint p(left_margin_x, starting_y);
266 QString fitting_line = words.mid(0, word_index).join(
" ");
267 path1.addText(p, font, fitting_line);
268 painter.drawPath(path1);
271 starting_y += path1.boundingRect().height() + (metrics.lineSpacing() * scale_factor);
274 words = words.mid(word_index, words.length());
275 words_remaining = words.length();
276 words_displayed =
true;
281 if (words_displayed ==
false) {
325 root[
"caption_text"] = caption_text;
342 catch (
const std::exception& e)
345 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
356 if (!root[
"color"].isNull())
358 if (!root[
"stroke"].isNull())
360 if (!root[
"background"].isNull())
362 if (!root[
"background_alpha"].isNull())
364 if (!root[
"background_corner"].isNull())
366 if (!root[
"background_padding"].isNull())
368 if (!root[
"stroke_width"].isNull())
370 if (!root[
"font_size"].isNull())
372 if (!root[
"font_alpha"].isNull())
374 if (!root[
"fade_in"].isNull())
376 if (!root[
"fade_out"].isNull())
378 if (!root[
"left"].isNull())
380 if (!root[
"top"].isNull())
382 if (!root[
"right"].isNull())
384 if (!root[
"caption_text"].isNull())
385 caption_text = root[
"caption_text"].asString();
386 if (!root[
"caption_font"].isNull())
387 font_name = root[
"caption_font"].asString();
398 root[
"id"] =
add_property_json(
"ID", 0.0,
"string",
Id(), NULL, -1, -1,
true, requested_frame);
399 root[
"position"] =
add_property_json(
"Position",
Position(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
401 root[
"start"] =
add_property_json(
"Start",
Start(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
402 root[
"end"] =
add_property_json(
"End",
End(),
"float",
"", NULL, 0, 1000 * 60 * 30,
false, requested_frame);
403 root[
"duration"] =
add_property_json(
"Duration",
Duration(),
"float",
"", NULL, 0, 1000 * 60 * 30,
true, requested_frame);
429 root[
"caption_text"] =
add_property_json(
"Captions", 0.0,
"caption", caption_text, NULL, -1, -1,
false, requested_frame);
436 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 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 End() const
Get end position (in seconds) of clip (trim end of video)
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)
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.