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();
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) {
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();
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
void SetJson(const std::string value) override
Load JSON string into this object.
int num
Numerator for the fraction.
Keyframe font_alpha
Font color alpha.
std::string Id() const
Get the Id of this clip object.
float Start() const
Get start position (in seconds) of clip (trim start of video)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
int width
The width of the video (in pixesl)
std::string Json() const override
Generate JSON string of this object.
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline...
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Keyframe left
Size of left bar.
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
const Json::Value stringToJson(const std::string value)
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
std::string font_name
Font string.
Keyframe line_spacing
Distance between lines (1.0 default / 100%)
openshot::Keyframe blue
Curve representing the red value (0 - 255)
bool has_audio
Determines if this effect manipulates the audio of a frame.
Header file for all Exception classes.
This class represents a clip (used to arrange readers on the timeline)
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Keyframe background_alpha
Background color alpha.
openshot::Keyframe green
Curve representing the green value (0 - 255)
Color color
Color of caption text.
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
std::string CaptionText()
Set the caption string to use (see VTT format)
Header file for Caption effect class.
This class represents a fraction.
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
Color background
Color of caption area background.
Keyframe background_corner
Background cornder radius.
Keyframe stroke_width
Width of text border / stroke.
std::string PropertiesJSON(int64_t requested_frame) const override
std::string class_name
The class name of the effect.
Keyframe font_size
Font size in points.
std::string name
The name of the effect.
openshot::ReaderInfo info
Information about the current media file.
Keyframe fade_in
Fade in per caption (# of seconds)
Keyframe top
Size of top bar.
float Duration() const
Get the length of this clip (in seconds)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
This namespace is the default namespace for all code in the openshot library.
Keyframe background_padding
Background padding.
Json::Value JsonValue() const
Generate Json::Value for this object.
std::string description
The description of this effect and what it does.
Json::Value JsonValue() const override
Generate Json::Value for this object.
bool has_video
Determines if this effect manipulates the image of a frame.
Exception for invalid JSON.
double GetValue(int64_t index) const
Get the value at a specific index.
Caption()
Blank constructor, useful when using Json to load the effect properties.
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
openshot::Keyframe red
Curve representing the red value (0 - 255)
openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
std::string parent_effect_id
Id of the parent effect (if there is one)
Color stroke
Color of text border / stroke.
float Position() const
Get position on timeline (in seconds)
int den
Denominator for the fraction.
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...
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.
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
Keyframe fade_out
Fade in per caption (# of seconds)
EffectInfoStruct info
Information about the current effect.
Json::Value JsonValue() const
Generate Json::Value for this object.
This class represents a timeline.