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