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