OpenShot Library | libopenshot  0.7.0
Timeline.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2019 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "Timeline.h"
14 
15 #include "CacheBase.h"
16 #include "CacheDisk.h"
17 #include "CacheMemory.h"
18 #include "CrashHandler.h"
19 #include "FrameMapper.h"
20 #include "Exceptions.h"
21 #include "effects/Mask.h"
22 
23 #include <algorithm>
24 #include <QDir>
25 #include <QFileInfo>
26 #include <QRegularExpression>
27 #include <unordered_map>
28 #include <cmath>
29 #include <cstdint>
30 
31 using namespace openshot;
32 
33 // Default Constructor for the timeline (which sets the canvas width and height)
34 Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) :
35  is_open(false), auto_map_clips(true), managed_cache(true), path(""), max_time(0.0), cache_epoch(0)
36 {
37  // Create CrashHandler and Attach (incase of errors)
39 
40  // Init viewport size (curve based, because it can be animated)
41  viewport_scale = Keyframe(100.0);
42  viewport_x = Keyframe(0.0);
43  viewport_y = Keyframe(0.0);
44 
45  // Init background color
46  color.red = Keyframe(0.0);
47  color.green = Keyframe(0.0);
48  color.blue = Keyframe(0.0);
49 
50  // Init FileInfo struct (clear all values)
51  info.width = width;
52  info.height = height;
55  info.fps = fps;
56  info.sample_rate = sample_rate;
57  info.channels = channels;
58  info.channel_layout = channel_layout;
60  info.duration = 60 * 30; // 30 minute default duration
61  info.has_audio = true;
62  info.has_video = true;
64  info.display_ratio = openshot::Fraction(width, height);
67  info.acodec = "openshot::timeline";
68  info.vcodec = "openshot::timeline";
69 
70  // Init max image size
72 
73  // Init cache
74  final_cache = new CacheMemory();
75  const int cache_frames = std::max(Settings::Instance()->CACHE_MIN_FRAMES, OPEN_MP_NUM_PROCESSORS * 4);
76  final_cache->SetMaxBytesFromInfo(cache_frames, info.width, info.height, info.sample_rate, info.channels);
77 }
78 
79 // Delegating constructor that copies parameters from a provided ReaderInfo
81  info.width, info.height, info.fps, info.sample_rate,
82  info.channels, info.channel_layout) {}
83 
84 // Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline)
85 Timeline::Timeline(const std::string& projectPath, bool convert_absolute_paths) :
86  is_open(false), auto_map_clips(true), managed_cache(true), path(projectPath), max_time(0.0), cache_epoch(0) {
87 
88  // Create CrashHandler and Attach (incase of errors)
90 
91  // Init final cache as NULL (will be created after loading json)
92  final_cache = NULL;
93 
94  // Init viewport size (curve based, because it can be animated)
95  viewport_scale = Keyframe(100.0);
96  viewport_x = Keyframe(0.0);
97  viewport_y = Keyframe(0.0);
98 
99  // Init background color
100  color.red = Keyframe(0.0);
101  color.green = Keyframe(0.0);
102  color.blue = Keyframe(0.0);
103 
104  // Check if path exists
105  QFileInfo filePath(QString::fromStdString(path));
106  if (!filePath.exists()) {
107  throw InvalidFile("Timeline project file could not be opened.", path);
108  }
109 
110  // Check OpenShot Install Path exists
112  QDir openshotPath(QString::fromStdString(s->PATH_OPENSHOT_INSTALL));
113  if (!openshotPath.exists()) {
114  throw InvalidFile("PATH_OPENSHOT_INSTALL could not be found.", s->PATH_OPENSHOT_INSTALL);
115  }
116  QDir openshotTransPath(openshotPath.filePath("transitions"));
117  if (!openshotTransPath.exists()) {
118  throw InvalidFile("PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
119  }
120 
121  // Determine asset path
122  QString asset_name = filePath.baseName().left(30) + "_assets";
123  QDir asset_folder(filePath.dir().filePath(asset_name));
124  if (!asset_folder.exists()) {
125  // Create directory if needed
126  asset_folder.mkpath(".");
127  }
128 
129  // Load UTF-8 project file into QString
130  QFile projectFile(QString::fromStdString(path));
131  projectFile.open(QFile::ReadOnly);
132  QString projectContents = QString::fromUtf8(projectFile.readAll());
133 
134  // Convert all relative paths into absolute paths (if requested)
135  if (convert_absolute_paths) {
136 
137  // Find all "image" or "path" references in JSON (using regex). Must loop through match results
138  // due to our path matching needs, which are not possible with the QString::replace() function.
139  QRegularExpression allPathsRegex(QStringLiteral("\"(image|path)\":.*?\"(.*?)\""));
140  std::vector<QRegularExpressionMatch> matchedPositions;
141  QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
142  while (i.hasNext()) {
143  QRegularExpressionMatch match = i.next();
144  if (match.hasMatch()) {
145  // Push all match objects into a vector (so we can reverse them later)
146  matchedPositions.push_back(match);
147  }
148  }
149 
150  // Reverse the matches (bottom of file to top, so our replacements don't break our match positions)
151  std::vector<QRegularExpressionMatch>::reverse_iterator itr;
152  for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
153  QRegularExpressionMatch match = *itr;
154  QString relativeKey = match.captured(1); // image or path
155  QString relativePath = match.captured(2); // relative file path
156  QString absolutePath = "";
157 
158  // Find absolute path of all path, image (including special replacements of @assets and @transitions)
159  if (relativePath.startsWith("@assets")) {
160  absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace("@assets", "."))).canonicalFilePath();
161  } else if (relativePath.startsWith("@transitions")) {
162  absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace("@transitions", "."))).canonicalFilePath();
163  } else {
164  absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
165  }
166 
167  // Replace path in JSON content, if an absolute path was successfully found
168  if (!absolutePath.isEmpty()) {
169  projectContents.replace(match.capturedStart(0), match.capturedLength(0), "\"" + relativeKey + "\": \"" + absolutePath + "\"");
170  }
171  }
172  // Clear matches
173  matchedPositions.clear();
174  }
175 
176  // Set JSON of project
177  SetJson(projectContents.toStdString());
178 
179  // Calculate valid duration and set has_audio and has_video
180  // based on content inside this Timeline's clips.
181  float calculated_duration = 0.0;
182  for (auto clip : clips)
183  {
184  float clip_last_frame = clip->Position() + clip->Duration();
185  if (clip_last_frame > calculated_duration)
186  calculated_duration = clip_last_frame;
187  if (clip->Reader() && clip->Reader()->info.has_audio)
188  info.has_audio = true;
189  if (clip->Reader() && clip->Reader()->info.has_video)
190  info.has_video = true;
191 
192  }
193  info.video_length = calculated_duration * info.fps.ToFloat();
194  info.duration = calculated_duration;
195 
196  // Init FileInfo settings
197  info.acodec = "openshot::timeline";
198  info.vcodec = "openshot::timeline";
200  info.has_video = true;
201  info.has_audio = true;
202 
203  // Init max image size
205 
206  // Init cache
207  final_cache = new CacheMemory();
208  const int cache_frames = std::max(Settings::Instance()->CACHE_MIN_FRAMES, OPEN_MP_NUM_PROCESSORS * 4);
209  final_cache->SetMaxBytesFromInfo(cache_frames, info.width, info.height, info.sample_rate, info.channels);
210 }
211 
213  if (is_open) {
214  // Auto Close if not already
215  Close();
216  }
217 
218  // Remove all clips, effects, and frame mappers
219  Clear();
220 
221  // Destroy previous cache (if managed by timeline)
222  if (managed_cache && final_cache) {
223  delete final_cache;
224  final_cache = NULL;
225  }
226 }
227 
228 // Add to the tracked_objects map a pointer to a tracked object (TrackedObjectBBox)
229 void Timeline::AddTrackedObject(std::shared_ptr<openshot::TrackedObjectBase> trackedObject){
230 
231  // Search for the tracked object on the map
232  auto iterator = tracked_objects.find(trackedObject->Id());
233 
234  if (iterator != tracked_objects.end()){
235  // Tracked object's id already present on the map, overwrite it
236  iterator->second = trackedObject;
237  }
238  else{
239  // Tracked object's id not present -> insert it on the map
240  tracked_objects[trackedObject->Id()] = trackedObject;
241  }
242 
243  return;
244 }
245 
246 // Return tracked object pointer by it's id
247 std::shared_ptr<openshot::TrackedObjectBase> Timeline::GetTrackedObject(std::string id) const{
248 
249  // Search for the tracked object on the map
250  auto iterator = tracked_objects.find(id);
251 
252  if (iterator != tracked_objects.end()){
253  // Id found, return the pointer to the tracked object
254  std::shared_ptr<openshot::TrackedObjectBase> trackedObject = iterator->second;
255  return trackedObject;
256  }
257  else {
258  // Id not found, return a null pointer
259  return nullptr;
260  }
261 }
262 
263 // Return the ID's of the tracked objects as a list of strings
264 std::list<std::string> Timeline::GetTrackedObjectsIds() const{
265 
266  // Create a list of strings
267  std::list<std::string> trackedObjects_ids;
268 
269  // Iterate through the tracked_objects map
270  for (auto const& it: tracked_objects){
271  // Add the IDs to the list
272  trackedObjects_ids.push_back(it.first);
273  }
274 
275  return trackedObjects_ids;
276 }
277 
278 #ifdef USE_OPENCV
279 // Return the trackedObject's properties as a JSON string
280 std::string Timeline::GetTrackedObjectValues(std::string id, int64_t frame_number) const {
281 
282  // Initialize the JSON object
283  Json::Value trackedObjectJson;
284 
285  // Search for the tracked object on the map
286  auto iterator = tracked_objects.find(id);
287 
288  if (iterator != tracked_objects.end())
289  {
290  // Id found, Get the object pointer and cast it as a TrackedObjectBBox
291  std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(iterator->second);
292 
293  // Get the trackedObject values for it's first frame
294  if (trackedObject->ExactlyContains(frame_number)){
295  BBox box = trackedObject->GetBox(frame_number);
296  float x1 = box.cx - (box.width/2);
297  float y1 = box.cy - (box.height/2);
298  float x2 = box.cx + (box.width/2);
299  float y2 = box.cy + (box.height/2);
300  float rotation = box.angle;
301 
302  trackedObjectJson["x1"] = x1;
303  trackedObjectJson["y1"] = y1;
304  trackedObjectJson["x2"] = x2;
305  trackedObjectJson["y2"] = y2;
306  trackedObjectJson["rotation"] = rotation;
307 
308  } else {
309  BBox box = trackedObject->BoxVec.begin()->second;
310  float x1 = box.cx - (box.width/2);
311  float y1 = box.cy - (box.height/2);
312  float x2 = box.cx + (box.width/2);
313  float y2 = box.cy + (box.height/2);
314  float rotation = box.angle;
315 
316  trackedObjectJson["x1"] = x1;
317  trackedObjectJson["y1"] = y1;
318  trackedObjectJson["x2"] = x2;
319  trackedObjectJson["y2"] = y2;
320  trackedObjectJson["rotation"] = rotation;
321  }
322 
323  }
324  else {
325  // Id not found, return all 0 values
326  trackedObjectJson["x1"] = 0;
327  trackedObjectJson["y1"] = 0;
328  trackedObjectJson["x2"] = 0;
329  trackedObjectJson["y2"] = 0;
330  trackedObjectJson["rotation"] = 0;
331  }
332 
333  return trackedObjectJson.toStyledString();
334 }
335 #endif
336 
337 // Add an openshot::Clip to the timeline
339 {
340  // Get lock (prevent getting frames while this happens)
341  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
342 
343  // Assign timeline to clip
344  clip->ParentTimeline(this);
345 
346  // Clear cache of clip and nested reader (if any)
347  if (clip->Reader() && clip->Reader()->GetCache())
348  clip->Reader()->GetCache()->Clear();
349 
350  // All clips should be converted to the frame rate of this timeline
351  if (auto_map_clips) {
352  // Apply framemapper (or update existing framemapper)
353  apply_mapper_to_clip(clip);
354  }
355 
356  InvalidateCacheForClip(clip);
357 
358  // Add clip to list
359  clips.push_back(clip);
360 
361  // Sort clips
362  sort_clips();
363 }
364 
365 // Add an effect to the timeline
367 {
368  // Get lock (prevent getting frames while this happens)
369  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
370 
371  // Assign timeline to effect
372  effect->ParentTimeline(this);
373 
374  // Add effect to list
375  effects.push_back(effect);
376 
377  // Sort effects
378  sort_effects();
379 }
380 
381 // Remove an effect from the timeline
383 {
384  // Get lock (prevent getting frames while this happens)
385  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
386 
387  effects.remove(effect);
388 
389  // Delete effect object (if timeline allocated it)
390  if (allocated_effects.count(effect)) {
391  allocated_effects.erase(effect); // erase before nulling the pointer
392  delete effect;
393  effect = NULL;
394  }
395 
396  // Sort effects
397  sort_effects();
398 }
399 
400 // Remove an openshot::Clip to the timeline
402 {
403  // Get lock (prevent getting frames while this happens)
404  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
405 
406  clips.remove(clip);
407 
408  // Delete clip object (if timeline allocated it)
409  if (allocated_clips.count(clip)) {
410  allocated_clips.erase(clip); // erase before nulling the pointer
411  delete clip;
412  clip = NULL;
413  }
414 
415  // Sort clips
416  sort_clips();
417 }
418 
419 // Look up a clip
420 openshot::Clip* Timeline::GetClip(const std::string& id)
421 {
422  // Find the matching clip (if any)
423  for (const auto& clip : clips) {
424  if (clip->Id() == id) {
425  return clip;
426  }
427  }
428  return nullptr;
429 }
430 
431 // Look up a timeline effect
433 {
434  // Find the matching effect (if any)
435  for (const auto& effect : effects) {
436  if (effect->Id() == id) {
437  return effect;
438  }
439  }
440  return nullptr;
441 }
442 
444 {
445  // Search all clips for matching effect ID
446  for (const auto& clip : clips) {
447  const auto e = clip->GetEffect(id);
448  if (e != nullptr) {
449  return e;
450  }
451  }
452  return nullptr;
453 }
454 
455 // Return the list of effects on all clips
456 std::list<openshot::EffectBase*> Timeline::ClipEffects() const {
457 
458  // Initialize the list
459  std::list<EffectBase*> timelineEffectsList;
460 
461  // Loop through all clips
462  for (const auto& clip : clips) {
463 
464  // Get the clip's list of effects
465  std::list<EffectBase*> clipEffectsList = clip->Effects();
466 
467  // Append the clip's effects to the list
468  timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
469  }
470 
471  return timelineEffectsList;
472 }
473 
474 // Compute the end time of the latest timeline element
476  // Return cached max_time variable (threadsafe)
477  return max_time;
478 }
479 
480 // Compute the highest frame# based on the latest time and FPS
482  const double fps = info.fps.ToDouble();
483  const double t = GetMaxTime();
484  const double frames = t * fps;
485  constexpr double frame_boundary_epsilon = 1e-4;
486 
487  // End is exclusive; ignore tiny float overshoots at frame boundaries.
488  if (frames > 0.0 && frames < frame_boundary_epsilon)
489  return 1;
490  return static_cast<int64_t>(std::ceil(frames - frame_boundary_epsilon));
491 }
492 
493 // Compute the first frame# based on the first clip position
495  const double fps = info.fps.ToDouble();
496  const double t = GetMinTime();
497  // Inclusive start -> floor at the start boundary, then 1-index
498  return static_cast<int64_t>(std::floor(t * fps)) + 1;
499 }
500 
501 // Compute the start time of the first timeline clip
503  // Return cached min_time variable (threadsafe)
504  return min_time;
505 }
506 
507 // Apply a FrameMapper to a clip which matches the settings of this timeline
508 void Timeline::apply_mapper_to_clip(Clip* clip)
509 {
510  // Serialize mapper replacement/reconfiguration with active frame generation.
511  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
512 
513  // Determine type of reader
514  ReaderBase* clip_reader = NULL;
515  if (clip->Reader()->Name() == "FrameMapper")
516  {
517  // Get the existing reader
518  clip_reader = (ReaderBase*) clip->Reader();
519 
520  // Update the mapping
521  FrameMapper* clip_mapped_reader = (FrameMapper*) clip_reader;
523 
524  } else {
525 
526  // Create a new FrameMapper to wrap the current reader
528  allocated_frame_mappers.insert(mapper);
529  clip_reader = (ReaderBase*) mapper;
530  }
531 
532  // Update clip reader
533  clip->Reader(clip_reader);
534 }
535 
536 // Apply the timeline's framerate and samplerate to all clips
538 {
539  // Clear all cached frames
540  ClearAllCache();
541 
542  // Loop through all clips
543  for (auto clip : clips)
544  {
545  // Apply framemapper (or update existing framemapper)
546  apply_mapper_to_clip(clip);
547  }
548 }
549 
550 // Calculate time of a frame number, based on a framerate
551 double Timeline::calculate_time(int64_t number, Fraction rate)
552 {
553  // Get float version of fps fraction
554  double raw_fps = rate.ToFloat();
555 
556  // Return the time (in seconds) of this frame
557  return double(number - 1) / raw_fps;
558 }
559 
560 // Apply effects to the source frame (if any)
561 std::shared_ptr<Frame> Timeline::apply_effects(std::shared_ptr<Frame> frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct* options)
562 {
563  // Debug output
565  "Timeline::apply_effects",
566  "frame->number", frame->number,
567  "timeline_frame_number", timeline_frame_number,
568  "layer", layer);
569 
570  // Find Effects at this position and layer
571  for (auto effect : effects)
572  {
573  // Does clip intersect the current requested time
574  const double fpsD = info.fps.ToDouble();
575  int64_t effect_start_position = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
576  int64_t effect_end_position = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
577 
578  bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
579 
580  // Clip is visible
581  if (does_effect_intersect)
582  {
583  // Determine the frame needed for this clip (based on the position on the timeline)
584  int64_t effect_start_frame = static_cast<int64_t>(std::llround(effect->Start() * fpsD)) + 1;
585  int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
586 
587  if (!options->is_top_clip)
588  continue; // skip effect, if overlapped/covered by another clip on same layer
589 
590  if (options->is_before_clip_keyframes != effect->info.apply_before_clip)
591  continue; // skip effect, if this filter does not match
592 
593  // Debug output
595  "Timeline::apply_effects (Process Effect)",
596  "effect_frame_number", effect_frame_number,
597  "does_effect_intersect", does_effect_intersect);
598 
599  // Apply the effect to this frame
600  frame = effect->ProcessFrame(frame, effect_frame_number);
601  }
602 
603  } // end effect loop
604 
605  // Return modified frame
606  return frame;
607 }
608 
609 // Get or generate a blank frame
610 std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame, Clip* clip, int64_t number, openshot::TimelineInfoStruct* options)
611 {
612  std::shared_ptr<Frame> new_frame;
613 
614  // Init some basic properties about this frame
615  int samples_in_frame = Frame::GetSamplesPerFrame(number, info.fps, info.sample_rate, info.channels);
616 
617  try {
618  // Debug output
620  "Timeline::GetOrCreateFrame (from reader)",
621  "number", number,
622  "samples_in_frame", samples_in_frame);
623 
624  // Attempt to get a frame (but this could fail if a reader has just been closed)
625  new_frame = std::shared_ptr<Frame>(clip->GetFrame(background_frame, number, options));
626 
627  // Return real frame
628  return new_frame;
629 
630  } catch (const ReaderClosed & e) {
631  // ...
632  } catch (const OutOfBoundsFrame & e) {
633  // ...
634  }
635 
636  // Debug output
638  "Timeline::GetOrCreateFrame (create blank)",
639  "number", number,
640  "samples_in_frame", samples_in_frame);
641 
642  // Create blank frame
643  return new_frame;
644 }
645 
646 // Process a new layer of video or audio
647 void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, int64_t clip_frame_number, bool is_top_clip, float max_volume)
648 {
649  // Create timeline options (with details about this current frame request)
650  TimelineInfoStruct options{};
651  options.is_top_clip = is_top_clip;
652  options.is_before_clip_keyframes = true;
653 
654  // Get the clip's frame, composited on top of the current timeline frame
655  std::shared_ptr<Frame> source_frame;
656  source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
657 
658  // No frame found... so bail
659  if (!source_frame)
660  return;
661 
662  // Debug output
664  "Timeline::add_layer",
665  "new_frame->number", new_frame->number,
666  "clip_frame_number", clip_frame_number);
667 
668  /* COPY AUDIO - with correct volume */
669  if (source_clip->Reader()->info.has_audio) {
670  // Debug output
672  "Timeline::add_layer (Copy Audio)",
673  "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio,
674  "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
675  "info.channels", info.channels,
676  "clip_frame_number", clip_frame_number);
677 
678  if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0)
679  {
680  // Ensure timeline frame matches the source samples once per frame
681  if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
682  new_frame->ResizeAudio(info.channels, source_frame->GetAudioSamplesCount(), info.sample_rate, info.channel_layout);
683  }
684 
685  // Apply transition-driven equal-power audio fades for clips covered by a Mask transition.
686  const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
687 
688  for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
689  {
690  // Get volume from previous frame and this frame
691  float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1);
692  float volume = source_clip->volume.GetValue(clip_frame_number);
693  previous_volume *= transition_audio_gains.first;
694  volume *= transition_audio_gains.second;
695  int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1)
696  int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1)
697 
698  // Apply volume mixing strategy
699  if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) {
700  // Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume
701  previous_volume = previous_volume / max_volume;
702  volume = volume / max_volume;
703  }
704  else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) {
705  // Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will)
706  previous_volume = previous_volume * 0.77;
707  volume = volume * 0.77;
708  }
709 
710  // If channel filter enabled, check for correct channel (and skip non-matching channels)
711  if (channel_filter != -1 && channel_filter != channel)
712  continue; // skip to next channel
713 
714  // If no volume on this frame or previous frame, do nothing
715  if (previous_volume == 0.0 && volume == 0.0)
716  continue; // skip to next channel
717 
718  // If channel mapping disabled, just use the current channel
719  if (channel_mapping == -1)
720  channel_mapping = channel;
721 
722  // Apply ramp to source frame (if needed)
723  if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
724  source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
725 
726  // Copy audio samples (and set initial volume). Mix samples with existing audio samples. The gains are added together, to
727  // be sure to set the gain's correctly, so the sum does not exceed 1.0 (of audio distortion will happen).
728  new_frame->AddAudio(false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
729  }
730  }
731  else
732  // Debug output
734  "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
735  "source_clip->Reader()->info.has_audio",
736  source_clip->Reader()->info.has_audio,
737  "source_frame->GetAudioChannelsCount()",
738  source_frame->GetAudioChannelsCount(),
739  "info.channels", info.channels,
740  "clip_frame_number", clip_frame_number);
741  }
742 
743  // Debug output
745  "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
746  "source_frame->number", source_frame->number,
747  "new_frame->GetImage()->width()", new_frame->GetWidth(),
748  "new_frame->GetImage()->height()", new_frame->GetHeight());
749 }
750 
751 // Update the list of 'opened' clips
752 void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
753 {
754  // Get lock (prevent getting frames while this happens)
755  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
756 
758  "Timeline::update_open_clips (before)",
759  "does_clip_intersect", does_clip_intersect,
760  "closing_clips.size()", closing_clips.size(),
761  "open_clips.size()", open_clips.size());
762 
763  // is clip already in list?
764  bool clip_found = open_clips.count(clip);
765 
766  if (clip_found && !does_clip_intersect)
767  {
768  // Remove clip from 'opened' list, because it's closed now
769  open_clips.erase(clip);
770 
771  // Close clip
772  clip->Close();
773  }
774  else if (!clip_found && does_clip_intersect)
775  {
776  try {
777  // Open the clip
778  clip->Open();
779  // Add clip to 'opened' list only after a successful open.
780  open_clips[clip] = clip;
781 
782  } catch (const InvalidFile & e) {
783  // ...
784  }
785  }
786 
787  // Debug output
789  "Timeline::update_open_clips (after)",
790  "does_clip_intersect", does_clip_intersect,
791  "clip_found", clip_found,
792  "closing_clips.size()", closing_clips.size(),
793  "open_clips.size()", open_clips.size());
794 }
795 
796 // Calculate the max and min duration (in seconds) of the timeline, based on all the clips, and cache the value
797 void Timeline::calculate_max_duration() {
798  double last_clip = 0.0;
799  double last_effect = 0.0;
800  double first_clip = std::numeric_limits<double>::max();
801  double first_effect = std::numeric_limits<double>::max();
802 
803  // Find the last and first clip
804  if (!clips.empty()) {
805  // Find the clip with the maximum end frame
806  const auto max_clip = std::max_element(
807  clips.begin(), clips.end(), CompareClipEndFrames());
808  last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
809 
810  // Find the clip with the minimum start position (ignoring layer)
811  const auto min_clip = std::min_element(
812  clips.begin(), clips.end(), [](const openshot::Clip* lhs, const openshot::Clip* rhs) {
813  return lhs->Position() < rhs->Position();
814  });
815  first_clip = (*min_clip)->Position();
816  }
817 
818  // Find the last and first effect
819  if (!effects.empty()) {
820  // Find the effect with the maximum end frame
821  const auto max_effect = std::max_element(
822  effects.begin(), effects.end(), CompareEffectEndFrames());
823  last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
824 
825  // Find the effect with the minimum start position
826  const auto min_effect = std::min_element(
827  effects.begin(), effects.end(), [](const openshot::EffectBase* lhs, const openshot::EffectBase* rhs) {
828  return lhs->Position() < rhs->Position();
829  });
830  first_effect = (*min_effect)->Position();
831  }
832 
833  // Calculate the max and min time
834  max_time = std::max(last_clip, last_effect);
835  min_time = std::min(first_clip, first_effect);
836 
837  // If no clips or effects exist, set min_time to 0
838  if (clips.empty() && effects.empty()) {
839  min_time = 0.0;
840  max_time = 0.0;
841  }
842 }
843 
844 // Sort clips by position on the timeline
845 void Timeline::sort_clips()
846 {
847  // Get lock (prevent getting frames while this happens)
848  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
849 
850  // Debug output
852  "Timeline::SortClips",
853  "clips.size()", clips.size());
854 
855  // sort clips
856  clips.sort(CompareClips());
857 
858  // calculate max timeline duration
859  calculate_max_duration();
860 }
861 
862 // Sort effects by position on the timeline
863 void Timeline::sort_effects()
864 {
865  // Get lock (prevent getting frames while this happens)
866  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
867 
868  // sort clips
869  effects.sort(CompareEffects());
870 
871  // calculate max timeline duration
872  calculate_max_duration();
873 }
874 
875 // Clear all clips from timeline
877 {
878  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Clear");
879 
880  // Get lock (prevent getting frames while this happens)
881  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
882 
883  // Close all open clips
884  for (auto clip : clips)
885  {
886  update_open_clips(clip, false);
887 
888  // Delete clip object (if timeline allocated it)
889  bool allocated = allocated_clips.count(clip);
890  if (allocated) {
891  delete clip;
892  }
893  }
894  // Clear all clips
895  clips.clear();
896  allocated_clips.clear();
897 
898  // Close all effects
899  for (auto effect : effects)
900  {
901  // Delete effect object (if timeline allocated it)
902  bool allocated = allocated_effects.count(effect);
903  if (allocated) {
904  delete effect;
905  }
906  }
907  // Clear all effects
908  effects.clear();
909  allocated_effects.clear();
910 
911  // Delete all FrameMappers
912  for (auto mapper : allocated_frame_mappers)
913  {
914  mapper->Reader(NULL);
915  mapper->Close();
916  delete mapper;
917  }
918  allocated_frame_mappers.clear();
919 }
920 
921 // Close the reader (and any resources it was consuming)
923 {
924  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Close");
925 
926  // Get lock (prevent getting frames while this happens)
927  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
928 
929  // Close all open clips
930  for (auto clip : clips)
931  {
932  // Open or Close this clip, based on if it's intersecting or not
933  update_open_clips(clip, false);
934  }
935 
936  // Mark timeline as closed
937  is_open = false;
938 
939  // Clear all cache (deep clear, including nested Readers)
940  ClearAllCache(true);
941 }
942 
943 // Open the reader (and start consuming resources)
945 {
946  is_open = true;
947 }
948 
949 // Compare 2 floating point numbers for equality
950 bool Timeline::isEqual(double a, double b)
951 {
952  return fabs(a - b) < 0.000001;
953 }
954 
955 // Get an openshot::Frame object for a specific frame number of this reader.
956 std::shared_ptr<Frame> Timeline::GetFrame(int64_t requested_frame)
957 {
958  // Adjust out of bounds frame number
959  if (requested_frame < 1)
960  requested_frame = 1;
961  const int64_t max_frame = GetMaxFrame();
962  const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
963 
964  // Check cache
965  std::shared_ptr<Frame> frame;
966  if (!past_timeline_end)
967  frame = final_cache->GetFrame(requested_frame);
968  if (frame) {
969  // Debug output
971  "Timeline::GetFrame (Cached frame found)",
972  "requested_frame", requested_frame);
973 
974  // Return cached frame
975  return frame;
976  }
977  else
978  {
979  // Prevent async calls to the following code
980  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
981 
982  // Check cache 2nd time
983  std::shared_ptr<Frame> frame;
984  if (!past_timeline_end)
985  frame = final_cache->GetFrame(requested_frame);
986  if (frame) {
987  // Debug output
989  "Timeline::GetFrame (Cached frame found on 2nd check)",
990  "requested_frame", requested_frame);
991 
992  // Return cached frame
993  return frame;
994  } else {
995  // Get a list of clips that intersect with the requested section of timeline
996  // This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
997  std::vector<Clip *> nearby_clips;
998  nearby_clips = find_intersecting_clips(requested_frame, 1, true);
999 
1000  // Debug output
1002  "Timeline::GetFrame (processing frame)",
1003  "requested_frame", requested_frame,
1004  "omp_get_thread_num()", omp_get_thread_num());
1005 
1006  // Init some basic properties about this frame
1007  int samples_in_frame = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
1008 
1009  // Create blank frame (which will become the requested frame)
1010  std::shared_ptr<Frame> new_frame(std::make_shared<Frame>(requested_frame, preview_width, preview_height, "#000000", samples_in_frame, info.channels));
1011  new_frame->AddAudioSilence(samples_in_frame);
1012  new_frame->SampleRate(info.sample_rate);
1013  new_frame->ChannelsLayout(info.channel_layout);
1014 
1015  // Debug output
1017  "Timeline::GetFrame (Adding solid color)",
1018  "requested_frame", requested_frame,
1019  "info.width", info.width,
1020  "info.height", info.height);
1021 
1022  // Add Background Color to 1st layer (if animated or not black)
1023  if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) ||
1024  (color.red.GetValue(requested_frame) != 0.0 || color.green.GetValue(requested_frame) != 0.0 ||
1025  color.blue.GetValue(requested_frame) != 0.0))
1026  new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame));
1027 
1028  // Debug output
1030  "Timeline::GetFrame (Loop through clips)",
1031  "requested_frame", requested_frame,
1032  "clips.size()", clips.size(),
1033  "nearby_clips.size()", nearby_clips.size());
1034 
1035  // Precompute per-clip timing for this requested frame
1036  struct ClipInfo {
1037  Clip* clip;
1038  int64_t start_pos;
1039  int64_t end_pos;
1040  int64_t start_frame;
1041  int64_t frame_number;
1042  bool intersects;
1043  };
1044  std::vector<ClipInfo> clip_infos;
1045  clip_infos.reserve(nearby_clips.size());
1046  const double fpsD = info.fps.ToDouble();
1047 
1048  for (auto clip : nearby_clips) {
1049  int64_t start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1050  int64_t end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1051  bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1052  int64_t start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1053  int64_t frame_number = requested_frame - start_pos + start_frame;
1054  clip_infos.push_back({clip, start_pos, end_pos, start_frame, frame_number, intersects});
1055  }
1056 
1057  // Determine top clip per layer (linear, no nested loop)
1058  std::unordered_map<int, int64_t> top_start_for_layer;
1059  std::unordered_map<int, Clip*> top_clip_for_layer;
1060  for (const auto& ci : clip_infos) {
1061  if (!ci.intersects) continue;
1062  const int layer = ci.clip->Layer();
1063  auto it = top_start_for_layer.find(layer);
1064  if (it == top_start_for_layer.end() || ci.start_pos > it->second) {
1065  top_start_for_layer[layer] = ci.start_pos; // strictly greater to match prior logic
1066  top_clip_for_layer[layer] = ci.clip;
1067  }
1068  }
1069 
1070  // Compute max_volume across all overlapping clips once
1071  float max_volume_sum = 0.0f;
1072  for (const auto& ci : clip_infos) {
1073  if (!ci.intersects) continue;
1074  if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1075  ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1076  max_volume_sum += static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1077  }
1078  }
1079 
1080  // Compose intersecting clips in a single pass
1081  for (const auto& ci : clip_infos) {
1082  // Debug output
1084  "Timeline::GetFrame (Does clip intersect)",
1085  "requested_frame", requested_frame,
1086  "clip->Position()", ci.clip->Position(),
1087  "clip->Duration()", ci.clip->Duration(),
1088  "does_clip_intersect", ci.intersects);
1089 
1090  // Clip is visible
1091  if (ci.intersects) {
1092  // Is this the top clip on its layer?
1093  bool is_top_clip = false;
1094  const int layer = ci.clip->Layer();
1095  auto top_it = top_clip_for_layer.find(layer);
1096  if (top_it != top_clip_for_layer.end())
1097  is_top_clip = (top_it->second == ci.clip);
1098 
1099  // Determine the frame needed for this clip (based on the position on the timeline)
1100  int64_t clip_frame_number = ci.frame_number;
1101 
1102  // Debug output
1104  "Timeline::GetFrame (Calculate clip's frame #)",
1105  "clip->Position()", ci.clip->Position(),
1106  "clip->Start()", ci.clip->Start(),
1107  "info.fps.ToFloat()", info.fps.ToFloat(),
1108  "clip_frame_number", clip_frame_number);
1109 
1110  // Add clip's frame as layer
1111  add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1112 
1113  } else {
1114  // Debug output
1116  "Timeline::GetFrame (clip does not intersect)",
1117  "requested_frame", requested_frame,
1118  "does_clip_intersect", ci.intersects);
1119  }
1120 
1121  } // end clip loop
1122 
1123  // Debug output
1125  "Timeline::GetFrame (Add frame to cache)",
1126  "requested_frame", requested_frame,
1127  "info.width", info.width,
1128  "info.height", info.height);
1129 
1130  // Set frame # on mapped frame
1131  new_frame->SetFrameNumber(requested_frame);
1132 
1133  // Add final frame to cache (only for valid timeline range)
1134  if (!past_timeline_end)
1135  final_cache->Add(new_frame);
1136  // Return frame (or blank frame)
1137  return new_frame;
1138  }
1139  }
1140 }
1141 
1142 
1143 // Find intersecting clips (or non intersecting clips)
1144 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include)
1145 {
1146  // Find matching clips
1147  std::vector<Clip*> matching_clips;
1148 
1149  // Calculate time of frame
1150  const int64_t min_requested_frame = requested_frame;
1151  const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1152 
1153  // Find Clips at this time
1154  matching_clips.reserve(clips.size());
1155  const double fpsD = info.fps.ToDouble();
1156  for (auto clip : clips)
1157  {
1158  // Does clip intersect the current requested time
1159  int64_t clip_start_position = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1160  int64_t clip_end_position = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1161 
1162  bool does_clip_intersect =
1163  (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1164  (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1165 
1166  // Debug output
1168  "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1169  "requested_frame", requested_frame,
1170  "min_requested_frame", min_requested_frame,
1171  "max_requested_frame", max_requested_frame,
1172  "clip->Position()", clip->Position(),
1173  "does_clip_intersect", does_clip_intersect);
1174 
1175  // Open (or schedule for closing) this clip, based on if it's intersecting or not
1176  update_open_clips(clip, does_clip_intersect);
1177 
1178  // Clip is visible
1179  if (does_clip_intersect && include)
1180  // Add the intersecting clip
1181  matching_clips.push_back(clip);
1182 
1183  else if (!does_clip_intersect && !include)
1184  // Add the non-intersecting clip
1185  matching_clips.push_back(clip);
1186 
1187  } // end clip loop
1188 
1189  // return list
1190  return matching_clips;
1191 }
1192 
1193 // Set the cache object used by this reader
1194 void Timeline::SetCache(CacheBase* new_cache) {
1195  // Get lock (prevent getting frames while this happens)
1196  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1197 
1198  // Destroy previous cache (if managed by timeline)
1199  if (managed_cache && final_cache) {
1200  delete final_cache;
1201  final_cache = NULL;
1202  managed_cache = false;
1203  }
1204 
1205  // Set new cache
1206  final_cache = new_cache;
1207 }
1208 
1209 // Generate JSON string of this object
1210 std::string Timeline::Json() const {
1211 
1212  // Return formatted string
1213  return JsonValue().toStyledString();
1214 }
1215 
1216 // Generate Json::Value for this object
1217 Json::Value Timeline::JsonValue() const {
1218 
1219  // Create root json object
1220  Json::Value root = ReaderBase::JsonValue(); // get parent properties
1221  root["type"] = "Timeline";
1222  root["viewport_scale"] = viewport_scale.JsonValue();
1223  root["viewport_x"] = viewport_x.JsonValue();
1224  root["viewport_y"] = viewport_y.JsonValue();
1225  root["color"] = color.JsonValue();
1226  root["path"] = path;
1227 
1228  // Add array of clips
1229  root["clips"] = Json::Value(Json::arrayValue);
1230 
1231  // Find Clips at this time
1232  for (const auto existing_clip : clips)
1233  {
1234  root["clips"].append(existing_clip->JsonValue());
1235  }
1236 
1237  // Add array of effects
1238  root["effects"] = Json::Value(Json::arrayValue);
1239 
1240  // loop through effects
1241  for (const auto existing_effect: effects)
1242  {
1243  root["effects"].append(existing_effect->JsonValue());
1244  }
1245 
1246  // return JsonValue
1247  return root;
1248 }
1249 
1250 // Load JSON string into this object
1251 void Timeline::SetJson(const std::string value) {
1252 
1253  // Get lock (prevent getting frames while this happens)
1254  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1255 
1256  // Parse JSON string into JSON objects
1257  try
1258  {
1259  const Json::Value root = openshot::stringToJson(value);
1260  // Set all values that match
1261  SetJsonValue(root);
1262  }
1263  catch (const std::exception& e)
1264  {
1265  // Error parsing JSON (or missing keys)
1266  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1267  }
1268 }
1269 
1270 // Load Json::Value into this object
1271 void Timeline::SetJsonValue(const Json::Value root) {
1272 
1273  // Get lock (prevent getting frames while this happens)
1274  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1275 
1276  // Close timeline before we do anything (this closes all clips)
1277  bool was_open = is_open;
1278  Close();
1279 
1280  // Set parent data
1282 
1283  // Set data from Json (if key is found)
1284  if (!root["path"].isNull())
1285  path = root["path"].asString();
1286 
1287  if (!root["clips"].isNull()) {
1288  // Clear existing clips
1289  clips.clear();
1290 
1291  // loop through clips
1292  for (const Json::Value existing_clip : root["clips"]) {
1293  // Skip NULL nodes
1294  if (existing_clip.isNull()) {
1295  continue;
1296  }
1297 
1298  // Create Clip
1299  Clip *c = new Clip();
1300 
1301  // Keep track of allocated clip objects
1302  allocated_clips.insert(c);
1303 
1304  // When a clip is attached to an object, it searches for the object
1305  // on it's parent timeline. Setting the parent timeline of the clip here
1306  // allows attaching it to an object when exporting the project (because)
1307  // the exporter script initializes the clip and it's effects
1308  // before setting its parent timeline.
1309  c->ParentTimeline(this);
1310 
1311  // Load Json into Clip
1312  c->SetJsonValue(existing_clip);
1313 
1314  // Add Clip to Timeline
1315  AddClip(c);
1316  }
1317  }
1318 
1319  if (!root["effects"].isNull()) {
1320  // Clear existing effects
1321  effects.clear();
1322 
1323  // loop through effects
1324  for (const Json::Value existing_effect :root["effects"]) {
1325  // Skip NULL nodes
1326  if (existing_effect.isNull()) {
1327  continue;
1328  }
1329 
1330  // Create Effect
1331  EffectBase *e = NULL;
1332 
1333  if (!existing_effect["type"].isNull()) {
1334  // Create instance of effect
1335  if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) {
1336 
1337  // Keep track of allocated effect objects
1338  allocated_effects.insert(e);
1339 
1340  // Load Json into Effect
1341  e->SetJsonValue(existing_effect);
1342 
1343  // Add Effect to Timeline
1344  AddEffect(e);
1345  }
1346  }
1347  }
1348  }
1349 
1350  if (!root["duration"].isNull()) {
1351  // Update duration of timeline
1352  info.duration = root["duration"].asDouble();
1354  }
1355 
1356  // Update preview settings
1359 
1360  // Resort (and recalculate min/max duration)
1361  sort_clips();
1362  sort_effects();
1363 
1364  // Re-open if needed
1365  if (was_open)
1366  Open();
1367 
1368  // Timeline content changed: notify cache clients to rescan active window.
1369  BumpCacheEpoch();
1370 }
1371 
1372 // Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
1373 void Timeline::ApplyJsonDiff(std::string value) {
1374 
1375  // Get lock (prevent getting frames while this happens)
1376  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1377 
1378  // Parse JSON string into JSON objects
1379  try
1380  {
1381  const Json::Value root = openshot::stringToJson(value);
1382  const uint64_t initial_cache_epoch = CacheEpoch();
1383  // Process the JSON change array, loop through each item
1384  for (const Json::Value change : root) {
1385  std::string change_key = change["key"][(uint)0].asString();
1386 
1387  // Process each type of change
1388  if (change_key == "clips")
1389  // Apply to CLIPS
1390  apply_json_to_clips(change);
1391 
1392  else if (change_key == "effects")
1393  // Apply to EFFECTS
1394  apply_json_to_effects(change);
1395 
1396  else
1397  // Apply to TIMELINE
1398  apply_json_to_timeline(change);
1399 
1400  }
1401 
1402  // Timeline content changed: notify cache clients to rescan active window.
1403  if (!root.empty() && CacheEpoch() == initial_cache_epoch) {
1404  BumpCacheEpoch();
1405  }
1406  }
1407  catch (const std::exception& e)
1408  {
1409  // Error parsing JSON (or missing keys)
1410  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1411  }
1412 }
1413 
1414 void Timeline::BumpCacheEpoch() {
1415  cache_epoch.fetch_add(1, std::memory_order_relaxed);
1416 }
1417 
1418 void Timeline::InvalidateCacheForClip(const Clip* clip) {
1419  if (!clip || !final_cache) {
1420  return;
1421  }
1422 
1423  const double fpsD = info.fps.ToDouble();
1424  const int64_t starting_frame = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1425  const int64_t ending_frame = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1426  final_cache->Remove(starting_frame - 8, ending_frame + 8);
1427  BumpCacheEpoch();
1428 }
1429 
1430 // Apply JSON diff to clips
1431 void Timeline::apply_json_to_clips(Json::Value change) {
1432 
1433  // Get key and type of change
1434  std::string change_type = change["type"].asString();
1435  std::string clip_id = "";
1436  Clip *existing_clip = NULL;
1437 
1438  // Find id of clip (if any)
1439  for (auto key_part : change["key"]) {
1440  // Get each change
1441  if (key_part.isObject()) {
1442  // Check for id
1443  if (!key_part["id"].isNull()) {
1444  // Set the id
1445  clip_id = key_part["id"].asString();
1446 
1447  // Find matching clip in timeline (if any)
1448  for (auto c : clips)
1449  {
1450  if (c->Id() == clip_id) {
1451  existing_clip = c;
1452  break; // clip found, exit loop
1453  }
1454  }
1455  break; // id found, exit loop
1456  }
1457  }
1458  }
1459 
1460  // Check for a more specific key (targetting this clip's effects)
1461  // For example: ["clips", {"id:123}, "effects", {"id":432}]
1462  if (existing_clip && change["key"].size() == 4 && change["key"][2] == "effects")
1463  {
1464  // This change is actually targetting a specific effect under a clip (and not the clip)
1465  Json::Value key_part = change["key"][3];
1466 
1467  if (key_part.isObject()) {
1468  // Check for id
1469  if (!key_part["id"].isNull())
1470  {
1471  // Set the id
1472  std::string effect_id = key_part["id"].asString();
1473 
1474  // Find matching effect in timeline (if any)
1475  std::list<EffectBase*> effect_list = existing_clip->Effects();
1476  for (auto e : effect_list)
1477  {
1478  if (e->Id() == effect_id) {
1479  // Apply the change to the effect directly
1480  apply_json_to_effects(change, e);
1481 
1482  // Effect-only diffs must clear the owning clip cache.
1483  if (existing_clip->GetCache()) {
1484  existing_clip->GetCache()->Clear();
1485  }
1486 
1487  // Calculate start and end frames that this impacts, and remove those frames from the cache
1488  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1489  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1490  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1491 
1492  return; // effect found, don't update clip
1493  }
1494  }
1495  }
1496  }
1497  }
1498 
1499  // Determine type of change operation
1500  if (change_type == "insert") {
1501 
1502  // Create clip
1503  Clip *clip = new Clip();
1504 
1505  // Keep track of allocated clip objects
1506  allocated_clips.insert(clip);
1507 
1508  // Match full timeline JSON loading: parent timeline must be available
1509  // before clip JSON can inflate nested readers/effects.
1510  clip->ParentTimeline(this);
1511 
1512  // Set properties of clip from JSON
1513  clip->SetJsonValue(change["value"]);
1514 
1515  // Add clip to timeline
1516  AddClip(clip);
1517 
1518  } else if (change_type == "update") {
1519 
1520  // Update existing clip
1521  if (existing_clip) {
1522  // Calculate start and end frames prior to the update
1523  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1524  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1525 
1526  // Update clip properties from JSON
1527  existing_clip->SetJsonValue(change["value"]);
1528 
1529  // Calculate new start and end frames after the update
1530  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1531  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1532 
1533  // Remove both the old and new ranges from the timeline cache
1534  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1535  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1536 
1537  // Apply framemapper (or update existing framemapper)
1538  if (auto_map_clips) {
1539  apply_mapper_to_clip(existing_clip);
1540  }
1541  }
1542 
1543  } else if (change_type == "delete") {
1544 
1545  // Remove existing clip
1546  if (existing_clip) {
1547  // Remove clip from timeline
1548  RemoveClip(existing_clip);
1549 
1550  // Calculate start and end frames that this impacts, and remove those frames from the cache
1551  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1552  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1553  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1554  }
1555 
1556  }
1557 
1558  // Re-Sort Clips (since they likely changed)
1559  sort_clips();
1560 }
1561 
1562 // Apply JSON diff to effects
1563 void Timeline::apply_json_to_effects(Json::Value change) {
1564 
1565  // Get key and type of change
1566  std::string change_type = change["type"].asString();
1567  EffectBase *existing_effect = NULL;
1568 
1569  // Find id of an effect (if any)
1570  for (auto key_part : change["key"]) {
1571 
1572  if (key_part.isObject()) {
1573  // Check for id
1574  if (!key_part["id"].isNull())
1575  {
1576  // Set the id
1577  std::string effect_id = key_part["id"].asString();
1578 
1579  // Find matching effect in timeline (if any)
1580  for (auto e : effects)
1581  {
1582  if (e->Id() == effect_id) {
1583  existing_effect = e;
1584  break; // effect found, exit loop
1585  }
1586  }
1587  break; // id found, exit loop
1588  }
1589  }
1590  }
1591 
1592  // Now that we found the effect, apply the change to it
1593  if (existing_effect || change_type == "insert") {
1594  // Apply change to effect
1595  apply_json_to_effects(change, existing_effect);
1596  }
1597 }
1598 
1599 // Apply JSON diff to effects (if you already know which effect needs to be updated)
1600 void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_effect) {
1601 
1602  // Get key and type of change
1603  std::string change_type = change["type"].asString();
1604 
1605  // Calculate start and end frames that this impacts, and remove those frames from the cache
1606  if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1607  int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1608  int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1609  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1610  }
1611 
1612  // Determine type of change operation
1613  if (change_type == "insert") {
1614 
1615  // Determine type of effect
1616  std::string effect_type = change["value"]["type"].asString();
1617 
1618  // Create Effect
1619  EffectBase *e = NULL;
1620 
1621  // Init the matching effect object
1622  if ( (e = EffectInfo().CreateEffect(effect_type)) ) {
1623 
1624  // Keep track of allocated effect objects
1625  allocated_effects.insert(e);
1626 
1627  // Load Json into Effect
1628  e->SetJsonValue(change["value"]);
1629 
1630  // Add Effect to Timeline
1631  AddEffect(e);
1632  }
1633 
1634  } else if (change_type == "update") {
1635 
1636  // Update existing effect
1637  if (existing_effect) {
1638 
1639  // Calculate start and end frames that this impacts, and remove those frames from the cache
1640  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1641  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1642  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1643 
1644  // Update effect properties from JSON
1645  existing_effect->SetJsonValue(change["value"]);
1646  }
1647 
1648  } else if (change_type == "delete") {
1649 
1650  // Remove existing effect
1651  if (existing_effect) {
1652 
1653  // Calculate start and end frames that this impacts, and remove those frames from the cache
1654  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1655  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1656  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1657 
1658  // Remove effect from timeline
1659  RemoveEffect(existing_effect);
1660  }
1661 
1662  }
1663 
1664  // Re-Sort Effects (since they likely changed)
1665  sort_effects();
1666 }
1667 
1668 // Apply JSON diff to timeline properties
1669 void Timeline::apply_json_to_timeline(Json::Value change) {
1670  bool cache_dirty = true;
1671 
1672  // Get key and type of change
1673  std::string change_type = change["type"].asString();
1674  std::string root_key = change["key"][(uint)0].asString();
1675  std::string sub_key = "";
1676  if (change["key"].size() >= 2)
1677  sub_key = change["key"][(uint)1].asString();
1678 
1679  // Determine type of change operation
1680  if (change_type == "insert" || change_type == "update") {
1681 
1682  // INSERT / UPDATE
1683  // Check for valid property
1684  if (root_key == "color")
1685  // Set color
1686  color.SetJsonValue(change["value"]);
1687  else if (root_key == "viewport_scale")
1688  // Set viewport scale
1689  viewport_scale.SetJsonValue(change["value"]);
1690  else if (root_key == "viewport_x")
1691  // Set viewport x offset
1692  viewport_x.SetJsonValue(change["value"]);
1693  else if (root_key == "viewport_y")
1694  // Set viewport y offset
1695  viewport_y.SetJsonValue(change["value"]);
1696  else if (root_key == "duration") {
1697  // Update duration of timeline
1698  info.duration = change["value"].asDouble();
1700 
1701  // We don't want to clear cache for duration adjustments
1702  cache_dirty = false;
1703  }
1704  else if (root_key == "width") {
1705  // Set width
1706  info.width = change["value"].asInt();
1708  }
1709  else if (root_key == "height") {
1710  // Set height
1711  info.height = change["value"].asInt();
1713  }
1714  else if (root_key == "fps" && sub_key == "" && change["value"].isObject()) {
1715  // Set fps fraction
1716  if (!change["value"]["num"].isNull())
1717  info.fps.num = change["value"]["num"].asInt();
1718  if (!change["value"]["den"].isNull())
1719  info.fps.den = change["value"]["den"].asInt();
1720  }
1721  else if (root_key == "fps" && sub_key == "num")
1722  // Set fps.num
1723  info.fps.num = change["value"].asInt();
1724  else if (root_key == "fps" && sub_key == "den")
1725  // Set fps.den
1726  info.fps.den = change["value"].asInt();
1727  else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) {
1728  // Set display_ratio fraction
1729  if (!change["value"]["num"].isNull())
1730  info.display_ratio.num = change["value"]["num"].asInt();
1731  if (!change["value"]["den"].isNull())
1732  info.display_ratio.den = change["value"]["den"].asInt();
1733  }
1734  else if (root_key == "display_ratio" && sub_key == "num")
1735  // Set display_ratio.num
1736  info.display_ratio.num = change["value"].asInt();
1737  else if (root_key == "display_ratio" && sub_key == "den")
1738  // Set display_ratio.den
1739  info.display_ratio.den = change["value"].asInt();
1740  else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) {
1741  // Set pixel_ratio fraction
1742  if (!change["value"]["num"].isNull())
1743  info.pixel_ratio.num = change["value"]["num"].asInt();
1744  if (!change["value"]["den"].isNull())
1745  info.pixel_ratio.den = change["value"]["den"].asInt();
1746  }
1747  else if (root_key == "pixel_ratio" && sub_key == "num")
1748  // Set pixel_ratio.num
1749  info.pixel_ratio.num = change["value"].asInt();
1750  else if (root_key == "pixel_ratio" && sub_key == "den")
1751  // Set pixel_ratio.den
1752  info.pixel_ratio.den = change["value"].asInt();
1753 
1754  else if (root_key == "sample_rate")
1755  // Set sample rate
1756  info.sample_rate = change["value"].asInt();
1757  else if (root_key == "channels")
1758  // Set channels
1759  info.channels = change["value"].asInt();
1760  else if (root_key == "channel_layout")
1761  // Set channel layout
1762  info.channel_layout = (ChannelLayout) change["value"].asInt();
1763  else
1764  // Error parsing JSON (or missing keys)
1765  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1766 
1767 
1768  } else if (change["type"].asString() == "delete") {
1769 
1770  // DELETE / RESET
1771  // Reset the following properties (since we can't delete them)
1772  if (root_key == "color") {
1773  color = Color();
1774  color.red = Keyframe(0.0);
1775  color.green = Keyframe(0.0);
1776  color.blue = Keyframe(0.0);
1777  }
1778  else if (root_key == "viewport_scale")
1779  viewport_scale = Keyframe(1.0);
1780  else if (root_key == "viewport_x")
1781  viewport_x = Keyframe(0.0);
1782  else if (root_key == "viewport_y")
1783  viewport_y = Keyframe(0.0);
1784  else
1785  // Error parsing JSON (or missing keys)
1786  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1787 
1788  }
1789 
1790  if (cache_dirty) {
1791  // Clear entire cache
1792  ClearAllCache();
1793  }
1794 }
1795 
1796 // Clear all caches
1797 void Timeline::ClearAllCache(bool deep) {
1798  // Get lock (prevent getting frames while this happens)
1799  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
1800 
1801  // Clear primary cache
1802  if (final_cache) {
1803  final_cache->Clear();
1804  }
1805 
1806  // Loop through all clips
1807  try {
1808  for (const auto clip : clips) {
1809  // Clear cache on clip and reader if present
1810  if (clip->Reader()) {
1811  if (auto rc = clip->Reader()->GetCache())
1812  rc->Clear();
1813 
1814  // Clear nested Reader (if deep clear requested)
1815  if (deep && clip->Reader()->Name() == "FrameMapper") {
1816  FrameMapper *nested_reader = static_cast<FrameMapper *>(clip->Reader());
1817  if (nested_reader->Reader()) {
1818  if (auto nc = nested_reader->Reader()->GetCache())
1819  nc->Clear();
1820  }
1821  }
1822  }
1823 
1824  // Clear clip cache
1825  if (auto cc = clip->GetCache())
1826  cc->Clear();
1827  }
1828  } catch (const ReaderClosed & e) {
1829  // ...
1830  }
1831 
1832  // Cache content changed: notify cache clients to rebuild their window baseline.
1833  BumpCacheEpoch();
1834 }
1835 
1836 // Set Max Image Size (used for performance optimization). Convenience function for setting
1837 // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT.
1838 void Timeline::SetMaxSize(int width, int height) {
1839  // Maintain aspect ratio regardless of what size is passed in
1840  QSize display_ratio_size = QSize(info.width, info.height);
1841  QSize proposed_size = QSize(std::min(width, info.width), std::min(height, info.height));
1842 
1843  // Scale QSize up to proposed size
1844  display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1845 
1846  // Update preview settings
1847  preview_width = display_ratio_size.width();
1848  preview_height = display_ratio_size.height();
1849 }
1850 
1851 // Resolve equal-power audio gains from transition placement relative to the clip edges.
1852 std::pair<float, float> Timeline::ResolveTransitionAudioGains(Clip* source_clip, int64_t timeline_frame_number, bool is_top_clip) const
1853 {
1854  constexpr double half_pi = 1.57079632679489661923;
1855 
1856  if (!source_clip)
1857  return {1.0f, 1.0f};
1858 
1859  const double fpsD = info.fps.ToDouble();
1860  Mask* active_mask = nullptr;
1861  int64_t effect_start_position = 0;
1862  int64_t effect_end_position = 0;
1863 
1864  // Find the single active transition on this layer that requested overlapping audio fades.
1865  for (auto effect : effects) {
1866  if (effect->Layer() != source_clip->Layer())
1867  continue;
1868 
1869  auto* mask = dynamic_cast<Mask*>(effect);
1870  if (!mask || !mask->fade_audio_hint)
1871  continue;
1872 
1873  const int64_t start_pos = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
1874  const int64_t end_pos = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1875  if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1876  continue;
1877 
1878  if (active_mask)
1879  return {1.0f, 1.0f};
1880 
1881  active_mask = mask;
1882  effect_start_position = start_pos;
1883  effect_end_position = end_pos;
1884  }
1885 
1886  if (!active_mask)
1887  return {1.0f, 1.0f};
1888 
1889  struct AudibleClipInfo {
1890  Clip* clip;
1891  int64_t start_pos;
1892  int64_t end_pos;
1893  };
1894 
1895  std::vector<AudibleClipInfo> audible_clips;
1896  audible_clips.reserve(2);
1897 
1898  // Collect the audible clips covered by this transition on the current layer.
1899  for (auto clip : clips) {
1900  if (clip->Layer() != source_clip->Layer())
1901  continue;
1902  if (!clip->Reader() || !clip->Reader()->info.has_audio)
1903  continue;
1904 
1905  const int64_t clip_start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1906  const int64_t clip_end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1907  if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1908  continue;
1909 
1910  const int64_t clip_start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1911  const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1912  if (clip->has_audio.GetInt(clip_frame_number) == 0)
1913  continue;
1914 
1915  audible_clips.push_back({clip, clip_start_pos, clip_end_pos});
1916  if (audible_clips.size() > 2)
1917  return {1.0f, 1.0f};
1918  }
1919 
1920  if (audible_clips.empty())
1921  return {1.0f, 1.0f};
1922 
1923  // Skip clips that are not actually participating in this transition audio decision.
1924  const auto source_it = std::find_if(
1925  audible_clips.begin(),
1926  audible_clips.end(),
1927  [source_clip](const AudibleClipInfo& info) {
1928  return info.clip == source_clip;
1929  });
1930  if (source_it == audible_clips.end())
1931  return {1.0f, 1.0f};
1932 
1933  // Keep the current top/non-top clip routing intact when two clips overlap.
1934  if (audible_clips.size() == 2) {
1935  auto top_it = std::max_element(
1936  audible_clips.begin(),
1937  audible_clips.end(),
1938  [](const AudibleClipInfo& lhs, const AudibleClipInfo& rhs) {
1939  if (lhs.start_pos != rhs.start_pos)
1940  return lhs.start_pos < rhs.start_pos;
1941  return std::less<Clip*>()(lhs.clip, rhs.clip);
1942  });
1943  if ((is_top_clip && source_clip != top_it->clip) || (!is_top_clip && source_clip == top_it->clip))
1944  return {1.0f, 1.0f};
1945  }
1946 
1947  // Infer fade direction from which transition edge is closer to this clip.
1948  const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1949  const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1950  const bool clip_fades_in = left_distance <= right_distance;
1951 
1952  // Evaluate the current frame and previous frame so Timeline can preserve per-frame gain ramps.
1953  const auto compute_gain = [&](int64_t frame_number) -> float {
1954  if (effect_end_position <= effect_start_position)
1955  return 1.0f;
1956 
1957  const double span = static_cast<double>(effect_end_position - effect_start_position);
1958  double t = static_cast<double>(frame_number - effect_start_position) / span;
1959  if (t < 0.0)
1960  t = 0.0;
1961  else if (t > 1.0)
1962  t = 1.0;
1963 
1964  return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
1965  };
1966 
1967  return {compute_gain(timeline_frame_number - 1), compute_gain(timeline_frame_number)};
1968 }
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::CacheMemory::Clear
void Clear()
Clear the cache of all frames.
Definition: CacheMemory.cpp:224
openshot::Timeline::RemoveClip
void RemoveClip(openshot::Clip *clip)
Remove an openshot::Clip from the timeline.
Definition: Timeline.cpp:401
openshot::FrameMapper::ChangeMapping
void ChangeMapping(Fraction target_fps, PulldownType pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout)
Change frame rate or audio mapping details.
Definition: FrameMapper.cpp:821
openshot::ReaderInfo::sample_rate
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
openshot::EffectInfo
This class returns a listing of all effects supported by libopenshot.
Definition: EffectInfo.h:28
openshot::Fraction::ToFloat
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
openshot::Timeline::GetFrame
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Definition: Timeline.cpp:956
openshot::EffectBase
This abstract class is the base class, used by all effects in libopenshot.
Definition: EffectBase.h:56
openshot::ReaderBase::JsonValue
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
openshot::Timeline::~Timeline
virtual ~Timeline()
Definition: Timeline.cpp:212
openshot::CacheBase::Clear
virtual void Clear()=0
Clear the cache of all frames.
openshot::CacheBase::GetFrame
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
Get a frame from the cache.
openshot::Timeline::viewport_x
openshot::Keyframe viewport_x
Curve representing the x coordinate for the viewport.
Definition: Timeline.h:336
openshot::TimelineBase::preview_width
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
openshot::CompareClipEndFrames
Definition: Timeline.h:74
openshot::Timeline::SetMaxSize
void SetMaxSize(int width, int height)
Definition: Timeline.cpp:1838
openshot::Mask
This class uses the image libraries to apply alpha (or transparency) masks to any frame....
Definition: Mask.h:36
openshot::BBox::height
float height
bounding box height
Definition: TrackedObjectBBox.h:51
openshot::CrashHandler::Instance
static CrashHandler * Instance()
Definition: CrashHandler.cpp:27
openshot::EffectInfo::CreateEffect
EffectBase * CreateEffect(std::string effect_type)
Create an instance of an effect (factory style)
Definition: EffectInfo.cpp:27
openshot::Clip::GetCache
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
Definition: Clip.h:217
openshot::ReaderBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::TimelineBase::preview_height
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
openshot::CacheBase::Add
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
openshot::Timeline::ApplyJsonDiff
void ApplyJsonDiff(std::string value)
Apply a special formatted JSON object, which represents a change to the timeline (add,...
Definition: Timeline.cpp:1373
openshot::Clip
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::Fraction
This class represents a fraction.
Definition: Fraction.h:30
openshot::BBox::cy
float cy
y-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:49
openshot::ReaderBase::info
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
openshot::Settings
This class is contains settings used by libopenshot (and can be safely toggled at any point)
Definition: Settings.h:26
openshot::Timeline::GetMinFrame
int64_t GetMinFrame()
Look up the start frame number of the first element on the timeline (first frame is 1)
Definition: Timeline.cpp:494
Timeline.h
Header file for Timeline class.
openshot::Clip::ParentTimeline
void ParentTimeline(openshot::TimelineBase *new_timeline) override
Set associated Timeline pointer.
Definition: Clip.cpp:450
openshot::Timeline::ClearAllCache
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1797
openshot::Timeline::GetTrackedObjectsIds
std::list< std::string > GetTrackedObjectsIds() const
Return the ID's of the tracked objects as a list of strings.
Definition: Timeline.cpp:264
openshot::CompareEffectEndFrames
Like CompareClipEndFrames, but for effects.
Definition: Timeline.h:80
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::Clip::Effects
std::list< openshot::EffectBase * > Effects()
Return the list of effects on the timeline.
Definition: Clip.h:256
openshot::ReaderInfo::duration
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
CacheDisk.h
Header file for CacheDisk class.
openshot::Clip::channel_mapping
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
Definition: Clip.h:363
openshot::ReaderInfo::has_video
bool has_video
Determines if this file has a video stream.
Definition: ReaderBase.h:40
openshot::ReaderInfo::width
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
openshot::ClipBase::Position
void Position(float value)
Set the Id of this clip object
Definition: ClipBase.cpp:19
openshot::CacheBase
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:34
openshot::Clip::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Clip.cpp:1043
CacheBase.h
Header file for CacheBase class.
openshot::OutOfBoundsFrame
Exception for frames that are out of bounds.
Definition: Exceptions.h:306
openshot::Fraction::ToDouble
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
openshot::Timeline::apply_effects
std::shared_ptr< openshot::Frame > apply_effects(std::shared_ptr< openshot::Frame > frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct *options)
Apply global/timeline effects to the source frame (if any)
Definition: Timeline.cpp:561
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::CacheBase::Remove
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
FrameMapper.h
Header file for the FrameMapper class.
openshot::ReaderBase::clip
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: ReaderBase.h:80
openshot::CacheBase::SetMaxBytesFromInfo
void SetMaxBytesFromInfo(int64_t number_of_frames, int width, int height, int sample_rate, int channels)
Set maximum bytes to a different amount based on a ReaderInfo struct.
Definition: CacheBase.cpp:28
openshot::Color
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
openshot::ClipBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ClipBase.cpp:80
openshot::ReaderInfo::video_length
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
openshot::ReaderInfo::height
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
openshot::VOLUME_MIX_REDUCE
@ VOLUME_MIX_REDUCE
Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%)
Definition: Enums.h:71
openshot::BBox::angle
float angle
bounding box rotation angle [degrees]
Definition: TrackedObjectBBox.h:52
openshot::Fraction::num
int num
Numerator for the fraction.
Definition: Fraction.h:32
openshot::Timeline::GetClip
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
Definition: Timeline.cpp:420
openshot::Fraction::den
int den
Denominator for the fraction.
Definition: Fraction.h:33
OPEN_MP_NUM_PROCESSORS
#define OPEN_MP_NUM_PROCESSORS
Definition: OpenMPUtilities.h:23
openshot::Keyframe
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
CrashHandler.h
Header file for CrashHandler class.
openshot::Fraction::Reduce
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Definition: Fraction.cpp:65
openshot::Color::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: Color.cpp:117
openshot::Timeline::Open
void Open() override
Open the reader (and start consuming resources)
Definition: Timeline.cpp:944
openshot::Timeline::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Timeline.cpp:1251
openshot::TimelineInfoStruct::is_before_clip_keyframes
bool is_before_clip_keyframes
Is this before clip keyframes are applied.
Definition: TimelineBase.h:35
openshot::Fraction::Reciprocal
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition: Fraction.cpp:78
openshot::ReaderInfo::has_audio
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::Timeline::GetTrackedObjectValues
std::string GetTrackedObjectValues(std::string id, int64_t frame_number) const
Return the trackedObject's properties as a JSON string.
Definition: Timeline.cpp:280
openshot::CacheMemory
This class is a memory-based cache manager for Frame objects.
Definition: CacheMemory.h:29
openshot::FrameMapper::Close
void Close() override
Close the openshot::FrameMapper and internal reader.
Definition: FrameMapper.cpp:738
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::Timeline::GetMaxTime
double GetMaxTime()
Look up the end time of the latest timeline element.
Definition: Timeline.cpp:475
openshot::BBox::width
float width
bounding box width
Definition: TrackedObjectBBox.h:50
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:153
openshot::Timeline::ClipEffects
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
Definition: Timeline.cpp:456
CacheMemory.h
Header file for CacheMemory class.
openshot::Color::green
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
openshot::ReaderInfo
This struct contains info about a media file, such as height, width, frames per second,...
Definition: ReaderBase.h:38
openshot::CompareEffects
Definition: Timeline.h:64
openshot::Timeline::CacheEpoch
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
Definition: Timeline.h:326
openshot::ReaderInfo::video_timebase
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition: ReaderBase.h:55
openshot::Settings::Instance
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: Settings.cpp:43
openshot::TimelineInfoStruct
This struct contains info about the current Timeline clip instance.
Definition: TimelineBase.h:32
openshot::Timeline::Clear
void Clear()
Clear all clips, effects, and frame mappers from timeline (and free memory)
Definition: Timeline.cpp:876
openshot::Timeline::RemoveEffect
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the timeline.
Definition: Timeline.cpp:382
openshot::ClipBase::Start
void Start(float value)
Set start position (in seconds) of clip (trim start of video)
Definition: ClipBase.cpp:42
path
path
Definition: FFmpegWriter.cpp:1481
openshot::Settings::PATH_OPENSHOT_INSTALL
std::string PATH_OPENSHOT_INSTALL
Definition: Settings.h:123
openshot::FrameMapper
This class creates a mapping between 2 different frame rates, applying a specific pull-down technique...
Definition: FrameMapper.h:193
openshot::Timeline::Close
void Close() override
Close the timeline reader (and any resources it was consuming)
Definition: Timeline.cpp:922
openshot::Frame::GetSamplesPerFrame
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Definition: Frame.cpp:484
openshot::Timeline::AddClip
void AddClip(openshot::Clip *clip)
Add an openshot::Clip to the timeline.
Definition: Timeline.cpp:338
openshot::InvalidFile
Exception for files that can not be found or opened.
Definition: Exceptions.h:193
openshot::ZmqLogger::Instance
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: ZmqLogger.cpp:35
Mask.h
Header file for Mask class.
openshot::ZmqLogger::AppendDebugMethod
void AppendDebugMethod(std::string method_name, std::string arg1_name="", float arg1_value=-1.0, std::string arg2_name="", float arg2_value=-1.0, std::string arg3_name="", float arg3_value=-1.0, std::string arg4_name="", float arg4_value=-1.0, std::string arg5_name="", float arg5_value=-1.0, std::string arg6_name="", float arg6_value=-1.0)
Append debug information.
Definition: ZmqLogger.cpp:178
openshot::Clip::volume
openshot::Keyframe volume
Curve representing the volume (0 to 1)
Definition: Clip.h:346
openshot::ReaderInfo::vcodec
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition: ReaderBase.h:52
openshot::Timeline::AddTrackedObject
void AddTrackedObject(std::shared_ptr< openshot::TrackedObjectBase > trackedObject)
Add to the tracked_objects map a pointer to a tracked object (TrackedObjectBBox)
Definition: Timeline.cpp:229
openshot::Color::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:86
openshot::Keyframe::GetInt
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition: KeyFrame.cpp:282
openshot::BBox
This struct holds the information of a bounding-box.
Definition: TrackedObjectBBox.h:46
openshot::Timeline::color
openshot::Color color
Background color of timeline canvas.
Definition: Timeline.h:340
openshot::ReaderClosed
Exception when a reader is closed, and a frame is requested.
Definition: Exceptions.h:369
openshot::Timeline::GetEffect
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition: Timeline.cpp:432
openshot::ReaderInfo::channel_layout
openshot::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
Definition: ReaderBase.h:62
openshot::CompareClips
Definition: Timeline.h:48
openshot::Clip::channel_filter
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
Definition: Clip.h:362
openshot::ClipBase::Id
void Id(std::string value)
Definition: ClipBase.h:94
openshot::Keyframe::GetCount
int64_t GetCount() const
Get the number of points (i.e. # of points)
Definition: KeyFrame.cpp:424
openshot::Timeline::Timeline
Timeline(int width, int height, openshot::Fraction fps, int sample_rate, int channels, openshot::ChannelLayout channel_layout)
Constructor for the timeline (which configures the default frame properties)
Definition: Timeline.cpp:34
openshot::ReaderInfo::fps
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:75
openshot::Timeline::Json
std::string Json() const override
Generate JSON string of this object.
Definition: Timeline.cpp:1210
openshot::Timeline::GetMaxFrame
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition: Timeline.cpp:481
openshot::ClipBase::GetFrame
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...
openshot::Timeline::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Timeline.cpp:1271
openshot::VOLUME_MIX_AVERAGE
@ VOLUME_MIX_AVERAGE
Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100%.
Definition: Enums.h:70
openshot::Timeline::ApplyMapperToClips
void ApplyMapperToClips()
Apply the timeline's framerate and samplerate to all clips.
Definition: Timeline.cpp:537
openshot::Timeline::viewport_y
openshot::Keyframe viewport_y
Curve representing the y coordinate for the viewport.
Definition: Timeline.h:337
openshot::Clip::has_audio
openshot::Keyframe has_audio
An optional override to determine if this clip has audio (-1=undefined, 0=no, 1=yes)
Definition: Clip.h:366
openshot::ChannelLayout
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
Definition: ChannelLayouts.h:28
openshot::ReaderInfo::pixel_ratio
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
Definition: ReaderBase.h:50
openshot::BBox::cx
float cx
x-coordinate of the bounding box center
Definition: TrackedObjectBBox.h:48
openshot::Clip::Reader
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition: Clip.cpp:340
openshot::Timeline::GetClipEffect
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:443
openshot::Color::red
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::FrameMapper::Reader
ReaderBase * Reader()
Get the current reader.
Definition: FrameMapper.cpp:67
openshot::ReaderInfo::acodec
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition: ReaderBase.h:58
openshot::PULLDOWN_NONE
@ PULLDOWN_NONE
Do not apply pull-down techniques, just repeat or skip entire frames.
Definition: FrameMapper.h:46
openshot::ReaderInfo::display_ratio
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
Definition: ReaderBase.h:51
openshot::TimelineInfoStruct::is_top_clip
bool is_top_clip
Is clip on top (if overlapping another clip)
Definition: TimelineBase.h:34
openshot::InvalidJSONKey
Exception for missing JSON Change key.
Definition: Exceptions.h:268
openshot::ClipBase::Layer
void Layer(int value)
Set layer of clip on timeline (lower number is covered by higher numbers)
Definition: ClipBase.cpp:31
openshot::Color::GetColorHex
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
Definition: Color.cpp:47
openshot::ReaderInfo::channels
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Timeline::viewport_scale
openshot::Keyframe viewport_scale
Curve representing the scale of the viewport (0 to 100)
Definition: Timeline.h:335
openshot::Timeline::AddEffect
void AddEffect(openshot::EffectBase *effect)
Add an effect to the timeline.
Definition: Timeline.cpp:366
openshot::Timeline::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Timeline.cpp:1217
openshot::ReaderBase::GetCache
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
openshot::Color::blue
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
openshot::Timeline::GetTrackedObject
std::shared_ptr< openshot::TrackedObjectBase > GetTrackedObject(std::string id) const
Return tracked object pointer by it's id.
Definition: Timeline.cpp:247
Exceptions.h
Header file for all Exception classes.
openshot::Clip::mixing
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
Definition: Clip.h:189
openshot::Timeline::GetMinTime
double GetMinTime()
Look up the position/start time of the first timeline element.
Definition: Timeline.cpp:502
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
openshot::Timeline::SetCache
void SetCache(openshot::CacheBase *new_cache)
Definition: Timeline.cpp:1194
openshot::ReaderBase::getFrameMutex
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: ReaderBase.h:79