OpenShot Library | libopenshot  0.5.0
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 
32 using namespace std;
33 using namespace openshot;
34 using google::protobuf::util::TimeUtil;
35 
36 
37 // Default constructor
38 Tracker::Tracker()
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
57 void Tracker::init_effect_details()
58 {
60  InitEffectInfo();
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;
68  info.has_tracked_object = 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
75 std::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
130 std::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
156 std::string Tracker::Json() const {
157 
158  // Return formatted string
159  return JsonValue().toStyledString();
160 }
161 
162 // Generate Json::Value for this object
163 Json::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
189 void 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
207 void Tracker::SetJsonValue(const Json::Value root) {
208 
209  // Set parent data
210  EffectBase::SetJsonValue(root);
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
291 std::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 }
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:42
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::BBox::cy
float cy
y-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:40
Timeline.h
Header file for Timeline class.
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:217
openshot::BBox::width
float width
bounding box width
Definition: TrackedObjectBBox.h:41
Tracker.h
Header file for Tracker effect class.
openshot::BBox
This struct holds the information of a bounding-box.
Definition: TrackedObjectBBox.h:37
openshot::BBox::cx
float cx
x-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:39
Exceptions.h
Header file for all Exception classes.