16 #include "../Timeline.h"
27 #include <QFontMetricsF>
30 #include <QPainterPath>
39 int ClampGravity(
int gravity)
59 background(
"#000000"),
67 background_alpha(0.45),
68 background_padding(14.0),
69 background_corner(6.0)
71 init_effect_details();
74 void Timer::init_effect_details()
79 info.
description =
"Render a styled count up, count down, clock, timecode, or frame number overlay.";
84 double Timer::ResolveFps()
const
96 return clip->Reader()->info.fps.ToDouble();
98 return timeline->info.fps.ToDouble();
99 if (
clip != NULL &&
clip->Reader() != NULL &&
clip->Reader()->info.fps.ToDouble() > 0.0)
100 return clip->Reader()->info.fps.ToDouble();
104 int64_t Timer::EffectiveFrameNumber(int64_t frame_number)
const
107 return std::max<int64_t>(1, frame_number);
110 if (
clip != NULL &&
clip->time.GetLength() > 1)
111 return std::max<int64_t>(1,
clip->time.GetLong(frame_number));
113 return std::max<int64_t>(1, frame_number);
116 double Timer::CountdownDuration(int64_t frame_number)
const
119 if (configured_duration > 0.0)
120 return configured_duration;
129 std::string Timer::FormatSeconds(
double seconds,
double fps,
bool duration_style)
const
131 if (duration_style &&
clamp && seconds < 0.0)
134 const bool negative = seconds < 0.0;
135 const int64_t total_milliseconds = std::llround(std::fabs(seconds) * 1000.0);
136 const int milliseconds =
static_cast<int>(total_milliseconds % 1000);
137 const int64_t total_seconds = total_milliseconds / 1000;
138 const int hours =
static_cast<int>(total_seconds / 3600);
139 const int minutes =
static_cast<int>((total_seconds / 60) % 60);
140 const int whole_seconds =
static_cast<int>(total_seconds % 60);
142 std::ostringstream text;
147 text << std::setfill(
'0') << std::setw(2) << hours <<
":"
148 << std::setw(2) << minutes <<
":"
149 << std::setw(2) << whole_seconds <<
"."
150 << std::setw(3) << milliseconds;
152 text << std::setfill(
'0') << std::setw(2) << hours <<
":"
153 << std::setw(2) << minutes <<
":"
154 << std::setw(2) << whole_seconds;
156 text << FormatTimecode(seconds, fps);
158 text << static_cast<int64_t>(std::llround(seconds * fps));
160 const int total_minutes = hours * 60 + minutes;
161 text << std::setfill(
'0') << std::setw(2) << total_minutes <<
":"
162 << std::setw(2) << whole_seconds;
168 std::string Timer::FormatTimecode(
double seconds,
double fps)
const
170 const bool negative = seconds < 0.0;
171 const int64_t total_frames = std::llround(std::fabs(seconds) * fps);
172 const int64_t rounded_fps = std::max<int64_t>(1, std::llround(fps));
173 const int64_t frames = total_frames % rounded_fps;
174 const int64_t total_seconds = total_frames / rounded_fps;
175 const int64_t secs = total_seconds % 60;
176 const int64_t mins = (total_seconds / 60) % 60;
177 const int64_t hours = total_seconds / 3600;
179 std::ostringstream text;
182 text << std::setfill(
'0') << std::setw(2) << hours <<
":"
183 << std::setw(2) << mins <<
":"
184 << std::setw(2) << secs <<
":"
185 << std::setw(2) << frames;
191 const double fps = ResolveFps();
192 const int64_t effective_frame = EffectiveFrameNumber(frame_number);
193 const double elapsed =
static_cast<double>(effective_frame - 1) / fps;
203 const double fps = ResolveFps();
204 const int64_t effective_frame = EffectiveFrameNumber(frame_number);
205 std::ostringstream text;
210 text << (effective_frame + frame_offset);
212 text << FormatTimecode(
TimerSeconds(frame_number), fps);
214 double seconds = std::fmod(
TimerSeconds(frame_number), 24.0 * 60.0 * 60.0);
216 seconds += 24.0 * 60.0 * 60.0;
217 text << FormatSeconds(seconds, fps,
false);
219 text << FormatSeconds(
TimerSeconds(frame_number), fps,
true);
226 std::string Timer::TimerLayoutText(int64_t frame_number)
const
231 std::string timer_template;
233 timer_template =
"88:88:88:88";
235 timer_template =
"88:88:88.888";
237 timer_template =
"88:88:88";
239 timer_template =
"88:88";
244 std::shared_ptr<openshot::Frame>
Timer::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
248 QSize image_size(1280, 720);
256 if (timeline != NULL) {
258 }
else if (
clip != NULL &&
clip->Reader() != NULL) {
259 image_size = QSize(
clip->Reader()->info.width,
clip->Reader()->info.height);
262 if (!frame->has_image_data)
263 frame->AddColor(image_size.width(), image_size.height(),
"#00000000");
265 std::shared_ptr<QImage> frame_image = frame->GetImage();
266 if (!frame_image || frame_image->isNull())
269 const double scale_factor = frame_image->width() / 600.0;
270 const double font_size_value = std::max(1.0,
font_size.
GetValue(frame_number) * scale_factor);
271 const double stroke_width_value = std::max(0.0,
stroke_width.
GetValue(frame_number) * scale_factor);
274 const QString timer_text = QString::fromStdString(
TimerText(frame_number));
275 const QString layout_text = QString::fromStdString(TimerLayoutText(frame_number));
277 QFont font(QString::fromStdString(
font_name),
int(font_size_value));
278 font.setPixelSize(font_size_value);
279 QFontMetricsF metrics(font);
280 QRectF text_bounds = metrics.tightBoundingRect(timer_text);
281 if (text_bounds.isEmpty())
282 text_bounds = metrics.boundingRect(timer_text);
283 QRectF layout_bounds = metrics.tightBoundingRect(layout_text);
284 if (layout_bounds.isEmpty())
285 layout_bounds = metrics.boundingRect(layout_text);
287 const double text_width = std::max(1.0, text_bounds.width());
288 const double text_height = std::max(1.0, text_bounds.height());
289 double digit_slot_width = 0.0;
290 for (
int digit = 0; digit <= 9; ++digit)
291 digit_slot_width = std::max(digit_slot_width, metrics.horizontalAdvance(QString::number(digit)));
293 const bool use_slot_layout = timer_text.size() == layout_text.size() &&
295 std::vector<double> slot_widths;
296 double layout_width = 0.0;
297 if (use_slot_layout) {
298 slot_widths.reserve(layout_text.size());
299 for (
int index = 0; index < layout_text.size(); ++index) {
300 const QString layout_char(layout_text[index]);
301 const double slot_width = layout_text[index].isDigit() ? digit_slot_width : metrics.horizontalAdvance(layout_char);
302 slot_widths.push_back(slot_width);
303 layout_width += slot_width;
306 layout_width = std::max(text_width, layout_bounds.width());
308 layout_width = std::max(1.0, layout_width);
309 const double rendered_box_width = layout_width + (padding_value * 2.0);
310 const double box_height = std::max(text_height, layout_bounds.height()) + (padding_value * 2.0);
312 double x = padding_value;
314 const int resolved_gravity = ClampGravity(
gravity);
316 x = (frame_image->width() - rendered_box_width) / 2.0;
318 x = frame_image->width() - rendered_box_width - padding_value;
320 switch (resolved_gravity) {
328 y = (frame_image->height() - box_height) / 2.0;
333 y = frame_image->height() - box_height;
343 double text_x = x + padding_value - text_bounds.left();
345 text_x = x + padding_value + ((layout_width - text_width) / 2.0) - text_bounds.left();
347 text_x = x + padding_value + layout_width - text_width - text_bounds.left();
348 text_x = std::round(text_x);
349 const double text_y = std::round(y + padding_value - text_bounds.top());
351 QPainter painter(frame_image.get());
352 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
true);
353 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
358 painter.setPen(Qt::NoPen);
359 painter.setBrush(QBrush(background_qcolor));
360 painter.drawRoundedRect(QRectF(x, y, rendered_box_width, box_height), corner_value, corner_value);
364 if (use_slot_layout) {
365 double cursor_x = x + padding_value;
366 for (
int index = 0; index < timer_text.size(); ++index) {
367 const QString text_char(timer_text[index]);
368 QRectF char_bounds = metrics.tightBoundingRect(text_char);
369 if (char_bounds.isEmpty())
370 char_bounds = metrics.boundingRect(text_char);
372 const double char_x = std::round(cursor_x);
373 path.addText(QPointF(char_x - char_bounds.left(), text_y), font, text_char);
374 cursor_x += slot_widths[index];
377 path.addText(QPointF(text_x, text_y), font, timer_text);
381 stroke_qcolor.setAlphaF(std::max(0.0, std::min(1.0,
font_alpha.
GetValue(frame_number))));
382 QPen pen(stroke_qcolor);
383 pen.setWidthF(stroke_width_value);
384 painter.setPen(stroke_width_value <= 0.0 ? Qt::NoPen : pen);
386 QColor font_qcolor(QString::fromStdString(
color.
GetColorHex(frame_number)));
387 font_qcolor.setAlphaF(std::max(0.0, std::min(1.0,
font_alpha.
GetValue(frame_number))));
388 painter.setBrush(QBrush(font_qcolor));
389 painter.drawPath(
path);
407 root[
"clamp"] =
clamp;
408 root[
"gravity"] = ClampGravity(
gravity);
436 catch (
const std::exception& e)
438 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
446 if (!root[
"mode"].isNull())
447 mode = root[
"mode"].asInt();
448 if (!root[
"time_source"].isNull())
450 if (!root[
"format"].isNull())
451 format = root[
"format"].asInt();
452 if (!root[
"clamp"].isNull())
453 clamp = root[
"clamp"].asInt();
454 if (!root[
"gravity"].isNull())
455 gravity = ClampGravity(root[
"gravity"].asInt());
456 if (!root[
"show_background"].isNull())
458 if (!root[
"font_name"].isNull())
459 font_name = root[
"font_name"].asString();
460 if (!root[
"prefix"].isNull())
461 prefix = root[
"prefix"].asString();
462 if (!root[
"suffix"].isNull())
463 suffix = root[
"suffix"].asString();
464 if (!root[
"color"].isNull())
466 if (!root[
"stroke"].isNull())
468 if (!root[
"background"].isNull())
470 if (!root[
"start_time"].isNull())
472 if (!root[
"end_time"].isNull())
474 if (!root[
"font_size"].isNull())
476 if (!root[
"font_alpha"].isNull())
478 if (!root[
"stroke_width"].isNull())
480 if (!root[
"x_offset"].isNull())
482 if (!root[
"y_offset"].isNull())
484 if (!root[
"background_alpha"].isNull())
486 if (!root[
"background_padding"].isNull())
488 if (!root[
"background_corner"].isNull())
530 const int resolved_gravity = ClampGravity(
gravity);
554 return root.toStyledString();
Header file for all Exception classes.
Header file for Timer effect class.
float Start() const
Get start position (in seconds) of clip (trim start of video)
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
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)
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
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.
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.
This class represents a timeline.
std::string TimerText(int64_t frame_number) const
Json::Value JsonValue() const override
Generate Json::Value for this object.
Keyframe background_padding
Keyframe background_alpha
double TimerSeconds(int64_t frame_number) const
void SetJson(const std::string value) override
Load JSON string into this object.
std::string Json() const override
Generate JSON string of this object.
std::string PropertiesJSON(int64_t requested_frame) const override
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...
Keyframe background_corner
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
This namespace is the default namespace for all code in the openshot library.
@ GRAVITY_TOP_LEFT
Align clip to the top left of its parent.
@ GRAVITY_LEFT
Align clip to the left of its parent (middle aligned)
@ GRAVITY_TOP_RIGHT
Align clip to the top right of its parent.
@ GRAVITY_RIGHT
Align clip to the right of its parent (middle aligned)
@ GRAVITY_BOTTOM_LEFT
Align clip to the bottom left of its parent.
@ GRAVITY_BOTTOM
Align clip to the bottom center of its parent.
@ GRAVITY_TOP
Align clip to the top center of its parent.
@ GRAVITY_BOTTOM_RIGHT
Align clip to the bottom right of its parent.
@ GRAVITY_CENTER
Align clip to the center of its parent (middle aligned)
@ TIMER_FORMAT_HH_MM_SS_MILLISECONDS
const Json::Value stringToJson(const std::string value)
@ TIMER_MODE_FRAME_NUMBER
bool has_video
Determines if this effect manipulates the image of a frame.
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.