35 #ifdef USE_IMAGEMAGICK
69 *out << std::fixed << std::setprecision(2) << std::boolalpha;
70 *out <<
"----------------------------" << std::endl;
71 *out <<
"----- Effect Information -----" << std::endl;
72 *out <<
"----------------------------" << std::endl;
73 *out <<
"--> Name: " <<
info.
name << std::endl;
78 *out <<
"--> Order: " << order << std::endl;
79 *out <<
"----------------------------" << std::endl;
88 else if (color_value > 255)
114 root[
"order"] =
Order();
116 root[
"mask_source_id"] = mask_source_id;
120 root[
"mask_reader"] = mask_reader->
JsonValue();
122 root[
"mask_reader"] = Json::objectValue;
138 catch (
const std::exception& e)
141 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
147 const std::string original_id = this->
Id();
152 const bool applying_parent_payload =
153 !original_id.empty() &&
154 !original_parent_effect_id.empty() &&
155 !root[
"id"].isNull() &&
156 root[
"id"].asString() == original_parent_effect_id &&
157 root[
"id"].asString() != original_id;
158 if (applying_parent_payload) {
160 my_root[
"id"] = original_id;
161 my_root[
"parent_effect_id"] = original_parent_effect_id;
164 my_root[
"id"] = this->
Id();
172 if (my_root[
"start"].isNull() && !my_root[
"mask_start"].isNull())
173 my_root[
"start"] = my_root[
"mask_start"];
174 if (my_root[
"end"].isNull() && !my_root[
"mask_end"].isNull())
175 my_root[
"end"] = my_root[
"mask_end"];
181 if (!my_root[
"order"].isNull())
182 Order(my_root[
"order"].asInt());
184 if (!my_root[
"apply_before_clip"].isNull())
187 if (!my_root[
"mask_invert"].isNull())
189 if (!my_root[
"mask_source_id"].isNull())
191 if (!my_root[
"mask_time_mode"].isNull()) {
192 const int time_mode = my_root[
"mask_time_mode"].asInt();
196 if (!my_root[
"mask_loop_mode"].isNull()) {
197 const int loop_mode = my_root[
"mask_loop_mode"].asInt();
204 const Json::Value mask_reader_json =
205 !my_root[
"mask_reader"].isNull() ? my_root[
"mask_reader"] : my_root[
"reader"];
207 if (!mask_reader_json.isNull()) {
208 if (!mask_reader_json[
"type"].isNull()) {
210 }
else if (mask_reader_json.isObject() && mask_reader_json.empty()) {
215 if (!my_root[
"parent_effect_id"].isNull()){
228 std::list<EffectBase*> effects = parentTimeline->
ClipEffects();
233 for (
auto const& effect : effects){
235 if ((effect->info.parent_effect_id == this->Id()) && (effect->Id() != this->
Id()))
236 effect->SetJsonValue(my_root);
260 root[
"id"] =
add_property_json(
"ID", 0.0,
"string",
Id(), NULL, -1, -1,
true, requested_frame);
261 root[
"position"] =
add_property_json(
"Position",
Position(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
263 root[
"start"] =
add_property_json(
"Start",
Start(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
264 root[
"end"] =
add_property_json(
"End",
End(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
false, requested_frame);
265 root[
"duration"] =
add_property_json(
"Duration",
Duration(),
"float",
"", NULL, 0, 30 * 60 * 60 * 48,
true, requested_frame);
290 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader", mask_reader->
Json(), NULL, 0, 1,
false, requested_frame);
292 root[
"mask_reader"] =
add_property_json(
"Mask: Source", 0.0,
"reader",
"{}", NULL, 0, 1,
false, requested_frame);
294 root[
"mask_source_id"] =
add_property_json(
"Mask: Effect Source", 0.0,
"string", mask_source_id, NULL, -1, -1,
false, requested_frame);
301 if (reader_json[
"type"].isNull())
305 const std::string type = reader_json[
"type"].asString();
307 if (type ==
"FFmpegReader") {
308 reader =
new FFmpegReader(reader_json[
"path"].asString());
314 }
else if (type ==
"QtImageReader") {
317 }
else if (type ==
"ChunkReader") {
318 reader =
new ChunkReader(reader_json[
"path"].asString(),
319 static_cast<ChunkVersion>(reader_json[
"chunk_version"].asInt()));
321 #ifdef USE_IMAGEMAGICK
322 }
else if (type ==
"ImageReader") {
323 reader =
new ImageReader(reader_json[
"path"].asString());
332 if (mask_reader == new_reader)
336 mask_reader->
Close();
340 mask_reader = new_reader;
341 cached_single_mask_image.reset();
342 cached_single_mask_width = 0;
343 cached_single_mask_height = 0;
349 mask_source_id = new_mask_source_id;
350 cached_single_mask_image.reset();
351 cached_single_mask_width = 0;
352 cached_single_mask_height = 0;
355 EffectBase* EffectBase::ResolveMaskSourceEffect() {
356 if (mask_source_id.empty())
362 if (source && source !=
this)
367 if (parent_timeline) {
370 source = parent_timeline->
GetEffect(mask_source_id);
371 if (source && source !=
this)
414 int64_t requested_index = std::max(int64_t(0), frame_number - 1);
417 if (host_fps > 0.0) {
418 const int64_t start_offset =
static_cast<int64_t
>(std::llround(std::max(0.0f,
Start()) * host_fps));
419 requested_index = std::max(int64_t(0), requested_index - start_offset);
422 int64_t mapped_index = requested_index;
428 if (host_fps > 0.0 && source_fps > 0.0) {
429 const double seconds =
static_cast<double>(requested_index) / host_fps;
430 mapped_index =
static_cast<int64_t
>(std::llround(seconds * source_fps));
438 const double start_sec = std::min<double>(std::max(0.0f,
Start()), source_duration);
439 const double end_sec = std::min<double>(std::max(0.0f,
End()), source_duration);
441 const int64_t range_start = std::max(int64_t(1),
static_cast<int64_t
>(std::llround(start_sec * source_fps)) + 1);
442 int64_t range_end = (end_sec > 0.0)
443 ?
static_cast<int64_t
>(std::llround(end_sec * source_fps)) + 1
446 range_end = std::min(range_end, source_len);
447 if (range_end < range_start)
448 range_end = range_start;
450 const int64_t range_len = std::max(int64_t(1), range_end - range_start + 1);
451 int64_t range_index = mapped_index;
455 range_index = mapped_index % range_len;
459 const int64_t cycle_len = (range_len * 2) - 2;
460 int64_t phase = mapped_index % cycle_len;
461 if (phase >= range_len)
462 phase = cycle_len - phase;
470 if (mapped_index < 0)
472 else if (mapped_index >= range_len)
473 range_index = range_len - 1;
475 range_index = mapped_index;
479 int64_t mapped_frame = range_start + range_index;
481 mapped_frame = std::min(std::max(int64_t(1), mapped_frame), source_len);
482 return std::max(int64_t(1), mapped_frame);
485 std::shared_ptr<QImage> EffectBase::GetMaskImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
486 if (!target_image || target_image->isNull())
489 EffectBase* source_effect = ResolveMaskSourceEffect();
491 auto generated_mask = source_effect->
TrackedObjectMask(target_image, frame_number);
492 if (generated_mask && !generated_mask->isNull())
493 return generated_mask;
495 auto empty_mask = std::make_shared<QImage>(
496 target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
497 empty_mask->fill(QColor(0, 0, 0, 255));
504 std::shared_ptr<QImage> source_mask;
505 bool used_cached_scaled =
false;
506 #pragma omp critical (open_effect_mask_reader)
509 if (!mask_reader->
IsOpen())
513 cached_single_mask_image &&
514 cached_single_mask_width == target_image->width() &&
515 cached_single_mask_height == target_image->height()) {
516 source_mask = cached_single_mask_image;
517 used_cached_scaled =
true;
521 auto source_frame = mask_reader->
GetFrame(mapped_frame);
522 if (source_frame && source_frame->GetImage() && !source_frame->GetImage()->isNull())
523 source_mask = std::make_shared<QImage>(*source_frame->GetImage());
525 }
catch (
const std::exception& e) {
527 std::string(
"EffectBase::GetMaskImage unable to read mask frame: ") + e.what());
532 if (!source_mask || source_mask->isNull())
535 if (used_cached_scaled)
538 auto scaled_mask = std::make_shared<QImage>(
540 target_image->width(), target_image->height(),
541 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
543 cached_single_mask_image = scaled_mask;
544 cached_single_mask_width = target_image->width();
545 cached_single_mask_height = target_image->height();
551 if (!target_image || target_image->isNull() ||
trackedObjects.empty())
554 auto mask_image = std::make_shared<QImage>(
555 target_image->width(), target_image->height(), QImage::Format_RGBA8888_Premultiplied);
556 mask_image->fill(QColor(0, 0, 0, 255));
558 QPainter painter(mask_image.get());
559 painter.setRenderHint(QPainter::Antialiasing,
true);
560 painter.setPen(Qt::NoPen);
561 painter.setBrush(QBrush(QColor(255, 255, 255, 255)));
563 bool drew_any_box =
false;
565 auto bbox = std::dynamic_pointer_cast<TrackedObjectBBox>(trackedObject.second);
568 if (!bbox->Contains(frame_number) || bbox->visible.GetValue(frame_number) != 1)
571 BBox box = bbox->GetBox(frame_number);
572 if (box.
width <= 0.0f || box.
height <= 0.0f || box.
cx < 0.0f || box.
cy < 0.0f)
575 const double x = (box.
cx - box.
width / 2.0) * target_image->
width();
576 const double y = (box.
cy - box.
height / 2.0) * target_image->height();
577 const double w = box.
width * target_image->width();
578 const double h = box.
height * target_image->height();
579 const double corner = bbox->background_corner.GetValue(frame_number);
580 QRectF rect(x, y, w, h);
582 if (std::abs(box.
angle) > 0.0001f) {
584 painter.translate(rect.center());
585 painter.rotate(box.
angle);
586 painter.drawRoundedRect(QRectF(-w / 2.0, -h / 2.0, w, h), corner, corner);
589 painter.drawRoundedRect(rect, corner, corner);
600 void EffectBase::BlendWithMask(std::shared_ptr<QImage> original_image, std::shared_ptr<QImage> effected_image,
601 std::shared_ptr<QImage> mask_image)
const {
602 if (!original_image || !effected_image || !mask_image)
604 if (original_image->size() != effected_image->size() || effected_image->size() != mask_image->size())
607 unsigned char* original_pixels =
reinterpret_cast<unsigned char*
>(original_image->bits());
608 unsigned char* effected_pixels =
reinterpret_cast<unsigned char*
>(effected_image->bits());
609 unsigned char* mask_pixels =
reinterpret_cast<unsigned char*
>(mask_image->bits());
610 const int width = effected_image->width();
611 const int height = effected_image->height();
612 const int original_stride = original_image->bytesPerLine();
613 const int effected_stride = effected_image->bytesPerLine();
614 const int mask_stride = mask_image->bytesPerLine();
616 #pragma omp parallel for schedule(static)
617 for (
int y = 0; y < height; ++y) {
618 unsigned char* original_row = original_pixels + y * original_stride;
619 unsigned char* effected_row = effected_pixels + y * effected_stride;
620 unsigned char* mask_row = mask_pixels + y * mask_stride;
621 for (
int x = 0; x < width; ++x) {
622 const int idx = x * 4;
623 int gray = qGray(mask_row[idx], mask_row[idx + 1], mask_row[idx + 2]);
626 const float factor =
static_cast<float>(gray) / 255.0f;
627 const float inverse = 1.0f - factor;
629 effected_row[idx] =
static_cast<unsigned char>(
630 (original_row[idx] * inverse) + (effected_row[idx] * factor));
631 effected_row[idx + 1] =
static_cast<unsigned char>(
632 (original_row[idx + 1] * inverse) + (effected_row[idx + 1] * factor));
633 effected_row[idx + 2] =
static_cast<unsigned char>(
634 (original_row[idx + 2] * inverse) + (effected_row[idx + 2] * factor));
635 effected_row[idx + 3] =
static_cast<unsigned char>(
636 (original_row[idx + 3] * inverse) + (effected_row[idx + 3] * factor));
643 if (!
info.
has_video || (!mask_reader && mask_source_id.empty()))
644 return GetFrame(frame, frame_number);
648 return GetFrame(frame, frame_number);
650 auto pre_image = frame->GetImage();
651 if (!pre_image || pre_image->isNull())
652 return GetFrame(frame, frame_number);
654 const auto original_image = std::make_shared<QImage>(pre_image->copy());
655 auto output_frame =
GetFrame(frame, frame_number);
658 auto effected_image = output_frame->GetImage();
659 if (!effected_image || effected_image->isNull() ||
660 effected_image->size() != original_image->size())
663 auto mask_image = GetMaskImage(effected_image, frame_number);
664 if (!mask_image || mask_image->isNull())
670 BlendWithMask(original_image, effected_image, mask_image);
698 if (parentEffectPtr){
704 EffectJSON[
"id"] = this->
Id();
Header file for ChunkReader class.
Header file for Clip class.
Header file for EffectBase class.
Header file for all Exception classes.
Header file for FFmpegReader class.
Header file for ImageReader class.
Header file for QtImageReader class.
Header file for ReaderBase class.
Header file for Timeline class.
Header file for the TrackedObjectBBox class.
Header file for ZeroMQ-based Logger class.
This class reads a special chunk-formatted file, which can be easily shared in a distributed environm...
This abstract class is the base class, used by all clips in libopenshot.
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.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
float Position() const
Get position on timeline (in seconds)
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)
openshot::EffectBase * GetEffect(const std::string &id)
Look up an effect by ID.
This abstract class is the base class, used by all effects in libopenshot.
ReaderBase * CreateReaderFromJson(const Json::Value &reader_json) const
Create a reader instance from reader JSON.
int mask_loop_mode
Behavior when mask range reaches the end.
virtual bool UseCustomMaskBlend(int64_t frame_number) const
Optional override for effects that need custom mask behavior.
virtual void SetJson(const std::string value)
Load JSON string into this object.
EffectBase * parentEffect
Parent effect (which properties will set this effect properties)
Json::Value JsonInfo() const
Generate JSON object of meta data / info.
virtual bool HandlesMaskInternally() const
Optional override for effects that apply mask processing inside GetFrame().
ReaderBase * MaskReader()
Get the common mask reader.
bool mask_invert
Invert grayscale mask values before blending.
std::shared_ptr< openshot::Frame > ProcessFrame(std::shared_ptr< openshot::Frame > frame, int64_t frame_number)
Apply effect processing with common mask support (if enabled).
std::string ParentClipId() const
Return the ID of this effect's parent clip.
void SetParentEffect(std::string parentEffect_id)
Set the parent effect from which this properties will be set to.
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)
std::string MaskSourceId() const
Get/Set effect ID used as a generated mask source.
int constrain(int color_value)
Constrain a color value from 0 to 255.
virtual std::string Json() const
Generate JSON string of this object.
virtual std::shared_ptr< QImage > TrackedObjectMask(std::shared_ptr< QImage > target_image, int64_t frame_number) const
Generate a black/white mask from tracked object data.
double ResolveMaskHostFps()
Determine host FPS used to convert timeline frames to mask source FPS.
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
int mask_time_mode
How effect frames map to mask source frames.
virtual void ApplyCustomMaskBlend(std::shared_ptr< QImage > original_image, std::shared_ptr< QImage > effected_image, std::shared_ptr< QImage > mask_image, int64_t frame_number) const
Optional override for effects with custom mask implementation.
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
int Order() const
Get the order that this effect should be executed.
double ResolveMaskSourceDuration() const
Determine mask source duration in seconds.
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
EffectInfoStruct info
Information about the current effect.
std::map< int, std::shared_ptr< openshot::TrackedObjectBase > > trackedObjects
Map of Tracked Object's by their indices (used by Effects that track objects on clips)
int64_t MapMaskFrameNumber(int64_t frame_number)
Convert an effect frame number to a mask source frame number.
void DisplayInfo(std::ostream *out=&std::cout)
Display effect information in the standard output stream (stdout)
This class uses the FFmpeg libraries, to open video files and audio files, and return openshot::Frame...
int num
Numerator for the fraction.
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
int den
Denominator for the fraction.
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Exception for invalid JSON.
This class uses the Qt library, to open image files, and return openshot::Frame objects containing th...
This abstract class is the base class, used by all readers in libopenshot.
virtual bool IsOpen()=0
Determine if reader is open or closed.
openshot::ReaderInfo info
Information about the current media file.
virtual std::string Json() const =0
Generate JSON string of this object.
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
virtual void Open()=0
Open the reader (and start consuming resources, such as images or video files)
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
virtual void Close()=0
Close the reader (and any resources it was consuming)
This class represents a timeline.
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
void Log(std::string message)
Log message to all subscribers of this logger (if any)
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
This namespace is the default namespace for all code in the openshot library.
ChunkVersion
This enumeration allows the user to choose which version of the chunk they would like (low,...
const Json::Value stringToJson(const std::string value)
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]
bool has_video
Determines if this effect manipulates the image of a frame.
bool apply_before_clip
Apply effect to source before we evaluate the clip's keyframes.
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.
bool has_tracked_object
Determines if this effect track objects through the clip.
bool has_single_image
Determines if this file only contains a single image.
float duration
Length of time (in seconds)
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
int64_t video_length
The number of frames in the video stream.
bool has_audio
Determines if this file has an audio stream.
int audio_stream_index
The index of the audio stream.