OpenShot Library | libopenshot 0.5.0
Loading...
Searching...
No Matches
Tracker.cpp
Go to the documentation of this file.
1
10// Copyright (c) 2008-2019 OpenShot Studios, LLC
11//
12// SPDX-License-Identifier: LGPL-3.0-or-later
13
14#include <string>
15#include <memory>
16#include <iostream>
17#include <algorithm>
18
19#include "effects/Tracker.h"
20#include "Exceptions.h"
21#include "Timeline.h"
22#include "trackerdata.pb.h"
23
24#include <google/protobuf/util/time_util.h>
25
26#include <QImage>
27#include <QPainter>
28#include <QPen>
29#include <QBrush>
30#include <QRectF>
31
32using namespace std;
33using namespace openshot;
34using google::protobuf::util::TimeUtil;
35
36
37// Default constructor
39{
40 // Initialize effect metadata
41 init_effect_details();
42
43 // Create a placeholder object so we always have index 0 available
44 trackedData = std::make_shared<TrackedObjectBBox>();
45 trackedData->ParentClip(this->ParentClip());
46
47 // Seed our map with a single entry at index 0
48 trackedObjects.clear();
49 trackedObjects.emplace(0, trackedData);
50
51 // Assign ID to the placeholder object
52 if (trackedData)
53 trackedData->Id(Id() + "-0");
54}
55
56// Init effect settings
57void Tracker::init_effect_details()
58{
61
63 info.class_name = "Tracker";
64 info.name = "Tracker";
65 info.description = "Track the selected bounding box through the video.";
66 info.has_audio = false;
67 info.has_video = true;
69
70 this->TimeScale = 1.0;
71}
72
73// This method is required for all derived classes of EffectBase, and returns a
74// modified openshot::Frame object
75std::shared_ptr<Frame> Tracker::GetFrame(std::shared_ptr<Frame> frame, int64_t frame_number)
76{
77 // Sanity‐check
78 if (!frame) return frame;
79 auto frame_image = frame->GetImage();
80 if (!frame_image || frame_image->isNull()) return frame;
81 if (!trackedData) return frame;
82
83 // 2) Only proceed if we actually have a box and it's visible
84 if (!trackedData->Contains(frame_number) ||
85 trackedData->visible.GetValue(frame_number) != 1)
86 return frame;
87
88 QPainter painter(frame_image.get());
89 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
90
91 // Draw the box
92 BBox fd = trackedData->GetBox(frame_number);
93 QRectF boxRect(
94 (fd.cx - fd.width/2) * frame_image->width(),
95 (fd.cy - fd.height/2) * frame_image->height(),
96 fd.width * frame_image->width(),
97 fd.height * frame_image->height()
98 );
99
100 if (trackedData->draw_box.GetValue(frame_number) == 1)
101 {
102 auto stroke_rgba = trackedData->stroke.GetColorRGBA(frame_number);
103 int stroke_width = trackedData->stroke_width.GetValue(frame_number);
104 float stroke_alpha = trackedData->stroke_alpha.GetValue(frame_number);
105 auto bg_rgba = trackedData->background.GetColorRGBA(frame_number);
106 float bg_alpha = trackedData->background_alpha.GetValue(frame_number);
107 float bg_corner = trackedData->background_corner.GetValue(frame_number);
108
109 QPen pen(QColor(
110 stroke_rgba[0], stroke_rgba[1], stroke_rgba[2],
111 int(255 * stroke_alpha)
112 ));
113 pen.setWidth(stroke_width);
114 painter.setPen(pen);
115
116 QBrush brush(QColor(
117 bg_rgba[0], bg_rgba[1], bg_rgba[2],
118 int(255 * bg_alpha)
119 ));
120 painter.setBrush(brush);
121
122 painter.drawRoundedRect(boxRect, bg_corner, bg_corner);
123 }
124
125 painter.end();
126 return frame;
127}
128
129// Get the indexes and IDs of all visible objects in the given frame
130std::string Tracker::GetVisibleObjects(int64_t frame_number) const
131{
132 Json::Value root;
133 root["visible_objects_index"] = Json::Value(Json::arrayValue);
134 root["visible_objects_id"] = Json::Value(Json::arrayValue);
135
136 if (trackedObjects.empty())
137 return root.toStyledString();
138
139 for (auto const& kv : trackedObjects) {
140 auto ptr = kv.second;
141 if (!ptr) continue;
142
143 // Directly get the Json::Value for this object's properties
144 Json::Value propsJson = ptr->PropertiesJSON(frame_number);
145
146 if (propsJson["visible"]["value"].asBool()) {
147 root["visible_objects_index"].append(kv.first);
148 root["visible_objects_id"].append(ptr->Id());
149 }
150 }
151
152 return root.toStyledString();
153}
154
155// Generate JSON string of this object
156std::string Tracker::Json() const {
157
158 // Return formatted string
159 return JsonValue().toStyledString();
160}
161
162// Generate Json::Value for this object
163Json::Value Tracker::JsonValue() const {
164
165 // Create root json object
166 Json::Value root = EffectBase::JsonValue(); // get parent properties
167
168 // Save the effect's properties on root
169 root["type"] = info.class_name;
170 root["protobuf_data_path"] = protobuf_data_path;
171 root["BaseFPS"]["num"] = BaseFPS.num;
172 root["BaseFPS"]["den"] = BaseFPS.den;
173 root["TimeScale"] = this->TimeScale;
174
175 // Add trackedObjects IDs to JSON
176 Json::Value objects;
177 for (auto const& trackedObject : trackedObjects){
178 Json::Value trackedObjectJSON = trackedObject.second->JsonValue();
179 // add object json
180 objects[trackedObject.second->Id()] = trackedObjectJSON;
181 }
182 root["objects"] = objects;
183
184 // return JsonValue
185 return root;
186}
187
188// Load JSON string into this object
189void Tracker::SetJson(const std::string value) {
190
191 // Parse JSON string into JSON objects
192 try
193 {
194 const Json::Value root = openshot::stringToJson(value);
195 // Set all values that match
196 SetJsonValue(root);
197 }
198 catch (const std::exception& e)
199 {
200 // Error parsing JSON (or missing keys)
201 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
202 }
203 return;
204}
205
206// Load Json::Value into this object
207void Tracker::SetJsonValue(const Json::Value root) {
208
209 // Set parent data
211
212 if (!root["BaseFPS"].isNull()) {
213 if (!root["BaseFPS"]["num"].isNull())
214 BaseFPS.num = root["BaseFPS"]["num"].asInt();
215 if (!root["BaseFPS"]["den"].isNull())
216 BaseFPS.den = root["BaseFPS"]["den"].asInt();
217 }
218
219 if (!root["TimeScale"].isNull()) {
220 TimeScale = root["TimeScale"].asDouble();
221 }
222
223 if (!root["protobuf_data_path"].isNull()) {
224 std::string new_path = root["protobuf_data_path"].asString();
225 if (protobuf_data_path != new_path || trackedData->GetLength() == 0) {
226 protobuf_data_path = new_path;
227 if (!trackedData->LoadBoxData(protobuf_data_path)) {
228 std::clog << "Invalid protobuf data path " << protobuf_data_path << '\n';
229 protobuf_data_path.clear();
230 }
231 else {
232 // prefix "<effectUUID>-<index>" for each entry
233 for (auto& kv : trackedObjects) {
234 auto idx = kv.first;
235 auto ptr = kv.second;
236 if (ptr) {
237 std::string prefix = this->Id();
238 if (!prefix.empty())
239 prefix += "-";
240 ptr->Id(prefix + std::to_string(idx));
241 }
242 }
243 }
244 }
245 }
246
247 // then any per-object JSON overrides...
248 if (!root["objects"].isNull()) {
249 // Iterate over the supplied objects (indexed by id or position)
250 const auto memberNames = root["objects"].getMemberNames();
251 for (const auto& name : memberNames)
252 {
253 // Determine the numeric index of this object
254 int index = -1;
255 bool numeric_key = std::all_of(name.begin(), name.end(), ::isdigit);
256 if (numeric_key) {
257 index = std::stoi(name);
258 }
259 else
260 {
261 size_t pos = name.find_last_of('-');
262 if (pos != std::string::npos) {
263 try {
264 index = std::stoi(name.substr(pos + 1));
265 } catch (...) {
266 index = -1;
267 }
268 }
269 }
270
271 auto obj_it = trackedObjects.find(index);
272 if (obj_it != trackedObjects.end() && obj_it->second) {
273 // Update object id if provided as a non-numeric key
274 if (!numeric_key)
275 obj_it->second->Id(name);
276 obj_it->second->SetJsonValue(root["objects"][name]);
277 }
278 }
279 }
280
281 // Set the tracked object's ids (legacy format)
282 if (!root["objects_id"].isNull()) {
283 for (auto& kv : trackedObjects) {
284 if (!root["objects_id"][kv.first].isNull())
285 kv.second->Id(root["objects_id"][kv.first].asString());
286 }
287 }
288}
289
290// Get all properties for a specific frame
291std::string Tracker::PropertiesJSON(int64_t requested_frame) const {
292
293 // Generate JSON properties list
294 Json::Value root = BasePropertiesJSON(requested_frame);
295
296 // Add trackedObject properties to JSON
297 Json::Value objects;
298 for (auto const& trackedObject : trackedObjects){
299 Json::Value trackedObjectJSON = trackedObject.second->PropertiesJSON(requested_frame);
300 // add object json
301 objects[trackedObject.second->Id()] = trackedObjectJSON;
302 }
303 root["objects"] = objects;
304
305 // Return formatted string
306 return root.toStyledString();
307}
Header file for all Exception classes.
Header file for Timeline class.
Header file for Tracker effect class.
std::string Id() const
Get the Id of this clip object.
Definition ClipBase.h:85
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.
EffectInfoStruct info
Information about the current effect.
Definition EffectBase.h:69
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)
Definition EffectBase.h:66
int num
Numerator for the fraction.
Definition Fraction.h:32
int den
Denominator for the fraction.
Definition Fraction.h:33
Exception for invalid JSON.
Definition Exceptions.h:218
std::string Json() const override
Generate JSON string of this object.
Definition Tracker.cpp:156
std::string GetVisibleObjects(int64_t frame_number) const override
Get the indexes and IDs of all visible objects in the given frame.
Definition Tracker.cpp:130
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition Tracker.cpp:163
void SetJson(const std::string value) override
Load JSON string into this object.
Definition Tracker.cpp:189
std::shared_ptr< Frame > GetFrame(std::shared_ptr< Frame > frame, int64_t frame_number) override
Apply this effect to an openshot::Frame.
Definition Tracker.cpp:75
std::string PropertiesJSON(int64_t requested_frame) const override
Definition Tracker.cpp:291
std::shared_ptr< TrackedObjectBBox > trackedData
Pointer to an object that holds the bounding-box data and it's Keyframes.
Definition Tracker.h:52
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition Tracker.cpp:207
Tracker()
Default constructor.
Definition Tracker.cpp:38
std::string protobuf_data_path
Path to the protobuf file that holds the bounding-box data.
Definition Tracker.h:51
This namespace is the default namespace for all code in the openshot library.
Definition Compressor.h:29
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
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
bool has_video
Determines if this effect manipulates the image of a frame.
Definition EffectBase.h:40
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition EffectBase.h:41
std::string class_name
The class name of the effect.
Definition EffectBase.h:36
std::string name
The name of the effect.
Definition EffectBase.h:37
std::string description
The description of this effect and what it does.
Definition EffectBase.h:38
bool has_tracked_object
Determines if this effect track objects through the clip.
Definition EffectBase.h:42