OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
Timeline.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2019 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "Timeline.h"
14#include "PreviewSize.h"
15
16#include "CacheBase.h"
17#include "CacheDisk.h"
18#include "CacheMemory.h"
19#include "CrashHandler.h"
20#include "FrameMapper.h"
21#include "Exceptions.h"
22#include "effects/Mask.h"
23
24#include <algorithm>
25#include <utility>
26#include <QDir>
27#include <QFileInfo>
28#include <QRegularExpression>
29#include <unordered_map>
30#include <cmath>
31#include <cstdint>
32
33using namespace openshot;
34
35// Default Constructor for the timeline (which sets the canvas width and height)
36Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) :
37 is_open(false), auto_map_clips(true), managed_cache(true), path(""), max_time(0.0), cache_epoch(0)
38{
39 // Create CrashHandler and Attach (incase of errors)
41
42 // Init viewport size (curve based, because it can be animated)
43 viewport_scale = Keyframe(100.0);
44 viewport_x = Keyframe(0.0);
45 viewport_y = Keyframe(0.0);
46
47 // Init background color
48 color.red = Keyframe(0.0);
49 color.green = Keyframe(0.0);
50 color.blue = Keyframe(0.0);
51
52 // Init FileInfo struct (clear all values)
53 info.width = width;
54 info.height = height;
57 info.fps = fps;
58 info.sample_rate = sample_rate;
59 info.channels = channels;
60 info.channel_layout = channel_layout;
62 info.duration = 60 * 30; // 30 minute default duration
63 info.has_audio = true;
64 info.has_video = true;
66 info.display_ratio = openshot::Fraction(width, height);
69 info.acodec = "openshot::timeline";
70 info.vcodec = "openshot::timeline";
71
72 // Init max image size
74
75 // Init cache
76 final_cache = new CacheMemory();
77 const int cache_frames = std::max(Settings::Instance()->CACHE_MIN_FRAMES, OPEN_MP_NUM_PROCESSORS * 4);
78 final_cache->SetMaxBytesFromInfo(cache_frames, info.width, info.height, info.sample_rate, info.channels);
79}
80
81// Delegating constructor that copies parameters from a provided ReaderInfo
83 info.width, info.height, info.fps, info.sample_rate,
84 info.channels, info.channel_layout) {}
85
86// Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline)
87Timeline::Timeline(const std::string& projectPath, bool convert_absolute_paths) :
88 is_open(false), auto_map_clips(true), managed_cache(true), path(projectPath), max_time(0.0), cache_epoch(0) {
89
90 // Create CrashHandler and Attach (incase of errors)
92
93 // Init final cache as NULL (will be created after loading json)
94 final_cache = NULL;
95
96 // Init viewport size (curve based, because it can be animated)
97 viewport_scale = Keyframe(100.0);
98 viewport_x = Keyframe(0.0);
99 viewport_y = Keyframe(0.0);
100
101 // Init background color
102 color.red = Keyframe(0.0);
103 color.green = Keyframe(0.0);
104 color.blue = Keyframe(0.0);
105
106 // Check if path exists
107 QFileInfo filePath(QString::fromStdString(path));
108 if (!filePath.exists()) {
109 throw InvalidFile("Timeline project file could not be opened.", path);
110 }
111
112 // Check OpenShot Install Path exists
114 QDir openshotPath(QString::fromStdString(s->PATH_OPENSHOT_INSTALL));
115 if (!openshotPath.exists()) {
116 throw InvalidFile("PATH_OPENSHOT_INSTALL could not be found.", s->PATH_OPENSHOT_INSTALL);
117 }
118 QDir openshotTransPath(openshotPath.filePath("transitions"));
119 if (!openshotTransPath.exists()) {
120 throw InvalidFile("PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
121 }
122
123 // Determine asset path
124 QString asset_name = filePath.baseName().left(30) + "_assets";
125 QDir asset_folder(filePath.dir().filePath(asset_name));
126 if (!asset_folder.exists()) {
127 // Create directory if needed
128 asset_folder.mkpath(".");
129 }
130
131 // Load UTF-8 project file into QString
132 QFile projectFile(QString::fromStdString(path));
133 projectFile.open(QFile::ReadOnly);
134 QString projectContents = QString::fromUtf8(projectFile.readAll());
135
136 // Convert all relative paths into absolute paths (if requested)
137 if (convert_absolute_paths) {
138
139 // Find all "image" or "path" references in JSON (using regex). Must loop through match results
140 // due to our path matching needs, which are not possible with the QString::replace() function.
141 QRegularExpression allPathsRegex(QStringLiteral("\"(image|path)\":.*?\"(.*?)\""));
142 std::vector<QRegularExpressionMatch> matchedPositions;
143 QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
144 while (i.hasNext()) {
145 QRegularExpressionMatch match = i.next();
146 if (match.hasMatch()) {
147 // Push all match objects into a vector (so we can reverse them later)
148 matchedPositions.push_back(match);
149 }
150 }
151
152 // Reverse the matches (bottom of file to top, so our replacements don't break our match positions)
153 std::vector<QRegularExpressionMatch>::reverse_iterator itr;
154 for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
155 QRegularExpressionMatch match = *itr;
156 QString relativeKey = match.captured(1); // image or path
157 QString relativePath = match.captured(2); // relative file path
158 QString absolutePath = "";
159
160 // Find absolute path of all path, image (including special replacements of @assets and @transitions)
161 if (relativePath.startsWith("@assets")) {
162 absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace("@assets", "."))).canonicalFilePath();
163 } else if (relativePath.startsWith("@transitions")) {
164 absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace("@transitions", "."))).canonicalFilePath();
165 } else {
166 absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
167 }
168
169 // Replace path in JSON content, if an absolute path was successfully found
170 if (!absolutePath.isEmpty()) {
171 projectContents.replace(match.capturedStart(0), match.capturedLength(0), "\"" + relativeKey + "\": \"" + absolutePath + "\"");
172 }
173 }
174 // Clear matches
175 matchedPositions.clear();
176 }
177
178 // Set JSON of project
179 SetJson(projectContents.toStdString());
180
181 // Calculate valid duration and set has_audio and has_video
182 // based on content inside this Timeline's clips.
183 float calculated_duration = 0.0;
184 for (auto clip : clips)
185 {
186 float clip_last_frame = clip->Position() + clip->Duration();
187 if (clip_last_frame > calculated_duration)
188 calculated_duration = clip_last_frame;
189 if (clip->Reader() && clip->Reader()->info.has_audio)
190 info.has_audio = true;
191 if (clip->Reader() && clip->Reader()->info.has_video)
192 info.has_video = true;
193
194 }
195 info.video_length = calculated_duration * info.fps.ToFloat();
196 info.duration = calculated_duration;
197
198 // Init FileInfo settings
199 info.acodec = "openshot::timeline";
200 info.vcodec = "openshot::timeline";
202 info.has_video = true;
203 info.has_audio = true;
204
205 // Init max image size
207
208 // Init cache
209 final_cache = new CacheMemory();
210 const int cache_frames = std::max(Settings::Instance()->CACHE_MIN_FRAMES, OPEN_MP_NUM_PROCESSORS * 4);
211 final_cache->SetMaxBytesFromInfo(cache_frames, info.width, info.height, info.sample_rate, info.channels);
212}
213
215 if (is_open) {
216 // Auto Close if not already
217 Close();
218 }
219
220 // Remove all clips, effects, and frame mappers
221 Clear();
222
223 // Destroy previous cache (if managed by timeline)
224 if (managed_cache && final_cache) {
225 delete final_cache;
226 final_cache = NULL;
227 }
228}
229
230// Add to the tracked_objects map a pointer to a tracked object (TrackedObjectBBox)
231void Timeline::AddTrackedObject(std::shared_ptr<openshot::TrackedObjectBase> trackedObject){
232
233 // Search for the tracked object on the map
234 auto iterator = tracked_objects.find(trackedObject->Id());
235
236 if (iterator != tracked_objects.end()){
237 // Tracked object's id already present on the map, overwrite it
238 iterator->second = trackedObject;
239 }
240 else{
241 // Tracked object's id not present -> insert it on the map
242 tracked_objects[trackedObject->Id()] = trackedObject;
243 }
244
245 return;
246}
247
248// Return tracked object pointer by it's id
249std::shared_ptr<openshot::TrackedObjectBase> Timeline::GetTrackedObject(std::string id) const{
250
251 // Search for the tracked object on the map
252 auto iterator = tracked_objects.find(id);
253
254 if (iterator != tracked_objects.end()){
255 // Id found, return the pointer to the tracked object
256 std::shared_ptr<openshot::TrackedObjectBase> trackedObject = iterator->second;
257 return trackedObject;
258 }
259 else {
260 // Id not found, return a null pointer
261 return nullptr;
262 }
263}
264
265// Return the ID's of the tracked objects as a list of strings
266std::list<std::string> Timeline::GetTrackedObjectsIds() const{
267
268 // Create a list of strings
269 std::list<std::string> trackedObjects_ids;
270
271 // Iterate through the tracked_objects map
272 for (auto const& it: tracked_objects){
273 // Add the IDs to the list
274 trackedObjects_ids.push_back(it.first);
275 }
276
277 return trackedObjects_ids;
278}
279
280#ifdef USE_OPENCV
281// Return the trackedObject's properties as a JSON string
282std::string Timeline::GetTrackedObjectValues(std::string id, int64_t frame_number) const {
283
284 // Initialize the JSON object
285 Json::Value trackedObjectJson;
286
287 // Search for the tracked object on the map
288 auto iterator = tracked_objects.find(id);
289
290 if (iterator != tracked_objects.end())
291 {
292 // Id found, Get the object pointer and cast it as a TrackedObjectBBox
293 std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(iterator->second);
294
295 // Get the trackedObject values for it's first frame
296 if (trackedObject->ExactlyContains(frame_number)){
297 BBox box = trackedObject->GetBox(frame_number);
298 float x1 = box.cx - (box.width/2);
299 float y1 = box.cy - (box.height/2);
300 float x2 = box.cx + (box.width/2);
301 float y2 = box.cy + (box.height/2);
302 float rotation = box.angle;
303
304 trackedObjectJson["x1"] = x1;
305 trackedObjectJson["y1"] = y1;
306 trackedObjectJson["x2"] = x2;
307 trackedObjectJson["y2"] = y2;
308 trackedObjectJson["rotation"] = rotation;
309
310 } else {
311 BBox box = trackedObject->BoxVec.begin()->second;
312 float x1 = box.cx - (box.width/2);
313 float y1 = box.cy - (box.height/2);
314 float x2 = box.cx + (box.width/2);
315 float y2 = box.cy + (box.height/2);
316 float rotation = box.angle;
317
318 trackedObjectJson["x1"] = x1;
319 trackedObjectJson["y1"] = y1;
320 trackedObjectJson["x2"] = x2;
321 trackedObjectJson["y2"] = y2;
322 trackedObjectJson["rotation"] = rotation;
323 }
324
325 }
326 else {
327 // Id not found, return all 0 values
328 trackedObjectJson["x1"] = 0;
329 trackedObjectJson["y1"] = 0;
330 trackedObjectJson["x2"] = 0;
331 trackedObjectJson["y2"] = 0;
332 trackedObjectJson["rotation"] = 0;
333 }
334
335 return trackedObjectJson.toStyledString();
336}
337#endif
338
339// Add an openshot::Clip to the timeline
341{
342 // Get lock (prevent getting frames while this happens)
343 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
344
345 // Assign timeline to clip
346 clip->ParentTimeline(this);
347
348 // Clear cache of clip and nested reader (if any)
349 if (clip->Reader() && clip->Reader()->GetCache())
350 clip->Reader()->GetCache()->Clear();
351
352 // All clips should be converted to the frame rate of this timeline
353 if (auto_map_clips) {
354 // Apply framemapper (or update existing framemapper)
355 apply_mapper_to_clip(clip);
356 }
357
358 InvalidateCacheForClip(clip);
359
360 // Add clip to list
361 clips.push_back(clip);
362
363 // Sort clips
364 sort_clips();
365}
366
367// Add an effect to the timeline
369{
370 // Get lock (prevent getting frames while this happens)
371 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
372
373 // Assign timeline to effect
374 effect->ParentTimeline(this);
375
376 // Add effect to list
377 effects.push_back(effect);
378
379 // Sort effects
380 sort_effects();
381}
382
383// Remove an effect from the timeline
385{
386 // Get lock (prevent getting frames while this happens)
387 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
388
389 effects.remove(effect);
390
391 // Delete effect object (if timeline allocated it)
392 if (allocated_effects.count(effect)) {
393 allocated_effects.erase(effect); // erase before nulling the pointer
394 delete effect;
395 effect = NULL;
396 }
397
398 // Sort effects
399 sort_effects();
400}
401
402// Remove an openshot::Clip to the timeline
404{
405 // Get lock (prevent getting frames while this happens)
406 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
407
408 clips.remove(clip);
409
410 // Delete clip object (if timeline allocated it)
411 if (allocated_clips.count(clip)) {
412 allocated_clips.erase(clip); // erase before nulling the pointer
413 delete clip;
414 clip = NULL;
415 }
416
417 // Sort clips
418 sort_clips();
419}
420
421// Look up a clip
422openshot::Clip* Timeline::GetClip(const std::string& id)
423{
424 // Find the matching clip (if any)
425 for (const auto& clip : clips) {
426 if (clip->Id() == id) {
427 return clip;
428 }
429 }
430 return nullptr;
431}
432
433// Look up a timeline effect
435{
436 // Find the matching effect (if any)
437 for (const auto& effect : effects) {
438 if (effect->Id() == id) {
439 return effect;
440 }
441 }
442 return nullptr;
443}
444
446{
447 // Search all clips for matching effect ID
448 for (const auto& clip : clips) {
449 const auto e = clip->GetEffect(id);
450 if (e != nullptr) {
451 return e;
452 }
453 }
454 return nullptr;
455}
456
457// Return the list of effects on all clips
458std::list<openshot::EffectBase*> Timeline::ClipEffects() const {
459
460 // Initialize the list
461 std::list<EffectBase*> timelineEffectsList;
462
463 // Loop through all clips
464 for (const auto& clip : clips) {
465
466 // Get the clip's list of effects
467 std::list<EffectBase*> clipEffectsList = clip->Effects();
468
469 // Append the clip's effects to the list
470 timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
471 }
472
473 return timelineEffectsList;
474}
475
476// Compute the end time of the latest timeline element
478 // Return cached max_time variable (threadsafe)
479 return max_time;
480}
481
482// Compute the highest frame# based on the latest time and FPS
484 const double fps = info.fps.ToDouble();
485 const double t = GetMaxTime();
486 const double frames = t * fps;
487 constexpr double frame_boundary_epsilon = 1e-4;
488
489 // End is exclusive; ignore tiny float overshoots at frame boundaries.
490 if (frames > 0.0 && frames < frame_boundary_epsilon)
491 return 1;
492 return static_cast<int64_t>(std::ceil(frames - frame_boundary_epsilon));
493}
494
495// Compute the first frame# based on the first clip position
497 const double fps = info.fps.ToDouble();
498 const double t = GetMinTime();
499 // Inclusive start -> floor at the start boundary, then 1-index
500 return static_cast<int64_t>(std::floor(t * fps)) + 1;
501}
502
503// Compute the start time of the first timeline clip
505 // Return cached min_time variable (threadsafe)
506 return min_time;
507}
508
509// Apply a FrameMapper to a clip which matches the settings of this timeline
510void Timeline::apply_mapper_to_clip(Clip* clip)
511{
512 // Serialize mapper replacement/reconfiguration with active frame generation.
513 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
514
515 // Determine type of reader
516 ReaderBase* clip_reader = NULL;
517 if (clip->Reader()->Name() == "FrameMapper")
518 {
519 // Get the existing reader
520 clip_reader = (ReaderBase*) clip->Reader();
521
522 // Update the mapping
523 FrameMapper* clip_mapped_reader = (FrameMapper*) clip_reader;
525
526 } else {
527
528 // Create a new FrameMapper to wrap the current reader
530 allocated_frame_mappers.insert(mapper);
531 clip_reader = (ReaderBase*) mapper;
532 }
533
534 // Update clip reader
535 clip->Reader(clip_reader);
536}
537
538// Apply the timeline's framerate and samplerate to all clips
540{
541 // Clear all cached frames
543
544 // Loop through all clips
545 for (auto clip : clips)
546 {
547 // Apply framemapper (or update existing framemapper)
548 apply_mapper_to_clip(clip);
549 }
550}
551
552// Calculate time of a frame number, based on a framerate
553double Timeline::calculate_time(int64_t number, Fraction rate)
554{
555 // Get float version of fps fraction
556 double raw_fps = rate.ToFloat();
557
558 // Return the time (in seconds) of this frame
559 return double(number - 1) / raw_fps;
560}
561
562// Apply effects to the source frame (if any)
563std::shared_ptr<Frame> Timeline::apply_effects(std::shared_ptr<Frame> frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct* options)
564{
565 // Debug output
567 "Timeline::apply_effects",
568 "frame->number", frame->number,
569 "timeline_frame_number", timeline_frame_number,
570 "layer", layer);
571
572 // Find Effects at this position and layer
573 for (auto effect : effects)
574 {
575 // Does clip intersect the current requested time
576 const double fpsD = info.fps.ToDouble();
577 int64_t effect_start_position = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
578 int64_t effect_end_position = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
579
580 bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
581
582 // Clip is visible
583 if (does_effect_intersect)
584 {
585 // Determine the frame needed for this clip (based on the position on the timeline)
586 int64_t effect_start_frame = static_cast<int64_t>(std::llround(effect->Start() * fpsD)) + 1;
587 int64_t effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
588
589 if (!options->is_top_clip)
590 continue; // skip effect, if overlapped/covered by another clip on same layer
591
592 if (options->is_before_clip_keyframes != effect->info.apply_before_clip)
593 continue; // skip effect, if this filter does not match
594
595 // Debug output
597 "Timeline::apply_effects (Process Effect)",
598 "effect_frame_number", effect_frame_number,
599 "does_effect_intersect", does_effect_intersect);
600
601 // Apply the effect to this frame
602 frame = effect->ProcessFrame(frame, effect_frame_number);
603 }
604
605 } // end effect loop
606
607 // Return modified frame
608 return frame;
609}
610
611// Get or generate a blank frame
612std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame, Clip* clip, int64_t number, openshot::TimelineInfoStruct* options)
613{
614 std::shared_ptr<Frame> new_frame;
615
616 // Init some basic properties about this frame
617 int samples_in_frame = Frame::GetSamplesPerFrame(number, info.fps, info.sample_rate, info.channels);
618
619 try {
620 // Debug output
622 "Timeline::GetOrCreateFrame (from reader)",
623 "number", number,
624 "samples_in_frame", samples_in_frame);
625
626 // Attempt to get a frame (but this could fail if a reader has just been closed)
627 new_frame = std::shared_ptr<Frame>(clip->GetFrame(background_frame, number, options));
628
629 // Return real frame
630 return new_frame;
631
632 } catch (const ReaderClosed & e) {
633 // ...
634 } catch (const OutOfBoundsFrame & e) {
635 // ...
636 }
637
638 // Debug output
640 "Timeline::GetOrCreateFrame (create blank)",
641 "number", number,
642 "samples_in_frame", samples_in_frame);
643
644 // Create blank frame
645 return new_frame;
646}
647
648// Process a new layer of video or audio
649void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, int64_t clip_frame_number, bool is_top_clip, float max_volume)
650{
651 // Create timeline options (with details about this current frame request)
652 TimelineInfoStruct options{};
653 options.is_top_clip = is_top_clip;
654 options.is_before_clip_keyframes = true;
655
656 // Get the clip's frame, composited on top of the current timeline frame
657 std::shared_ptr<Frame> source_frame;
658 source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, &options);
659
660 // No frame found... so bail
661 if (!source_frame)
662 return;
663
664 // Debug output
666 "Timeline::add_layer",
667 "new_frame->number", new_frame->number,
668 "clip_frame_number", clip_frame_number);
669
670 /* COPY AUDIO - with correct volume */
671 if (source_clip->Reader()->info.has_audio) {
672 // Debug output
674 "Timeline::add_layer (Copy Audio)",
675 "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio,
676 "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(),
677 "info.channels", info.channels,
678 "clip_frame_number", clip_frame_number);
679
680 if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0)
681 {
682 // Ensure timeline frame matches the source samples once per frame
683 if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
684 new_frame->ResizeAudio(info.channels, source_frame->GetAudioSamplesCount(), info.sample_rate, info.channel_layout);
685 }
686
687 // Apply transition-driven equal-power audio fades for clips covered by a Mask transition.
688 const auto transition_audio_gains = ResolveTransitionAudioGains(source_clip, new_frame->number, is_top_clip);
689
690 for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
691 {
692 // Get volume from previous frame and this frame
693 float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1);
694 float volume = source_clip->volume.GetValue(clip_frame_number);
695 previous_volume *= transition_audio_gains.first;
696 volume *= transition_audio_gains.second;
697 int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1)
698 int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1)
699
700 // Apply volume mixing strategy
701 if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) {
702 // Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume
703 previous_volume = previous_volume / max_volume;
704 volume = volume / max_volume;
705 }
706 else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) {
707 // Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will)
708 previous_volume = previous_volume * 0.77;
709 volume = volume * 0.77;
710 }
711
712 // If channel filter enabled, check for correct channel (and skip non-matching channels)
713 if (channel_filter != -1 && channel_filter != channel)
714 continue; // skip to next channel
715
716 // If no volume on this frame or previous frame, do nothing
717 if (previous_volume == 0.0 && volume == 0.0)
718 continue; // skip to next channel
719
720 // If channel mapping disabled, just use the current channel
721 if (channel_mapping == -1)
722 channel_mapping = channel;
723
724 // Apply ramp to source frame (if needed)
725 if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
726 source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
727
728 // Copy audio samples (and set initial volume). Mix samples with existing audio samples. The gains are added together, to
729 // be sure to set the gain's correctly, so the sum does not exceed 1.0 (of audio distortion will happen).
730 new_frame->AddAudio(false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
731 }
732 }
733 else
734 // Debug output
736 "Timeline::add_layer (No Audio Copied - Wrong # of Channels)",
737 "source_clip->Reader()->info.has_audio",
738 source_clip->Reader()->info.has_audio,
739 "source_frame->GetAudioChannelsCount()",
740 source_frame->GetAudioChannelsCount(),
741 "info.channels", info.channels,
742 "clip_frame_number", clip_frame_number);
743 }
744
745 // Debug output
747 "Timeline::add_layer (Transform: Composite Image Layer: Completed)",
748 "source_frame->number", source_frame->number,
749 "new_frame->GetImage()->width()", new_frame->GetWidth(),
750 "new_frame->GetImage()->height()", new_frame->GetHeight());
751}
752
753// Update the list of 'opened' clips
754void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
755{
756 // Get lock (prevent getting frames while this happens)
757 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
758
760 "Timeline::update_open_clips (before)",
761 "does_clip_intersect", does_clip_intersect,
762 "closing_clips.size()", closing_clips.size(),
763 "open_clips.size()", open_clips.size());
764
765 // is clip already in list?
766 bool clip_found = open_clips.count(clip);
767
768 // The registry, Clip, and nested Reader can become inconsistent after a
769 // transient reader close. Trust the actual objects over open_clips and run
770 // them through a clean Close/Open cycle while this clip still intersects.
771 // Otherwise FrameMapper converts ReaderClosed into a black frame which can
772 // be repeatedly cached on every still-intersecting timing update.
773 if (clip_found && does_clip_intersect)
774 {
775 bool clip_reader_open = false;
776 try {
777 clip_reader_open = clip->Reader() && clip->Reader()->IsOpen();
778 } catch (const ReaderClosed & e) {
779 // A missing/replaced reader is equivalent to a closed reader here.
780 clip_reader_open = false;
781 }
782 if (!clip->IsOpen() || !clip_reader_open)
783 {
784 open_clips.erase(clip);
785 clip->Close();
786 clip_found = false;
787 }
788 }
789
790 if (clip_found && !does_clip_intersect)
791 {
792 // Remove clip from 'opened' list, because it's closed now
793 open_clips.erase(clip);
794
795 // Close clip
796 clip->Close();
797 }
798 else if (!clip_found && does_clip_intersect)
799 {
800 try {
801 // Open the clip
802 clip->Open();
803 // Add clip to 'opened' list only after a successful open.
804 open_clips[clip] = clip;
805
806 } catch (const InvalidFile & e) {
807 // ...
808 }
809 }
810
811 // Debug output
813 "Timeline::update_open_clips (after)",
814 "does_clip_intersect", does_clip_intersect,
815 "clip_found", clip_found,
816 "closing_clips.size()", closing_clips.size(),
817 "open_clips.size()", open_clips.size());
818}
819
820// Calculate the max and min duration (in seconds) of the timeline, based on all the clips, and cache the value
821void Timeline::calculate_max_duration() {
822 double last_clip = 0.0;
823 double last_effect = 0.0;
824 double first_clip = std::numeric_limits<double>::max();
825 double first_effect = std::numeric_limits<double>::max();
826
827 // Find the last and first clip
828 if (!clips.empty()) {
829 // Find the clip with the maximum end frame
830 const auto max_clip = std::max_element(
831 clips.begin(), clips.end(), CompareClipEndFrames());
832 last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
833
834 // Find the clip with the minimum start position (ignoring layer)
835 const auto min_clip = std::min_element(
836 clips.begin(), clips.end(), [](const openshot::Clip* lhs, const openshot::Clip* rhs) {
837 return lhs->Position() < rhs->Position();
838 });
839 first_clip = (*min_clip)->Position();
840 }
841
842 // Find the last and first effect
843 if (!effects.empty()) {
844 // Find the effect with the maximum end frame
845 const auto max_effect = std::max_element(
846 effects.begin(), effects.end(), CompareEffectEndFrames());
847 last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
848
849 // Find the effect with the minimum start position
850 const auto min_effect = std::min_element(
851 effects.begin(), effects.end(), [](const openshot::EffectBase* lhs, const openshot::EffectBase* rhs) {
852 return lhs->Position() < rhs->Position();
853 });
854 first_effect = (*min_effect)->Position();
855 }
856
857 // Calculate the max and min time
858 max_time = std::max(last_clip, last_effect);
859 min_time = std::min(first_clip, first_effect);
860
861 // If no clips or effects exist, set min_time to 0
862 if (clips.empty() && effects.empty()) {
863 min_time = 0.0;
864 max_time = 0.0;
865 }
866}
867
868// Sort clips by position on the timeline
869void Timeline::sort_clips()
870{
871 // Get lock (prevent getting frames while this happens)
872 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
873
874 // Debug output
876 "Timeline::SortClips",
877 "clips.size()", clips.size());
878
879 // sort clips
880 clips.sort(CompareClips());
881
882 // calculate max timeline duration
883 calculate_max_duration();
884}
885
886// Sort effects by position on the timeline
887void Timeline::sort_effects()
888{
889 // Get lock (prevent getting frames while this happens)
890 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
891
892 // sort clips
893 effects.sort(CompareEffects());
894
895 // calculate max timeline duration
896 calculate_max_duration();
897}
898
899// Clear all clips from timeline
901{
902 Logger::Instance()->AppendDebugMethod("Timeline::Clear");
903
904 // Get lock (prevent getting frames while this happens)
905 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
906
907 // Close all open clips
908 for (auto clip : clips)
909 {
910 update_open_clips(clip, false);
911
912 // Delete clip object (if timeline allocated it)
913 bool allocated = allocated_clips.count(clip);
914 if (allocated) {
915 delete clip;
916 }
917 }
918 // Clear all clips
919 clips.clear();
920 allocated_clips.clear();
921
922 // Close all effects
923 for (auto effect : effects)
924 {
925 // Delete effect object (if timeline allocated it)
926 bool allocated = allocated_effects.count(effect);
927 if (allocated) {
928 delete effect;
929 }
930 }
931 // Clear all effects
932 effects.clear();
933 allocated_effects.clear();
934
935 // Delete all FrameMappers
936 for (auto mapper : allocated_frame_mappers)
937 {
938 mapper->Reader(NULL);
939 mapper->Close();
940 delete mapper;
941 }
942 allocated_frame_mappers.clear();
943}
944
945// Close the reader (and any resources it was consuming)
947{
948 Logger::Instance()->AppendDebugMethod("Timeline::Close");
949
950 // Get lock (prevent getting frames while this happens)
951 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
952
953 // Close all open clips
954 for (auto clip : clips)
955 {
956 // Open or Close this clip, based on if it's intersecting or not
957 update_open_clips(clip, false);
958 }
959
960 // Mark timeline as closed
961 is_open = false;
962
963 // Clear all cache (deep clear, including nested Readers)
964 ClearAllCache(true);
965}
966
967// Open the reader (and start consuming resources)
969{
970 is_open = true;
971}
972
973// Compare 2 floating point numbers for equality
974bool Timeline::isEqual(double a, double b)
975{
976 return fabs(a - b) < 0.000001;
977}
978
979// Get an openshot::Frame object for a specific frame number of this reader.
980std::shared_ptr<Frame> Timeline::GetFrame(int64_t requested_frame)
981{
982 // Adjust out of bounds frame number
983 if (requested_frame < 1)
984 requested_frame = 1;
985 const int64_t max_frame = GetMaxFrame();
986 const bool past_timeline_end = (max_frame > 0 && requested_frame > max_frame);
987
988 // Check cache
989 const uint64_t lookup_epoch = CacheEpoch();
990 std::shared_ptr<Frame> frame;
991 if (!past_timeline_end && !CacheRefreshPending())
992 frame = final_cache->GetFrame(requested_frame);
993 if (frame && !CacheRefreshPending() && CacheEpoch() == lookup_epoch) {
994 // Debug output
996 "Timeline::GetFrame (Cached frame found)",
997 "requested_frame", requested_frame);
998
999 // Return cached frame
1000 return frame;
1001 }
1002 else
1003 {
1004 // Prevent async calls to the following code
1005 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1006 if (CacheRefreshPending())
1007 ClearAllCache();
1008 const uint64_t decode_epoch = CacheEpoch();
1009
1010 // Check cache 2nd time
1011 std::shared_ptr<Frame> frame;
1012 if (!past_timeline_end)
1013 frame = final_cache->GetFrame(requested_frame);
1014 if (frame && !CacheRefreshPending() && CacheEpoch() == decode_epoch) {
1015 // Debug output
1017 "Timeline::GetFrame (Cached frame found on 2nd check)",
1018 "requested_frame", requested_frame);
1019
1020 // Return cached frame
1021 return frame;
1022 } else {
1023 // Get a list of clips that intersect with the requested section of timeline
1024 // This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
1025 std::vector<Clip *> nearby_clips;
1026 nearby_clips = find_intersecting_clips(requested_frame, 1, true);
1027
1028 // Debug output
1030 "Timeline::GetFrame (processing frame)",
1031 "requested_frame", requested_frame,
1032 "omp_get_thread_num()", omp_get_thread_num());
1033
1034 // Init some basic properties about this frame
1035 int samples_in_frame = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
1036
1037 // Create blank frame (which will become the requested frame)
1038 std::shared_ptr<Frame> new_frame(std::make_shared<Frame>(requested_frame, preview_width, preview_height, "#000000", samples_in_frame, info.channels));
1039 new_frame->AddAudioSilence(samples_in_frame);
1040 new_frame->SampleRate(info.sample_rate);
1041 new_frame->ChannelsLayout(info.channel_layout);
1042
1043 // Debug output
1045 "Timeline::GetFrame (Adding solid color)",
1046 "requested_frame", requested_frame,
1047 "info.width", info.width,
1048 "info.height", info.height);
1049
1050 // Add Background Color to 1st layer (if animated or not black)
1051 if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) ||
1052 (color.red.GetValue(requested_frame) != 0.0 || color.green.GetValue(requested_frame) != 0.0 ||
1053 color.blue.GetValue(requested_frame) != 0.0))
1054 new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame));
1055
1056 // Debug output
1058 "Timeline::GetFrame (Loop through clips)",
1059 "requested_frame", requested_frame,
1060 "clips.size()", clips.size(),
1061 "nearby_clips.size()", nearby_clips.size());
1062
1063 // Precompute per-clip timing for this requested frame
1064 struct ClipInfo {
1065 Clip* clip;
1066 int64_t start_pos;
1067 int64_t end_pos;
1068 int64_t start_frame;
1069 int64_t frame_number;
1070 bool intersects;
1071 };
1072 std::vector<ClipInfo> clip_infos;
1073 clip_infos.reserve(nearby_clips.size());
1074 const double fpsD = info.fps.ToDouble();
1075
1076 for (auto clip : nearby_clips) {
1077 int64_t start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1078 int64_t end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1079 bool intersects = (start_pos <= requested_frame && end_pos >= requested_frame);
1080 int64_t start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1081 int64_t frame_number = requested_frame - start_pos + start_frame;
1082 clip_infos.push_back({clip, start_pos, end_pos, start_frame, frame_number, intersects});
1083 }
1084
1085 // Determine top clip per layer (linear, no nested loop)
1086 std::unordered_map<int, int64_t> top_start_for_layer;
1087 std::unordered_map<int, Clip*> top_clip_for_layer;
1088 for (const auto& ci : clip_infos) {
1089 if (!ci.intersects) continue;
1090 const int layer = ci.clip->Layer();
1091 auto it = top_start_for_layer.find(layer);
1092 // The last composited clip wins ties at the same start frame.
1093 if (it == top_start_for_layer.end() || ci.start_pos >= it->second) {
1094 top_start_for_layer[layer] = ci.start_pos;
1095 top_clip_for_layer[layer] = ci.clip;
1096 }
1097 }
1098
1099 // Compute max_volume across all overlapping clips once
1100 float max_volume_sum = 0.0f;
1101 for (const auto& ci : clip_infos) {
1102 if (!ci.intersects) continue;
1103 if (ci.clip->Reader() && ci.clip->Reader()->info.has_audio &&
1104 ci.clip->has_audio.GetInt(ci.frame_number) != 0) {
1105 max_volume_sum += static_cast<float>(ci.clip->volume.GetValue(ci.frame_number));
1106 }
1107 }
1108
1109 // Compose intersecting clips in a single pass
1110 for (const auto& ci : clip_infos) {
1111 // Debug output
1113 "Timeline::GetFrame (Does clip intersect)",
1114 "requested_frame", requested_frame,
1115 "clip->Position()", ci.clip->Position(),
1116 "clip->Duration()", ci.clip->Duration(),
1117 "does_clip_intersect", ci.intersects);
1118
1119 // Clip is visible
1120 if (ci.intersects) {
1121 // Is this the top clip on its layer?
1122 bool is_top_clip = false;
1123 const int layer = ci.clip->Layer();
1124 auto top_it = top_clip_for_layer.find(layer);
1125 if (top_it != top_clip_for_layer.end())
1126 is_top_clip = (top_it->second == ci.clip);
1127
1128 // Determine the frame needed for this clip (based on the position on the timeline)
1129 int64_t clip_frame_number = ci.frame_number;
1130
1131 // Debug output
1133 "Timeline::GetFrame (Calculate clip's frame #)",
1134 "clip->Position()", ci.clip->Position(),
1135 "clip->Start()", ci.clip->Start(),
1136 "info.fps.ToFloat()", info.fps.ToFloat(),
1137 "clip_frame_number", clip_frame_number);
1138
1139 // Add clip's frame as layer
1140 add_layer(new_frame, ci.clip, clip_frame_number, is_top_clip, max_volume_sum);
1141
1142 } else {
1143 // Debug output
1145 "Timeline::GetFrame (clip does not intersect)",
1146 "requested_frame", requested_frame,
1147 "does_clip_intersect", ci.intersects);
1148 }
1149
1150 } // end clip loop
1151
1152 // Debug output
1154 "Timeline::GetFrame (Add frame to cache)",
1155 "requested_frame", requested_frame,
1156 "info.width", info.width,
1157 "info.height", info.height);
1158
1159 // Set frame # on mapped frame
1160 new_frame->SetFrameNumber(requested_frame);
1161
1162 // Add final frame to cache (only for valid timeline range)
1163 if (!past_timeline_end && CacheEpoch() == decode_epoch
1164 && !CacheRefreshPending())
1165 final_cache->Add(new_frame);
1166 // Return frame (or blank frame)
1167 return new_frame;
1168 }
1169 }
1170}
1171
1172
1173// Find intersecting clips (or non intersecting clips)
1174std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include)
1175{
1176 // Find matching clips
1177 std::vector<Clip*> matching_clips;
1178
1179 // Calculate time of frame
1180 const int64_t min_requested_frame = requested_frame;
1181 const int64_t max_requested_frame = requested_frame + (number_of_frames - 1);
1182
1183 // Find Clips at this time
1184 matching_clips.reserve(clips.size());
1185 const double fpsD = info.fps.ToDouble();
1186 for (auto clip : clips)
1187 {
1188 // Does clip intersect the current requested time
1189 int64_t clip_start_position = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1190 int64_t clip_end_position = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1191
1192 bool does_clip_intersect =
1193 (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
1194 (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
1195
1196 // Debug output
1198 "Timeline::find_intersecting_clips (Is clip near or intersecting)",
1199 "requested_frame", requested_frame,
1200 "min_requested_frame", min_requested_frame,
1201 "max_requested_frame", max_requested_frame,
1202 "clip->Position()", clip->Position(),
1203 "does_clip_intersect", does_clip_intersect);
1204
1205 // Open (or schedule for closing) this clip, based on if it's intersecting or not
1206 update_open_clips(clip, does_clip_intersect);
1207
1208 // Clip is visible
1209 if (does_clip_intersect && include)
1210 // Add the intersecting clip
1211 matching_clips.push_back(clip);
1212
1213 else if (!does_clip_intersect && !include)
1214 // Add the non-intersecting clip
1215 matching_clips.push_back(clip);
1216
1217 } // end clip loop
1218
1219 // return list
1220 return matching_clips;
1221}
1222
1223// Set the cache object used by this reader
1225 // Get lock (prevent getting frames while this happens)
1226 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1227
1228 // Destroy previous cache (if managed by timeline)
1229 if (managed_cache && final_cache) {
1230 delete final_cache;
1231 final_cache = NULL;
1232 managed_cache = false;
1233 }
1234
1235 // Set new cache
1236 final_cache = new_cache;
1237}
1238
1239// Generate JSON string of this object
1240std::string Timeline::Json() const {
1241
1242 // Return formatted string
1243 return JsonValue().toStyledString();
1244}
1245
1246// Generate Json::Value for this object
1247Json::Value Timeline::JsonValue() const {
1248
1249 // Create root json object
1250 Json::Value root = ReaderBase::JsonValue(); // get parent properties
1251 root["type"] = "Timeline";
1252 root["viewport_scale"] = viewport_scale.JsonValue();
1253 root["viewport_x"] = viewport_x.JsonValue();
1254 root["viewport_y"] = viewport_y.JsonValue();
1255 root["color"] = color.JsonValue();
1256 root["path"] = path;
1257
1258 // Add array of clips
1259 root["clips"] = Json::Value(Json::arrayValue);
1260
1261 // Find Clips at this time
1262 for (const auto existing_clip : clips)
1263 {
1264 root["clips"].append(existing_clip->JsonValue());
1265 }
1266
1267 // Add array of effects
1268 root["effects"] = Json::Value(Json::arrayValue);
1269
1270 // loop through effects
1271 for (const auto existing_effect: effects)
1272 {
1273 root["effects"].append(existing_effect->JsonValue());
1274 }
1275
1276 // return JsonValue
1277 return root;
1278}
1279
1280// Load JSON string into this object
1281void Timeline::SetJson(const std::string value) {
1282
1283 // Get lock (prevent getting frames while this happens)
1284 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1285
1286 // Parse JSON string into JSON objects
1287 try
1288 {
1289 const Json::Value root = openshot::stringToJson(value);
1290 // Set all values that match
1291 SetJsonValue(root);
1292 }
1293 catch (const std::exception& e)
1294 {
1295 // Error parsing JSON (or missing keys)
1296 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1297 }
1298}
1299
1300// Load Json::Value into this object
1301void Timeline::SetJsonValue(const Json::Value root) {
1302
1303 // Get lock (prevent getting frames while this happens)
1304 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1305
1306 // Close timeline before we do anything (this closes all clips)
1307 bool was_open = is_open;
1308 Close();
1309
1310 // Set parent data
1312
1313 // Set data from Json (if key is found)
1314 if (!root["path"].isNull())
1315 path = root["path"].asString();
1316
1317 if (!root["clips"].isNull()) {
1318 // Clear existing clips
1319 clips.clear();
1320
1321 // loop through clips
1322 for (const Json::Value existing_clip : root["clips"]) {
1323 // Skip NULL nodes
1324 if (existing_clip.isNull()) {
1325 continue;
1326 }
1327
1328 // Create Clip
1329 Clip *c = new Clip();
1330
1331 // Keep track of allocated clip objects
1332 allocated_clips.insert(c);
1333
1334 // When a clip is attached to an object, it searches for the object
1335 // on it's parent timeline. Setting the parent timeline of the clip here
1336 // allows attaching it to an object when exporting the project (because)
1337 // the exporter script initializes the clip and it's effects
1338 // before setting its parent timeline.
1339 c->ParentTimeline(this);
1340
1341 // Load Json into Clip
1342 c->SetJsonValue(existing_clip);
1343
1344 // Add Clip to Timeline
1345 AddClip(c);
1346 }
1347 }
1348
1349 if (!root["effects"].isNull()) {
1350 // Clear existing effects
1351 effects.clear();
1352
1353 // loop through effects
1354 for (const Json::Value existing_effect :root["effects"]) {
1355 // Skip NULL nodes
1356 if (existing_effect.isNull()) {
1357 continue;
1358 }
1359
1360 // Create Effect
1361 EffectBase *e = NULL;
1362
1363 if (!existing_effect["type"].isNull()) {
1364 // Create instance of effect
1365 if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) {
1366
1367 // Keep track of allocated effect objects
1368 allocated_effects.insert(e);
1369
1370 // Load Json into Effect
1371 e->SetJsonValue(existing_effect);
1372
1373 // Add Effect to Timeline
1374 AddEffect(e);
1375 }
1376 }
1377 }
1378 }
1379
1380 if (!root["duration"].isNull()) {
1381 // Update duration of timeline
1382 info.duration = root["duration"].asDouble();
1384 }
1385
1386 // Update preview settings
1389
1390 // Resort (and recalculate min/max duration)
1391 sort_clips();
1392 sort_effects();
1393
1394 // Re-open if needed
1395 if (was_open)
1396 Open();
1397
1398 // Timeline content changed: notify cache clients to rescan active window.
1399 BumpCacheEpoch();
1400}
1401
1402// Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
1403void Timeline::ApplyJsonDiff(std::string value) {
1404
1405 // Get lock (prevent getting frames while this happens)
1406 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1407
1408 // Parse JSON string into JSON objects
1409 try
1410 {
1411 Json::Value root = openshot::stringToJson(value);
1412 const uint64_t initial_cache_epoch = CacheEpoch();
1413 // Each change is owned here and consumed once. Move it through the
1414 // existing by-value API instead of copying entire animation curves.
1415 for (auto& change : root) {
1416 std::string change_key = change["key"][(uint)0].asString();
1417
1418 // Process each type of change
1419 if (change_key == "clips")
1420 // Apply to CLIPS
1421 apply_json_to_clips(std::move(change));
1422
1423 else if (change_key == "effects")
1424 // Apply to EFFECTS
1425 apply_json_to_effects(std::move(change));
1426
1427 else
1428 // Apply to TIMELINE
1429 apply_json_to_timeline(std::move(change));
1430
1431 }
1432
1433 // Timeline content changed: notify cache clients to rescan active window.
1434 if (!root.empty() && CacheEpoch() == initial_cache_epoch) {
1435 BumpCacheEpoch();
1436 }
1437 }
1438 catch (const std::exception& e)
1439 {
1440 // Error parsing JSON (or missing keys)
1441 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1442 }
1443}
1444
1445void Timeline::BumpCacheEpoch() {
1446 cache_epoch.fetch_add(1, std::memory_order_relaxed);
1447}
1448
1449void Timeline::InvalidateCacheForClip(const Clip* clip) {
1450 if (!clip || !final_cache) {
1451 return;
1452 }
1453
1454 const double fpsD = info.fps.ToDouble();
1455 const int64_t starting_frame = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1456 const int64_t ending_frame = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD)) + 1;
1457 final_cache->Remove(starting_frame - 8, ending_frame + 8);
1458 BumpCacheEpoch();
1459}
1460
1461// Apply JSON diff to clips
1462void Timeline::apply_json_to_clips(Json::Value change) {
1463
1464 // Get key and type of change
1465 std::string change_type = change["type"].asString();
1466 std::string clip_id = "";
1467 Clip *existing_clip = NULL;
1468
1469 // Find id of clip (if any)
1470 for (auto key_part : change["key"]) {
1471 // Get each change
1472 if (key_part.isObject()) {
1473 // Check for id
1474 if (!key_part["id"].isNull()) {
1475 // Set the id
1476 clip_id = key_part["id"].asString();
1477
1478 // Find matching clip in timeline (if any)
1479 for (auto c : clips)
1480 {
1481 if (c->Id() == clip_id) {
1482 existing_clip = c;
1483 break; // clip found, exit loop
1484 }
1485 }
1486 break; // id found, exit loop
1487 }
1488 }
1489 }
1490
1491 // Check for a more specific key (targetting this clip's effects)
1492 // For example: ["clips", {"id:123}, "effects", {"id":432}]
1493 if (existing_clip && change["key"].size() == 4 && change["key"][2] == "effects")
1494 {
1495 // This change is actually targetting a specific effect under a clip (and not the clip)
1496 Json::Value key_part = change["key"][3];
1497
1498 if (key_part.isObject()) {
1499 // Check for id
1500 if (!key_part["id"].isNull())
1501 {
1502 // Set the id
1503 std::string effect_id = key_part["id"].asString();
1504
1505 // Find matching effect in timeline (if any)
1506 std::list<EffectBase*> effect_list = existing_clip->Effects();
1507 for (auto e : effect_list)
1508 {
1509 if (e->Id() == effect_id) {
1510 // Apply the change to the effect directly
1511 apply_json_to_effects(std::move(change), e);
1512
1513 // Effect-only diffs must clear the owning clip cache.
1514 if (existing_clip->GetCache()) {
1515 existing_clip->GetCache()->Clear();
1516 }
1517
1518 // Calculate start and end frames that this impacts, and remove those frames from the cache
1519 int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1520 int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1521 final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1522
1523 return; // effect found, don't update clip
1524 }
1525 }
1526 }
1527 }
1528 }
1529
1530 // Determine type of change operation
1531 if (change_type == "insert") {
1532
1533 // Create clip
1534 Clip *clip = new Clip();
1535
1536 // Keep track of allocated clip objects
1537 allocated_clips.insert(clip);
1538
1539 // Match full timeline JSON loading: parent timeline must be available
1540 // before clip JSON can inflate nested readers/effects.
1541 clip->ParentTimeline(this);
1542
1543 // Set properties of clip from JSON
1544 clip->SetJsonValue(std::move(change["value"]));
1545
1546 // Add clip to timeline
1547 AddClip(clip);
1548
1549 } else if (change_type == "update") {
1550
1551 // Update existing clip
1552 if (existing_clip) {
1553 // Calculate start and end frames prior to the update
1554 int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1555 int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1556
1557 // Update clip properties from JSON
1558 existing_clip->SetJsonValue(std::move(change["value"]));
1559
1560 // Calculate new start and end frames after the update
1561 int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1562 int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1563
1564 // Remove both the old and new ranges from the timeline cache
1565 final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1566 final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1567
1568 // Apply framemapper (or update existing framemapper)
1569 if (auto_map_clips) {
1570 apply_mapper_to_clip(existing_clip);
1571 }
1572 }
1573
1574 } else if (change_type == "delete") {
1575
1576 // Remove existing clip
1577 if (existing_clip) {
1578 // Calculate start and end frames that this impacts, and remove those frames from the cache
1579 int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1580 int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1581
1582 // RemoveClip deletes clips owned by the timeline. Read their bounds first.
1583 RemoveClip(existing_clip);
1584 final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1585 }
1586
1587 }
1588
1589 // Re-Sort Clips (since they likely changed)
1590 sort_clips();
1591}
1592
1593// Apply JSON diff to effects
1594void Timeline::apply_json_to_effects(Json::Value change) {
1595
1596 // Get key and type of change
1597 std::string change_type = change["type"].asString();
1598 EffectBase *existing_effect = NULL;
1599
1600 // Find id of an effect (if any)
1601 for (auto key_part : change["key"]) {
1602
1603 if (key_part.isObject()) {
1604 // Check for id
1605 if (!key_part["id"].isNull())
1606 {
1607 // Set the id
1608 std::string effect_id = key_part["id"].asString();
1609
1610 // Find matching effect in timeline (if any)
1611 for (auto e : effects)
1612 {
1613 if (e->Id() == effect_id) {
1614 existing_effect = e;
1615 break; // effect found, exit loop
1616 }
1617 }
1618 break; // id found, exit loop
1619 }
1620 }
1621 }
1622
1623 // Now that we found the effect, apply the change to it
1624 if (existing_effect || change_type == "insert") {
1625 // Apply change to effect
1626 apply_json_to_effects(std::move(change), existing_effect);
1627 }
1628}
1629
1630// Apply JSON diff to effects (if you already know which effect needs to be updated)
1631void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_effect) {
1632
1633 // Get key and type of change
1634 std::string change_type = change["type"].asString();
1635
1636 // Calculate start and end frames that this impacts, and remove those frames from the cache
1637 if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1638 int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1639 int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1640 final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1641 }
1642
1643 // Determine type of change operation
1644 if (change_type == "insert") {
1645
1646 // Determine type of effect
1647 std::string effect_type = change["value"]["type"].asString();
1648
1649 // Create Effect
1650 EffectBase *e = NULL;
1651
1652 // Init the matching effect object
1653 if ( (e = EffectInfo().CreateEffect(effect_type)) ) {
1654
1655 // Keep track of allocated effect objects
1656 allocated_effects.insert(e);
1657
1658 // Load Json into Effect
1659 e->SetJsonValue(std::move(change["value"]));
1660
1661 // Add Effect to Timeline
1662 AddEffect(e);
1663 }
1664
1665 } else if (change_type == "update") {
1666
1667 // Update existing effect
1668 if (existing_effect) {
1669
1670 // Calculate start and end frames that this impacts, and remove those frames from the cache
1671 int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1672 int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1673 final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1674
1675 // Update effect properties from JSON
1676 existing_effect->SetJsonValue(std::move(change["value"]));
1677 }
1678
1679 } else if (change_type == "delete") {
1680
1681 // Remove existing effect
1682 if (existing_effect) {
1683
1684 // Calculate start and end frames that this impacts, and remove those frames from the cache
1685 int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1686 int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1687 final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1688
1689 // Remove effect from timeline
1690 RemoveEffect(existing_effect);
1691 }
1692
1693 }
1694
1695 // Re-Sort Effects (since they likely changed)
1696 sort_effects();
1697}
1698
1699// Apply JSON diff to timeline properties
1700void Timeline::apply_json_to_timeline(Json::Value change) {
1701 bool cache_dirty = true;
1702
1703 // Get key and type of change
1704 std::string change_type = change["type"].asString();
1705 std::string root_key = change["key"][(uint)0].asString();
1706 std::string sub_key = "";
1707 if (change["key"].size() >= 2)
1708 sub_key = change["key"][(uint)1].asString();
1709
1710 // Determine type of change operation
1711 if (change_type == "insert" || change_type == "update") {
1712
1713 // INSERT / UPDATE
1714 // Check for valid property
1715 if (root_key == "color")
1716 // Set color
1717 color.SetJsonValue(std::move(change["value"]));
1718 else if (root_key == "viewport_scale")
1719 // Set viewport scale
1720 viewport_scale.SetJsonValue(std::move(change["value"]));
1721 else if (root_key == "viewport_x")
1722 // Set viewport x offset
1723 viewport_x.SetJsonValue(std::move(change["value"]));
1724 else if (root_key == "viewport_y")
1725 // Set viewport y offset
1726 viewport_y.SetJsonValue(std::move(change["value"]));
1727 else if (root_key == "duration") {
1728 // Update duration of timeline
1729 info.duration = change["value"].asDouble();
1731
1732 // We don't want to clear cache for duration adjustments
1733 cache_dirty = false;
1734 }
1735 else if (root_key == "width") {
1736 // Set width
1737 info.width = change["value"].asInt();
1739 }
1740 else if (root_key == "height") {
1741 // Set height
1742 info.height = change["value"].asInt();
1744 }
1745 else if (root_key == "fps" && sub_key == "" && change["value"].isObject()) {
1746 // Set fps fraction
1747 if (!change["value"]["num"].isNull())
1748 info.fps.num = change["value"]["num"].asInt();
1749 if (!change["value"]["den"].isNull())
1750 info.fps.den = change["value"]["den"].asInt();
1751 }
1752 else if (root_key == "fps" && sub_key == "num")
1753 // Set fps.num
1754 info.fps.num = change["value"].asInt();
1755 else if (root_key == "fps" && sub_key == "den")
1756 // Set fps.den
1757 info.fps.den = change["value"].asInt();
1758 else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) {
1759 // Set display_ratio fraction
1760 if (!change["value"]["num"].isNull())
1761 info.display_ratio.num = change["value"]["num"].asInt();
1762 if (!change["value"]["den"].isNull())
1763 info.display_ratio.den = change["value"]["den"].asInt();
1764 }
1765 else if (root_key == "display_ratio" && sub_key == "num")
1766 // Set display_ratio.num
1767 info.display_ratio.num = change["value"].asInt();
1768 else if (root_key == "display_ratio" && sub_key == "den")
1769 // Set display_ratio.den
1770 info.display_ratio.den = change["value"].asInt();
1771 else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) {
1772 // Set pixel_ratio fraction
1773 if (!change["value"]["num"].isNull())
1774 info.pixel_ratio.num = change["value"]["num"].asInt();
1775 if (!change["value"]["den"].isNull())
1776 info.pixel_ratio.den = change["value"]["den"].asInt();
1777 }
1778 else if (root_key == "pixel_ratio" && sub_key == "num")
1779 // Set pixel_ratio.num
1780 info.pixel_ratio.num = change["value"].asInt();
1781 else if (root_key == "pixel_ratio" && sub_key == "den")
1782 // Set pixel_ratio.den
1783 info.pixel_ratio.den = change["value"].asInt();
1784
1785 else if (root_key == "sample_rate")
1786 // Set sample rate
1787 info.sample_rate = change["value"].asInt();
1788 else if (root_key == "channels")
1789 // Set channels
1790 info.channels = change["value"].asInt();
1791 else if (root_key == "channel_layout")
1792 // Set channel layout
1793 info.channel_layout = (ChannelLayout) change["value"].asInt();
1794 else
1795 // Error parsing JSON (or missing keys)
1796 throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1797
1798
1799 } else if (change["type"].asString() == "delete") {
1800
1801 // DELETE / RESET
1802 // Reset the following properties (since we can't delete them)
1803 if (root_key == "color") {
1804 color = Color();
1805 color.red = Keyframe(0.0);
1806 color.green = Keyframe(0.0);
1807 color.blue = Keyframe(0.0);
1808 }
1809 else if (root_key == "viewport_scale")
1810 viewport_scale = Keyframe(1.0);
1811 else if (root_key == "viewport_x")
1812 viewport_x = Keyframe(0.0);
1813 else if (root_key == "viewport_y")
1814 viewport_y = Keyframe(0.0);
1815 else
1816 // Error parsing JSON (or missing keys)
1817 throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1818
1819 }
1820
1821 if (cache_dirty) {
1822 // Clear entire cache
1823 ClearAllCache();
1824 }
1825}
1826
1827// Publish the request sequence first so cache lookups cannot observe a new
1828// epoch while treating the old cache as current. Completion has its own sequence
1829// so a request arriving during a clear cannot be lost.
1831 cache_clear_requested.fetch_add(1);
1832 BumpCacheEpoch();
1833}
1834
1835// Clear all caches
1837 // Get lock (prevent getting frames while this happens)
1838 const std::lock_guard<std::recursive_mutex> guard(getFrameMutex);
1839 // Keep the refresh pending throughout the clear (including the fast cache
1840 // hit path outside getFrameMutex). Newer requests remain pending.
1841 const uint64_t clear_request = cache_clear_requested.load();
1842
1843 // Clear primary cache
1844 if (final_cache) {
1845 final_cache->Clear();
1846 }
1847
1848 // Loop through all clips
1849 try {
1850 for (const auto clip : clips) {
1851 // Clear cache on clip and reader if present
1852 if (clip->Reader()) {
1853 if (auto rc = clip->Reader()->GetCache())
1854 rc->Clear();
1855
1856 // Clear nested Reader (if deep clear requested)
1857 if (deep && clip->Reader()->Name() == "FrameMapper") {
1858 FrameMapper *nested_reader = static_cast<FrameMapper *>(clip->Reader());
1859 if (nested_reader->Reader()) {
1860 if (auto nc = nested_reader->Reader()->GetCache())
1861 nc->Clear();
1862 }
1863 }
1864 }
1865
1866 // Clear clip cache
1867 if (auto cc = clip->GetCache())
1868 cc->Clear();
1869 }
1870 } catch (const ReaderClosed & e) {
1871 // ...
1872 }
1873
1874 // Cache content changed: notify cache clients to rebuild their window baseline.
1875 BumpCacheEpoch();
1876 cache_clear_completed.store(clear_request);
1877}
1878
1879// Set the preview size without changing the project's native dimensions.
1880void Timeline::SetMaxSize(int width, int height) {
1881 // Ignore transient invalid widget sizes (for example while hidden).
1882 if (width <= 0 || height <= 0 || info.width <= 0 || info.height <= 0)
1883 return;
1884 // Maintain aspect ratio regardless of what size is passed in
1885 QSize display_ratio_size = QSize(info.width, info.height);
1886 QSize proposed_size = QSize(std::min(width, info.width), std::min(height, info.height));
1887
1888 // Scale QSize up to proposed size
1889 display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1890 // Preserve exact full-resolution output, including non-aligned profiles and
1891 // tiny test images. Only reduced previews use the aligned sampling grid.
1892 if (display_ratio_size != QSize(info.width, info.height) &&
1893 info.width >= 4 && info.height >= 4)
1894 display_ratio_size = AlignPreviewSize(display_ratio_size);
1895
1896 // Update preview settings
1897 preview_width = display_ratio_size.width();
1898 preview_height = display_ratio_size.height();
1899}
1900
1901// Resolve equal-power audio gains from transition placement relative to the clip edges.
1902std::pair<float, float> Timeline::ResolveTransitionAudioGains(Clip* source_clip, int64_t timeline_frame_number, bool is_top_clip) const
1903{
1904 constexpr double half_pi = 1.57079632679489661923;
1905
1906 if (!source_clip)
1907 return {1.0f, 1.0f};
1908
1909 const double fpsD = info.fps.ToDouble();
1910 Mask* active_mask = nullptr;
1911 int64_t effect_start_position = 0;
1912 int64_t effect_end_position = 0;
1913
1914 // Find the single active transition on this layer that requested overlapping audio fades.
1915 for (auto effect : effects) {
1916 if (effect->Layer() != source_clip->Layer())
1917 continue;
1918
1919 auto* mask = dynamic_cast<Mask*>(effect);
1920 if (!mask || !mask->fade_audio_hint)
1921 continue;
1922
1923 const int64_t start_pos = static_cast<int64_t>(std::llround(effect->Position() * fpsD)) + 1;
1924 const int64_t end_pos = static_cast<int64_t>(std::llround((effect->Position() + effect->Duration()) * fpsD));
1925 if (start_pos > timeline_frame_number || end_pos < timeline_frame_number)
1926 continue;
1927
1928 if (active_mask)
1929 return {1.0f, 1.0f};
1930
1931 active_mask = mask;
1932 effect_start_position = start_pos;
1933 effect_end_position = end_pos;
1934 }
1935
1936 if (!active_mask)
1937 return {1.0f, 1.0f};
1938
1939 struct AudibleClipInfo {
1940 Clip* clip;
1941 int64_t start_pos;
1942 int64_t end_pos;
1943 };
1944
1945 std::vector<AudibleClipInfo> audible_clips;
1946 audible_clips.reserve(2);
1947
1948 // Collect the audible clips covered by this transition on the current layer.
1949 for (auto clip : clips) {
1950 if (clip->Layer() != source_clip->Layer())
1951 continue;
1952 if (!clip->Reader() || !clip->Reader()->info.has_audio)
1953 continue;
1954
1955 const int64_t clip_start_pos = static_cast<int64_t>(std::llround(clip->Position() * fpsD)) + 1;
1956 const int64_t clip_end_pos = static_cast<int64_t>(std::llround((clip->Position() + clip->Duration()) * fpsD));
1957 if (clip_start_pos > timeline_frame_number || clip_end_pos < timeline_frame_number)
1958 continue;
1959
1960 const int64_t clip_start_frame = static_cast<int64_t>(std::llround(clip->Start() * fpsD)) + 1;
1961 const int64_t clip_frame_number = timeline_frame_number - clip_start_pos + clip_start_frame;
1962 if (clip->has_audio.GetInt(clip_frame_number) == 0)
1963 continue;
1964
1965 audible_clips.push_back({clip, clip_start_pos, clip_end_pos});
1966 if (audible_clips.size() > 2)
1967 return {1.0f, 1.0f};
1968 }
1969
1970 if (audible_clips.empty())
1971 return {1.0f, 1.0f};
1972
1973 // Skip clips that are not actually participating in this transition audio decision.
1974 const auto source_it = std::find_if(
1975 audible_clips.begin(),
1976 audible_clips.end(),
1977 [source_clip](const AudibleClipInfo& info) {
1978 return info.clip == source_clip;
1979 });
1980 if (source_it == audible_clips.end())
1981 return {1.0f, 1.0f};
1982
1983 // Keep the current top/non-top clip routing intact when two clips overlap.
1984 if (audible_clips.size() == 2) {
1985 // Collected in compositing order, including equal-position ties.
1986 Clip* top_clip = audible_clips.back().clip;
1987 if ((is_top_clip && source_clip != top_clip) || (!is_top_clip && source_clip == top_clip))
1988 return {1.0f, 1.0f};
1989 }
1990
1991 // Infer fade direction from which transition edge is closer to this clip.
1992 const int64_t left_distance = std::llabs(effect_start_position - source_it->start_pos);
1993 const int64_t right_distance = std::llabs(effect_end_position - source_it->end_pos);
1994 const bool clip_fades_in = left_distance <= right_distance;
1995
1996 // Evaluate the current frame and previous frame so Timeline can preserve per-frame gain ramps.
1997 const auto compute_gain = [&](int64_t frame_number) -> float {
1998 if (effect_end_position <= effect_start_position)
1999 return 1.0f;
2000
2001 const double span = static_cast<double>(effect_end_position - effect_start_position);
2002 double t = static_cast<double>(frame_number - effect_start_position) / span;
2003 if (t < 0.0)
2004 t = 0.0;
2005 else if (t > 1.0)
2006 t = 1.0;
2007
2008 return static_cast<float>(clip_fades_in ? std::sin(t * half_pi) : std::cos(t * half_pi));
2009 };
2010
2011 return {compute_gain(timeline_frame_number - 1), compute_gain(timeline_frame_number)};
2012}
Header file for CacheBase class.
Header file for CacheDisk class.
Header file for CacheMemory class.
Header file for CrashHandler class.
Header file for all Exception classes.
Header file for the FrameMapper class.
Header file for Mask class.
#define OPEN_MP_NUM_PROCESSORS
Header file for Timeline class.
All cache managers in libopenshot are based on this CacheBase class.
Definition CacheBase.h:35
virtual void Clear()=0
Clear the cache of all frames.
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
Get a frame from the cache.
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
void SetMaxBytesFromInfo(int64_t number_of_frames, int width, int height, int sample_rate, int channels)
Set maximum bytes to a different amount based on a ReaderInfo struct.
Definition CacheBase.cpp:28
This class is a memory-based cache manager for Frame objects.
Definition CacheMemory.h:29
void Clear()
Clear the cache of all frames.
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition ClipBase.h:91
float Duration() const
Get the length of this clip (in seconds)
Definition ClipBase.h:93
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number)=0
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
std::string Id() const
Get the Id of this clip object.
Definition ClipBase.h:88
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
Definition ClipBase.h:90
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition ClipBase.cpp:80
float Position() const
Get position on timeline (in seconds)
Definition ClipBase.h:89
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Definition ClipBase.h:94
This class represents a clip (used to arrange readers on the timeline)
Definition Clip.h:89
openshot::VolumeMixType mixing
What strategy should be followed when mixing audio with other clips.
Definition Clip.h:192
openshot::Keyframe channel_filter
A number representing an audio channel to filter (clears all other channels)
Definition Clip.h:367
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition Clip.cpp:1054
openshot::CacheMemory * GetCache() override
Get the cache object (always return NULL for this reader)
Definition Clip.h:220
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::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition Clip.h:310
std::list< openshot::EffectBase * > Effects()
Return the list of effects on the timeline.
Definition Clip.h:259
openshot::Keyframe volume
Curve representing the volume (0 to 1)
Definition Clip.h:351
openshot::Keyframe channel_mapping
A number representing an audio channel to output (only works when filtering a channel)
Definition Clip.h:368
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition Clip.cpp:346
This class represents a color (used on the timeline and clips)
Definition Color.h:27
std::string GetColorHex(int64_t frame_number)
Get the HEX value of a color at a specific frame.
Definition Color.cpp:48
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
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition Color.cpp:87
static CrashHandler * Instance()
This abstract class is the base class, used by all effects in libopenshot.
Definition EffectBase.h:57
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
This class returns a listing of all effects supported by libopenshot.
Definition EffectInfo.h:29
EffectBase * CreateEffect(std::string effect_type)
Create an instance of an effect (factory style)
This class represents a fraction.
Definition Fraction.h:30
int num
Numerator for the fraction.
Definition Fraction.h:32
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition Fraction.cpp:35
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition Fraction.cpp:40
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Definition Fraction.cpp:65
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition Fraction.cpp:78
int den
Denominator for the fraction.
Definition Fraction.h:33
This class creates a mapping between 2 different frame rates, applying a specific pull-down technique...
void ChangeMapping(Fraction target_fps, PulldownType pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout)
Change frame rate or audio mapping details.
ReaderBase * Reader()
Get the current reader.
void Close() override
Close the openshot::FrameMapper and internal reader.
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Definition Frame.cpp:486
Exception for files that can not be found or opened.
Definition Exceptions.h:194
Exception for missing JSON Change key.
Definition Exceptions.h:269
Exception for invalid JSON.
Definition Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition KeyFrame.h:53
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition KeyFrame.cpp:282
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition KeyFrame.cpp:339
int64_t GetCount() const
Get the number of points (i.e. # of points)
Definition KeyFrame.cpp: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
This class uses the image libraries to apply alpha (or transparency) masks to any frame....
Definition Mask.h:37
Exception for frames that are out of bounds.
Definition Exceptions.h:307
This abstract class is the base class, used by all readers in libopenshot.
Definition ReaderBase.h:76
openshot::ReaderInfo info
Information about the current media file.
Definition ReaderBase.h:91
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition ReaderBase.h:79
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition ReaderBase.h:80
Exception when a reader is closed, and a frame is requested.
Definition Exceptions.h:370
This class is contains settings used by libopenshot (and can be safely toggled at any point)
Definition Settings.h:26
std::string PATH_OPENSHOT_INSTALL
Definition Settings.h:123
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition Settings.cpp:43
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
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
Json::Value JsonValue() const override
Generate Json::Value for this object.
openshot::Keyframe viewport_scale
Curve representing the scale of the viewport (0 to 100)
Definition Timeline.h:343
void ApplyJsonDiff(std::string value)
Apply a special formatted JSON object, which represents a change to the timeline (add,...
openshot::EffectBase * GetClipEffect(const std::string &id)
Look up a clip effect by ID.
Definition Timeline.cpp:445
void AddClip(openshot::Clip *clip)
Add an openshot::Clip to the timeline.
Definition Timeline.cpp:340
virtual ~Timeline()
Definition Timeline.cpp:214
void RequestClearAllCache()
std::list< openshot::EffectBase * > ClipEffects() const
Return the list of effects on all clips.
Definition Timeline.cpp:458
std::list< std::string > GetTrackedObjectsIds() const
Return the ID's of the tracked objects as a list of strings.
Definition Timeline.cpp:266
std::string Json() const override
Generate JSON string of this object.
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition Timeline.cpp:483
double GetMinTime()
Look up the position/start time of the first timeline element.
Definition Timeline.cpp:504
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Definition Timeline.cpp:980
void ApplyMapperToClips()
Apply the timeline's framerate and samplerate to all clips.
Definition Timeline.cpp:539
openshot::Color color
Background color of timeline canvas.
Definition Timeline.h:348
std::string GetTrackedObjectValues(std::string id, int64_t frame_number) const
Return the trackedObject's properties as a JSON string.
Definition Timeline.cpp:282
Timeline(int width, int height, openshot::Fraction fps, int sample_rate, int channels, openshot::ChannelLayout channel_layout)
Constructor for the timeline (which configures the default frame properties)
Definition Timeline.cpp:36
std::shared_ptr< openshot::TrackedObjectBase > GetTrackedObject(std::string id) const
Return tracked object pointer by it's id.
Definition Timeline.cpp:249
int64_t GetMinFrame()
Look up the start frame number of the first element on the timeline (first frame is 1)
Definition Timeline.cpp:496
openshot::EffectBase * GetEffect(const std::string &id)
Look up a timeline effect by ID.
Definition Timeline.cpp:434
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
openshot::Clip * GetClip(const std::string &id)
Look up a single clip by ID.
Definition Timeline.cpp:422
void ClearAllCache(bool deep=false)
void AddEffect(openshot::EffectBase *effect)
Add an effect to the timeline.
Definition Timeline.cpp:368
bool CacheRefreshPending() const
Definition Timeline.h:331
void SetCache(openshot::CacheBase *new_cache)
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
Definition Timeline.h:334
void Clear()
Clear all clips, effects, and frame mappers from timeline (and free memory)
Definition Timeline.cpp:900
openshot::Keyframe viewport_x
Curve representing the x coordinate for the viewport.
Definition Timeline.h:344
void RemoveClip(openshot::Clip *clip)
Remove an openshot::Clip from the timeline.
Definition Timeline.cpp:403
void SetMaxSize(int width, int height)
double GetMaxTime()
Look up the end time of the latest timeline element.
Definition Timeline.cpp:477
void RemoveEffect(openshot::EffectBase *effect)
Remove an effect from the timeline.
Definition Timeline.cpp:384
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
void Open() override
Open the reader (and start consuming resources)
Definition Timeline.cpp:968
void SetJson(const std::string value) override
Load JSON string into this object.
openshot::Keyframe viewport_y
Curve representing the y coordinate for the viewport.
Definition Timeline.h:345
void Close() override
Close the timeline reader (and any resources it was consuming)
Definition Timeline.cpp:946
This namespace is the default namespace for all code in the openshot library.
@ PULLDOWN_NONE
Do not apply pull-down techniques, just repeat or skip entire frames.
Definition FrameMapper.h:46
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...
@ VOLUME_MIX_AVERAGE
Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100%.
Definition Enums.h:70
@ VOLUME_MIX_REDUCE
Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%)
Definition Enums.h:71
QSize AlignPreviewSize(const QSize &size)
Definition PreviewSize.h:14
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]
Like CompareClipEndFrames, but for effects.
Definition Timeline.h:81
This struct contains info about a media file, such as height, width, frames per second,...
Definition ReaderBase.h:39
float duration
Length of time (in seconds)
Definition ReaderBase.h:43
int width
The width of the video (in pixesl)
Definition ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition ReaderBase.h:48
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
Definition ReaderBase.h:51
int height
The height of the video (in pixels)
Definition ReaderBase.h:45
int64_t video_length
The number of frames in the video stream.
Definition ReaderBase.h:53
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition ReaderBase.h:58
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition ReaderBase.h:52
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
Definition ReaderBase.h:50
openshot::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
Definition ReaderBase.h:62
bool has_video
Determines if this file has a video stream.
Definition ReaderBase.h:40
bool has_audio
Determines if this file has an audio stream.
Definition ReaderBase.h:41
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition ReaderBase.h:55
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition ReaderBase.h:60
This struct contains info about the current Timeline clip instance.
bool is_before_clip_keyframes
Is this before clip keyframes are applied.
bool is_top_clip
Is clip on top (if overlapping another clip)