OpenShot Library | libopenshot  1.0.0
Clip.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 "Clip.h"
14 
15 #include "AudioResampler.h"
16 #include "Exceptions.h"
17 #include "FFmpegReader.h"
18 #include "FrameMapper.h"
19 #include "QtImageReader.h"
20 #include "ChunkReader.h"
21 #include "DummyReader.h"
22 #include "Timeline.h"
23 #include "ZmqLogger.h"
25 
26 #include <algorithm>
27 #include <cmath>
28 #include <sstream>
29 #include <QPainter>
30 #include <QPainterPath>
31 
32 #ifdef USE_IMAGEMAGICK
33  #include "MagickUtilities.h"
34  #include "ImageReader.h"
35  #include "TextReader.h"
36 #endif
37 
38 #include <Qt>
39 
40 using namespace openshot;
41 
42 namespace {
43  struct CompositeChoice { const char* name; CompositeType value; };
44  const CompositeChoice composite_choices[] = {
45  {"Normal", COMPOSITE_SOURCE_OVER},
46 
47  // Darken group
48  {"Darken", COMPOSITE_DARKEN},
49  {"Multiply", COMPOSITE_MULTIPLY},
50  {"Color Burn", COMPOSITE_COLOR_BURN},
51 
52  // Lighten group
53  {"Lighten", COMPOSITE_LIGHTEN},
54  {"Screen", COMPOSITE_SCREEN},
55  {"Color Dodge", COMPOSITE_COLOR_DODGE},
56  {"Add", COMPOSITE_PLUS},
57 
58  // Contrast group
59  {"Overlay", COMPOSITE_OVERLAY},
60  {"Soft Light", COMPOSITE_SOFT_LIGHT},
61  {"Hard Light", COMPOSITE_HARD_LIGHT},
62 
63  // Compare
64  {"Difference", COMPOSITE_DIFFERENCE},
65  {"Exclusion", COMPOSITE_EXCLUSION},
66  };
67  const int composite_choices_count = sizeof(composite_choices)/sizeof(CompositeChoice);
68 }
69 
70 // Init default settings for a clip
72 {
73  // Init clip settings
74  Position(0.0);
75  Layer(0);
76  Start(0.0);
77  ClipBase::End(0.0);
79  scale = SCALE_FIT;
84  waveform = false;
86  reader_orientation_mode = ReaderOrientationMode::Reader;
88  parentObjectId = "";
89 
90  // Init scale curves
91  scale_x = Keyframe(1.0);
92  scale_y = Keyframe(1.0);
93 
94  // Init location curves
95  location_x = Keyframe(0.0);
96  location_y = Keyframe(0.0);
97 
98  // Init alpha
99  alpha = Keyframe(1.0);
100  margin = Keyframe(0.0);
101  corner_radius = Keyframe(0.0);
102 
103  // Init time & volume
104  time = Keyframe(1.0);
105  volume = Keyframe(1.0);
106 
107  // Init audio waveform color
108  wave_color = Color((unsigned char)0, (unsigned char)123, (unsigned char)255, (unsigned char)255);
109 
110  // Init shear and perspective curves
111  shear_x = Keyframe(0.0);
112  shear_y = Keyframe(0.0);
113  origin_x = Keyframe(0.5);
114  origin_y = Keyframe(0.5);
115  perspective_c1_x = Keyframe(-1.0);
116  perspective_c1_y = Keyframe(-1.0);
117  perspective_c2_x = Keyframe(-1.0);
118  perspective_c2_y = Keyframe(-1.0);
119  perspective_c3_x = Keyframe(-1.0);
120  perspective_c3_y = Keyframe(-1.0);
121  perspective_c4_x = Keyframe(-1.0);
122  perspective_c4_y = Keyframe(-1.0);
123 
124  // Init audio channel filter and mappings
125  channel_filter = Keyframe(-1.0);
126  channel_mapping = Keyframe(-1.0);
127 
128  // Init audio and video overrides
129  has_audio = Keyframe(-1.0);
130  has_video = Keyframe(-1.0);
131 
132  // Initialize the attached object and attached clip as null pointers
133  parentTrackedObject = nullptr;
134  parentClipObject = NULL;
135 
136  // Init reader info struct
138 }
139 
140 // Init reader info details
142  if (reader) {
143  // Init rotation (if any)
145 
146  // Initialize info struct
147  info = reader->info;
148 
149  // Init cache
151  }
152 }
153 
155  // Only apply metadata rotation if clip rotation has not been explicitly set.
156  if (rotation.GetCount() > 0 || !reader)
157  return;
158 
159  if (reader->ApplyOrientationMetadata()) {
160  rotation = Keyframe(0.0f);
161  return;
162  }
163 
164  const auto rotate_meta = reader->info.metadata.find("rotate");
165  if (rotate_meta == reader->info.metadata.end()) {
166  // Ensure rotation keyframes always start with a default 0° point.
167  rotation = Keyframe(0.0f);
168  return;
169  }
170 
171  float rotate_angle = 0.0f;
172  try {
173  rotate_angle = strtof(rotate_meta->second.c_str(), nullptr);
174  } catch (const std::exception& e) {
175  return; // ignore invalid metadata
176  }
177 
178  rotation = Keyframe(rotate_angle);
179 
180  // Do not overwrite user-authored scale curves.
181  auto has_default_scale = [](const Keyframe& kf) {
182  return kf.GetCount() == 1 && fabs(kf.GetPoint(0).co.Y - 1.0) < 0.00001;
183  };
184  if (!has_default_scale(scale_x) || !has_default_scale(scale_y))
185  return;
186 
187  // No need to adjust scaling when the metadata rotation is effectively zero.
188  if (fabs(rotate_angle) < 0.0001f)
189  return;
190 
191  float w = static_cast<float>(reader->info.width);
192  float h = static_cast<float>(reader->info.height);
193  if (w <= 0.0f || h <= 0.0f)
194  return;
195 
196  float rad = rotate_angle * static_cast<float>(M_PI) / 180.0f;
197 
198  float new_width = fabs(w * cos(rad)) + fabs(h * sin(rad));
199  float new_height = fabs(w * sin(rad)) + fabs(h * cos(rad));
200  if (new_width <= 0.0f || new_height <= 0.0f)
201  return;
202 
203  float uniform_scale = std::min(w / new_width, h / new_height);
204 
205  scale_x = Keyframe(uniform_scale);
206  scale_y = Keyframe(uniform_scale);
207 }
208 
209 // Default Constructor for a clip
210 Clip::Clip() : resampler(NULL), reader(NULL), allocated_reader(NULL), is_open(false)
211 {
212  // Init all default settings
213  init_settings();
214 }
215 
216 // Constructor with reader
217 Clip::Clip(ReaderBase* new_reader) : resampler(NULL), reader(new_reader), allocated_reader(NULL), is_open(false)
218 {
219  // Init all default settings
220  init_settings();
221 
222  // Open and Close the reader (to set the duration of the clip)
223  Open();
224  Close();
225 
226  // Update duration and set parent
227  if (reader) {
228  ClipBase::End(reader->info.duration);
229  reader->ParentClip(this);
230  // Init reader info struct
232  }
233 }
234 
235 // Constructor with filepath
236 Clip::Clip(std::string path) : resampler(NULL), reader(NULL), allocated_reader(NULL), is_open(false)
237 {
238  // Init all default settings
239  init_settings();
240  reader = CreateReader(path);
241 
242  // Update duration and set parent
243  if (reader) {
244  ClipBase::End(reader->info.duration);
245  reader->ParentClip(this);
246  allocated_reader = reader;
247  // Init reader info struct
249  }
250 }
251 
252 ReaderBase* Clip::CreateReader(std::string path, bool inspect_reader)
253 {
254  // Get file extension (and convert to lower case)
255  std::string ext = get_file_extension(path);
256  std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
257 
258  // Determine if common video formats (or image sequences)
259  if (ext=="avi" || ext=="flac" || ext=="mov" || ext=="mkv" || ext=="mpg" || ext=="mpeg" || ext=="mp3" || ext=="mp4" || ext=="mts" ||
260  ext=="ogg" || ext=="wav" || ext=="wmv" || ext=="webm" || ext=="vob" || ext=="gif" || path.find("%") != std::string::npos)
261  {
262  try
263  {
264  return new openshot::FFmpegReader(path, inspect_reader);
265  } catch(...) { }
266  }
267  if (ext=="osp")
268  {
269  try
270  {
271  return new openshot::Timeline(path, true);
272  } catch(...) { }
273  }
274 
275  // If no video found, try each reader
276  try
277  {
278  return new openshot::QtImageReader(path, inspect_reader);
279  } catch(...) {
280  try
281  {
282  return new openshot::FFmpegReader(path, inspect_reader);
283  } catch(...) { }
284  }
285 
286  return NULL;
287 }
288 
289 // Destructor
291 {
292  // Delete the reader if clip created it
293  if (allocated_reader) {
294  delete allocated_reader;
295  allocated_reader = NULL;
296  reader = NULL;
297  }
298 
299  // Close the resampler
300  if (resampler) {
301  delete resampler;
302  resampler = NULL;
303  }
304 
305  // Close clip
306  Close();
307 }
308 
309 // Attach clip to bounding box
310 void Clip::AttachToObject(std::string object_id)
311 {
312  // Search for the tracked object on the timeline
313  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
314 
315  if (parentTimeline) {
316  // Create a smart pointer to the tracked object from the timeline
317  std::shared_ptr<openshot::TrackedObjectBase> trackedObject = parentTimeline->GetTrackedObject(object_id);
318  Clip* clipObject = parentTimeline->GetClip(object_id);
319 
320  // Check for valid tracked object
321  if (trackedObject){
322  SetAttachedObject(trackedObject);
323  parentClipObject = NULL;
324  }
325  else if (clipObject) {
326  SetAttachedClip(clipObject);
327  parentTrackedObject = nullptr;
328  }
329  }
330 }
331 
332 // Set the pointer to the trackedObject this clip is attached to
333 void Clip::SetAttachedObject(std::shared_ptr<openshot::TrackedObjectBase> trackedObject){
334  parentTrackedObject = trackedObject;
335 }
336 
337 // Set the pointer to the clip this clip is attached to
338 void Clip::SetAttachedClip(Clip* clipObject){
339  parentClipObject = clipObject;
340 }
341 
343 void Clip::Reader(ReaderBase* new_reader)
344 {
345  // Delete previously allocated reader (if not related to new reader)
346  // FrameMappers that point to the same allocated reader are ignored
347  bool is_same_reader = false;
348  if (new_reader && allocated_reader) {
349  if (new_reader->Name() == "FrameMapper") {
350  // Determine if FrameMapper is pointing at the same allocated ready
351  FrameMapper* clip_mapped_reader = static_cast<FrameMapper*>(new_reader);
352  if (allocated_reader == clip_mapped_reader->Reader()) {
353  is_same_reader = true;
354  }
355  }
356  }
357  // Clear existing allocated reader (if different)
358  if (allocated_reader && !is_same_reader) {
359  reader->Close();
360  allocated_reader->Close();
361  delete allocated_reader;
362  reader = NULL;
363  allocated_reader = NULL;
364  }
365 
366  // set reader pointer
367  reader = new_reader;
368 
369  // set parent
370  if (reader) {
371  reader->ApplyOrientationMetadata(reader_orientation_mode == ReaderOrientationMode::Reader);
372  reader->ParentClip(this);
373 
374  // Init reader info struct
376  }
377 }
378 
381 {
382  if (reader)
383  return reader;
384  else
385  // Throw error if reader not initialized
386  throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
387 }
388 
389 // Open the internal reader
391 {
392  if (reader)
393  {
394  // Open the reader
395  reader->Open();
396  is_open = true;
397 
398  // Copy Reader info to Clip
399  info = reader->info;
400 
401  // Set some clip properties from the file reader
402  if (end == 0.0)
403  ClipBase::End(reader->info.duration);
404  }
405  else
406  // Throw error if reader not initialized
407  throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
408 }
409 
410 // Close the internal reader
412 {
413  if (is_open && reader) {
414  ZmqLogger::Instance()->AppendDebugMethod("Clip::Close");
415 
416  // Close the reader
417  reader->Close();
418  }
419 
420  // Clear cache
421  final_cache.Clear();
422  is_open = false;
423 }
424 
425 // Get end position of clip (trim end of video), which can be affected by the time curve.
426 float Clip::End() const
427 {
428  // if a time curve is present, use its length
429  if (time.GetCount() > 1)
430  {
431  // Determine the FPS fo this clip
432  float fps = 24.0;
433  if (reader)
434  // file reader
435  fps = reader->info.fps.ToFloat();
436  else
437  // Throw error if reader not initialized
438  throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
439 
440  return float(time.GetLength()) / fps;
441  }
442  else
443  // just use the duration (as detected by the reader)
444  return end;
445 }
446 
447 // Override End() position
448 void Clip::End(float value) {
449  ClipBase::End(value);
450 }
451 
452 // Set associated Timeline pointer
454  timeline = new_timeline;
455 
456  // Clear cache (it might have changed)
457  final_cache.Clear();
458 }
459 
460 // Create an openshot::Frame object for a specific frame number of this reader.
461 std::shared_ptr<Frame> Clip::GetFrame(int64_t clip_frame_number)
462 {
463  // Call override of GetFrame
464  return GetFrame(NULL, clip_frame_number, NULL);
465 }
466 
467 // Create an openshot::Frame object for a specific frame number of this reader.
468 // NOTE: background_frame is ignored in this method (this method is only used by Effect classes)
469 std::shared_ptr<Frame> Clip::GetFrame(std::shared_ptr<openshot::Frame> background_frame, int64_t clip_frame_number)
470 {
471  // Call override of GetFrame
472  return GetFrame(background_frame, clip_frame_number, NULL);
473 }
474 
475 // Use an existing openshot::Frame object and draw this Clip's frame onto it
476 std::shared_ptr<Frame> Clip::GetFrame(std::shared_ptr<openshot::Frame> background_frame, int64_t clip_frame_number, openshot::TimelineInfoStruct* options)
477 {
478  // Check for open reader (or throw exception)
479  if (!is_open)
480  throw ReaderClosed("The Clip is closed. Call Open() before calling this method.");
481 
482  if (reader)
483  {
484  // Get frame object
485  std::shared_ptr<Frame> frame = NULL;
486 
487  // Generate clip frame
488  frame = GetOrCreateFrame(clip_frame_number);
489 
490  // Get frame size and frame #
491  int64_t timeline_frame_number = clip_frame_number;
492  QSize timeline_size(frame->GetWidth(), frame->GetHeight());
493  if (background_frame) {
494  // If a background frame is provided, use it instead
495  timeline_frame_number = background_frame->number;
496  timeline_size.setWidth(background_frame->GetWidth());
497  timeline_size.setHeight(background_frame->GetHeight());
498  }
499 
500  // Get time mapped frame object (used to increase speed, change direction, etc...)
501  apply_timemapping(frame);
502 
503  // Apply waveform image (if any)
504  apply_waveform(frame, timeline_size);
505 
506  // Apply effects BEFORE applying keyframes (if any local or global effects are used)
507  apply_effects(frame, timeline_frame_number, options, true);
508 
509  // Apply keyframe / transforms to current clip image
510  apply_keyframes(frame, timeline_size);
511 
512  // Apply effects AFTER applying keyframes (if any local or global effects are used)
513  apply_effects(frame, timeline_frame_number, options, false);
514 
515  // Timeline composition can paint directly into the timeline-owned background
516  // without mutating the cached clip frame.
517  if (options) {
518  if (!background_frame) {
519  background_frame = std::make_shared<Frame>(frame->number, frame->GetWidth(), frame->GetHeight(),
520  "#00000000", frame->GetAudioSamplesCount(),
521  frame->GetAudioChannelsCount());
522  }
523  apply_background(frame, background_frame, false);
524  return frame;
525  }
526 
527  // No background: return the frame directly.
528  if (!background_frame) {
529  return frame;
530  }
531 
532  // Always composite on a copy so cached frame pixels remain immutable.
533  auto output = std::make_shared<Frame>(*frame.get());
534  apply_background(output, background_frame, true);
535  return output;
536  }
537  else
538  // Throw error if reader not initialized
539  throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
540 }
541 
542 // Look up an effect by ID
543 openshot::EffectBase* Clip::GetEffect(const std::string& id)
544 {
545  // Find the matching effect (if any)
546  for (const auto& effect : effects) {
547  if (effect->Id() == id) {
548  return effect;
549  }
550  }
551  return nullptr;
552 }
553 
554 // Return the associated ParentClip (if any)
556  if (!parentObjectId.empty() && (!parentClipObject && !parentTrackedObject)) {
557  // Attach parent clip OR object to this clip
558  AttachToObject(parentObjectId);
559  }
560  return parentClipObject;
561 }
562 
563 // Return the associated Parent Tracked Object (if any)
564 std::shared_ptr<openshot::TrackedObjectBase> Clip::GetParentTrackedObject() {
565  if (!parentObjectId.empty() && (!parentClipObject && !parentTrackedObject)) {
566  // Attach parent clip OR object to this clip
567  AttachToObject(parentObjectId);
568  }
569  return parentTrackedObject;
570 }
571 
572 // Get file extension
573 std::string Clip::get_file_extension(std::string path)
574 {
575  // Return last part of path safely (handle filenames without a dot)
576  const auto dot_pos = path.find_last_of('.');
577  if (dot_pos == std::string::npos || dot_pos + 1 >= path.size()) {
578  return std::string();
579  }
580 
581  return path.substr(dot_pos + 1);
582 }
583 
584 // Adjust the audio and image of a time mapped frame
585 void Clip::apply_timemapping(std::shared_ptr<Frame> frame)
586 {
587  // Check for valid reader
588  if (!reader)
589  // Throw error if reader not initialized
590  throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method.");
591 
592  // Check for a valid time map curve
593  if (time.GetLength() > 1)
594  {
595  const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
596 
597  int64_t clip_frame_number = frame->number;
598  int64_t new_frame_number = adjust_frame_number_minimum(time.GetLong(clip_frame_number));
599 
600  // create buffer
601  juce::AudioBuffer<float> *source_samples = nullptr;
602 
603  // Get delta (difference from this frame to the next time mapped frame: Y value)
604  double delta = time.GetDelta(clip_frame_number + 1);
605  const bool prev_is_increasing = time.IsIncreasing(clip_frame_number);
606  const bool is_increasing = time.IsIncreasing(clip_frame_number + 1);
607 
608  // Determine length of source audio (in samples)
609  // A delta of 1.0 == normal expected samples
610  // A delta of 0.5 == 50% of normal expected samples
611  // A delta of 2.0 == 200% of normal expected samples
612  int target_sample_count = Frame::GetSamplesPerFrame(adjust_timeline_framenumber(clip_frame_number), Reader()->info.fps,
614  Reader()->info.channels);
615  int source_sample_count = round(target_sample_count * fabs(delta));
616 
617  // Determine starting audio location
618  AudioLocation location;
619  if (previous_location.frame == 0 || abs(new_frame_number - previous_location.frame) > 2 || prev_is_increasing != is_increasing) {
620  // No previous location OR gap detected
621  location.frame = new_frame_number;
622  location.sample_start = 0;
623 
624  // Create / Reset resampler
625  // We don't want to interpolate between unrelated audio data
626  if (resampler) {
627  delete resampler;
628  resampler = nullptr;
629  }
630  // Init resampler with # channels from Reader (should match the timeline)
631  resampler = new AudioResampler(Reader()->info.channels);
632 
633  // Allocate buffer of silence to initialize some data inside the resampler
634  // To prevent it from becoming input limited
635  juce::AudioBuffer<float> init_samples(Reader()->info.channels, 64);
636  init_samples.clear();
637  resampler->SetBuffer(&init_samples, 1.0);
638  resampler->GetResampledBuffer();
639 
640  } else {
641  // Use previous location
642  location = previous_location;
643  }
644 
645  if (source_sample_count <= 0) {
646  // Add silence and bail (we don't need any samples)
647  frame->AddAudioSilence(target_sample_count);
648  return;
649  }
650 
651  // Allocate a new sample buffer for these delta frames
652  source_samples = new juce::AudioBuffer<float>(Reader()->info.channels, source_sample_count);
653  source_samples->clear();
654 
655  // Determine ending audio location
656  int remaining_samples = source_sample_count;
657  int source_pos = 0;
658  while (remaining_samples > 0) {
659  std::shared_ptr<Frame> source_frame = GetOrCreateFrame(location.frame, false);
660  int frame_sample_count = source_frame->GetAudioSamplesCount() - location.sample_start;
661 
662  // Inform FrameMapper of the direction for THIS mapper frame
663  if (auto *fm = dynamic_cast<FrameMapper*>(reader)) {
664  fm->SetDirectionHint(is_increasing);
665  }
666  source_frame->SetAudioDirection(is_increasing);
667 
668  if (frame_sample_count == 0) {
669  // No samples found in source frame (fill with silence)
670  if (is_increasing) {
671  location.frame++;
672  } else {
673  location.frame--;
674  }
675  location.sample_start = 0;
676  break;
677  }
678  if (remaining_samples - frame_sample_count >= 0) {
679  // Use all frame samples & increment location
680  for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++) {
681  source_samples->addFrom(channel, source_pos, source_frame->GetAudioSamples(channel) + location.sample_start, frame_sample_count, 1.0f);
682  }
683  if (is_increasing) {
684  location.frame++;
685  } else {
686  location.frame--;
687  }
688  location.sample_start = 0;
689  remaining_samples -= frame_sample_count;
690  source_pos += frame_sample_count;
691 
692  } else {
693  // Use just what is needed (and reverse samples)
694  for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++) {
695  source_samples->addFrom(channel, source_pos, source_frame->GetAudioSamples(channel) + location.sample_start, remaining_samples, 1.0f);
696  }
697  location.sample_start += remaining_samples;
698  remaining_samples = 0;
699  source_pos += remaining_samples;
700  }
701 
702  }
703 
704  // Resize audio for current frame object + fill with silence
705  // We are fixing to clobber this with actual audio data (possibly resampled)
706  frame->AddAudioSilence(target_sample_count);
707 
708  if (source_sample_count != target_sample_count) {
709  // Resample audio (if needed)
710  double resample_ratio = double(source_sample_count) / double(target_sample_count);
711  resampler->SetBuffer(source_samples, resample_ratio);
712 
713  // Resample the data
714  juce::AudioBuffer<float> *resampled_buffer = resampler->GetResampledBuffer();
715 
716  // Fill the frame with resampled data
717  for (int channel = 0; channel < Reader()->info.channels; channel++) {
718  // Add new (slower) samples, to the frame object
719  frame->AddAudio(true, channel, 0, resampled_buffer->getReadPointer(channel, 0), std::min(resampled_buffer->getNumSamples(), target_sample_count), 1.0f);
720  }
721  } else {
722  // Fill the frame
723  for (int channel = 0; channel < Reader()->info.channels; channel++) {
724  // Add new (slower) samples, to the frame object
725  frame->AddAudio(true, channel, 0, source_samples->getReadPointer(channel, 0), target_sample_count, 1.0f);
726  }
727  }
728 
729  // Clean up
730  delete source_samples;
731 
732  // Set previous location
733  previous_location = location;
734  }
735 }
736 
737 // Adjust frame number minimum value
738 int64_t Clip::adjust_frame_number_minimum(int64_t frame_number)
739 {
740  // Never return a frame number 0 or below
741  if (frame_number < 1)
742  return 1;
743  else
744  return frame_number;
745 
746 }
747 
748 // Get or generate a blank frame
749 std::shared_ptr<Frame> Clip::GetOrCreateFrame(int64_t number, bool enable_time)
750 {
751  try {
752  // Init to requested frame
753  int64_t clip_frame_number = adjust_frame_number_minimum(number);
754  bool is_increasing = true;
755 
756  // Adjust for time-mapping (if any)
757  if (enable_time && time.GetLength() > 1) {
758  is_increasing = time.IsIncreasing(clip_frame_number + 1);
759  const int64_t time_frame_number = adjust_frame_number_minimum(time.GetLong(clip_frame_number));
760  if (auto *fm = dynamic_cast<FrameMapper*>(reader)) {
761  // Inform FrameMapper which direction this mapper frame is being requested
762  fm->SetDirectionHint(is_increasing);
763  }
764  clip_frame_number = time_frame_number;
765  }
766 
767  // Debug output
769  "Clip::GetOrCreateFrame (from reader)",
770  "number", number, "clip_frame_number", clip_frame_number);
771 
772  // Attempt to get a frame (but this could fail if a reader has just been closed)
773  auto reader_frame = reader->GetFrame(clip_frame_number);
774  if (reader_frame) {
775  // Override frame # (due to time-mapping might change it)
776  reader_frame->number = number;
777  reader_frame->SetAudioDirection(is_increasing);
778 
779  // Return real frame
780  // Create a new copy of reader frame
781  // This allows a clip to modify the pixels and audio of this frame without
782  // changing the underlying reader's frame data
783  auto reader_copy = std::make_shared<Frame>(*reader_frame.get());
784  if (has_video.GetInt(number) == 0) {
785  // No video, so add transparent pixels
786  reader_copy->AddColor(QColor(Qt::transparent));
787  }
788  if (has_audio.GetInt(number) == 0 || number > reader->info.video_length) {
789  // No audio, so include silence (also, mute audio if past end of reader)
790  reader_copy->AddAudioSilence(reader_copy->GetAudioSamplesCount());
791  }
792  return reader_copy;
793  }
794 
795  } catch (const ReaderClosed & e) {
796  // ...
797  } catch (const OutOfBoundsFrame & e) {
798  // ...
799  }
800 
801  // Estimate # of samples needed for this frame
802  int estimated_samples_in_frame = Frame::GetSamplesPerFrame(number, reader->info.fps, reader->info.sample_rate, reader->info.channels);
803 
804  // Debug output
806  "Clip::GetOrCreateFrame (create blank)",
807  "number", number,
808  "estimated_samples_in_frame", estimated_samples_in_frame);
809 
810  // Create blank frame
811  auto new_frame = std::make_shared<Frame>(
812  number, reader->info.width, reader->info.height,
813  "#000000", estimated_samples_in_frame, reader->info.channels);
814  new_frame->SampleRate(reader->info.sample_rate);
815  new_frame->ChannelsLayout(reader->info.channel_layout);
816  new_frame->AddAudioSilence(estimated_samples_in_frame);
817  return new_frame;
818 }
819 
820 // Generate JSON string of this object
821 std::string Clip::Json() const {
822 
823  // Return formatted string
824  return JsonValue().toStyledString();
825 }
826 
827 // Get all properties for a specific frame
828 std::string Clip::PropertiesJSON(int64_t requested_frame) const {
829 
830  // Generate JSON properties list
831  Json::Value root;
832  root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
833  root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
834  root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame);
835  root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
836  root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
837  root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 30 * 60 * 60 * 48, true, requested_frame);
838  root["gravity"] = add_property_json("Gravity", gravity, "int", "", NULL, 0, 8, false, requested_frame);
839  root["scale"] = add_property_json("Scale", scale, "int", "", NULL, 0, 3, false, requested_frame);
840  root["display"] = add_property_json("Frame Number", display, "int", "", NULL, 0, 3, false, requested_frame);
841  root["mixing"] = add_property_json("Volume Mixing", mixing, "int", "", NULL, 0, 2, false, requested_frame);
842  root["composite"] = add_property_json("Composite", composite, "int", "", NULL, 0, composite_choices_count - 1, false, requested_frame);
843  root["waveform"] = add_property_json("Waveform", waveform, "int", "", NULL, 0, 1, false, requested_frame);
844  root["waveform_mode"] = add_property_json("Waveform Mode", waveform_mode, "int", "", NULL, 0, AUDIO_VISUALIZATION_RADIAL_BARS, false, requested_frame);
845  root["parentObjectId"] = add_property_json("Parent", 0.0, "string", parentObjectId, NULL, -1, -1, false, requested_frame);
846 
847  // Add gravity choices (dropdown style)
848  root["gravity"]["choices"].append(add_property_choice_json("Top Left", GRAVITY_TOP_LEFT, gravity));
849  root["gravity"]["choices"].append(add_property_choice_json("Top Center", GRAVITY_TOP, gravity));
850  root["gravity"]["choices"].append(add_property_choice_json("Top Right", GRAVITY_TOP_RIGHT, gravity));
851  root["gravity"]["choices"].append(add_property_choice_json("Left", GRAVITY_LEFT, gravity));
852  root["gravity"]["choices"].append(add_property_choice_json("Center", GRAVITY_CENTER, gravity));
853  root["gravity"]["choices"].append(add_property_choice_json("Right", GRAVITY_RIGHT, gravity));
854  root["gravity"]["choices"].append(add_property_choice_json("Bottom Left", GRAVITY_BOTTOM_LEFT, gravity));
855  root["gravity"]["choices"].append(add_property_choice_json("Bottom Center", GRAVITY_BOTTOM, gravity));
856  root["gravity"]["choices"].append(add_property_choice_json("Bottom Right", GRAVITY_BOTTOM_RIGHT, gravity));
857 
858  // Add scale choices (dropdown style)
859  root["scale"]["choices"].append(add_property_choice_json("Crop", SCALE_CROP, scale));
860  root["scale"]["choices"].append(add_property_choice_json("Best Fit", SCALE_FIT, scale));
861  root["scale"]["choices"].append(add_property_choice_json("Stretch", SCALE_STRETCH, scale));
862  root["scale"]["choices"].append(add_property_choice_json("None", SCALE_NONE, scale));
863 
864  // Add frame number display choices (dropdown style)
865  root["display"]["choices"].append(add_property_choice_json("None", FRAME_DISPLAY_NONE, display));
866  root["display"]["choices"].append(add_property_choice_json("Clip", FRAME_DISPLAY_CLIP, display));
867  root["display"]["choices"].append(add_property_choice_json("Timeline", FRAME_DISPLAY_TIMELINE, display));
868  root["display"]["choices"].append(add_property_choice_json("Both", FRAME_DISPLAY_BOTH, display));
869 
870  // Add volume mixing choices (dropdown style)
871  root["mixing"]["choices"].append(add_property_choice_json("None", VOLUME_MIX_NONE, mixing));
872  root["mixing"]["choices"].append(add_property_choice_json("Average", VOLUME_MIX_AVERAGE, mixing));
873  root["mixing"]["choices"].append(add_property_choice_json("Reduce", VOLUME_MIX_REDUCE, mixing));
874 
875  // Add composite choices (dropdown style)
876  for (int i = 0; i < composite_choices_count; ++i)
877  root["composite"]["choices"].append(add_property_choice_json(composite_choices[i].name, composite_choices[i].value, composite));
878 
879  // Add waveform choices (dropdown style)
880  root["waveform"]["choices"].append(add_property_choice_json("Yes", true, waveform));
881  root["waveform"]["choices"].append(add_property_choice_json("No", false, waveform));
882 
883  // Add waveform mode choices (dropdown style)
884  root["waveform_mode"]["choices"].append(add_property_choice_json("Waveform", AUDIO_VISUALIZATION_WAVEFORM, waveform_mode));
885  root["waveform_mode"]["choices"].append(add_property_choice_json("Filled Waveform", AUDIO_VISUALIZATION_FILLED_WAVEFORM, waveform_mode));
886  root["waveform_mode"]["choices"].append(add_property_choice_json("Bars", AUDIO_VISUALIZATION_BARS, waveform_mode));
887  root["waveform_mode"]["choices"].append(add_property_choice_json("Radial", AUDIO_VISUALIZATION_RADIAL, waveform_mode));
888  root["waveform_mode"]["choices"].append(add_property_choice_json("Radial Bars", AUDIO_VISUALIZATION_RADIAL_BARS, waveform_mode));
889  root["waveform_mode"]["choices"].append(add_property_choice_json("Spectrum", AUDIO_VISUALIZATION_SPECTRUM, waveform_mode));
890  root["waveform_mode"]["choices"].append(add_property_choice_json("Phase Scope", AUDIO_VISUALIZATION_PHASE_SCOPE, waveform_mode));
891  root["waveform_mode"]["choices"].append(add_property_choice_json("Particles", AUDIO_VISUALIZATION_PARTICLES, waveform_mode));
892  root["waveform_mode"]["choices"].append(add_property_choice_json("VU Meter", AUDIO_VISUALIZATION_VU_METER, waveform_mode));
893 
894  // Add the parentClipObject's properties
895  if (parentClipObject)
896  {
897  // Convert Clip's frame position to Timeline's frame position
898  long clip_start_position = round(Position() * info.fps.ToDouble()) + 1;
899  long clip_start_frame = (Start() * info.fps.ToDouble()) + 1;
900  double timeline_frame_number = requested_frame + clip_start_position - clip_start_frame;
901 
902  // Correct the parent Clip Object properties by the clip's reference system
903  float parentObject_location_x = parentClipObject->location_x.GetValue(timeline_frame_number);
904  float parentObject_location_y = parentClipObject->location_y.GetValue(timeline_frame_number);
905  float parentObject_scale_x = parentClipObject->scale_x.GetValue(timeline_frame_number);
906  float parentObject_scale_y = parentClipObject->scale_y.GetValue(timeline_frame_number);
907  float parentObject_shear_x = parentClipObject->shear_x.GetValue(timeline_frame_number);
908  float parentObject_shear_y = parentClipObject->shear_y.GetValue(timeline_frame_number);
909  float parentObject_rotation = parentClipObject->rotation.GetValue(timeline_frame_number);
910 
911  // Add the parent Clip Object properties to JSON
912  root["location_x"] = add_property_json("Location X", parentObject_location_x, "float", "", &location_x, -1.0, 1.0, false, requested_frame);
913  root["location_y"] = add_property_json("Location Y", parentObject_location_y, "float", "", &location_y, -1.0, 1.0, false, requested_frame);
914  root["scale_x"] = add_property_json("Scale X", parentObject_scale_x, "float", "", &scale_x, 0.0, 1.0, false, requested_frame);
915  root["scale_y"] = add_property_json("Scale Y", parentObject_scale_y, "float", "", &scale_y, 0.0, 1.0, false, requested_frame);
916  root["rotation"] = add_property_json("Rotation", parentObject_rotation, "float", "", &rotation, -360, 360, false, requested_frame);
917  root["shear_x"] = add_property_json("Shear X", parentObject_shear_x, "float", "", &shear_x, -1.0, 1.0, false, requested_frame);
918  root["shear_y"] = add_property_json("Shear Y", parentObject_shear_y, "float", "", &shear_y, -1.0, 1.0, false, requested_frame);
919  }
920  else
921  {
922  // Add this own clip's properties to JSON
923  root["location_x"] = add_property_json("Location X", location_x.GetValue(requested_frame), "float", "", &location_x, -1.0, 1.0, false, requested_frame);
924  root["location_y"] = add_property_json("Location Y", location_y.GetValue(requested_frame), "float", "", &location_y, -1.0, 1.0, false, requested_frame);
925  root["scale_x"] = add_property_json("Scale X", scale_x.GetValue(requested_frame), "float", "", &scale_x, 0.0, 1.0, false, requested_frame);
926  root["scale_y"] = add_property_json("Scale Y", scale_y.GetValue(requested_frame), "float", "", &scale_y, 0.0, 1.0, false, requested_frame);
927  root["rotation"] = add_property_json("Rotation", rotation.GetValue(requested_frame), "float", "", &rotation, -360, 360, false, requested_frame);
928  root["shear_x"] = add_property_json("Shear X", shear_x.GetValue(requested_frame), "float", "", &shear_x, -1.0, 1.0, false, requested_frame);
929  root["shear_y"] = add_property_json("Shear Y", shear_y.GetValue(requested_frame), "float", "", &shear_y, -1.0, 1.0, false, requested_frame);
930  }
931 
932  // Keyframes
933  root["alpha"] = add_property_json("Alpha", alpha.GetValue(requested_frame), "float", "", &alpha, 0.0, 1.0, false, requested_frame);
934  root["corner_radius"] = add_property_json("Corner Radius", corner_radius.GetValue(requested_frame), "float", "", &corner_radius, 0.0, 0.5, false, requested_frame);
935  root["margin"] = add_property_json("Margin", margin.GetValue(requested_frame), "float", "", &margin, 0.0, 0.5, false, requested_frame);
936  root["origin_x"] = add_property_json("Origin X", origin_x.GetValue(requested_frame), "float", "", &origin_x, 0.0, 1.0, false, requested_frame);
937  root["origin_y"] = add_property_json("Origin Y", origin_y.GetValue(requested_frame), "float", "", &origin_y, 0.0, 1.0, false, requested_frame);
938  root["volume"] = add_property_json("Volume", volume.GetValue(requested_frame), "float", "", &volume, 0.0, 1.0, false, requested_frame);
939  root["time"] = add_property_json("Time", time.GetValue(requested_frame), "float", "", &time, 0.0, 30 * 60 * 60 * 48, false, requested_frame);
940  root["channel_filter"] = add_property_json("Channel Filter", channel_filter.GetValue(requested_frame), "int", "", &channel_filter, -1, 10, false, requested_frame);
941  root["channel_mapping"] = add_property_json("Channel Mapping", channel_mapping.GetValue(requested_frame), "int", "", &channel_mapping, -1, 10, false, requested_frame);
942  root["has_audio"] = add_property_json("Enable Audio", has_audio.GetValue(requested_frame), "int", "", &has_audio, -1, 1.0, false, requested_frame);
943  root["has_video"] = add_property_json("Enable Video", has_video.GetValue(requested_frame), "int", "", &has_video, -1, 1.0, false, requested_frame);
944 
945  // Add enable audio/video choices (dropdown style)
946  root["has_audio"]["choices"].append(add_property_choice_json("Auto", -1, has_audio.GetValue(requested_frame)));
947  root["has_audio"]["choices"].append(add_property_choice_json("Off", 0, has_audio.GetValue(requested_frame)));
948  root["has_audio"]["choices"].append(add_property_choice_json("On", 1, has_audio.GetValue(requested_frame)));
949  root["has_video"]["choices"].append(add_property_choice_json("Auto", -1, has_video.GetValue(requested_frame)));
950  root["has_video"]["choices"].append(add_property_choice_json("Off", 0, has_video.GetValue(requested_frame)));
951  root["has_video"]["choices"].append(add_property_choice_json("On", 1, has_video.GetValue(requested_frame)));
952 
953  root["wave_color"] = add_property_json("Wave Color", 0.0, "color", "", &wave_color.red, 0, 255, false, requested_frame);
954  root["wave_color"]["red"] = add_property_json("Red", wave_color.red.GetValue(requested_frame), "float", "", &wave_color.red, 0, 255, false, requested_frame);
955  root["wave_color"]["blue"] = add_property_json("Blue", wave_color.blue.GetValue(requested_frame), "float", "", &wave_color.blue, 0, 255, false, requested_frame);
956  root["wave_color"]["green"] = add_property_json("Green", wave_color.green.GetValue(requested_frame), "float", "", &wave_color.green, 0, 255, false, requested_frame);
957  root["wave_color"]["alpha"] = add_property_json("Alpha", wave_color.alpha.GetValue(requested_frame), "float", "", &wave_color.alpha, 0, 255, false, requested_frame);
958 
959  // Return formatted string
960  return root.toStyledString();
961 }
962 
963 // Generate Json::Value for this object
964 Json::Value Clip::JsonValue() const {
965 
966  // Create root json object
967  Json::Value root = ClipBase::JsonValue(); // get parent properties
968  root["parentObjectId"] = parentObjectId;
969  root["gravity"] = gravity;
970  root["scale"] = scale;
971  root["anchor"] = anchor;
972  root["display"] = display;
973  root["mixing"] = mixing;
974  root["composite"] = composite;
975  root["waveform"] = waveform;
976  root["waveform_mode"] = waveform_mode;
977  switch (reader_orientation_mode) {
978  case ReaderOrientationMode::LegacyClipTransform:
979  root["reader_orientation_mode"] = "legacy_clip_transform";
980  break;
981  case ReaderOrientationMode::Reader:
982  default:
983  root["reader_orientation_mode"] = "reader";
984  break;
985  }
986  root["scale_x"] = scale_x.JsonValue();
987  root["scale_y"] = scale_y.JsonValue();
988  root["location_x"] = location_x.JsonValue();
989  root["location_y"] = location_y.JsonValue();
990  root["alpha"] = alpha.JsonValue();
991  root["corner_radius"] = corner_radius.JsonValue();
992  root["margin"] = margin.JsonValue();
993  root["rotation"] = rotation.JsonValue();
994  root["time"] = time.JsonValue();
995  root["volume"] = volume.JsonValue();
996  root["wave_color"] = wave_color.JsonValue();
997  root["shear_x"] = shear_x.JsonValue();
998  root["shear_y"] = shear_y.JsonValue();
999  root["origin_x"] = origin_x.JsonValue();
1000  root["origin_y"] = origin_y.JsonValue();
1001  root["channel_filter"] = channel_filter.JsonValue();
1002  root["channel_mapping"] = channel_mapping.JsonValue();
1003  root["has_audio"] = has_audio.JsonValue();
1004  root["has_video"] = has_video.JsonValue();
1005  root["perspective_c1_x"] = perspective_c1_x.JsonValue();
1006  root["perspective_c1_y"] = perspective_c1_y.JsonValue();
1007  root["perspective_c2_x"] = perspective_c2_x.JsonValue();
1008  root["perspective_c2_y"] = perspective_c2_y.JsonValue();
1009  root["perspective_c3_x"] = perspective_c3_x.JsonValue();
1010  root["perspective_c3_y"] = perspective_c3_y.JsonValue();
1011  root["perspective_c4_x"] = perspective_c4_x.JsonValue();
1012  root["perspective_c4_y"] = perspective_c4_y.JsonValue();
1013 
1014  // Add array of effects
1015  root["effects"] = Json::Value(Json::arrayValue);
1016 
1017  // loop through effects
1018  for (auto existing_effect : effects)
1019  {
1020  root["effects"].append(existing_effect->JsonValue());
1021  }
1022 
1023  if (reader)
1024  root["reader"] = reader->JsonValue();
1025  else
1026  root["reader"] = Json::Value(Json::objectValue);
1027 
1028  // return JsonValue
1029  return root;
1030 }
1031 
1032 // Load JSON string into this object
1033 void Clip::SetJson(const std::string value) {
1034 
1035  // Parse JSON string into JSON objects
1036  try
1037  {
1038  const Json::Value root = openshot::stringToJson(value);
1039  // Set all values that match
1040  SetJsonValue(root);
1041  }
1042  catch (const std::exception& e)
1043  {
1044  // Error parsing JSON (or missing keys)
1045  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1046  }
1047 }
1048 
1049 // Load Json::Value into this object
1050 void Clip::SetJsonValue(const Json::Value root) {
1051  auto ensure_default_keyframe = [](Keyframe& kf, double default_value) {
1052  if (kf.GetCount() == 0) {
1053  kf = Keyframe(default_value);
1054  }
1055  };
1056 
1057  // Set parent data
1058  ClipBase::SetJsonValue(root);
1059 
1060  // Older project files predate reader-applied orientation metadata and stored
1061  // phone/camera rotation as ordinary clip rotation/scale keyframes.
1062  if (root["reader_orientation_mode"].isNull()) {
1063  reader_orientation_mode = ReaderOrientationMode::LegacyClipTransform;
1064  } else {
1065  const std::string mode = root["reader_orientation_mode"].asString();
1066  if (mode == "legacy_clip_transform") {
1067  reader_orientation_mode = ReaderOrientationMode::LegacyClipTransform;
1068  } else {
1069  reader_orientation_mode = ReaderOrientationMode::Reader;
1070  }
1071  }
1072 
1073  // Set data from Json (if key is found)
1074  if (!root["parentObjectId"].isNull()){
1075  parentObjectId = root["parentObjectId"].asString();
1076  if (parentObjectId.size() > 0 && parentObjectId != ""){
1077  AttachToObject(parentObjectId);
1078  } else{
1079  parentTrackedObject = nullptr;
1080  parentClipObject = NULL;
1081  }
1082  }
1083  if (!root["gravity"].isNull())
1084  gravity = (GravityType) root["gravity"].asInt();
1085  if (!root["scale"].isNull())
1086  scale = (ScaleType) root["scale"].asInt();
1087  if (!root["anchor"].isNull())
1088  anchor = (AnchorType) root["anchor"].asInt();
1089  if (!root["display"].isNull())
1090  display = (FrameDisplayType) root["display"].asInt();
1091  if (!root["mixing"].isNull())
1092  mixing = (VolumeMixType) root["mixing"].asInt();
1093  if (!root["composite"].isNull())
1094  composite = (CompositeType) root["composite"].asInt();
1095  if (!root["waveform"].isNull())
1096  waveform = root["waveform"].asBool();
1097  if (!root["waveform_mode"].isNull())
1098  waveform_mode = root["waveform_mode"].asInt();
1099  if (!root["scale_x"].isNull())
1100  scale_x.SetJsonValue(root["scale_x"]);
1101  if (!root["scale_y"].isNull())
1102  scale_y.SetJsonValue(root["scale_y"]);
1103  if (!root["location_x"].isNull())
1104  location_x.SetJsonValue(root["location_x"]);
1105  if (!root["location_y"].isNull())
1106  location_y.SetJsonValue(root["location_y"]);
1107  if (!root["alpha"].isNull())
1108  alpha.SetJsonValue(root["alpha"]);
1109  if (!root["corner_radius"].isNull())
1110  corner_radius.SetJsonValue(root["corner_radius"]);
1111  if (!root["margin"].isNull())
1112  margin.SetJsonValue(root["margin"]);
1113  if (!root["rotation"].isNull())
1114  rotation.SetJsonValue(root["rotation"]);
1115  if (!root["time"].isNull())
1116  time.SetJsonValue(root["time"]);
1117  if (!root["volume"].isNull())
1118  volume.SetJsonValue(root["volume"]);
1119  if (!root["wave_color"].isNull())
1120  wave_color.SetJsonValue(root["wave_color"]);
1121  if (!root["shear_x"].isNull())
1122  shear_x.SetJsonValue(root["shear_x"]);
1123  if (!root["shear_y"].isNull())
1124  shear_y.SetJsonValue(root["shear_y"]);
1125  if (!root["origin_x"].isNull())
1126  origin_x.SetJsonValue(root["origin_x"]);
1127  if (!root["origin_y"].isNull())
1128  origin_y.SetJsonValue(root["origin_y"]);
1129  if (!root["channel_filter"].isNull())
1130  channel_filter.SetJsonValue(root["channel_filter"]);
1131  if (!root["channel_mapping"].isNull())
1132  channel_mapping.SetJsonValue(root["channel_mapping"]);
1133  if (!root["has_audio"].isNull())
1134  has_audio.SetJsonValue(root["has_audio"]);
1135  if (!root["has_video"].isNull())
1136  has_video.SetJsonValue(root["has_video"]);
1137  if (!root["perspective_c1_x"].isNull())
1138  perspective_c1_x.SetJsonValue(root["perspective_c1_x"]);
1139  if (!root["perspective_c1_y"].isNull())
1140  perspective_c1_y.SetJsonValue(root["perspective_c1_y"]);
1141  if (!root["perspective_c2_x"].isNull())
1142  perspective_c2_x.SetJsonValue(root["perspective_c2_x"]);
1143  if (!root["perspective_c2_y"].isNull())
1144  perspective_c2_y.SetJsonValue(root["perspective_c2_y"]);
1145  if (!root["perspective_c3_x"].isNull())
1146  perspective_c3_x.SetJsonValue(root["perspective_c3_x"]);
1147  if (!root["perspective_c3_y"].isNull())
1148  perspective_c3_y.SetJsonValue(root["perspective_c3_y"]);
1149  if (!root["perspective_c4_x"].isNull())
1150  perspective_c4_x.SetJsonValue(root["perspective_c4_x"]);
1151  if (!root["perspective_c4_y"].isNull())
1152  perspective_c4_y.SetJsonValue(root["perspective_c4_y"]);
1153 
1154  // Core clip transforms should never remain empty after load. Empty JSON
1155  // point arrays can be produced by editing flows that remove every keyframe.
1156  ensure_default_keyframe(scale_x, 1.0);
1157  ensure_default_keyframe(scale_y, 1.0);
1158  ensure_default_keyframe(location_x, 0.0);
1159  ensure_default_keyframe(location_y, 0.0);
1160  ensure_default_keyframe(origin_x, 0.5);
1161  ensure_default_keyframe(origin_y, 0.5);
1162  ensure_default_keyframe(rotation, 0.0);
1163  ensure_default_keyframe(corner_radius, 0.0);
1164  ensure_default_keyframe(margin, 0.0);
1165  if (!root["effects"].isNull()) {
1166 
1167  // Clear existing effects
1168  effects.clear();
1169 
1170  // loop through effects
1171  for (const auto existing_effect : root["effects"]) {
1172  // Skip NULL nodes
1173  if (existing_effect.isNull()) {
1174  continue;
1175  }
1176 
1177  // Create Effect
1178  EffectBase *e = NULL;
1179  if (!existing_effect["type"].isNull()) {
1180 
1181  // Create instance of effect
1182  if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString()))) {
1183 
1184  // Load Json into Effect
1185  e->SetJsonValue(existing_effect);
1186 
1187  // Add Effect to Timeline
1188  AddEffect(e);
1189  }
1190  }
1191  }
1192  }
1193  if (!root["reader"].isNull()) // does Json contain a reader?
1194  {
1195  if (!root["reader"]["type"].isNull()) // does the reader Json contain a 'type'?
1196  {
1197  // Close previous reader (if any)
1198  bool already_open = false;
1199  if (reader)
1200  {
1201  // Track if reader was open
1202  already_open = reader->IsOpen();
1203 
1204  // Close and delete existing allocated reader (if any)
1205  Reader(NULL);
1206  }
1207 
1208  // Create new reader (and load properties)
1209  std::string type = root["reader"]["type"].asString();
1210 
1211  if (type == "FFmpegReader") {
1212 
1213  // Create new reader
1214  reader = new openshot::FFmpegReader(root["reader"]["path"].asString(), false);
1215  reader->SetJsonValue(root["reader"]);
1216 
1217  } else if (type == "QtImageReader") {
1218 
1219  // Create new reader
1220  reader = new openshot::QtImageReader(root["reader"]["path"].asString(), false);
1221  reader->SetJsonValue(root["reader"]);
1222 
1223 #ifdef USE_IMAGEMAGICK
1224  } else if (type == "ImageReader") {
1225 
1226  // Create new reader
1227  reader = new ImageReader(root["reader"]["path"].asString(), false);
1228  reader->SetJsonValue(root["reader"]);
1229 
1230  } else if (type == "TextReader") {
1231 
1232  // Create new reader
1233  reader = new TextReader();
1234  reader->SetJsonValue(root["reader"]);
1235 #endif
1236 
1237  } else if (type == "ChunkReader") {
1238 
1239  // Create new reader
1240  reader = new openshot::ChunkReader(root["reader"]["path"].asString(), (ChunkVersion) root["reader"]["chunk_version"].asInt());
1241  reader->SetJsonValue(root["reader"]);
1242 
1243  } else if (type == "DummyReader") {
1244 
1245  // Create new reader
1246  reader = new openshot::DummyReader();
1247  reader->SetJsonValue(root["reader"]);
1248 
1249  } else if (type == "Timeline") {
1250 
1251  // Create new reader (always load from file again)
1252  // This prevents FrameMappers from being loaded on accident
1253  reader = new openshot::Timeline(root["reader"]["path"].asString(), true);
1254  }
1255 
1256  // mark as managed reader and set parent
1257  if (reader) {
1258  reader->ApplyOrientationMetadata(reader_orientation_mode == ReaderOrientationMode::Reader);
1259  reader->ParentClip(this);
1260  allocated_reader = reader;
1261  }
1262 
1263  // Re-Open reader (if needed)
1264  if (already_open) {
1265  reader->Open();
1266  }
1267  }
1268  }
1269 
1270  // Clear cache (it might have changed)
1271  final_cache.Clear();
1272 }
1273 
1274 // Sort effects by order
1275 void Clip::sort_effects()
1276 {
1277  // sort clips
1278  effects.sort(CompareClipEffects());
1279 }
1280 
1281 // Add an effect to the clip
1283 {
1284  // Set parent clip pointer
1285  effect->ParentClip(this);
1286 
1287  // Add effect to list
1288  effects.push_back(effect);
1289 
1290  // Sort effects
1291  sort_effects();
1292 
1293  // Get the parent timeline of this clip
1294  Timeline* parentTimeline = static_cast<Timeline *>(ParentTimeline());
1295 
1296  if (parentTimeline)
1297  effect->ParentTimeline(parentTimeline);
1298 
1299  #ifdef USE_OPENCV
1300  // Add Tracked Object to Timeline
1301  if (effect->info.has_tracked_object){
1302 
1303  // Check if this clip has a parent timeline
1304  if (parentTimeline){
1305 
1306  effect->ParentTimeline(parentTimeline);
1307 
1308  // Iterate through effect's vector of Tracked Objects
1309  for (auto const& trackedObject : effect->trackedObjects){
1310 
1311  // Cast the Tracked Object as TrackedObjectBBox
1312  std::shared_ptr<TrackedObjectBBox> trackedObjectBBox = std::static_pointer_cast<TrackedObjectBBox>(trackedObject.second);
1313 
1314  // Set the Tracked Object's parent clip to this
1315  trackedObjectBBox->ParentClip(this);
1316 
1317  // Add the Tracked Object to the timeline
1318  parentTimeline->AddTrackedObject(trackedObjectBBox);
1319  }
1320  }
1321  }
1322  #endif
1323 
1324  // Clear cache (it might have changed)
1325  final_cache.Clear();
1326 }
1327 
1328 // Remove an effect from the clip
1330 {
1331  effects.remove(effect);
1332 
1333  // Clear cache (it might have changed)
1334  final_cache.Clear();
1335 }
1336 
1337 // Apply background image to the current clip image (i.e. flatten this image onto previous layer)
1338 void Clip::apply_background(std::shared_ptr<openshot::Frame> frame,
1339  std::shared_ptr<openshot::Frame> background_frame,
1340  bool update_frame_image) {
1341  // Add background canvas
1342  std::shared_ptr<QImage> background_canvas = background_frame->GetImage();
1343  QPainter painter(background_canvas.get());
1344 
1345  // Composite a new layer onto the image
1346  painter.setCompositionMode(static_cast<QPainter::CompositionMode>(composite));
1347  painter.drawImage(0, 0, *frame->GetImage());
1348  painter.end();
1349 
1350  // Standalone clip requests update frame->image, but timeline composition
1351  // draws onto the timeline-owned background frame only.
1352  if (update_frame_image)
1353  frame->AddImage(background_canvas);
1354 }
1355 
1356 // Apply effects to the source frame (if any)
1357 void Clip::apply_effects(std::shared_ptr<Frame> frame, int64_t timeline_frame_number, TimelineInfoStruct* options, bool before_keyframes)
1358 {
1359  for (auto effect : effects)
1360  {
1361  // Apply the effect to this frame
1362  if (effect->info.apply_before_clip && before_keyframes) {
1363  effect->ProcessFrame(frame, frame->number);
1364  } else if (!effect->info.apply_before_clip && !before_keyframes) {
1365  effect->ProcessFrame(frame, frame->number);
1366  }
1367  }
1368 
1369  if (timeline != NULL && options != NULL) {
1370  // Apply global timeline effects (i.e. transitions & masks... if any)
1371  Timeline* timeline_instance = static_cast<Timeline*>(timeline);
1372  options->is_before_clip_keyframes = before_keyframes;
1373  timeline_instance->apply_effects(frame, timeline_frame_number, Layer(), options);
1374  }
1375 }
1376 
1377 // Compare 2 floating point numbers for equality
1378 bool Clip::isNear(double a, double b)
1379 {
1380  return fabs(a - b) < 0.000001;
1381 }
1382 
1383 // Apply keyframes to the source frame (if any)
1384 void Clip::apply_keyframes(std::shared_ptr<Frame> frame, QSize timeline_size) {
1385  // Skip out if video was disabled or only an audio frame (no visualisation in use)
1386  if (!frame->has_image_data) {
1387  // Skip the rest of the image processing for performance reasons
1388  return;
1389  }
1390 
1391  // Get image from clip, and create transparent background image
1392  std::shared_ptr<QImage> source_image = frame->GetImage();
1393  std::shared_ptr<QImage> background_canvas = std::make_shared<QImage>(timeline_size.width(),
1394  timeline_size.height(),
1395  QImage::Format_RGBA8888_Premultiplied);
1396  background_canvas->fill(QColor(Qt::transparent));
1397 
1398  // Get transform from clip's keyframes
1399  QTransform transform = get_transform(frame, background_canvas->width(), background_canvas->height());
1400 
1401  // Load timeline's new frame image into a QPainter
1402  QPainter painter(background_canvas.get());
1403  painter.setRenderHint(QPainter::TextAntialiasing, true);
1404  if (!transform.isIdentity()) {
1405  painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
1406  }
1407  // Apply transform (translate, rotate, scale)
1408  painter.setTransform(transform);
1409 
1410  // Composite a new layer onto the image
1411  painter.setCompositionMode(static_cast<QPainter::CompositionMode>(composite));
1412 
1413  // Apply opacity via painter instead of per-pixel alpha manipulation
1414  const float alpha_value = alpha.GetValue(frame->number);
1415  const double corner_radius_value = std::max(0.0, std::min(0.5, corner_radius.GetValue(frame->number)));
1416  if (corner_radius_value > 0.0f) {
1417  painter.setRenderHint(QPainter::Antialiasing, true);
1418  const double radius_pixels = corner_radius_value
1419  * std::min(source_image->width(), source_image->height());
1420  QPainterPath clip_path;
1421  clip_path.addRoundedRect(
1422  QRectF(0, 0, source_image->width(), source_image->height()),
1423  radius_pixels,
1424  radius_pixels);
1425  painter.setClipPath(clip_path);
1426  }
1427  if (alpha_value != 1.0f) {
1428  painter.setOpacity(alpha_value);
1429  painter.drawImage(0, 0, *source_image);
1430  // Reset so any subsequent drawing (e.g., overlays) isn’t faded
1431  painter.setOpacity(1.0);
1432  } else {
1433  painter.drawImage(0, 0, *source_image);
1434  }
1435 
1436  if (timeline) {
1437  Timeline *t = static_cast<Timeline *>(timeline);
1438 
1439  // Draw frame #'s on top of image (if needed)
1440  if (display != FRAME_DISPLAY_NONE) {
1441  std::stringstream frame_number_str;
1442  switch (display) {
1443  case (FRAME_DISPLAY_NONE):
1444  // This is only here to prevent unused-enum warnings
1445  break;
1446 
1447  case (FRAME_DISPLAY_CLIP):
1448  frame_number_str << frame->number;
1449  break;
1450 
1451  case (FRAME_DISPLAY_TIMELINE):
1452  frame_number_str << round((Position() - Start()) * t->info.fps.ToFloat()) + frame->number;
1453  break;
1454 
1455  case (FRAME_DISPLAY_BOTH):
1456  frame_number_str << round((Position() - Start()) * t->info.fps.ToFloat()) + frame->number << " (" << frame->number << ")";
1457  break;
1458  }
1459 
1460  // Draw frame number on top of image
1461  painter.setPen(QColor("#ffffff"));
1462  painter.drawText(20, 20, QString(frame_number_str.str().c_str()));
1463  }
1464  }
1465  painter.end();
1466 
1467  // Add new QImage to frame
1468  frame->AddImage(background_canvas);
1469 }
1470 
1471 // Apply apply_waveform image to the source frame (if any)
1472 void Clip::apply_waveform(std::shared_ptr<Frame> frame, QSize timeline_size) {
1473 
1474  if (!Waveform()) {
1475  // Exit if no waveform is needed
1476  return;
1477  }
1478 
1479  // Debug output
1480  ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_waveform (Generate Waveform Image)",
1481  "frame->number", frame->number,
1482  "Waveform()", Waveform(),
1483  "width", timeline_size.width(),
1484  "height", timeline_size.height());
1485 
1486  // Get the color of the waveform
1487  int red = wave_color.red.GetInt(frame->number);
1488  int green = wave_color.green.GetInt(frame->number);
1489  int blue = wave_color.blue.GetInt(frame->number);
1490  int alpha = wave_color.alpha.GetInt(frame->number);
1491 
1492  // Render the waveform through the audio visualization effect so clip shortcuts
1493  // and explicit effects share the same rendering path.
1494  auto visual_frame = std::make_shared<Frame>(*frame.get());
1495  visual_frame->AddImage(std::make_shared<QImage>(
1496  timeline_size.width(), timeline_size.height(), QImage::Format_RGBA8888_Premultiplied));
1497  visual_frame->GetImage()->fill(Qt::transparent);
1498 
1499  AudioVisualization visualization;
1500  visualization.visualization_type = waveform_mode;
1501  visualization.style = AUDIO_VISUALIZATION_STYLE_MINIMAL;
1502  visualization.color = Color(
1503  static_cast<unsigned char>(red),
1504  static_cast<unsigned char>(green),
1505  static_cast<unsigned char>(blue),
1506  static_cast<unsigned char>(alpha));
1507  visualization.intensity = Keyframe(1.0);
1508  visualization.smoothing = Keyframe(0.25);
1509  visualization.detail = Keyframe(0.75);
1510  visualization.glow = Keyframe(0.0);
1511  visualization.color_spread = Keyframe(0.0);
1514  visualization.frequency_low = Keyframe(0.0);
1515  visualization.frequency_high = Keyframe(1.0);
1517  visualization.GetFrame(visual_frame, frame->number);
1518 
1519  frame->AddImage(visual_frame->GetImage());
1520 }
1521 
1522 // Scale a source size to a target size (given a specific scale-type)
1523 QSize Clip::scale_size(QSize source_size, ScaleType source_scale, int target_width, int target_height) {
1524  switch (source_scale)
1525  {
1526  case (SCALE_FIT): {
1527  source_size.scale(target_width, target_height, Qt::KeepAspectRatio);
1528  break;
1529  }
1530  case (SCALE_STRETCH): {
1531  source_size.scale(target_width, target_height, Qt::IgnoreAspectRatio);
1532  break;
1533  }
1534  case (SCALE_CROP): {
1535  source_size.scale(target_width, target_height, Qt::KeepAspectRatioByExpanding);;
1536  break;
1537  }
1538  }
1539 
1540  return source_size;
1541 }
1542 
1543 // Get QTransform from keyframes
1544 QTransform Clip::get_transform(std::shared_ptr<Frame> frame, int width, int height)
1545 {
1546  // Get image from clip
1547  std::shared_ptr<QImage> source_image = frame->GetImage();
1548 
1549  const double margin_value = std::max(0.0, std::min(0.5, margin.GetValue(frame->number)));
1550  const double margin_pixels = margin_value * std::min(width, height);
1551  const double layout_x = margin_pixels;
1552  const double layout_y = margin_pixels;
1553  const double layout_width = std::max(1.0, width - (margin_pixels * 2.0));
1554  const double layout_height = std::max(1.0, height - (margin_pixels * 2.0));
1555 
1556  /* RESIZE SOURCE IMAGE - based on scale type */
1557  QSize source_size = scale_size(source_image->size(), scale, layout_width, layout_height);
1558 
1559  // Initialize parent object's properties (Clip or Tracked Object)
1560  float parentObject_location_x = 0.0;
1561  float parentObject_location_y = 0.0;
1562  float parentObject_scale_x = 1.0;
1563  float parentObject_scale_y = 1.0;
1564  float parentObject_shear_x = 0.0;
1565  float parentObject_shear_y = 0.0;
1566  float parentObject_rotation = 0.0;
1567 
1568  // Get the parentClipObject properties
1569  if (GetParentClip()){
1570  // Get the start trim position of the parent clip
1571  long parent_start_offset = parentClipObject->Start() * info.fps.ToDouble();
1572  long parent_frame_number = frame->number + parent_start_offset;
1573 
1574  // Get parent object's properties (Clip)
1575  parentObject_location_x = parentClipObject->location_x.GetValue(parent_frame_number);
1576  parentObject_location_y = parentClipObject->location_y.GetValue(parent_frame_number);
1577  parentObject_scale_x = parentClipObject->scale_x.GetValue(parent_frame_number);
1578  parentObject_scale_y = parentClipObject->scale_y.GetValue(parent_frame_number);
1579  parentObject_shear_x = parentClipObject->shear_x.GetValue(parent_frame_number);
1580  parentObject_shear_y = parentClipObject->shear_y.GetValue(parent_frame_number);
1581  parentObject_rotation = parentClipObject->rotation.GetValue(parent_frame_number);
1582  }
1583 
1584  // Get the parentTrackedObject properties
1585  if (GetParentTrackedObject()){
1586  // Get the attached object's parent clip's properties
1587  Clip* parentClip = (Clip*) parentTrackedObject->ParentClip();
1588  if (parentClip)
1589  {
1590  // Get the start trim position of the parent clip
1591  long parent_start_offset = parentClip->Start() * info.fps.ToDouble();
1592  long parent_frame_number = frame->number + parent_start_offset;
1593 
1594  // Access the parentTrackedObject's properties
1595  std::map<std::string, float> trackedObjectProperties = parentTrackedObject->GetBoxValues(parent_frame_number);
1596 
1597  // Get actual scaled parent size
1598  QSize parent_size = scale_size(QSize(parentClip->info.width, parentClip->info.height),
1599  parentClip->scale, width, height);
1600 
1601  // Get actual scaled tracked object size
1602  int trackedWidth = trackedObjectProperties["w"] * trackedObjectProperties["sx"] * parent_size.width() *
1603  parentClip->scale_x.GetValue(parent_frame_number);
1604  int trackedHeight = trackedObjectProperties["h"] * trackedObjectProperties["sy"] * parent_size.height() *
1605  parentClip->scale_y.GetValue(parent_frame_number);
1606 
1607  // Scale the clip source_size based on the actual tracked object size
1608  source_size = scale_size(source_size, scale, trackedWidth, trackedHeight);
1609 
1610  // Update parentObject's properties based on the tracked object's properties and parent clip's scale
1611  parentObject_location_x = parentClip->location_x.GetValue(parent_frame_number) + ((trackedObjectProperties["cx"] - 0.5) * parentClip->scale_x.GetValue(parent_frame_number));
1612  parentObject_location_y = parentClip->location_y.GetValue(parent_frame_number) + ((trackedObjectProperties["cy"] - 0.5) * parentClip->scale_y.GetValue(parent_frame_number));
1613  parentObject_rotation = trackedObjectProperties["r"] + parentClip->rotation.GetValue(parent_frame_number);
1614  }
1615  }
1616 
1617  /* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */
1618  float x = 0.0; // left
1619  float y = 0.0; // top
1620 
1621  // Adjust size for scale x and scale y
1622  float sx = scale_x.GetValue(frame->number); // percentage X scale
1623  float sy = scale_y.GetValue(frame->number); // percentage Y scale
1624 
1625  // Change clip's scale to parentObject's scale
1626  if(parentObject_scale_x != 0.0 && parentObject_scale_y != 0.0){
1627  sx*= parentObject_scale_x;
1628  sy*= parentObject_scale_y;
1629  }
1630 
1631  float scaled_source_width = source_size.width() * sx;
1632  float scaled_source_height = source_size.height() * sy;
1633 
1634  switch (gravity)
1635  {
1636  case (GRAVITY_TOP_LEFT):
1637  x = layout_x;
1638  y = layout_y;
1639  // This is only here to prevent unused-enum warnings
1640  break;
1641  case (GRAVITY_TOP):
1642  x = layout_x + ((layout_width - scaled_source_width) / 2.0); // center
1643  y = layout_y;
1644  break;
1645  case (GRAVITY_TOP_RIGHT):
1646  x = layout_x + layout_width - scaled_source_width; // right
1647  y = layout_y;
1648  break;
1649  case (GRAVITY_LEFT):
1650  x = layout_x;
1651  y = layout_y + ((layout_height - scaled_source_height) / 2.0); // center
1652  break;
1653  case (GRAVITY_CENTER):
1654  x = layout_x + ((layout_width - scaled_source_width) / 2.0); // center
1655  y = layout_y + ((layout_height - scaled_source_height) / 2.0); // center
1656  break;
1657  case (GRAVITY_RIGHT):
1658  x = layout_x + layout_width - scaled_source_width; // right
1659  y = layout_y + ((layout_height - scaled_source_height) / 2.0); // center
1660  break;
1661  case (GRAVITY_BOTTOM_LEFT):
1662  x = layout_x;
1663  y = layout_y + (layout_height - scaled_source_height); // bottom
1664  break;
1665  case (GRAVITY_BOTTOM):
1666  x = layout_x + ((layout_width - scaled_source_width) / 2.0); // center
1667  y = layout_y + (layout_height - scaled_source_height); // bottom
1668  break;
1669  case (GRAVITY_BOTTOM_RIGHT):
1670  x = layout_x + layout_width - scaled_source_width; // right
1671  y = layout_y + (layout_height - scaled_source_height); // bottom
1672  break;
1673  }
1674 
1675  // Debug output
1677  "Clip::get_transform (Gravity)",
1678  "frame->number", frame->number,
1679  "source_clip->gravity", gravity,
1680  "scaled_source_width", scaled_source_width,
1681  "scaled_source_height", scaled_source_height);
1682 
1683  QTransform transform;
1684 
1685  /* LOCATION, ROTATION, AND SCALE */
1686  float r = rotation.GetValue(frame->number) + parentObject_rotation; // rotate in degrees
1687  float location_x_value = location_x.GetValue(frame->number) + parentObject_location_x;
1688  float location_y_value = location_y.GetValue(frame->number) + parentObject_location_y;
1689  auto location_offset = [](float location, float anchored_position, float canvas_size, float clip_size) {
1690  if (location < 0.0f) {
1691  return location * (anchored_position + clip_size);
1692  }
1693  return location * (canvas_size - anchored_position);
1694  };
1695  x += location_offset(location_x_value, x - layout_x, layout_width, scaled_source_width);
1696  y += location_offset(location_y_value, y - layout_y, layout_height, scaled_source_height);
1697  float shear_x_value = shear_x.GetValue(frame->number) + parentObject_shear_x;
1698  float shear_y_value = shear_y.GetValue(frame->number) + parentObject_shear_y;
1699  float origin_x_value = origin_x.GetValue(frame->number);
1700  float origin_y_value = origin_y.GetValue(frame->number);
1701 
1702  // Transform source image (if needed)
1704  "Clip::get_transform (Build QTransform - if needed)",
1705  "frame->number", frame->number,
1706  "x", x, "y", y,
1707  "r", r,
1708  "sx", sx, "sy", sy);
1709 
1710  if (!isNear(x, 0) || !isNear(y, 0)) {
1711  // TRANSLATE/MOVE CLIP
1712  transform.translate(x, y);
1713  }
1714  if (!isNear(r, 0) || !isNear(shear_x_value, 0) || !isNear(shear_y_value, 0)) {
1715  // ROTATE CLIP (around origin_x, origin_y)
1716  float origin_x_offset = (scaled_source_width * origin_x_value);
1717  float origin_y_offset = (scaled_source_height * origin_y_value);
1718  transform.translate(origin_x_offset, origin_y_offset);
1719  transform.rotate(r);
1720  transform.shear(shear_x_value, shear_y_value);
1721  transform.translate(-origin_x_offset,-origin_y_offset);
1722  }
1723  // SCALE CLIP (if needed)
1724  float source_width_scale = (float(source_size.width()) / float(source_image->width())) * sx;
1725  float source_height_scale = (float(source_size.height()) / float(source_image->height())) * sy;
1726  if (!isNear(source_width_scale, 1.0) || !isNear(source_height_scale, 1.0)) {
1727  transform.scale(source_width_scale, source_height_scale);
1728  }
1729 
1730  return transform;
1731 }
1732 
1733 // Adjust frame number for Clip position and start (which can result in a different number)
1734 int64_t Clip::adjust_timeline_framenumber(int64_t clip_frame_number) {
1735 
1736  // Get clip position from parent clip (if any)
1737  float position = 0.0;
1738  float start = 0.0;
1739  Clip *parent = static_cast<Clip *>(ParentClip());
1740  if (parent) {
1741  position = parent->Position();
1742  start = parent->Start();
1743  }
1744 
1745  // Adjust start frame and position based on parent clip.
1746  // This ensures the same frame # is used by mapped readers and clips,
1747  // when calculating samples per frame.
1748  // Thus, this prevents gaps and mismatches in # of samples.
1749  int64_t clip_start_frame = (start * info.fps.ToDouble()) + 1;
1750  int64_t clip_start_position = round(position * info.fps.ToDouble()) + 1;
1751  int64_t frame_number = clip_frame_number + clip_start_position - clip_start_frame;
1752 
1753  return frame_number;
1754 }
Header file for AudioResampler class.
Header file for AudioVisualization effect class.
Header file for ChunkReader class.
Header file for Clip class.
Header file for DummyReader class.
Header file for all Exception classes.
Header file for FFmpegReader class.
Header file for the FrameMapper class.
Header file for ImageReader class.
Header file for MagickUtilities (IM6/IM7 compatibility overlay)
Header file for QtImageReader class.
Header file for TextReader class.
Header file for Timeline class.
Header file for ZeroMQ-based Logger class.
This class is used to resample audio data for many sequential frames.
void SetBuffer(juce::AudioBuffer< float > *new_buffer, double sample_rate, double new_sample_rate)
Sets the audio buffer and key settings.
juce::AudioBuffer< float > * GetResampledBuffer()
Get the resampled audio buffer.
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
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
void Clear()
Clear the cache of all frames.
This class reads a special chunk-formatted file, which can be easily shared in a distributed environm...
Definition: ChunkReader.h:79
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:88
float start
The position in seconds to start playing (used to trim the beginning of a clip)
Definition: ClipBase.h:37
float Duration() const
Get the length of this clip (in seconds)
Definition: ClipBase.h:90
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
Definition: ClipBase.h:89
std::string Id() const
Get the Id of this clip object.
Definition: ClipBase.h:85
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ClipBase.cpp:64
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Definition: ClipBase.cpp:132
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
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
Definition: ClipBase.h:40
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
float position
The position on the timeline where this clip should start playing.
Definition: ClipBase.h:35
float end
The position in seconds to end playing (used to trim the ending of a clip)
Definition: ClipBase.h:38
std::string previous_properties
This string contains the previous JSON properties.
Definition: ClipBase.h:39
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
Definition: ClipBase.cpp:96
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
void SetAttachedObject(std::shared_ptr< openshot::TrackedObjectBase > trackedObject)
Set the pointer to the trackedObject this clip is attached to.
Definition: Clip.cpp:333
openshot::Keyframe scale_x
Curve representing the horizontal scaling in percent (0 to 1)
Definition: Clip.h:331
openshot::Keyframe location_y
Curve representing the relative Y position in percent based on the gravity (-1 to 1)
Definition: Clip.h:334
openshot::Keyframe shear_x
Curve representing X shear angle in degrees (-45.0=left, 45.0=right)
Definition: Clip.h:341
openshot::Keyframe perspective_c4_x
Curves representing X for coordinate 4.
Definition: Clip.h:360
openshot::AnchorType anchor
The anchor determines what parent a clip should snap to.
Definition: Clip.h:187
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
Definition: Clip.h:189
void Open() override
Open the internal reader.
Definition: Clip.cpp:390
openshot::Keyframe rotation
Curve representing the rotation (0 to 360)
Definition: Clip.h:340
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
Definition: Clip.h:364
openshot::FrameDisplayType display
The format to display the frame number (if any)
Definition: Clip.h:188
void init_reader_rotation()
Update default rotation from reader.
Definition: Clip.cpp:154
Clip()
Default Constructor.
Definition: Clip.cpp:210
openshot::Keyframe perspective_c1_x
Curves representing X for coordinate 1.
Definition: Clip.h:354
void AttachToObject(std::string object_id)
Attach clip to Tracked Object or to another Clip.
Definition: Clip.cpp:310
std::string Json() const override
Generate JSON string of this object.
Definition: Clip.cpp:821
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:307
openshot::EffectBase * GetEffect(const std::string &id)
Look up an effect by ID.
Definition: Clip.cpp:543
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Clip.cpp:1050
openshot::Keyframe alpha
Curve representing the alpha (1 to 0)
Definition: Clip.h:335
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 perspective_c3_x
Curves representing X for coordinate 3.
Definition: Clip.h:358
void init_reader_settings()
Init reader info details.
Definition: Clip.cpp:141
openshot::Keyframe perspective_c1_y
Curves representing Y for coordinate 1.
Definition: Clip.h:355
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Clip.cpp:964
void SetAttachedClip(Clip *clipObject)
Set the pointer to the clip this clip is attached to.
Definition: Clip.cpp:338
openshot::Keyframe perspective_c4_y
Curves representing Y for coordinate 4.
Definition: Clip.h:361
openshot::Keyframe time
Curve representing the frames over time to play (used for speed and direction of video)
Definition: Clip.h:347
openshot::Clip * GetParentClip()
Return the associated ParentClip (if any)
Definition: Clip.cpp:555
bool Waveform()
Get the waveform property of this clip.
Definition: Clip.h:325
openshot::CompositeType composite
How this clip is composited onto lower layers.
Definition: Clip.h:190
openshot::GravityType gravity
The gravity of a clip determines where it snaps to its parent.
Definition: Clip.h:185
AudioLocation previous_location
Previous time-mapped audio location.
Definition: Clip.h:95
openshot::Keyframe perspective_c3_y
Curves representing Y for coordinate 3.
Definition: Clip.h:359
std::shared_ptr< openshot::TrackedObjectBase > GetParentTrackedObject()
Return the associated Parent Tracked Object (if any)
Definition: Clip.cpp:564
void AddEffect(openshot::EffectBase *effect)
Add an effect to the clip.
Definition: Clip.cpp:1282
void Close() override
Close the internal reader.
Definition: Clip.cpp:411
virtual ~Clip()
Destructor.
Definition: Clip.cpp:290
openshot::Keyframe perspective_c2_y
Curves representing Y for coordinate 2.
Definition: Clip.h:357
openshot::Keyframe volume
Curve representing the volume (0 to 1)
Definition: Clip.h:348
openshot::Keyframe shear_y
Curve representing Y shear angle in degrees (-45.0=down, 45.0=up)
Definition: Clip.h:342
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:332
float End() const override
Get end position (in seconds) of clip (trim end of video), which can be affected by the time curve.
Definition: Clip.cpp:426
std::shared_ptr< openshot::Frame > GetFrame(int64_t clip_frame_number) override
Get an openshot::Frame object for a specific frame number of this clip. The image size and number of ...
Definition: Clip.cpp:461
openshot::ReaderBase * Reader()
Get the current reader.
Definition: Clip.cpp:380
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the clip.
Definition: Clip.cpp:1329
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
Definition: Clip.h:365
openshot::Keyframe has_video
An optional override to determine if this clip has video (-1=undefined, 0=no, 1=yes)
Definition: Clip.h:369
openshot::Keyframe corner_radius
Curve representing corner radius as a percent of the clip's shortest side.
Definition: Clip.h:337
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Clip.cpp:828
openshot::Color wave_color
Curve representing the color of the audio wave form.
Definition: Clip.h:351
void init_settings()
Init default settings for a clip.
Definition: Clip.cpp:71
openshot::Keyframe perspective_c2_x
Curves representing X for coordinate 2.
Definition: Clip.h:356
static openshot::ReaderBase * CreateReader(std::string path, bool inspect_reader=true)
Definition: Clip.cpp:252
openshot::Keyframe margin
Curve representing edge margin as a percent of the canvas' shortest side.
Definition: Clip.h:336
openshot::ScaleType scale
The scale determines how a clip should be resized to fit its parent.
Definition: Clip.h:186
openshot::Keyframe location_x
Curve representing the relative X position in percent based on the gravity (-1 to 1)
Definition: Clip.h:333
openshot::Keyframe origin_x
Curve representing X origin point (0.0=0% (left), 1.0=100% (right))
Definition: Clip.h:343
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: Clip.h:92
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Clip.cpp:1033
openshot::Keyframe origin_y
Curve representing Y origin point (0.0=0% (top), 1.0=100% (bottom))
Definition: Clip.h:344
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
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
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition: Color.h:33
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:86
This class is used as a simple, dummy reader, which can be very useful when writing unit tests....
Definition: DummyReader.h:86
This abstract class is the base class, used by all effects in libopenshot.
Definition: EffectBase.h:57
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:676
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:146
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
std::map< int, std::shared_ptr< openshot::TrackedObjectBase > > trackedObjects
Map of Tracked Object's by their indices (used by Effects that track objects on clips)
Definition: EffectBase.h:111
This class returns a listing of all effects supported by libopenshot.
Definition: EffectInfo.h:29
This class uses the FFmpeg libraries, to open video files and audio files, and return openshot::Frame...
Definition: FFmpegReader.h:103
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
This class creates a mapping between 2 different frame rates, applying a specific pull-down technique...
Definition: FrameMapper.h:193
ReaderBase * Reader()
Get the current reader.
Definition: FrameMapper.cpp:67
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
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Definition: ImageReader.h:56
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 GetDelta(int64_t index) const
Get the change in Y value (from the previous Y value)
Definition: KeyFrame.cpp:399
int64_t GetLength() const
Definition: KeyFrame.cpp:417
int64_t GetLong(int64_t index) const
Get the rounded LONG value at a specific index.
Definition: KeyFrame.cpp:287
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
bool IsIncreasing(int index) const
Get the direction of the curve at a specific index (increasing or decreasing)
Definition: KeyFrame.cpp:292
int64_t GetCount() const
Get the number of points (i.e. # of points)
Definition: KeyFrame.cpp:424
Exception for frames that are out of bounds.
Definition: Exceptions.h:307
This class uses the Qt library, to open image files, and return openshot::Frame objects containing th...
Definition: QtImageReader.h:75
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:76
virtual bool IsOpen()=0
Determine if reader is open or closed.
virtual std::string Name()=0
Return the type name of the class.
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
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
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
void ApplyOrientationMetadata(bool value)
Set whether readers should apply source orientation metadata to returned frames.
Definition: ReaderBase.cpp:270
virtual void Open()=0
Open the reader (and start consuming resources, such as images or video files)
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
virtual void Close()=0
Close the reader (and any resources it was consuming)
Exception when a reader is closed, and a frame is requested.
Definition: Exceptions.h:370
This class uses the ImageMagick++ libraries, to create frames with "Text", and return openshot::Frame...
Definition: TextReader.h:63
This class represents a timeline (used for building generic timeline implementations)
Definition: TimelineBase.h:41
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
std::shared_ptr< openshot::TrackedObjectBase > GetTrackedObject(std::string id) const
Return tracked object pointer by it's id.
Definition: Timeline.cpp:247
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
Definition: Timeline.cpp:420
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 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
@ AUDIO_VISUALIZATION_CHANNEL_AUTO
AnchorType
This enumeration determines what parent a clip should be aligned to.
Definition: Enums.h:45
@ ANCHOR_CANVAS
Anchor the clip to the canvas.
Definition: Enums.h:46
ChunkVersion
This enumeration allows the user to choose which version of the chunk they would like (low,...
Definition: ChunkReader.h:50
GravityType
This enumeration determines how clips are aligned to their parent container.
Definition: Enums.h:22
@ GRAVITY_TOP_LEFT
Align clip to the top left of its parent.
Definition: Enums.h:23
@ GRAVITY_LEFT
Align clip to the left of its parent (middle aligned)
Definition: Enums.h:26
@ GRAVITY_TOP_RIGHT
Align clip to the top right of its parent.
Definition: Enums.h:25
@ GRAVITY_RIGHT
Align clip to the right of its parent (middle aligned)
Definition: Enums.h:28
@ GRAVITY_BOTTOM_LEFT
Align clip to the bottom left of its parent.
Definition: Enums.h:29
@ GRAVITY_BOTTOM
Align clip to the bottom center of its parent.
Definition: Enums.h:30
@ GRAVITY_TOP
Align clip to the top center of its parent.
Definition: Enums.h:24
@ GRAVITY_BOTTOM_RIGHT
Align clip to the bottom right of its parent.
Definition: Enums.h:31
@ GRAVITY_CENTER
Align clip to the center of its parent (middle aligned)
Definition: Enums.h:27
ScaleType
This enumeration determines how clips are scaled to fit their parent container.
Definition: Enums.h:36
@ SCALE_FIT
Scale the clip until either height or width fills the canvas (with no cropping)
Definition: Enums.h:38
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
Definition: Enums.h:39
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
Definition: Enums.h:37
@ SCALE_NONE
Do not scale the clip.
Definition: Enums.h:40
@ AUDIO_VISUALIZATION_BACKGROUND_TRANSPARENT
VolumeMixType
This enumeration determines the strategy when mixing audio with other clips.
Definition: Enums.h:68
@ VOLUME_MIX_AVERAGE
Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100%.
Definition: Enums.h:70
@ VOLUME_MIX_NONE
Do not apply any volume mixing adjustments. Just add the samples together.
Definition: Enums.h:69
@ 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
@ AUDIO_VISUALIZATION_COLOR_SEED
@ AUDIO_VISUALIZATION_VU_METER
@ AUDIO_VISUALIZATION_BARS
@ AUDIO_VISUALIZATION_SPECTRUM
@ AUDIO_VISUALIZATION_WAVEFORM
@ AUDIO_VISUALIZATION_PHASE_SCOPE
@ AUDIO_VISUALIZATION_RADIAL_BARS
@ AUDIO_VISUALIZATION_PARTICLES
@ AUDIO_VISUALIZATION_FILLED_WAVEFORM
@ AUDIO_VISUALIZATION_RADIAL
FrameDisplayType
This enumeration determines the display format of the clip's frame number (if any)....
Definition: Enums.h:52
@ FRAME_DISPLAY_CLIP
Display the clip's internal frame number.
Definition: Enums.h:54
@ FRAME_DISPLAY_TIMELINE
Display the timeline's frame number.
Definition: Enums.h:55
@ FRAME_DISPLAY_BOTH
Display both the clip's and timeline's frame number.
Definition: Enums.h:56
@ FRAME_DISPLAY_NONE
Do not display the frame number.
Definition: Enums.h:53
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
CompositeType
This enumeration determines how clips are composited onto lower layers.
Definition: Enums.h:75
@ COMPOSITE_LIGHTEN
Definition: Enums.h:95
@ COMPOSITE_MULTIPLY
Definition: Enums.h:91
@ COMPOSITE_EXCLUSION
Definition: Enums.h:101
@ COMPOSITE_PLUS
Definition: Enums.h:90
@ COMPOSITE_SOURCE_OVER
Definition: Enums.h:76
@ COMPOSITE_DARKEN
Definition: Enums.h:94
@ COMPOSITE_COLOR_DODGE
Definition: Enums.h:96
@ COMPOSITE_SOFT_LIGHT
Definition: Enums.h:99
@ COMPOSITE_DIFFERENCE
Definition: Enums.h:100
@ COMPOSITE_HARD_LIGHT
Definition: Enums.h:98
@ COMPOSITE_OVERLAY
Definition: Enums.h:93
@ COMPOSITE_COLOR_BURN
Definition: Enums.h:97
@ COMPOSITE_SCREEN
Definition: Enums.h:92
@ AUDIO_VISUALIZATION_STYLE_MINIMAL
This struct holds the associated video frame and starting sample # for an audio packet.
Definition: AudioLocation.h:25
bool has_tracked_object
Determines if this effect track objects through the clip.
Definition: EffectBase.h:45
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
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::map< std::string, std::string > metadata
An optional map/dictionary of metadata for this reader.
Definition: ReaderBase.h:65
openshot::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
Definition: ReaderBase.h:62
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