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  // Add clip to list
357  clips.push_back(clip);
358 
359  // Sort clips
360  sort_clips();
361 }
362 
363 // Add an effect to the timeline
365 {
366  // Get lock (prevent getting frames while this happens)
367  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
368 
369  // Assign timeline to effect
370  effect->ParentTimeline(this);
371 
372  // Add effect to list
373  effects.push_back(effect);
374 
375  // Sort effects
376  sort_effects();
377 }
378 
379 // Remove an effect from the timeline
381 {
382  // Get lock (prevent getting frames while this happens)
383  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
384 
385  effects.remove(effect);
386 
387  // Delete effect object (if timeline allocated it)
388  if (allocated_effects.count(effect)) {
389  allocated_effects.erase(effect); // erase before nulling the pointer
390  delete effect;
391  effect = NULL;
392  }
393 
394  // Sort effects
395  sort_effects();
396 }
397 
398 // Remove an openshot::Clip to the timeline
400 {
401  // Get lock (prevent getting frames while this happens)
402  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
403 
404  clips.remove(clip);
405 
406  // Delete clip object (if timeline allocated it)
407  if (allocated_clips.count(clip)) {
408  allocated_clips.erase(clip); // erase before nulling the pointer
409  delete clip;
410  clip = NULL;
411  }
412 
413  // Sort clips
414  sort_clips();
415 }
416 
417 // Look up a clip
418 openshot::Clip* Timeline::GetClip(const std::string& id)
419 {
420  // Find the matching clip (if any)
421  for (const auto& clip : clips) {
422  if (clip->Id() == id) {
423  return clip;
424  }
425  }
426  return nullptr;
427 }
428 
429 // Look up a timeline effect
431 {
432  // Find the matching effect (if any)
433  for (const auto& effect : effects) {
434  if (effect->Id() == id) {
435  return effect;
436  }
437  }
438  return nullptr;
439 }
440 
442 {
443  // Search all clips for matching effect ID
444  for (const auto& clip : clips) {
445  const auto e = clip->GetEffect(id);
446  if (e != nullptr) {
447  return e;
448  }
449  }
450  return nullptr;
451 }
452 
453 // Return the list of effects on all clips
454 std::list<openshot::EffectBase*> Timeline::ClipEffects() const {
455 
456  // Initialize the list
457  std::list<EffectBase*> timelineEffectsList;
458 
459  // Loop through all clips
460  for (const auto& clip : clips) {
461 
462  // Get the clip's list of effects
463  std::list<EffectBase*> clipEffectsList = clip->Effects();
464 
465  // Append the clip's effects to the list
466  timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
467  }
468 
469  return timelineEffectsList;
470 }
471 
472 // Compute the end time of the latest timeline element
474  // Return cached max_time variable (threadsafe)
475  return max_time;
476 }
477 
478 // Compute the highest frame# based on the latest time and FPS
480  const double fps = info.fps.ToDouble();
481  const double t = GetMaxTime();
482  // Inclusive start, exclusive end -> ceil at the end boundary
483  return static_cast<int64_t>(std::ceil(t * fps));
484 }
485 
486 // Compute the first frame# based on the first clip position
488  const double fps = info.fps.ToDouble();
489  const double t = GetMinTime();
490  // Inclusive start -> floor at the start boundary, then 1-index
491  return static_cast<int64_t>(std::floor(t * fps)) + 1;
492 }
493 
494 // Compute the start time of the first timeline clip
496  // Return cached min_time variable (threadsafe)
497  return min_time;
498 }
499 
500 // Apply a FrameMapper to a clip which matches the settings of this timeline
501 void Timeline::apply_mapper_to_clip(Clip* clip)
502 {
503  // Serialize mapper replacement/reconfiguration with active frame generation.
504  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
505 
506  // Determine type of reader
507  ReaderBase* clip_reader = NULL;
508  if (clip->Reader()->Name() == "FrameMapper")
509  {
510  // Get the existing reader
511  clip_reader = (ReaderBase*) clip->Reader();
512 
513  // Update the mapping
514  FrameMapper* clip_mapped_reader = (FrameMapper*) clip_reader;
516 
517  } else {
518 
519  // Create a new FrameMapper to wrap the current reader
521  allocated_frame_mappers.insert(mapper);
522  clip_reader = (ReaderBase*) mapper;
523  }
524 
525  // Update clip reader
526  clip->Reader(clip_reader);
527 }
528 
529 // Apply the timeline's framerate and samplerate to all clips
531 {
532  // Clear all cached frames
533  ClearAllCache();
534 
535  // Loop through all clips
536  for (auto clip : clips)
537  {
538  // Apply framemapper (or update existing framemapper)
539  apply_mapper_to_clip(clip);
540  }
541 }
542 
543 // Calculate time of a frame number, based on a framerate
544 double Timeline::calculate_time(int64_t number, Fraction rate)
545 {
546  // Get float version of fps fraction
547  double raw_fps = rate.ToFloat();
548 
549  // Return the time (in seconds) of this frame
550  return double(number - 1) / raw_fps;
551 }
552 
553 // Apply effects to the source frame (if any)
554 std::shared_ptr<Frame> Timeline::apply_effects(std::shared_ptr<Frame> frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct* options)
555 {
556  // Debug output
558  "Timeline::apply_effects",
559  "frame->number", frame->number,
560  "timeline_frame_number", timeline_frame_number,
561  "layer", layer);
562 
563  // Find Effects at this position and layer
564  for (auto effect : effects)
565  {
566  // Does clip intersect the current requested time
567  const double fpsD = info.fps.ToDouble();
568  int64_t effect_start_position = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
569  int64_t effect_end_position = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
570 
571  bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
572 
573  // Clip is visible
574  if (does_effect_intersect)
575  {
576  // Determine the frame needed for this clip (based on the position on the timeline)
577  int64_t effect_start_frame = static_cast<int64_t>(std::llround(effect->Start() * fpsD)) + 1;
578  int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
579 
580  if (!options->is_top_clip)
581  continue; // skip effect, if overlapped/covered by another clip on same layer
582 
583  if (options->is_before_clip_keyframes != effect->info.apply_before_clip)
584  continue; // skip effect, if this filter does not match
585 
586  // Debug output
588  "Timeline::apply_effects (Process Effect)",
589  "effect_frame_number", effect_frame_number,
590  "does_effect_intersect", does_effect_intersect);
591 
592  // Apply the effect to this frame
593  frame = effect->ProcessFrame(frame, effect_frame_number);
594  }
595 
596  } // end effect loop
597 
598  // Return modified frame
599  return frame;
600 }
601 
602 // Get or generate a blank frame
603 std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame, Clip* clip, int64_t number, openshot::TimelineInfoStruct* options)
604 {
605  std::shared_ptr<Frame> new_frame;
606 
607  // Init some basic properties about this frame
608  int samples_in_frame = Frame::GetSamplesPerFrame(number, info.fps, info.sample_rate, info.channels);
609 
610  try {
611  // Debug output
613  "Timeline::GetOrCreateFrame (from reader)",
614  "number", number,
615  "samples_in_frame", samples_in_frame);
616 
617  // Attempt to get a frame (but this could fail if a reader has just been closed)
618  new_frame = std::shared_ptr<Frame>(clip->GetFrame(background_frame, number, options));
619 
620  // Return real frame
621  return new_frame;
622 
623  } catch (const ReaderClosed & e) {
624  // ...
625  } catch (const OutOfBoundsFrame & e) {
626  // ...
627  }
628 
629  // Debug output
631  "Timeline::GetOrCreateFrame (create blank)",
632  "number", number,
633  "samples_in_frame", samples_in_frame);
634 
635  // Create blank frame
636  return new_frame;
637 }
638 
639 // Process a new layer of video or audio
640 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)
641 {
642  // Create timeline options (with details about this current frame request)
643  TimelineInfoStruct options{};
644  options.is_top_clip = is_top_clip;
645  options.is_before_clip_keyframes = true;
646 
647  // Get the clip's frame, composited on top of the current timeline frame
648  std::shared_ptr<Frame> source_frame;
649  source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
650 
651  // No frame found... so bail
652  if (!source_frame)
653  return;
654 
655  // Debug output
657  "Timeline::add_layer",
658  "new_frame->number", new_frame->number,
659  "clip_frame_number", clip_frame_number);
660 
661  /* COPY AUDIO - with correct volume */
662  if (source_clip->Reader()->info.has_audio) {
663  // Debug output
665  "Timeline::add_layer (Copy Audio)",
666  "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio,
667  "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
668  "info.channels", info.channels,
669  "clip_frame_number", clip_frame_number);
670 
671  if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0)
672  {
673  // Ensure timeline frame matches the source samples once per frame
674  if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
675  new_frame->ResizeAudio(info.channels, source_frame->GetAudioSamplesCount(), info.sample_rate, info.channel_layout);
676  }
677 
678  // Apply transition-driven equal-power audio fades for clips covered by a Mask transition.
679  const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
680 
681  for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
682  {
683  // Get volume from previous frame and this frame
684  float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1);
685  float volume = source_clip->volume.GetValue(clip_frame_number);
686  previous_volume *= transition_audio_gains.first;
687  volume *= transition_audio_gains.second;
688  int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1)
689  int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1)
690 
691  // Apply volume mixing strategy
692  if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) {
693  // Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume
694  previous_volume = previous_volume / max_volume;
695  volume = volume / max_volume;
696  }
697  else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) {
698  // Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will)
699  previous_volume = previous_volume * 0.77;
700  volume = volume * 0.77;
701  }
702 
703  // If channel filter enabled, check for correct channel (and skip non-matching channels)
704  if (channel_filter != -1 && channel_filter != channel)
705  continue; // skip to next channel
706 
707  // If no volume on this frame or previous frame, do nothing
708  if (previous_volume == 0.0 && volume == 0.0)
709  continue; // skip to next channel
710 
711  // If channel mapping disabled, just use the current channel
712  if (channel_mapping == -1)
713  channel_mapping = channel;
714 
715  // Apply ramp to source frame (if needed)
716  if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
717  source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
718 
719  // Copy audio samples (and set initial volume). Mix samples with existing audio samples. The gains are added together, to
720  // be sure to set the gain's correctly, so the sum does not exceed 1.0 (of audio distortion will happen).
721  new_frame->AddAudio(false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
722  }
723  }
724  else
725  // Debug output
727  "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
728  "source_clip->Reader()->info.has_audio",
729  source_clip->Reader()->info.has_audio,
730  "source_frame->GetAudioChannelsCount()",
731  source_frame->GetAudioChannelsCount(),
732  "info.channels", info.channels,
733  "clip_frame_number", clip_frame_number);
734  }
735 
736  // Debug output
738  "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
739  "source_frame->number", source_frame->number,
740  "new_frame->GetImage()->width()", new_frame->GetWidth(),
741  "new_frame->GetImage()->height()", new_frame->GetHeight());
742 }
743 
744 // Update the list of 'opened' clips
745 void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
746 {
747  // Get lock (prevent getting frames while this happens)
748  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
749 
751  "Timeline::update_open_clips (before)",
752  "does_clip_intersect", does_clip_intersect,
753  "closing_clips.size()", closing_clips.size(),
754  "open_clips.size()", open_clips.size());
755 
756  // is clip already in list?
757  bool clip_found = open_clips.count(clip);
758 
759  if (clip_found && !does_clip_intersect)
760  {
761  // Remove clip from 'opened' list, because it's closed now
762  open_clips.erase(clip);
763 
764  // Close clip
765  clip->Close();
766  }
767  else if (!clip_found && does_clip_intersect)
768  {
769  // Add clip to 'opened' list, because it's missing
770  open_clips[clip] = clip;
771 
772  try {
773  // Open the clip
774  clip->Open();
775 
776  } catch (const InvalidFile & e) {
777  // ...
778  }
779  }
780 
781  // Debug output
783  "Timeline::update_open_clips (after)",
784  "does_clip_intersect", does_clip_intersect,
785  "clip_found", clip_found,
786  "closing_clips.size()", closing_clips.size(),
787  "open_clips.size()", open_clips.size());
788 }
789 
790 // Calculate the max and min duration (in seconds) of the timeline, based on all the clips, and cache the value
791 void Timeline::calculate_max_duration() {
792  double last_clip = 0.0;
793  double last_effect = 0.0;
794  double first_clip = std::numeric_limits<double>::max();
795  double first_effect = std::numeric_limits<double>::max();
796 
797  // Find the last and first clip
798  if (!clips.empty()) {
799  // Find the clip with the maximum end frame
800  const auto max_clip = std::max_element(
801  clips.begin(), clips.end(), CompareClipEndFrames());
802  last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
803 
804  // Find the clip with the minimum start position (ignoring layer)
805  const auto min_clip = std::min_element(
806  clips.begin(), clips.end(), [](const openshot::Clip* lhs, const openshot::Clip* rhs) {
807  return lhs->Position() < rhs->Position();
808  });
809  first_clip = (*min_clip)->Position();
810  }
811 
812  // Find the last and first effect
813  if (!effects.empty()) {
814  // Find the effect with the maximum end frame
815  const auto max_effect = std::max_element(
816  effects.begin(), effects.end(), CompareEffectEndFrames());
817  last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
818 
819  // Find the effect with the minimum start position
820  const auto min_effect = std::min_element(
821  effects.begin(), effects.end(), [](const openshot::EffectBase* lhs, const openshot::EffectBase* rhs) {
822  return lhs->Position() < rhs->Position();
823  });
824  first_effect = (*min_effect)->Position();
825  }
826 
827  // Calculate the max and min time
828  max_time = std::max(last_clip, last_effect);
829  min_time = std::min(first_clip, first_effect);
830 
831  // If no clips or effects exist, set min_time to 0
832  if (clips.empty() && effects.empty()) {
833  min_time = 0.0;
834  max_time = 0.0;
835  }
836 }
837 
838 // Sort clips by position on the timeline
839 void Timeline::sort_clips()
840 {
841  // Get lock (prevent getting frames while this happens)
842  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
843 
844  // Debug output
846  "Timeline::SortClips",
847  "clips.size()", clips.size());
848 
849  // sort clips
850  clips.sort(CompareClips());
851 
852  // calculate max timeline duration
853  calculate_max_duration();
854 }
855 
856 // Sort effects by position on the timeline
857 void Timeline::sort_effects()
858 {
859  // Get lock (prevent getting frames while this happens)
860  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
861 
862  // sort clips
863  effects.sort(CompareEffects());
864 
865  // calculate max timeline duration
866  calculate_max_duration();
867 }
868 
869 // Clear all clips from timeline
871 {
872  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Clear");
873 
874  // Get lock (prevent getting frames while this happens)
875  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
876 
877  // Close all open clips
878  for (auto clip : clips)
879  {
880  update_open_clips(clip, false);
881 
882  // Delete clip object (if timeline allocated it)
883  bool allocated = allocated_clips.count(clip);
884  if (allocated) {
885  delete clip;
886  }
887  }
888  // Clear all clips
889  clips.clear();
890  allocated_clips.clear();
891 
892  // Close all effects
893  for (auto effect : effects)
894  {
895  // Delete effect object (if timeline allocated it)
896  bool allocated = allocated_effects.count(effect);
897  if (allocated) {
898  delete effect;
899  }
900  }
901  // Clear all effects
902  effects.clear();
903  allocated_effects.clear();
904 
905  // Delete all FrameMappers
906  for (auto mapper : allocated_frame_mappers)
907  {
908  mapper->Reader(NULL);
909  mapper->Close();
910  delete mapper;
911  }
912  allocated_frame_mappers.clear();
913 }
914 
915 // Close the reader (and any resources it was consuming)
917 {
918  ZmqLogger::Instance()->AppendDebugMethod("Timeline::Close");
919 
920  // Get lock (prevent getting frames while this happens)
921  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
922 
923  // Close all open clips
924  for (auto clip : clips)
925  {
926  // Open or Close this clip, based on if it's intersecting or not
927  update_open_clips(clip, false);
928  }
929 
930  // Mark timeline as closed
931  is_open = false;
932 
933  // Clear all cache (deep clear, including nested Readers)
934  ClearAllCache(true);
935 }
936 
937 // Open the reader (and start consuming resources)
939 {
940  is_open = true;
941 }
942 
943 // Compare 2 floating point numbers for equality
944 bool Timeline::isEqual(double a, double b)
945 {
946  return fabs(a - b) < 0.000001;
947 }
948 
949 // Get an openshot::Frame object for a specific frame number of this reader.
950 std::shared_ptr<Frame> Timeline::GetFrame(int64_t requested_frame)
951 {
952  // Adjust out of bounds frame number
953  if (requested_frame < 1)
954  requested_frame = 1;
955  const int64_t max_frame = GetMaxFrame();
956  const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
957 
958  // Check cache
959  std::shared_ptr<Frame> frame;
960  if (!past_timeline_end)
961  frame = final_cache->GetFrame(requested_frame);
962  if (frame) {
963  // Debug output
965  "Timeline::GetFrame (Cached frame found)",
966  "requested_frame", requested_frame);
967 
968  // Return cached frame
969  return frame;
970  }
971  else
972  {
973  // Prevent async calls to the following code
974  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
975 
976  // Check cache 2nd time
977  std::shared_ptr<Frame> frame;
978  if (!past_timeline_end)
979  frame = final_cache->GetFrame(requested_frame);
980  if (frame) {
981  // Debug output
983  "Timeline::GetFrame (Cached frame found on 2nd check)",
984  "requested_frame", requested_frame);
985 
986  // Return cached frame
987  return frame;
988  } else {
989  // Get a list of clips that intersect with the requested section of timeline
990  // This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
991  std::vector<Clip *> nearby_clips;
992  nearby_clips = find_intersecting_clips(requested_frame, 1, true);
993 
994  // Debug output
996  "Timeline::GetFrame (processing frame)",
997  "requested_frame", requested_frame,
998  "omp_get_thread_num()", omp_get_thread_num());
999 
1000  // Init some basic properties about this frame
1001  int samples_in_frame = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
1002 
1003  // Create blank frame (which will become the requested frame)
1004  std::shared_ptr<Frame> new_frame(std::make_shared<Frame>(requested_frame, preview_width, preview_height, "#000000", samples_in_frame, info.channels));
1005  new_frame->AddAudioSilence(samples_in_frame);
1006  new_frame->SampleRate(info.sample_rate);
1007  new_frame->ChannelsLayout(info.channel_layout);
1008 
1009  // Debug output
1011  "Timeline::GetFrame (Adding solid color)",
1012  "requested_frame", requested_frame,
1013  "info.width", info.width,
1014  "info.height", info.height);
1015 
1016  // Add Background Color to 1st layer (if animated or not black)
1017  if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) ||
1018  (color.red.GetValue(requested_frame) != 0.0 || color.green.GetValue(requested_frame) != 0.0 ||
1019  color.blue.GetValue(requested_frame) != 0.0))
1020  new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame));
1021 
1022  // Debug output
1024  "Timeline::GetFrame (Loop through clips)",
1025  "requested_frame", requested_frame,
1026  "clips.size()", clips.size(),
1027  "nearby_clips.size()", nearby_clips.size());
1028 
1029  // Precompute per-clip timing for this requested frame
1030  struct ClipInfo {
1031  Clip* clip;
1032  int64_t start_pos;
1033  int64_t end_pos;
1034  int64_t start_frame;
1035  int64_t frame_number;
1036  bool intersects;
1037  };
1038  std::vector<ClipInfo> clip_infos;
1039  clip_infos.reserve(nearby_clips.size());
1040  const double fpsD = info.fps.ToDouble();
1041 
1042  for (auto clip : nearby_clips) {
1043  int64_t start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1044  int64_t end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1045  bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1046  int64_t start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1047  int64_t frame_number = requested_frame - start_pos + start_frame;
1048  clip_infos.push_back({clip, start_pos, end_pos, start_frame, frame_number, intersects});
1049  }
1050 
1051  // Determine top clip per layer (linear, no nested loop)
1052  std::unordered_map<int, int64_t> top_start_for_layer;
1053  std::unordered_map<int, Clip*> top_clip_for_layer;
1054  for (const auto& ci : clip_infos) {
1055  if (!ci.intersects) continue;
1056  const int layer = ci.clip->Layer();
1057  auto it = top_start_for_layer.find(layer);
1058  if (it == top_start_for_layer.end() || ci.start_pos > it->second) {
1059  top_start_for_layer[layer] = ci.start_pos; // strictly greater to match prior logic
1060  top_clip_for_layer[layer] = ci.clip;
1061  }
1062  }
1063 
1064  // Compute max_volume across all overlapping clips once
1065  float max_volume_sum = 0.0f;
1066  for (const auto& ci : clip_infos) {
1067  if (!ci.intersects) continue;
1068  if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1069  ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1070  max_volume_sum += static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1071  }
1072  }
1073 
1074  // Compose intersecting clips in a single pass
1075  for (const auto& ci : clip_infos) {
1076  // Debug output
1078  "Timeline::GetFrame (Does clip intersect)",
1079  "requested_frame", requested_frame,
1080  "clip->Position()", ci.clip->Position(),
1081  "clip->Duration()", ci.clip->Duration(),
1082  "does_clip_intersect", ci.intersects);
1083 
1084  // Clip is visible
1085  if (ci.intersects) {
1086  // Is this the top clip on its layer?
1087  bool is_top_clip = false;
1088  const int layer = ci.clip->Layer();
1089  auto top_it = top_clip_for_layer.find(layer);
1090  if (top_it != top_clip_for_layer.end())
1091  is_top_clip = (top_it->second == ci.clip);
1092 
1093  // Determine the frame needed for this clip (based on the position on the timeline)
1094  int64_t clip_frame_number = ci.frame_number;
1095 
1096  // Debug output
1098  "Timeline::GetFrame (Calculate clip's frame #)",
1099  "clip->Position()", ci.clip->Position(),
1100  "clip->Start()", ci.clip->Start(),
1101  "info.fps.ToFloat()", info.fps.ToFloat(),
1102  "clip_frame_number", clip_frame_number);
1103 
1104  // Add clip's frame as layer
1105  add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1106 
1107  } else {
1108  // Debug output
1110  "Timeline::GetFrame (clip does not intersect)",
1111  "requested_frame", requested_frame,
1112  "does_clip_intersect", ci.intersects);
1113  }
1114 
1115  } // end clip loop
1116 
1117  // Debug output
1119  "Timeline::GetFrame (Add frame to cache)",
1120  "requested_frame", requested_frame,
1121  "info.width", info.width,
1122  "info.height", info.height);
1123 
1124  // Set frame # on mapped frame
1125  new_frame->SetFrameNumber(requested_frame);
1126 
1127  // Add final frame to cache (only for valid timeline range)
1128  if (!past_timeline_end)
1129  final_cache->Add(new_frame);
1130  // Return frame (or blank frame)
1131  return new_frame;
1132  }
1133  }
1134 }
1135 
1136 
1137 // Find intersecting clips (or non intersecting clips)
1138 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include)
1139 {
1140  // Find matching clips
1141  std::vector<Clip*> matching_clips;
1142 
1143  // Calculate time of frame
1144  const int64_t min_requested_frame = requested_frame;
1145  const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1146 
1147  // Find Clips at this time
1148  matching_clips.reserve(clips.size());
1149  const double fpsD = info.fps.ToDouble();
1150  for (auto clip : clips)
1151  {
1152  // Does clip intersect the current requested time
1153  int64_t clip_start_position = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1154  int64_t clip_end_position = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1155 
1156  bool does_clip_intersect =
1157  (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1158  (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1159 
1160  // Debug output
1162  "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1163  "requested_frame", requested_frame,
1164  "min_requested_frame", min_requested_frame,
1165  "max_requested_frame", max_requested_frame,
1166  "clip->Position()", clip->Position(),
1167  "does_clip_intersect", does_clip_intersect);
1168 
1169  // Open (or schedule for closing) this clip, based on if it's intersecting or not
1170  update_open_clips(clip, does_clip_intersect);
1171 
1172  // Clip is visible
1173  if (does_clip_intersect && include)
1174  // Add the intersecting clip
1175  matching_clips.push_back(clip);
1176 
1177  else if (!does_clip_intersect && !include)
1178  // Add the non-intersecting clip
1179  matching_clips.push_back(clip);
1180 
1181  } // end clip loop
1182 
1183  // return list
1184  return matching_clips;
1185 }
1186 
1187 // Set the cache object used by this reader
1188 void Timeline::SetCache(CacheBase* new_cache) {
1189  // Get lock (prevent getting frames while this happens)
1190  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1191 
1192  // Destroy previous cache (if managed by timeline)
1193  if (managed_cache && final_cache) {
1194  delete final_cache;
1195  final_cache = NULL;
1196  managed_cache = false;
1197  }
1198 
1199  // Set new cache
1200  final_cache = new_cache;
1201 }
1202 
1203 // Generate JSON string of this object
1204 std::string Timeline::Json() const {
1205 
1206  // Return formatted string
1207  return JsonValue().toStyledString();
1208 }
1209 
1210 // Generate Json::Value for this object
1211 Json::Value Timeline::JsonValue() const {
1212 
1213  // Create root json object
1214  Json::Value root = ReaderBase::JsonValue(); // get parent properties
1215  root["type"] = "Timeline";
1216  root["viewport_scale"] = viewport_scale.JsonValue();
1217  root["viewport_x"] = viewport_x.JsonValue();
1218  root["viewport_y"] = viewport_y.JsonValue();
1219  root["color"] = color.JsonValue();
1220  root["path"] = path;
1221 
1222  // Add array of clips
1223  root["clips"] = Json::Value(Json::arrayValue);
1224 
1225  // Find Clips at this time
1226  for (const auto existing_clip : clips)
1227  {
1228  root["clips"].append(existing_clip->JsonValue());
1229  }
1230 
1231  // Add array of effects
1232  root["effects"] = Json::Value(Json::arrayValue);
1233 
1234  // loop through effects
1235  for (const auto existing_effect: effects)
1236  {
1237  root["effects"].append(existing_effect->JsonValue());
1238  }
1239 
1240  // return JsonValue
1241  return root;
1242 }
1243 
1244 // Load JSON string into this object
1245 void Timeline::SetJson(const std::string value) {
1246 
1247  // Get lock (prevent getting frames while this happens)
1248  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1249 
1250  // Parse JSON string into JSON objects
1251  try
1252  {
1253  const Json::Value root = openshot::stringToJson(value);
1254  // Set all values that match
1255  SetJsonValue(root);
1256  }
1257  catch (const std::exception& e)
1258  {
1259  // Error parsing JSON (or missing keys)
1260  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1261  }
1262 }
1263 
1264 // Load Json::Value into this object
1265 void Timeline::SetJsonValue(const Json::Value root) {
1266 
1267  // Get lock (prevent getting frames while this happens)
1268  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1269 
1270  // Close timeline before we do anything (this closes all clips)
1271  bool was_open = is_open;
1272  Close();
1273 
1274  // Set parent data
1276 
1277  // Set data from Json (if key is found)
1278  if (!root["path"].isNull())
1279  path = root["path"].asString();
1280 
1281  if (!root["clips"].isNull()) {
1282  // Clear existing clips
1283  clips.clear();
1284 
1285  // loop through clips
1286  for (const Json::Value existing_clip : root["clips"]) {
1287  // Skip NULL nodes
1288  if (existing_clip.isNull()) {
1289  continue;
1290  }
1291 
1292  // Create Clip
1293  Clip *c = new Clip();
1294 
1295  // Keep track of allocated clip objects
1296  allocated_clips.insert(c);
1297 
1298  // When a clip is attached to an object, it searches for the object
1299  // on it's parent timeline. Setting the parent timeline of the clip here
1300  // allows attaching it to an object when exporting the project (because)
1301  // the exporter script initializes the clip and it's effects
1302  // before setting its parent timeline.
1303  c->ParentTimeline(this);
1304 
1305  // Load Json into Clip
1306  c->SetJsonValue(existing_clip);
1307 
1308  // Add Clip to Timeline
1309  AddClip(c);
1310  }
1311  }
1312 
1313  if (!root["effects"].isNull()) {
1314  // Clear existing effects
1315  effects.clear();
1316 
1317  // loop through effects
1318  for (const Json::Value existing_effect :root["effects"]) {
1319  // Skip NULL nodes
1320  if (existing_effect.isNull()) {
1321  continue;
1322  }
1323 
1324  // Create Effect
1325  EffectBase *e = NULL;
1326 
1327  if (!existing_effect["type"].isNull()) {
1328  // Create instance of effect
1329  if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) {
1330 
1331  // Keep track of allocated effect objects
1332  allocated_effects.insert(e);
1333 
1334  // Load Json into Effect
1335  e->SetJsonValue(existing_effect);
1336 
1337  // Add Effect to Timeline
1338  AddEffect(e);
1339  }
1340  }
1341  }
1342  }
1343 
1344  if (!root["duration"].isNull()) {
1345  // Update duration of timeline
1346  info.duration = root["duration"].asDouble();
1348  }
1349 
1350  // Update preview settings
1353 
1354  // Resort (and recalculate min/max duration)
1355  sort_clips();
1356  sort_effects();
1357 
1358  // Re-open if needed
1359  if (was_open)
1360  Open();
1361 
1362  // Timeline content changed: notify cache clients to rescan active window.
1363  BumpCacheEpoch();
1364 }
1365 
1366 // Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
1367 void Timeline::ApplyJsonDiff(std::string value) {
1368 
1369  // Get lock (prevent getting frames while this happens)
1370  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1371 
1372  // Parse JSON string into JSON objects
1373  try
1374  {
1375  const Json::Value root = openshot::stringToJson(value);
1376  // Process the JSON change array, loop through each item
1377  for (const Json::Value change : root) {
1378  std::string change_key = change["key"][(uint)0].asString();
1379 
1380  // Process each type of change
1381  if (change_key == "clips")
1382  // Apply to CLIPS
1383  apply_json_to_clips(change);
1384 
1385  else if (change_key == "effects")
1386  // Apply to EFFECTS
1387  apply_json_to_effects(change);
1388 
1389  else
1390  // Apply to TIMELINE
1391  apply_json_to_timeline(change);
1392 
1393  }
1394 
1395  // Timeline content changed: notify cache clients to rescan active window.
1396  if (!root.empty()) {
1397  BumpCacheEpoch();
1398  }
1399  }
1400  catch (const std::exception& e)
1401  {
1402  // Error parsing JSON (or missing keys)
1403  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1404  }
1405 }
1406 
1407 void Timeline::BumpCacheEpoch() {
1408  cache_epoch.fetch_add(1, std::memory_order_relaxed);
1409 }
1410 
1411 // Apply JSON diff to clips
1412 void Timeline::apply_json_to_clips(Json::Value change) {
1413 
1414  // Get key and type of change
1415  std::string change_type = change["type"].asString();
1416  std::string clip_id = "";
1417  Clip *existing_clip = NULL;
1418 
1419  // Find id of clip (if any)
1420  for (auto key_part : change["key"]) {
1421  // Get each change
1422  if (key_part.isObject()) {
1423  // Check for id
1424  if (!key_part["id"].isNull()) {
1425  // Set the id
1426  clip_id = key_part["id"].asString();
1427 
1428  // Find matching clip in timeline (if any)
1429  for (auto c : clips)
1430  {
1431  if (c->Id() == clip_id) {
1432  existing_clip = c;
1433  break; // clip found, exit loop
1434  }
1435  }
1436  break; // id found, exit loop
1437  }
1438  }
1439  }
1440 
1441  // Check for a more specific key (targetting this clip's effects)
1442  // For example: ["clips", {"id:123}, "effects", {"id":432}]
1443  if (existing_clip && change["key"].size() == 4 && change["key"][2] == "effects")
1444  {
1445  // This change is actually targetting a specific effect under a clip (and not the clip)
1446  Json::Value key_part = change["key"][3];
1447 
1448  if (key_part.isObject()) {
1449  // Check for id
1450  if (!key_part["id"].isNull())
1451  {
1452  // Set the id
1453  std::string effect_id = key_part["id"].asString();
1454 
1455  // Find matching effect in timeline (if any)
1456  std::list<EffectBase*> effect_list = existing_clip->Effects();
1457  for (auto e : effect_list)
1458  {
1459  if (e->Id() == effect_id) {
1460  // Apply the change to the effect directly
1461  apply_json_to_effects(change, e);
1462 
1463  // Effect-only diffs must clear the owning clip cache.
1464  if (existing_clip->GetCache()) {
1465  existing_clip->GetCache()->Clear();
1466  }
1467 
1468  // Calculate start and end frames that this impacts, and remove those frames from the cache
1469  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1470  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1471  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1472 
1473  return; // effect found, don't update clip
1474  }
1475  }
1476  }
1477  }
1478  }
1479 
1480  // Determine type of change operation
1481  if (change_type == "insert") {
1482 
1483  // Create clip
1484  Clip *clip = new Clip();
1485 
1486  // Keep track of allocated clip objects
1487  allocated_clips.insert(clip);
1488 
1489  // Set properties of clip from JSON
1490  clip->SetJsonValue(change["value"]);
1491 
1492  // Add clip to timeline
1493  AddClip(clip);
1494 
1495  // Calculate start and end frames that this impacts, and remove those frames from the cache
1496  int64_t new_starting_frame = (clip->Position() * info.fps.ToDouble()) + 1;
1497  int64_t new_ending_frame = ((clip->Position() + clip->Duration()) * info.fps.ToDouble()) + 1;
1498  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1499 
1500  } else if (change_type == "update") {
1501 
1502  // Update existing clip
1503  if (existing_clip) {
1504  // Calculate start and end frames prior to the update
1505  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1506  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1507 
1508  // Update clip properties from JSON
1509  existing_clip->SetJsonValue(change["value"]);
1510 
1511  // Calculate new start and end frames after the update
1512  int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1513  int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1514 
1515  // Remove both the old and new ranges from the timeline cache
1516  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1517  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1518 
1519  // Apply framemapper (or update existing framemapper)
1520  if (auto_map_clips) {
1521  apply_mapper_to_clip(existing_clip);
1522  }
1523  }
1524 
1525  } else if (change_type == "delete") {
1526 
1527  // Remove existing clip
1528  if (existing_clip) {
1529  // Remove clip from timeline
1530  RemoveClip(existing_clip);
1531 
1532  // Calculate start and end frames that this impacts, and remove those frames from the cache
1533  int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1534  int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1535  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1536  }
1537 
1538  }
1539 
1540  // Re-Sort Clips (since they likely changed)
1541  sort_clips();
1542 }
1543 
1544 // Apply JSON diff to effects
1545 void Timeline::apply_json_to_effects(Json::Value change) {
1546 
1547  // Get key and type of change
1548  std::string change_type = change["type"].asString();
1549  EffectBase *existing_effect = NULL;
1550 
1551  // Find id of an effect (if any)
1552  for (auto key_part : change["key"]) {
1553 
1554  if (key_part.isObject()) {
1555  // Check for id
1556  if (!key_part["id"].isNull())
1557  {
1558  // Set the id
1559  std::string effect_id = key_part["id"].asString();
1560 
1561  // Find matching effect in timeline (if any)
1562  for (auto e : effects)
1563  {
1564  if (e->Id() == effect_id) {
1565  existing_effect = e;
1566  break; // effect found, exit loop
1567  }
1568  }
1569  break; // id found, exit loop
1570  }
1571  }
1572  }
1573 
1574  // Now that we found the effect, apply the change to it
1575  if (existing_effect || change_type == "insert") {
1576  // Apply change to effect
1577  apply_json_to_effects(change, existing_effect);
1578  }
1579 }
1580 
1581 // Apply JSON diff to effects (if you already know which effect needs to be updated)
1582 void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_effect) {
1583 
1584  // Get key and type of change
1585  std::string change_type = change["type"].asString();
1586 
1587  // Calculate start and end frames that this impacts, and remove those frames from the cache
1588  if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1589  int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1590  int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1591  final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1592  }
1593 
1594  // Determine type of change operation
1595  if (change_type == "insert") {
1596 
1597  // Determine type of effect
1598  std::string effect_type = change["value"]["type"].asString();
1599 
1600  // Create Effect
1601  EffectBase *e = NULL;
1602 
1603  // Init the matching effect object
1604  if ( (e = EffectInfo().CreateEffect(effect_type)) ) {
1605 
1606  // Keep track of allocated effect objects
1607  allocated_effects.insert(e);
1608 
1609  // Load Json into Effect
1610  e->SetJsonValue(change["value"]);
1611 
1612  // Add Effect to Timeline
1613  AddEffect(e);
1614  }
1615 
1616  } else if (change_type == "update") {
1617 
1618  // Update existing effect
1619  if (existing_effect) {
1620 
1621  // Calculate start and end frames that this impacts, and remove those frames from the cache
1622  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1623  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1624  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1625 
1626  // Update effect properties from JSON
1627  existing_effect->SetJsonValue(change["value"]);
1628  }
1629 
1630  } else if (change_type == "delete") {
1631 
1632  // Remove existing effect
1633  if (existing_effect) {
1634 
1635  // Calculate start and end frames that this impacts, and remove those frames from the cache
1636  int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1637  int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1638  final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1639 
1640  // Remove effect from timeline
1641  RemoveEffect(existing_effect);
1642  }
1643 
1644  }
1645 
1646  // Re-Sort Effects (since they likely changed)
1647  sort_effects();
1648 }
1649 
1650 // Apply JSON diff to timeline properties
1651 void Timeline::apply_json_to_timeline(Json::Value change) {
1652  bool cache_dirty = true;
1653 
1654  // Get key and type of change
1655  std::string change_type = change["type"].asString();
1656  std::string root_key = change["key"][(uint)0].asString();
1657  std::string sub_key = "";
1658  if (change["key"].size() >= 2)
1659  sub_key = change["key"][(uint)1].asString();
1660 
1661  // Determine type of change operation
1662  if (change_type == "insert" || change_type == "update") {
1663 
1664  // INSERT / UPDATE
1665  // Check for valid property
1666  if (root_key == "color")
1667  // Set color
1668  color.SetJsonValue(change["value"]);
1669  else if (root_key == "viewport_scale")
1670  // Set viewport scale
1671  viewport_scale.SetJsonValue(change["value"]);
1672  else if (root_key == "viewport_x")
1673  // Set viewport x offset
1674  viewport_x.SetJsonValue(change["value"]);
1675  else if (root_key == "viewport_y")
1676  // Set viewport y offset
1677  viewport_y.SetJsonValue(change["value"]);
1678  else if (root_key == "duration") {
1679  // Update duration of timeline
1680  info.duration = change["value"].asDouble();
1682 
1683  // We don't want to clear cache for duration adjustments
1684  cache_dirty = false;
1685  }
1686  else if (root_key == "width") {
1687  // Set width
1688  info.width = change["value"].asInt();
1690  }
1691  else if (root_key == "height") {
1692  // Set height
1693  info.height = change["value"].asInt();
1695  }
1696  else if (root_key == "fps" && sub_key == "" && change["value"].isObject()) {
1697  // Set fps fraction
1698  if (!change["value"]["num"].isNull())
1699  info.fps.num = change["value"]["num"].asInt();
1700  if (!change["value"]["den"].isNull())
1701  info.fps.den = change["value"]["den"].asInt();
1702  }
1703  else if (root_key == "fps" && sub_key == "num")
1704  // Set fps.num
1705  info.fps.num = change["value"].asInt();
1706  else if (root_key == "fps" && sub_key == "den")
1707  // Set fps.den
1708  info.fps.den = change["value"].asInt();
1709  else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) {
1710  // Set display_ratio fraction
1711  if (!change["value"]["num"].isNull())
1712  info.display_ratio.num = change["value"]["num"].asInt();
1713  if (!change["value"]["den"].isNull())
1714  info.display_ratio.den = change["value"]["den"].asInt();
1715  }
1716  else if (root_key == "display_ratio" && sub_key == "num")
1717  // Set display_ratio.num
1718  info.display_ratio.num = change["value"].asInt();
1719  else if (root_key == "display_ratio" && sub_key == "den")
1720  // Set display_ratio.den
1721  info.display_ratio.den = change["value"].asInt();
1722  else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) {
1723  // Set pixel_ratio fraction
1724  if (!change["value"]["num"].isNull())
1725  info.pixel_ratio.num = change["value"]["num"].asInt();
1726  if (!change["value"]["den"].isNull())
1727  info.pixel_ratio.den = change["value"]["den"].asInt();
1728  }
1729  else if (root_key == "pixel_ratio" && sub_key == "num")
1730  // Set pixel_ratio.num
1731  info.pixel_ratio.num = change["value"].asInt();
1732  else if (root_key == "pixel_ratio" && sub_key == "den")
1733  // Set pixel_ratio.den
1734  info.pixel_ratio.den = change["value"].asInt();
1735 
1736  else if (root_key == "sample_rate")
1737  // Set sample rate
1738  info.sample_rate = change["value"].asInt();
1739  else if (root_key == "channels")
1740  // Set channels
1741  info.channels = change["value"].asInt();
1742  else if (root_key == "channel_layout")
1743  // Set channel layout
1744  info.channel_layout = (ChannelLayout) change["value"].asInt();
1745  else
1746  // Error parsing JSON (or missing keys)
1747  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1748 
1749 
1750  } else if (change["type"].asString() == "delete") {
1751 
1752  // DELETE / RESET
1753  // Reset the following properties (since we can't delete them)
1754  if (root_key == "color") {
1755  color = Color();
1756  color.red = Keyframe(0.0);
1757  color.green = Keyframe(0.0);
1758  color.blue = Keyframe(0.0);
1759  }
1760  else if (root_key == "viewport_scale")
1761  viewport_scale = Keyframe(1.0);
1762  else if (root_key == "viewport_x")
1763  viewport_x = Keyframe(0.0);
1764  else if (root_key == "viewport_y")
1765  viewport_y = Keyframe(0.0);
1766  else
1767  // Error parsing JSON (or missing keys)
1768  throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1769 
1770  }
1771 
1772  if (cache_dirty) {
1773  // Clear entire cache
1774  ClearAllCache();
1775  }
1776 }
1777 
1778 // Clear all caches
1779 void Timeline::ClearAllCache(bool deep) {
1780  // Get lock (prevent getting frames while this happens)
1781  const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
1782 
1783  // Clear primary cache
1784  if (final_cache) {
1785  final_cache->Clear();
1786  }
1787 
1788  // Loop through all clips
1789  try {
1790  for (const auto clip : clips) {
1791  // Clear cache on clip and reader if present
1792  if (clip->Reader()) {
1793  if (auto rc = clip->Reader()->GetCache())
1794  rc->Clear();
1795 
1796  // Clear nested Reader (if deep clear requested)
1797  if (deep && clip->Reader()->Name() == "FrameMapper") {
1798  FrameMapper *nested_reader = static_cast<FrameMapper *>(clip->Reader());
1799  if (nested_reader->Reader()) {
1800  if (auto nc = nested_reader->Reader()->GetCache())
1801  nc->Clear();
1802  }
1803  }
1804  }
1805 
1806  // Clear clip cache
1807  if (auto cc = clip->GetCache())
1808  cc->Clear();
1809  }
1810  } catch (const ReaderClosed & e) {
1811  // ...
1812  }
1813 
1814  // Cache content changed: notify cache clients to rebuild their window baseline.
1815  BumpCacheEpoch();
1816 }
1817 
1818 // Set Max Image Size (used for performance optimization). Convenience function for setting
1819 // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT.
1820 void Timeline::SetMaxSize(int width, int height) {
1821  // Maintain aspect ratio regardless of what size is passed in
1822  QSize display_ratio_size = QSize(info.width, info.height);
1823  QSize proposed_size = QSize(std::min(width, info.width), std::min(height, info.height));
1824 
1825  // Scale QSize up to proposed size
1826  display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1827 
1828  // Update preview settings
1829  preview_width = display_ratio_size.width();
1830  preview_height = display_ratio_size.height();
1831 }
1832 
1833 // Resolve equal-power audio gains from transition placement relative to the clip edges.
1834 std::pair<float, float> Timeline::ResolveTransitionAudioGains(Clip* source_clip, int64_t timeline_frame_number, bool is_top_clip) const
1835 {
1836  constexpr double half_pi = 1.57079632679489661923;
1837 
1838  if (!source_clip)
1839  return {1.0f, 1.0f};
1840 
1841  const double fpsD = info.fps.ToDouble();
1842  Mask* active_mask = nullptr;
1843  int64_t effect_start_position = 0;
1844  int64_t effect_end_position = 0;
1845 
1846  // Find the single active transition on this layer that requested overlapping audio fades.
1847  for (auto effect : effects) {
1848  if (effect->Layer() != source_clip->Layer())
1849  continue;
1850 
1851  auto* mask = dynamic_cast<Mask*>(effect);
1852  if (!mask || !mask->fade_audio_hint)
1853  continue;
1854 
1855  const int64_t start_pos = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
1856  const int64_t end_pos = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1857  if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1858  continue;
1859 
1860  if (active_mask)
1861  return {1.0f, 1.0f};
1862 
1863  active_mask = mask;
1864  effect_start_position = start_pos;
1865  effect_end_position = end_pos;
1866  }
1867 
1868  if (!active_mask)
1869  return {1.0f, 1.0f};
1870 
1871  struct AudibleClipInfo {
1872  Clip* clip;
1873  int64_t start_pos;
1874  int64_t end_pos;
1875  };
1876 
1877  std::vector<AudibleClipInfo> audible_clips;
1878  audible_clips.reserve(2);
1879 
1880  // Collect the audible clips covered by this transition on the current layer.
1881  for (auto clip : clips) {
1882  if (clip->Layer() != source_clip->Layer())
1883  continue;
1884  if (!clip->Reader() || !clip->Reader()->info.has_audio)
1885  continue;
1886 
1887  const int64_t clip_start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1888  const int64_t clip_end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1889  if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1890  continue;
1891 
1892  const int64_t clip_start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1893  const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1894  if (clip->has_audio.GetInt(clip_frame_number) == 0)
1895  continue;
1896 
1897  audible_clips.push_back({clip, clip_start_pos, clip_end_pos});
1898  if (audible_clips.size() > 2)
1899  return {1.0f, 1.0f};
1900  }
1901 
1902  if (audible_clips.empty())
1903  return {1.0f, 1.0f};
1904 
1905  // Skip clips that are not actually participating in this transition audio decision.
1906  const auto source_it = std::find_if(
1907  audible_clips.begin(),
1908  audible_clips.end(),
1909  [source_clip](const AudibleClipInfo& info) {
1910  return info.clip == source_clip;
1911  });
1912  if (source_it == audible_clips.end())
1913  return {1.0f, 1.0f};
1914 
1915  // Keep the current top/non-top clip routing intact when two clips overlap.
1916  if (audible_clips.size() == 2) {
1917  auto top_it = std::max_element(
1918  audible_clips.begin(),
1919  audible_clips.end(),
1920  [](const AudibleClipInfo& lhs, const AudibleClipInfo& rhs) {
1921  if (lhs.start_pos != rhs.start_pos)
1922  return lhs.start_pos < rhs.start_pos;
1923  return std::less<Clip*>()(lhs.clip, rhs.clip);
1924  });
1925  if ((is_top_clip && source_clip != top_it->clip) || (!is_top_clip && source_clip == top_it->clip))
1926  return {1.0f, 1.0f};
1927  }
1928 
1929  // Infer fade direction from which transition edge is closer to this clip.
1930  const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1931  const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1932  const bool clip_fades_in = left_distance <= right_distance;
1933 
1934  // Evaluate the current frame and previous frame so Timeline can preserve per-frame gain ramps.
1935  const auto compute_gain = [&](int64_t frame_number) -> float {
1936  if (effect_end_position <= effect_start_position)
1937  return 1.0f;
1938 
1939  const double span = static_cast<double>(effect_end_position - effect_start_position);
1940  double t = static_cast<double>(frame_number - effect_start_position) / span;
1941  if (t < 0.0)
1942  t = 0.0;
1943  else if (t > 1.0)
1944  t = 1.0;
1945 
1946  return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
1947  };
1948 
1949  return {compute_gain(timeline_frame_number - 1), compute_gain(timeline_frame_number)};
1950 }
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:249
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
Definition: Clip.h:182
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
Definition: Clip.h:353
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:300
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Clip.cpp:1012
openshot::Keyframe has_audio
An optional override to determine if this clip has audio (-1=undefined, 0=no, 1=yes)
Definition: Clip.h:357
openshot::Keyframe volume
Curve representing the volume (0 to 1)
Definition: Clip.h:337
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
Definition: Clip.h:354
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition: Clip.cpp:332
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
Definition: Clip.h:210
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:139
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:484
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:90
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:160
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:109
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:1211
openshot::Keyframe viewport_scale
Curve representing the scale of the viewport (0 to 100)
Definition: Timeline.h:332
void ApplyJsonDiff(std::string value)
Apply a special formatted JSON object, which represents a change to the timeline (add,...
Definition: Timeline.cpp:1367
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition: Timeline.cpp:441
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:454
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:1204
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition: Timeline.cpp:479
double GetMinTime()
Look up the position/start time of the first timeline element.
Definition: Timeline.cpp:495
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Definition: Timeline.cpp:950
void ApplyMapperToClips()
Apply the timeline's framerate and samplerate to all clips.
Definition: Timeline.cpp:530
openshot::Color color
Background color of timeline canvas.
Definition: Timeline.h:337
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:487
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition: Timeline.cpp:430
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Timeline.cpp:1265
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
Definition: Timeline.cpp:418
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1779
void AddEffect(openshot::EffectBase *effect)
Add an effect to the timeline.
Definition: Timeline.cpp:364
void SetCache(openshot::CacheBase *new_cache)
Definition: Timeline.cpp:1188
void Clear()
Clear all clips, effects, and frame mappers from timeline (and free memory)
Definition: Timeline.cpp:870
openshot::Keyframe viewport_x
Curve representing the x coordinate for the viewport.
Definition: Timeline.h:333
void RemoveClip(openshot::Clip *clip)
Remove an openshot::Clip from the timeline.
Definition: Timeline.cpp:399
void SetMaxSize(int width, int height)
Definition: Timeline.cpp:1820
double GetMaxTime()
Look up the end time of the latest timeline element.
Definition: Timeline.cpp:473
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the timeline.
Definition: Timeline.cpp:380
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:554
void Open() override
Open the reader (and start consuming resources)
Definition: Timeline.cpp:938
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Timeline.cpp:1245
openshot::Keyframe viewport_y
Curve representing the y coordinate for the viewport.
Definition: Timeline.h:334
void Close() override
Close the timeline reader (and any resources it was consuming)
Definition: Timeline.cpp:916
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: Compressor.h:29
@ 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