OpenShot Library | libopenshot  1.0.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  // The registry, Clip, and nested Reader can become inconsistent after a
767  // transient reader close. Trust the actual objects over open_clips and run
768  // them through a clean Close/Open cycle while this clip still intersects.
769  // Otherwise FrameMapper converts ReaderClosed into a black frame which can
770  // be repeatedly cached on every still-intersecting timing update.
771  if (clip_found && does_clip_intersect)
772  {
773  bool clip_reader_open = false;
774  try {
775  clip_reader_open = clip->Reader() && clip->Reader()->IsOpen();
776  } catch (const ReaderClosed & e) {
777  // A missing/replaced reader is equivalent to a closed reader here.
778  clip_reader_open = false;
779  }
780  if (!clip->IsOpen() || !clip_reader_open)
781  {
782  open_clips.erase(clip);
783  clip->Close();
784  clip_found = false;
785  }
786  }
787 
788  if (clip_found && !does_clip_intersect)
789  {
790  // Remove clip from 'opened' list, because it's closed now
791  open_clips.erase(clip);
792 
793  // Close clip
794  clip->Close();
795  }
796  else if (!clip_found && does_clip_intersect)
797  {
798  try {
799  // Open the clip
800  clip->Open();
801  // Add clip to 'opened' list only after a successful open.
802  open_clips[clip] = clip;
803 
804  } catch (const InvalidFile & e) {
805  // ...
806  }
807  }
808 
809  // Debug output
811  "Timeline::update_open_clips (after)",
812  "does_clip_intersect", does_clip_intersect,
813  "clip_found", clip_found,
814  "closing_clips.size()", closing_clips.size(),
815  "open_clips.size()", open_clips.size());
816 }
817 
818 // Calculate the max and min duration (in seconds) of the timeline, based on all the clips, and cache the value
819 void Timeline::calculate_max_duration() {
820  double last_clip = 0.0;
821  double last_effect = 0.0;
822  double first_clip = std::numeric_limits<double>::max();
823  double first_effect = std::numeric_limits<double>::max();
824 
825  // Find the last and first clip
826  if (!clips.empty()) {
827  // Find the clip with the maximum end frame
828  const auto max_clip = std::max_element(
829  clips.begin(), clips.end(), CompareClipEndFrames());
830  last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
831 
832  // Find the clip with the minimum start position (ignoring layer)
833  const auto min_clip = std::min_element(
834  clips.begin(), clips.end(), [](const openshot::Clip* lhs, const openshot::Clip* rhs) {
835  return lhs->Position() < rhs->Position();
836  });
837  first_clip = (*min_clip)->Position();
838  }
839 
840  // Find the last and first effect
841  if (!effects.empty()) {
842  // Find the effect with the maximum end frame
843  const auto max_effect = std::max_element(
844  effects.begin(), effects.end(), CompareEffectEndFrames());
845  last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
846 
847  // Find the effect with the minimum start position
848  const auto min_effect = std::min_element(
849  effects.begin(), effects.end(), [](const openshot::EffectBase* lhs, const openshot::EffectBase* rhs) {
850  return lhs->Position() < rhs->Position();
851  });
852  first_effect = (*min_effect)->Position();
853  }
854 
855  // Calculate the max and min time
856  max_time = std::max(last_clip, last_effect);
857  min_time = std::min(first_clip, first_effect);
858 
859  // If no clips or effects exist, set min_time to 0
860  if (clips.empty() && effects.empty()) {
861  min_time = 0.0;
862  max_time = 0.0;
863  }
864 }
865 
866 // Sort clips by position on the timeline
867 void Timeline::sort_clips()
868 {
869  // Get lock (prevent getting frames while this happens)
870  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
871 
872  // Debug output
874  "Timeline::SortClips",
875  "clips.size()", clips.size());
876 
877  // sort clips
878  clips.sort(CompareClips());
879 
880  // calculate max timeline duration
881  calculate_max_duration();
882 }
883 
884 // Sort effects by position on the timeline
885 void Timeline::sort_effects()
886 {
887  // Get lock (prevent getting frames while this happens)
888  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
889 
890  // sort clips
891  effects.sort(CompareEffects());
892 
893  // calculate max timeline duration
894  calculate_max_duration();
895 }
896 
897 // Clear all clips from timeline
899 {
900  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Clear");
901 
902  // Get lock (prevent getting frames while this happens)
903  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
904 
905  // Close all open clips
906  for (auto clip : clips)
907  {
908  update_open_clips(clip, false);
909 
910  // Delete clip object (if timeline allocated it)
911  bool allocated = allocated_clips.count(clip);
912  if (allocated) {
913  delete clip;
914  }
915  }
916  // Clear all clips
917  clips.clear();
918  allocated_clips.clear();
919 
920  // Close all effects
921  for (auto effect : effects)
922  {
923  // Delete effect object (if timeline allocated it)
924  bool allocated = allocated_effects.count(effect);
925  if (allocated) {
926  delete effect;
927  }
928  }
929  // Clear all effects
930  effects.clear();
931  allocated_effects.clear();
932 
933  // Delete all FrameMappers
934  for (auto mapper : allocated_frame_mappers)
935  {
936  mapper->Reader(NULL);
937  mapper->Close();
938  delete mapper;
939  }
940  allocated_frame_mappers.clear();
941 }
942 
943 // Close the reader (and any resources it was consuming)
945 {
946  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Close");
947 
948  // Get lock (prevent getting frames while this happens)
949  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
950 
951  // Close all open clips
952  for (auto clip : clips)
953  {
954  // Open or Close this clip, based on if it's intersecting or not
955  update_open_clips(clip, false);
956  }
957 
958  // Mark timeline as closed
959  is_open = false;
960 
961  // Clear all cache (deep clear, including nested Readers)
962  ClearAllCache(true);
963 }
964 
965 // Open the reader (and start consuming resources)
967 {
968  is_open = true;
969 }
970 
971 // Compare 2 floating point numbers for equality
972 bool Timeline::isEqual(double a, double b)
973 {
974  return fabs(a - b) < 0.000001;
975 }
976 
977 // Get an openshot::Frame object for a specific frame number of this reader.
978 std::shared_ptr<Frame> Timeline::GetFrame(int64_t requested_frame)
979 {
980  // Adjust out of bounds frame number
981  if (requested_frame < 1)
982  requested_frame = 1;
983  const int64_t max_frame = GetMaxFrame();
984  const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
985 
986  // Check cache
987  std::shared_ptr<Frame> frame;
988  if (!past_timeline_end)
989  frame = final_cache->GetFrame(requested_frame);
990  if (frame) {
991  // Debug output
993  "Timeline::GetFrame (Cached frame found)",
994  "requested_frame", requested_frame);
995 
996  // Return cached frame
997  return frame;
998  }
999  else
1000  {
1001  // Prevent async calls to the following code
1002  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1003 
1004  // Check cache 2nd time
1005  std::shared_ptr<Frame> frame;
1006  if (!past_timeline_end)
1007  frame = final_cache->GetFrame(requested_frame);
1008  if (frame) {
1009  // Debug output
1011  "Timeline::GetFrame (Cached frame found on 2nd check)",
1012  "requested_frame", requested_frame);
1013 
1014  // Return cached frame
1015  return frame;
1016  } else {
1017  // Get a list of clips that intersect with the requested section of timeline
1018  // This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
1019  std::vector<Clip *> nearby_clips;
1020  nearby_clips = find_intersecting_clips(requested_frame, 1, true);
1021 
1022  // Debug output
1024  "Timeline::GetFrame (processing frame)",
1025  "requested_frame", requested_frame,
1026  "omp_get_thread_num()", omp_get_thread_num());
1027 
1028  // Init some basic properties about this frame
1029  int samples_in_frame = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
1030 
1031  // Create blank frame (which will become the requested frame)
1032  std::shared_ptr<Frame> new_frame(std::make_shared<Frame>(requested_frame, preview_width, preview_height, "#000000", samples_in_frame, info.channels));
1033  new_frame->AddAudioSilence(samples_in_frame);
1034  new_frame->SampleRate(info.sample_rate);
1035  new_frame->ChannelsLayout(info.channel_layout);
1036 
1037  // Debug output
1039  "Timeline::GetFrame (Adding solid color)",
1040  "requested_frame", requested_frame,
1041  "info.width", info.width,
1042  "info.height", info.height);
1043 
1044  // Add Background Color to 1st layer (if animated or not black)
1045  if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) ||
1046  (color.red.GetValue(requested_frame) != 0.0 || color.green.GetValue(requested_frame) != 0.0 ||
1047  color.blue.GetValue(requested_frame) != 0.0))
1048  new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame));
1049 
1050  // Debug output
1052  "Timeline::GetFrame (Loop through clips)",
1053  "requested_frame", requested_frame,
1054  "clips.size()", clips.size(),
1055  "nearby_clips.size()", nearby_clips.size());
1056 
1057  // Precompute per-clip timing for this requested frame
1058  struct ClipInfo {
1059  Clip* clip;
1060  int64_t start_pos;
1061  int64_t end_pos;
1062  int64_t start_frame;
1063  int64_t frame_number;
1064  bool intersects;
1065  };
1066  std::vector<ClipInfo> clip_infos;
1067  clip_infos.reserve(nearby_clips.size());
1068  const double fpsD = info.fps.ToDouble();
1069 
1070  for (auto clip : nearby_clips) {
1071  int64_t start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1072  int64_t end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1073  bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1074  int64_t start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1075  int64_t frame_number = requested_frame - start_pos + start_frame;
1076  clip_infos.push_back({clip, start_pos, end_pos, start_frame, frame_number, intersects});
1077  }
1078 
1079  // Determine top clip per layer (linear, no nested loop)
1080  std::unordered_map<int, int64_t> top_start_for_layer;
1081  std::unordered_map<int, Clip*> top_clip_for_layer;
1082  for (const auto& ci : clip_infos) {
1083  if (!ci.intersects) continue;
1084  const int layer = ci.clip->Layer();
1085  auto it = top_start_for_layer.find(layer);
1086  if (it == top_start_for_layer.end() || ci.start_pos > it->second) {
1087  top_start_for_layer[layer] = ci.start_pos; // strictly greater to match prior logic
1088  top_clip_for_layer[layer] = ci.clip;
1089  }
1090  }
1091 
1092  // Compute max_volume across all overlapping clips once
1093  float max_volume_sum = 0.0f;
1094  for (const auto& ci : clip_infos) {
1095  if (!ci.intersects) continue;
1096  if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1097  ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1098  max_volume_sum += static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1099  }
1100  }
1101 
1102  // Compose intersecting clips in a single pass
1103  for (const auto& ci : clip_infos) {
1104  // Debug output
1106  "Timeline::GetFrame (Does clip intersect)",
1107  "requested_frame", requested_frame,
1108  "clip->Position()", ci.clip->Position(),
1109  "clip->Duration()", ci.clip->Duration(),
1110  "does_clip_intersect", ci.intersects);
1111 
1112  // Clip is visible
1113  if (ci.intersects) {
1114  // Is this the top clip on its layer?
1115  bool is_top_clip = false;
1116  const int layer = ci.clip->Layer();
1117  auto top_it = top_clip_for_layer.find(layer);
1118  if (top_it != top_clip_for_layer.end())
1119  is_top_clip = (top_it->second == ci.clip);
1120 
1121  // Determine the frame needed for this clip (based on the position on the timeline)
1122  int64_t clip_frame_number = ci.frame_number;
1123 
1124  // Debug output
1126  "Timeline::GetFrame (Calculate clip's frame #)",
1127  "clip->Position()", ci.clip->Position(),
1128  "clip->Start()", ci.clip->Start(),
1129  "info.fps.ToFloat()", info.fps.ToFloat(),
1130  "clip_frame_number", clip_frame_number);
1131 
1132  // Add clip's frame as layer
1133  add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1134 
1135  } else {
1136  // Debug output
1138  "Timeline::GetFrame (clip does not intersect)",
1139  "requested_frame", requested_frame,
1140  "does_clip_intersect", ci.intersects);
1141  }
1142 
1143  } // end clip loop
1144 
1145  // Debug output
1147  "Timeline::GetFrame (Add frame to cache)",
1148  "requested_frame", requested_frame,
1149  "info.width", info.width,
1150  "info.height", info.height);
1151 
1152  // Set frame # on mapped frame
1153  new_frame->SetFrameNumber(requested_frame);
1154 
1155  // Add final frame to cache (only for valid timeline range)
1156  if (!past_timeline_end)
1157  final_cache->Add(new_frame);
1158  // Return frame (or blank frame)
1159  return new_frame;
1160  }
1161  }
1162 }
1163 
1164 
1165 // Find intersecting clips (or non intersecting clips)
1166 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include)
1167 {
1168  // Find matching clips
1169  std::vector<Clip*> matching_clips;
1170 
1171  // Calculate time of frame
1172  const int64_t min_requested_frame = requested_frame;
1173  const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1174 
1175  // Find Clips at this time
1176  matching_clips.reserve(clips.size());
1177  const double fpsD = info.fps.ToDouble();
1178  for (auto clip : clips)
1179  {
1180  // Does clip intersect the current requested time
1181  int64_t clip_start_position = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1182  int64_t clip_end_position = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1183 
1184  bool does_clip_intersect =
1185  (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1186  (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1187 
1188  // Debug output
1190  "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1191  "requested_frame", requested_frame,
1192  "min_requested_frame", min_requested_frame,
1193  "max_requested_frame", max_requested_frame,
1194  "clip->Position()", clip->Position(),
1195  "does_clip_intersect", does_clip_intersect);
1196 
1197  // Open (or schedule for closing) this clip, based on if it's intersecting or not
1198  update_open_clips(clip, does_clip_intersect);
1199 
1200  // Clip is visible
1201  if (does_clip_intersect && include)
1202  // Add the intersecting clip
1203  matching_clips.push_back(clip);
1204 
1205  else if (!does_clip_intersect && !include)
1206  // Add the non-intersecting clip
1207  matching_clips.push_back(clip);
1208 
1209  } // end clip loop
1210 
1211  // return list
1212  return matching_clips;
1213 }
1214 
1215 // Set the cache object used by this reader
1216 void Timeline::SetCache(CacheBase* new_cache) {
1217  // Get lock (prevent getting frames while this happens)
1218  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1219 
1220  // Destroy previous cache (if managed by timeline)
1221  if (managed_cache && final_cache) {
1222  delete final_cache;
1223  final_cache = NULL;
1224  managed_cache = false;
1225  }
1226 
1227  // Set new cache
1228  final_cache = new_cache;
1229 }
1230 
1231 // Generate JSON string of this object
1232 std::string Timeline::Json() const {
1233 
1234  // Return formatted string
1235  return JsonValue().toStyledString();
1236 }
1237 
1238 // Generate Json::Value for this object
1239 Json::Value Timeline::JsonValue() const {
1240 
1241  // Create root json object
1242  Json::Value root = ReaderBase::JsonValue(); // get parent properties
1243  root["type"] = "Timeline";
1244  root["viewport_scale"] = viewport_scale.JsonValue();
1245  root["viewport_x"] = viewport_x.JsonValue();
1246  root["viewport_y"] = viewport_y.JsonValue();
1247  root["color"] = color.JsonValue();
1248  root["path"] = path;
1249 
1250  // Add array of clips
1251  root["clips"] = Json::Value(Json::arrayValue);
1252 
1253  // Find Clips at this time
1254  for (const auto existing_clip : clips)
1255  {
1256  root["clips"].append(existing_clip->JsonValue());
1257  }
1258 
1259  // Add array of effects
1260  root["effects"] = Json::Value(Json::arrayValue);
1261 
1262  // loop through effects
1263  for (const auto existing_effect: effects)
1264  {
1265  root["effects"].append(existing_effect->JsonValue());
1266  }
1267 
1268  // return JsonValue
1269  return root;
1270 }
1271 
1272 // Load JSON string into this object
1273 void Timeline::SetJson(const std::string value) {
1274 
1275  // Get lock (prevent getting frames while this happens)
1276  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1277 
1278  // Parse JSON string into JSON objects
1279  try
1280  {
1281  const Json::Value root = openshot::stringToJson(value);
1282  // Set all values that match
1283  SetJsonValue(root);
1284  }
1285  catch (const std::exception& e)
1286  {
1287  // Error parsing JSON (or missing keys)
1288  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1289  }
1290 }
1291 
1292 // Load Json::Value into this object
1293 void Timeline::SetJsonValue(const Json::Value root) {
1294 
1295  // Get lock (prevent getting frames while this happens)
1296  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1297 
1298  // Close timeline before we do anything (this closes all clips)
1299  bool was_open = is_open;
1300  Close();
1301 
1302  // Set parent data
1304 
1305  // Set data from Json (if key is found)
1306  if (!root["path"].isNull())
1307  path = root["path"].asString();
1308 
1309  if (!root["clips"].isNull()) {
1310  // Clear existing clips
1311  clips.clear();
1312 
1313  // loop through clips
1314  for (const Json::Value existing_clip : root["clips"]) {
1315  // Skip NULL nodes
1316  if (existing_clip.isNull()) {
1317  continue;
1318  }
1319 
1320  // Create Clip
1321  Clip *c = new Clip();
1322 
1323  // Keep track of allocated clip objects
1324  allocated_clips.insert(c);
1325 
1326  // When a clip is attached to an object, it searches for the object
1327  // on it's parent timeline. Setting the parent timeline of the clip here
1328  // allows attaching it to an object when exporting the project (because)
1329  // the exporter script initializes the clip and it's effects
1330  // before setting its parent timeline.
1331  c->ParentTimeline(this);
1332 
1333  // Load Json into Clip
1334  c->SetJsonValue(existing_clip);
1335 
1336  // Add Clip to Timeline
1337  AddClip(c);
1338  }
1339  }
1340 
1341  if (!root["effects"].isNull()) {
1342  // Clear existing effects
1343  effects.clear();
1344 
1345  // loop through effects
1346  for (const Json::Value existing_effect :root["effects"]) {
1347  // Skip NULL nodes
1348  if (existing_effect.isNull()) {
1349  continue;
1350  }
1351 
1352  // Create Effect
1353  EffectBase *e = NULL;
1354 
1355  if (!existing_effect["type"].isNull()) {
1356  // Create instance of effect
1357  if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) {
1358 
1359  // Keep track of allocated effect objects
1360  allocated_effects.insert(e);
1361 
1362  // Load Json into Effect
1363  e->SetJsonValue(existing_effect);
1364 
1365  // Add Effect to Timeline
1366  AddEffect(e);
1367  }
1368  }
1369  }
1370  }
1371 
1372  if (!root["duration"].isNull()) {
1373  // Update duration of timeline
1374  info.duration = root["duration"].asDouble();
1376  }
1377 
1378  // Update preview settings
1381 
1382  // Resort (and recalculate min/max duration)
1383  sort_clips();
1384  sort_effects();
1385 
1386  // Re-open if needed
1387  if (was_open)
1388  Open();
1389 
1390  // Timeline content changed: notify cache clients to rescan active window.
1391  BumpCacheEpoch();
1392 }
1393 
1394 // Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
1395 void Timeline::ApplyJsonDiff(std::string value) {
1396 
1397  // Get lock (prevent getting frames while this happens)
1398  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1399 
1400  // Parse JSON string into JSON objects
1401  try
1402  {
1403  const Json::Value root = openshot::stringToJson(value);
1404  const uint64_t initial_cache_epoch = CacheEpoch();
1405  // Process the JSON change array, loop through each item
1406  for (const Json::Value change : root) {
1407  std::string change_key = change["key"][(uint)0].asString();
1408 
1409  // Process each type of change
1410  if (change_key == "clips")
1411  // Apply to CLIPS
1412  apply_json_to_clips(change);
1413 
1414  else if (change_key == "effects")
1415  // Apply to EFFECTS
1416  apply_json_to_effects(change);
1417 
1418  else
1419  // Apply to TIMELINE
1420  apply_json_to_timeline(change);
1421 
1422  }
1423 
1424  // Timeline content changed: notify cache clients to rescan active window.
1425  if (!root.empty() && CacheEpoch() == initial_cache_epoch) {
1426  BumpCacheEpoch();
1427  }
1428  }
1429  catch (const std::exception& e)
1430  {
1431  // Error parsing JSON (or missing keys)
1432  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1433  }
1434 }
1435 
1436 void Timeline::BumpCacheEpoch() {
1437  cache_epoch.fetch_add(1, std::memory_order_relaxed);
1438 }
1439 
1440 void Timeline::InvalidateCacheForClip(const Clip* clip) {
1441  if (!clip || !final_cache) {
1442  return;
1443  }
1444 
1445  const double fpsD = info.fps.ToDouble();
1446  const int64_t starting_frame = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1447  const int64_t ending_frame = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1448  final_cache->Remove(starting_frame - 8, ending_frame + 8);
1449  BumpCacheEpoch();
1450 }
1451 
1452 // Apply JSON diff to clips
1453 void Timeline::apply_json_to_clips(Json::Value change) {
1454 
1455  // Get key and type of change
1456  std::string change_type = change["type"].asString();
1457  std::string clip_id = "";
1458  Clip *existing_clip = NULL;
1459 
1460  // Find id of clip (if any)
1461  for (auto key_part : change["key"]) {
1462  // Get each change
1463  if (key_part.isObject()) {
1464  // Check for id
1465  if (!key_part["id"].isNull()) {
1466  // Set the id
1467  clip_id = key_part["id"].asString();
1468 
1469  // Find matching clip in timeline (if any)
1470  for (auto c : clips)
1471  {
1472  if (c->Id() == clip_id) {
1473  existing_clip = c;
1474  break; // clip found, exit loop
1475  }
1476  }
1477  break; // id found, exit loop
1478  }
1479  }
1480  }
1481 
1482  // Check for a more specific key (targetting this clip's effects)
1483  // For example: ["clips", {"id:123}, "effects", {"id":432}]
1484  if (existing_clip && change["key"].size() == 4 && change["key"][2] == "effects")
1485  {
1486  // This change is actually targetting a specific effect under a clip (and not the clip)
1487  Json::Value key_part = change["key"][3];
1488 
1489  if (key_part.isObject()) {
1490  // Check for id
1491  if (!key_part["id"].isNull())
1492  {
1493  // Set the id
1494  std::string effect_id = key_part["id"].asString();
1495 
1496  // Find matching effect in timeline (if any)
1497  std::list<EffectBase*> effect_list = existing_clip->Effects();
1498  for (auto e : effect_list)
1499  {
1500  if (e->Id() == effect_id) {
1501  // Apply the change to the effect directly
1502  apply_json_to_effects(change, e);
1503 
1504  // Effect-only diffs must clear the owning clip cache.
1505  if (existing_clip->GetCache()) {
1506  existing_clip->GetCache()->Clear();
1507  }
1508 
1509  // Calculate start and end frames that this impacts, and remove those frames from the cache
1510  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1511  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1512  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1513 
1514  return; // effect found, don't update clip
1515  }
1516  }
1517  }
1518  }
1519  }
1520 
1521  // Determine type of change operation
1522  if (change_type == "insert") {
1523 
1524  // Create clip
1525  Clip *clip = new Clip();
1526 
1527  // Keep track of allocated clip objects
1528  allocated_clips.insert(clip);
1529 
1530  // Match full timeline JSON loading: parent timeline must be available
1531  // before clip JSON can inflate nested readers/effects.
1532  clip->ParentTimeline(this);
1533 
1534  // Set properties of clip from JSON
1535  clip->SetJsonValue(change["value"]);
1536 
1537  // Add clip to timeline
1538  AddClip(clip);
1539 
1540  } else if (change_type == "update") {
1541 
1542  // Update existing clip
1543  if (existing_clip) {
1544  // Calculate start and end frames prior to the update
1545  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1546  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1547 
1548  // Update clip properties from JSON
1549  existing_clip->SetJsonValue(change["value"]);
1550 
1551  // Calculate new start and end frames after the update
1552  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1553  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1554 
1555  // Remove both the old and new ranges from the timeline cache
1556  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1557  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1558 
1559  // Apply framemapper (or update existing framemapper)
1560  if (auto_map_clips) {
1561  apply_mapper_to_clip(existing_clip);
1562  }
1563  }
1564 
1565  } else if (change_type == "delete") {
1566 
1567  // Remove existing clip
1568  if (existing_clip) {
1569  // Remove clip from timeline
1570  RemoveClip(existing_clip);
1571 
1572  // Calculate start and end frames that this impacts, and remove those frames from the cache
1573  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1574  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1575  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1576  }
1577 
1578  }
1579 
1580  // Re-Sort Clips (since they likely changed)
1581  sort_clips();
1582 }
1583 
1584 // Apply JSON diff to effects
1585 void Timeline::apply_json_to_effects(Json::Value change) {
1586 
1587  // Get key and type of change
1588  std::string change_type = change["type"].asString();
1589  EffectBase *existing_effect = NULL;
1590 
1591  // Find id of an effect (if any)
1592  for (auto key_part : change["key"]) {
1593 
1594  if (key_part.isObject()) {
1595  // Check for id
1596  if (!key_part["id"].isNull())
1597  {
1598  // Set the id
1599  std::string effect_id = key_part["id"].asString();
1600 
1601  // Find matching effect in timeline (if any)
1602  for (auto e : effects)
1603  {
1604  if (e->Id() == effect_id) {
1605  existing_effect = e;
1606  break; // effect found, exit loop
1607  }
1608  }
1609  break; // id found, exit loop
1610  }
1611  }
1612  }
1613 
1614  // Now that we found the effect, apply the change to it
1615  if (existing_effect || change_type == "insert") {
1616  // Apply change to effect
1617  apply_json_to_effects(change, existing_effect);
1618  }
1619 }
1620 
1621 // Apply JSON diff to effects (if you already know which effect needs to be updated)
1622 void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_effect) {
1623 
1624  // Get key and type of change
1625  std::string change_type = change["type"].asString();
1626 
1627  // Calculate start and end frames that this impacts, and remove those frames from the cache
1628  if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1629  int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1630  int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1631  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1632  }
1633 
1634  // Determine type of change operation
1635  if (change_type == "insert") {
1636 
1637  // Determine type of effect
1638  std::string effect_type = change["value"]["type"].asString();
1639 
1640  // Create Effect
1641  EffectBase *e = NULL;
1642 
1643  // Init the matching effect object
1644  if ( (e = EffectInfo().CreateEffect(effect_type)) ) {
1645 
1646  // Keep track of allocated effect objects
1647  allocated_effects.insert(e);
1648 
1649  // Load Json into Effect
1650  e->SetJsonValue(change["value"]);
1651 
1652  // Add Effect to Timeline
1653  AddEffect(e);
1654  }
1655 
1656  } else if (change_type == "update") {
1657 
1658  // Update existing effect
1659  if (existing_effect) {
1660 
1661  // Calculate start and end frames that this impacts, and remove those frames from the cache
1662  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1663  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1664  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1665 
1666  // Update effect properties from JSON
1667  existing_effect->SetJsonValue(change["value"]);
1668  }
1669 
1670  } else if (change_type == "delete") {
1671 
1672  // Remove existing effect
1673  if (existing_effect) {
1674 
1675  // Calculate start and end frames that this impacts, and remove those frames from the cache
1676  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1677  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1678  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1679 
1680  // Remove effect from timeline
1681  RemoveEffect(existing_effect);
1682  }
1683 
1684  }
1685 
1686  // Re-Sort Effects (since they likely changed)
1687  sort_effects();
1688 }
1689 
1690 // Apply JSON diff to timeline properties
1691 void Timeline::apply_json_to_timeline(Json::Value change) {
1692  bool cache_dirty = true;
1693 
1694  // Get key and type of change
1695  std::string change_type = change["type"].asString();
1696  std::string root_key = change["key"][(uint)0].asString();
1697  std::string sub_key = "";
1698  if (change["key"].size() >= 2)
1699  sub_key = change["key"][(uint)1].asString();
1700 
1701  // Determine type of change operation
1702  if (change_type == "insert" || change_type == "update") {
1703 
1704  // INSERT / UPDATE
1705  // Check for valid property
1706  if (root_key == "color")
1707  // Set color
1708  color.SetJsonValue(change["value"]);
1709  else if (root_key == "viewport_scale")
1710  // Set viewport scale
1711  viewport_scale.SetJsonValue(change["value"]);
1712  else if (root_key == "viewport_x")
1713  // Set viewport x offset
1714  viewport_x.SetJsonValue(change["value"]);
1715  else if (root_key == "viewport_y")
1716  // Set viewport y offset
1717  viewport_y.SetJsonValue(change["value"]);
1718  else if (root_key == "duration") {
1719  // Update duration of timeline
1720  info.duration = change["value"].asDouble();
1722 
1723  // We don't want to clear cache for duration adjustments
1724  cache_dirty = false;
1725  }
1726  else if (root_key == "width") {
1727  // Set width
1728  info.width = change["value"].asInt();
1730  }
1731  else if (root_key == "height") {
1732  // Set height
1733  info.height = change["value"].asInt();
1735  }
1736  else if (root_key == "fps" && sub_key == "" && change["value"].isObject()) {
1737  // Set fps fraction
1738  if (!change["value"]["num"].isNull())
1739  info.fps.num = change["value"]["num"].asInt();
1740  if (!change["value"]["den"].isNull())
1741  info.fps.den = change["value"]["den"].asInt();
1742  }
1743  else if (root_key == "fps" && sub_key == "num")
1744  // Set fps.num
1745  info.fps.num = change["value"].asInt();
1746  else if (root_key == "fps" && sub_key == "den")
1747  // Set fps.den
1748  info.fps.den = change["value"].asInt();
1749  else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) {
1750  // Set display_ratio fraction
1751  if (!change["value"]["num"].isNull())
1752  info.display_ratio.num = change["value"]["num"].asInt();
1753  if (!change["value"]["den"].isNull())
1754  info.display_ratio.den = change["value"]["den"].asInt();
1755  }
1756  else if (root_key == "display_ratio" && sub_key == "num")
1757  // Set display_ratio.num
1758  info.display_ratio.num = change["value"].asInt();
1759  else if (root_key == "display_ratio" && sub_key == "den")
1760  // Set display_ratio.den
1761  info.display_ratio.den = change["value"].asInt();
1762  else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) {
1763  // Set pixel_ratio fraction
1764  if (!change["value"]["num"].isNull())
1765  info.pixel_ratio.num = change["value"]["num"].asInt();
1766  if (!change["value"]["den"].isNull())
1767  info.pixel_ratio.den = change["value"]["den"].asInt();
1768  }
1769  else if (root_key == "pixel_ratio" && sub_key == "num")
1770  // Set pixel_ratio.num
1771  info.pixel_ratio.num = change["value"].asInt();
1772  else if (root_key == "pixel_ratio" && sub_key == "den")
1773  // Set pixel_ratio.den
1774  info.pixel_ratio.den = change["value"].asInt();
1775 
1776  else if (root_key == "sample_rate")
1777  // Set sample rate
1778  info.sample_rate = change["value"].asInt();
1779  else if (root_key == "channels")
1780  // Set channels
1781  info.channels = change["value"].asInt();
1782  else if (root_key == "channel_layout")
1783  // Set channel layout
1784  info.channel_layout = (ChannelLayout) change["value"].asInt();
1785  else
1786  // Error parsing JSON (or missing keys)
1787  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1788 
1789 
1790  } else if (change["type"].asString() == "delete") {
1791 
1792  // DELETE / RESET
1793  // Reset the following properties (since we can't delete them)
1794  if (root_key == "color") {
1795  color = Color();
1796  color.red = Keyframe(0.0);
1797  color.green = Keyframe(0.0);
1798  color.blue = Keyframe(0.0);
1799  }
1800  else if (root_key == "viewport_scale")
1801  viewport_scale = Keyframe(1.0);
1802  else if (root_key == "viewport_x")
1803  viewport_x = Keyframe(0.0);
1804  else if (root_key == "viewport_y")
1805  viewport_y = Keyframe(0.0);
1806  else
1807  // Error parsing JSON (or missing keys)
1808  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1809 
1810  }
1811 
1812  if (cache_dirty) {
1813  // Clear entire cache
1814  ClearAllCache();
1815  }
1816 }
1817 
1818 // Clear all caches
1819 void Timeline::ClearAllCache(bool deep) {
1820  // Get lock (prevent getting frames while this happens)
1821  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
1822 
1823  // Clear primary cache
1824  if (final_cache) {
1825  final_cache->Clear();
1826  }
1827 
1828  // Loop through all clips
1829  try {
1830  for (const auto clip : clips) {
1831  // Clear cache on clip and reader if present
1832  if (clip->Reader()) {
1833  if (auto rc = clip->Reader()->GetCache())
1834  rc->Clear();
1835 
1836  // Clear nested Reader (if deep clear requested)
1837  if (deep && clip->Reader()->Name() == "FrameMapper") {
1838  FrameMapper *nested_reader = static_cast<FrameMapper *>(clip->Reader());
1839  if (nested_reader->Reader()) {
1840  if (auto nc = nested_reader->Reader()->GetCache())
1841  nc->Clear();
1842  }
1843  }
1844  }
1845 
1846  // Clear clip cache
1847  if (auto cc = clip->GetCache())
1848  cc->Clear();
1849  }
1850  } catch (const ReaderClosed & e) {
1851  // ...
1852  }
1853 
1854  // Cache content changed: notify cache clients to rebuild their window baseline.
1855  BumpCacheEpoch();
1856 }
1857 
1858 // Set Max Image Size (used for performance optimization). Convenience function for setting
1859 // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT.
1860 void Timeline::SetMaxSize(int width, int height) {
1861  // Maintain aspect ratio regardless of what size is passed in
1862  QSize display_ratio_size = QSize(info.width, info.height);
1863  QSize proposed_size = QSize(std::min(width, info.width), std::min(height, info.height));
1864 
1865  // Scale QSize up to proposed size
1866  display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1867 
1868  // Update preview settings
1869  preview_width = display_ratio_size.width();
1870  preview_height = display_ratio_size.height();
1871 }
1872 
1873 // Resolve equal-power audio gains from transition placement relative to the clip edges.
1874 std::pair<float, float> Timeline::ResolveTransitionAudioGains(Clip* source_clip, int64_t timeline_frame_number, bool is_top_clip) const
1875 {
1876  constexpr double half_pi = 1.57079632679489661923;
1877 
1878  if (!source_clip)
1879  return {1.0f, 1.0f};
1880 
1881  const double fpsD = info.fps.ToDouble();
1882  Mask* active_mask = nullptr;
1883  int64_t effect_start_position = 0;
1884  int64_t effect_end_position = 0;
1885 
1886  // Find the single active transition on this layer that requested overlapping audio fades.
1887  for (auto effect : effects) {
1888  if (effect->Layer() != source_clip->Layer())
1889  continue;
1890 
1891  auto* mask = dynamic_cast<Mask*>(effect);
1892  if (!mask || !mask->fade_audio_hint)
1893  continue;
1894 
1895  const int64_t start_pos = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
1896  const int64_t end_pos = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1897  if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1898  continue;
1899 
1900  if (active_mask)
1901  return {1.0f, 1.0f};
1902 
1903  active_mask = mask;
1904  effect_start_position = start_pos;
1905  effect_end_position = end_pos;
1906  }
1907 
1908  if (!active_mask)
1909  return {1.0f, 1.0f};
1910 
1911  struct AudibleClipInfo {
1912  Clip* clip;
1913  int64_t start_pos;
1914  int64_t end_pos;
1915  };
1916 
1917  std::vector<AudibleClipInfo> audible_clips;
1918  audible_clips.reserve(2);
1919 
1920  // Collect the audible clips covered by this transition on the current layer.
1921  for (auto clip : clips) {
1922  if (clip->Layer() != source_clip->Layer())
1923  continue;
1924  if (!clip->Reader() || !clip->Reader()->info.has_audio)
1925  continue;
1926 
1927  const int64_t clip_start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1928  const int64_t clip_end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1929  if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1930  continue;
1931 
1932  const int64_t clip_start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1933  const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1934  if (clip->has_audio.GetInt(clip_frame_number) == 0)
1935  continue;
1936 
1937  audible_clips.push_back({clip, clip_start_pos, clip_end_pos});
1938  if (audible_clips.size() > 2)
1939  return {1.0f, 1.0f};
1940  }
1941 
1942  if (audible_clips.empty())
1943  return {1.0f, 1.0f};
1944 
1945  // Skip clips that are not actually participating in this transition audio decision.
1946  const auto source_it = std::find_if(
1947  audible_clips.begin(),
1948  audible_clips.end(),
1949  [source_clip](const AudibleClipInfo& info) {
1950  return info.clip == source_clip;
1951  });
1952  if (source_it == audible_clips.end())
1953  return {1.0f, 1.0f};
1954 
1955  // Keep the current top/non-top clip routing intact when two clips overlap.
1956  if (audible_clips.size() == 2) {
1957  auto top_it = std::max_element(
1958  audible_clips.begin(),
1959  audible_clips.end(),
1960  [](const AudibleClipInfo& lhs, const AudibleClipInfo& rhs) {
1961  if (lhs.start_pos != rhs.start_pos)
1962  return lhs.start_pos < rhs.start_pos;
1963  return std::less<Clip*>()(lhs.clip, rhs.clip);
1964  });
1965  if ((is_top_clip && source_clip != top_it->clip) || (!is_top_clip && source_clip == top_it->clip))
1966  return {1.0f, 1.0f};
1967  }
1968 
1969  // Infer fade direction from which transition edge is closer to this clip.
1970  const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1971  const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1972  const bool clip_fades_in = left_distance <= right_distance;
1973 
1974  // Evaluate the current frame and previous frame so Timeline can preserve per-frame gain ramps.
1975  const auto compute_gain = [&](int64_t frame_number) -> float {
1976  if (effect_end_position <= effect_start_position)
1977  return 1.0f;
1978 
1979  const double span = static_cast<double>(effect_end_position - effect_start_position);
1980  double t = static_cast<double>(frame_number - effect_start_position) / span;
1981  if (t < 0.0)
1982  t = 0.0;
1983  else if (t > 1.0)
1984  t = 1.0;
1985 
1986  return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
1987  };
1988 
1989  return {compute_gain(timeline_frame_number - 1), compute_gain(timeline_frame_number)};
1990 }
Header file for CacheBase class.
Header file for CacheDisk class.
Header file for CacheMemory class.
Header file for CrashHandler class.
Header file for all Exception classes.
Header file for the FrameMapper class.
Header file for Mask class.
#define OPEN_MP_NUM_PROCESSORS
Header file for Timeline class.
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:35
virtual void Clear()=0
Clear the cache of all frames.
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
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
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
Get a frame from the cache.
This class is a memory-based cache manager for Frame objects.
Definition: CacheMemory.h:29
void Clear()
Clear the cache of all frames.
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:88
float Duration() const
Get the length of this clip (in seconds)
Definition: ClipBase.h:90
std::string Id() const
Get the Id of this clip object.
Definition: ClipBase.h:85
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
Definition: ClipBase.h:87
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ClipBase.cpp:80
float Position() const
Get position on timeline (in seconds)
Definition: ClipBase.h:86
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Definition: ClipBase.h:91
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
std::list< openshot::EffectBase * > Effects()
Return the list of effects on the timeline.
Definition: Clip.h:256
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
Definition: Clip.h:189
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
Definition: Clip.h:364
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:307
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Clip.cpp:1050
openshot::Keyframe has_audio
An optional override to determine if this clip has audio (-1=undefined, 0=no, 1=yes)
Definition: Clip.h:368
openshot::Keyframe volume
Curve representing the volume (0 to 1)
Definition: Clip.h:348
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
Definition: Clip.h:365
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition: Clip.cpp:343
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
Definition: Clip.h:217
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
Definition: Color.cpp:47
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: Color.cpp:117
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:86
static CrashHandler * Instance()
This abstract class is the base class, used by all effects in libopenshot.
Definition: EffectBase.h:57
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
This class returns a listing of all effects supported by libopenshot.
Definition: EffectInfo.h:29
EffectBase * CreateEffect(std::string effect_type)
Create an instance of an effect (factory style)
Definition: EffectInfo.cpp:27
This class represents a fraction.
Definition: Fraction.h:30
int num
Numerator for the fraction.
Definition: Fraction.h:32
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Definition: Fraction.cpp:65
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition: Fraction.cpp:78
int den
Denominator for the fraction.
Definition: Fraction.h:33
This class creates a mapping between 2 different frame rates, applying a specific pull-down technique...
Definition: FrameMapper.h:193
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.
ReaderBase * Reader()
Get the current reader.
Definition: FrameMapper.cpp:67
void Close() override
Close the openshot::FrameMapper and internal reader.
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:486
Exception for files that can not be found or opened.
Definition: Exceptions.h:194
Exception for missing JSON Change key.
Definition: Exceptions.h:269
Exception for invalid JSON.
Definition: Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition: KeyFrame.cpp:282
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
int64_t GetCount() const
Get the number of points (i.e. # of points)
Definition: KeyFrame.cpp:424
This class uses the image libraries to apply alpha (or transparency) masks to any frame....
Definition: Mask.h:37
Exception for frames that are out of bounds.
Definition: Exceptions.h:307
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:76
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: ReaderBase.h:79
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: ReaderBase.h:80
Exception when a reader is closed, and a frame is requested.
Definition: Exceptions.h:370
This class is contains settings used by libopenshot (and can be safely toggled at any point)
Definition: Settings.h:26
std::string PATH_OPENSHOT_INSTALL
Definition: Settings.h:123
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: Settings.cpp:43
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
This class represents a timeline.
Definition: Timeline.h:153
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
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Timeline.cpp:1239
openshot::Keyframe viewport_scale
Curve representing the scale of the viewport (0 to 100)
Definition: Timeline.h:335
void ApplyJsonDiff(std::string value)
Apply a special formatted JSON object, which represents a change to the timeline (add,...
Definition: Timeline.cpp:1395
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:443
void AddClip(openshot::Clip *clip)
Add an openshot::Clip to the timeline.
Definition: Timeline.cpp:338
virtual ~Timeline()
Definition: Timeline.cpp:212
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
Definition: Timeline.cpp:456
std::list< std::string > GetTrackedObjectsIds() const
Return the ID's of the tracked objects as a list of strings.
Definition: Timeline.cpp:264
std::string Json() const override
Generate JSON string of this object.
Definition: Timeline.cpp:1232
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition: Timeline.cpp:481
double GetMinTime()
Look up the position/start time of the first timeline element.
Definition: Timeline.cpp:502
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Definition: Timeline.cpp:978
void ApplyMapperToClips()
Apply the timeline's framerate and samplerate to all clips.
Definition: Timeline.cpp:537
openshot::Color color
Background color of timeline canvas.
Definition: Timeline.h:340
std::string GetTrackedObjectValues(std::string id, int64_t frame_number) const
Return the trackedObject's properties as a JSON string.
Definition: Timeline.cpp:280
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
std::shared_ptr< openshot::TrackedObjectBase > GetTrackedObject(std::string id) const
Return tracked object pointer by it's id.
Definition: Timeline.cpp:247
int64_t GetMinFrame()
Look up the start frame number of the first element on the timeline (first frame is 1)
Definition: Timeline.cpp:494
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition: Timeline.cpp:432
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Timeline.cpp:1293
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
Definition: Timeline.cpp:420
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1819
void AddEffect(openshot::EffectBase *effect)
Add an effect to the timeline.
Definition: Timeline.cpp:366
void SetCache(openshot::CacheBase *new_cache)
Definition: Timeline.cpp:1216
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
Definition: Timeline.h:326
void Clear()
Clear all clips, effects, and frame mappers from timeline (and free memory)
Definition: Timeline.cpp:898
openshot::Keyframe viewport_x
Curve representing the x coordinate for the viewport.
Definition: Timeline.h:336
void RemoveClip(openshot::Clip *clip)
Remove an openshot::Clip from the timeline.
Definition: Timeline.cpp:401
void SetMaxSize(int width, int height)
Definition: Timeline.cpp:1860
double GetMaxTime()
Look up the end time of the latest timeline element.
Definition: Timeline.cpp:475
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the timeline.
Definition: Timeline.cpp:382
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
void Open() override
Open the reader (and start consuming resources)
Definition: Timeline.cpp:966
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Timeline.cpp:1273
openshot::Keyframe viewport_y
Curve representing the y coordinate for the viewport.
Definition: Timeline.h:337
void Close() override
Close the timeline reader (and any resources it was consuming)
Definition: Timeline.cpp:944
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
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: ZmqLogger.cpp:35
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
@ PULLDOWN_NONE
Do not apply pull-down techniques, just repeat or skip entire frames.
Definition: FrameMapper.h:46
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
@ VOLUME_MIX_AVERAGE
Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100%.
Definition: Enums.h:70
@ 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
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]
Like CompareClipEndFrames, but for effects.
Definition: Timeline.h:80
This struct contains info about a media file, such as height, width, frames per second,...
Definition: ReaderBase.h:39
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
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
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition: ReaderBase.h:58
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition: ReaderBase.h:52
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::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
Definition: ReaderBase.h:62
bool has_video
Determines if this file has a video stream.
Definition: ReaderBase.h:40
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition: ReaderBase.h:55
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
This struct contains info about the current Timeline clip instance.
Definition: TimelineBase.h:33
bool is_before_clip_keyframes
Is this before clip keyframes are applied.
Definition: TimelineBase.h:35
bool is_top_clip
Is clip on top (if overlapping another clip)
Definition: TimelineBase.h:34