1 /**
2  * @file
3  * @brief Source file for Timeline class
4  * @author Jonathan Thomas <jonathan@openshot.org>
5  *
6  * @ref License
7  */
8 
9 /* LICENSE
10  *
11  * Copyright (c) 2008-2019 OpenShot Studios, LLC
12  * <http://www.openshotstudios.com/>. This file is part of
13  * OpenShot Library (libopenshot), an open-source project dedicated to
14  * delivering high quality video editing and animation solutions to the
15  * world. For more information visit <http://www.openshot.org/>.
16  *
17  * OpenShot Library (libopenshot) is free software: you can redistribute it
18  * and/or modify it under the terms of the GNU Lesser General Public License
19  * as published by the Free Software Foundation, either version 3 of the
20  * License, or (at your option) any later version.
21  *
22  * OpenShot Library (libopenshot) is distributed in the hope that it will be
23  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25  * GNU Lesser General Public License for more details.
26  *
27  * You should have received a copy of the GNU Lesser General Public License
28  * along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29  */
30 
31 #include "Timeline.h"
32 
33 #include "CacheBase.h"
34 #include "CacheDisk.h"
35 #include "CacheMemory.h"
36 #include "CrashHandler.h"
37 #include "FrameMapper.h"
38 #include "Exceptions.h"
39 
40 #include <QDir>
41 #include <QFileInfo>
42 
43 using namespace openshot;
44 
45 // Default Constructor for the timeline (which sets the canvas width and height)
Timeline(int width,int height,Fraction fps,int sample_rate,int channels,ChannelLayout channel_layout)46 Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) :
47 		is_open(false), auto_map_clips(true), managed_cache(true), path(""),
48 		max_concurrent_frames(OPEN_MP_NUM_PROCESSORS)
49 {
50 	// Create CrashHandler and Attach (incase of errors)
51 	CrashHandler::Instance();
52 
53 	// Init viewport size (curve based, because it can be animated)
54 	viewport_scale = Keyframe(100.0);
55 	viewport_x = Keyframe(0.0);
56 	viewport_y = Keyframe(0.0);
57 
58 	// Init background color
59 	color.red = Keyframe(0.0);
60 	color.green = Keyframe(0.0);
61 	color.blue = Keyframe(0.0);
62 
63 	// Init FileInfo struct (clear all values)
64 	info.width = width;
65 	info.height = height;
66 	preview_width = info.width;
67 	preview_height = info.height;
68 	info.fps = fps;
69 	info.sample_rate = sample_rate;
70 	info.channels = channels;
71 	info.channel_layout = channel_layout;
72 	info.video_timebase = fps.Reciprocal();
73 	info.duration = 60 * 30; // 30 minute default duration
74 	info.has_audio = true;
75 	info.has_video = true;
76 	info.video_length = info.fps.ToFloat() * info.duration;
77 	info.display_ratio = openshot::Fraction(width, height);
78 	info.display_ratio.Reduce();
79 	info.pixel_ratio = openshot::Fraction(1, 1);
80 	info.acodec = "openshot::timeline";
81 	info.vcodec = "openshot::timeline";
82 
83     // Init cache
84     final_cache = new CacheMemory();
85 
86 	// Init max image size
87 	SetMaxSize(info.width, info.height);
88 }
89 
90 // Delegating constructor that copies parameters from a provided ReaderInfo
Timeline(const ReaderInfo info)91 Timeline::Timeline(const ReaderInfo info) :
92 	Timeline::Timeline(info.width, info.height, info.fps, info.sample_rate,
93 	                   info.channels, info.channel_layout) {};
94 
95 // Constructor for the timeline (which loads a JSON structure from a file path, and initializes a timeline)
Timeline(const std::string & projectPath,bool convert_absolute_paths)96 Timeline::Timeline(const std::string& projectPath, bool convert_absolute_paths) :
97 		is_open(false), auto_map_clips(true), managed_cache(true), path(projectPath),
98         max_concurrent_frames(OPEN_MP_NUM_PROCESSORS) {
99 
100 	// Create CrashHandler and Attach (incase of errors)
101 	CrashHandler::Instance();
102 
103 	// Init final cache as NULL (will be created after loading json)
104 	final_cache = NULL;
105 
106 	// Init viewport size (curve based, because it can be animated)
107 	viewport_scale = Keyframe(100.0);
108 	viewport_x = Keyframe(0.0);
109 	viewport_y = Keyframe(0.0);
110 
111 	// Init background color
112 	color.red = Keyframe(0.0);
113 	color.green = Keyframe(0.0);
114 	color.blue = Keyframe(0.0);
115 
116 	// Check if path exists
117 	QFileInfo filePath(QString::fromStdString(path));
118 	if (!filePath.exists()) {
119 		throw InvalidFile("File could not be opened.", path);
120 	}
121 
122 	// Check OpenShot Install Path exists
123 	Settings *s = Settings::Instance();
124 	QDir openshotPath(QString::fromStdString(s->PATH_OPENSHOT_INSTALL));
125 	if (!openshotPath.exists()) {
126 		throw InvalidFile("PATH_OPENSHOT_INSTALL could not be found.", s->PATH_OPENSHOT_INSTALL);
127 	}
128 	QDir openshotTransPath(openshotPath.filePath("transitions"));
129 	if (!openshotTransPath.exists()) {
130 		throw InvalidFile("PATH_OPENSHOT_INSTALL/transitions could not be found.", openshotTransPath.path().toStdString());
131 	}
132 
133 	// Determine asset path
134 	QString asset_name = filePath.baseName().left(30) + "_assets";
135 	QDir asset_folder(filePath.dir().filePath(asset_name));
136 	if (!asset_folder.exists()) {
137 		// Create directory if needed
138 		asset_folder.mkpath(".");
139 	}
140 
141 	// Load UTF-8 project file into QString
142 	QFile projectFile(QString::fromStdString(path));
143 	projectFile.open(QFile::ReadOnly);
144 	QString projectContents = QString::fromUtf8(projectFile.readAll());
145 
146 	// Convert all relative paths into absolute paths (if requested)
147 	if (convert_absolute_paths) {
148 
149 		// Find all "image" or "path" references in JSON (using regex). Must loop through match results
150 		// due to our path matching needs, which are not possible with the QString::replace() function.
151 		QRegularExpression allPathsRegex(QStringLiteral("\"(image|path)\":.*?\"(.*?)\""));
152 		std::vector<QRegularExpressionMatch> matchedPositions;
153 		QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(projectContents);
154 		while (i.hasNext()) {
155 			QRegularExpressionMatch match = i.next();
156 			if (match.hasMatch()) {
157 				// Push all match objects into a vector (so we can reverse them later)
158 				matchedPositions.push_back(match);
159 			}
160 		}
161 
162 		// Reverse the matches (bottom of file to top, so our replacements don't break our match positions)
163 		std::vector<QRegularExpressionMatch>::reverse_iterator itr;
164 		for (itr = matchedPositions.rbegin(); itr != matchedPositions.rend(); itr++) {
165 			QRegularExpressionMatch match = *itr;
166 			QString relativeKey = match.captured(1); // image or path
167 			QString relativePath = match.captured(2); // relative file path
168 			QString absolutePath = "";
169 
170 			// Find absolute path of all path, image (including special replacements of @assets and @transitions)
171 			if (relativePath.startsWith("@assets")) {
172 				absolutePath = QFileInfo(asset_folder.absoluteFilePath(relativePath.replace("@assets", "."))).canonicalFilePath();
173 			} else if (relativePath.startsWith("@transitions")) {
174 				absolutePath = QFileInfo(openshotTransPath.absoluteFilePath(relativePath.replace("@transitions", "."))).canonicalFilePath();
175 			} else {
176 				absolutePath = QFileInfo(filePath.absoluteDir().absoluteFilePath(relativePath)).canonicalFilePath();
177 			}
178 
179 			// Replace path in JSON content, if an absolute path was successfully found
180 			if (!absolutePath.isEmpty()) {
181 				projectContents.replace(match.capturedStart(0), match.capturedLength(0), "\"" + relativeKey + "\": \"" + absolutePath + "\"");
182 			}
183 		}
184 		// Clear matches
185 		matchedPositions.clear();
186 	}
187 
188 	// Set JSON of project
189 	SetJson(projectContents.toStdString());
190 
191 	// Calculate valid duration and set has_audio and has_video
192 	// based on content inside this Timeline's clips.
193 	float calculated_duration = 0.0;
194 	for (auto clip : clips)
195 	{
196 		float clip_last_frame = clip->Position() + clip->Duration();
197 		if (clip_last_frame > calculated_duration)
198 			calculated_duration = clip_last_frame;
199 		if (clip->Reader() && clip->Reader()->info.has_audio)
200 			info.has_audio = true;
201 		if (clip->Reader() && clip->Reader()->info.has_video)
202 			info.has_video = true;
203 
204 	}
205 	info.video_length = calculated_duration * info.fps.ToFloat();
206 	info.duration = calculated_duration;
207 
208 	// Init FileInfo settings
209 	info.acodec = "openshot::timeline";
210 	info.vcodec = "openshot::timeline";
211 	info.video_timebase = info.fps.Reciprocal();
212 	info.has_video = true;
213 	info.has_audio = true;
214 
215     // Init cache
216     final_cache = new CacheMemory();
217 
218 	// Init max image size
219 	SetMaxSize(info.width, info.height);
220 }
221 
~Timeline()222 Timeline::~Timeline() {
223 	if (is_open)
224 		// Auto Close if not already
225 		Close();
226 
227 	// Free all allocated frame mappers
228 	std::set<FrameMapper *>::iterator it;
229 	for (it = allocated_frame_mappers.begin(); it != allocated_frame_mappers.end(); ) {
230 		// Dereference and clean up FrameMapper object
231 		FrameMapper *mapper = (*it);
232 		mapper->Reader(NULL);
233 		mapper->Close();
234 		delete mapper;
235 		// Remove reference and proceed to next element
236 		it = allocated_frame_mappers.erase(it);
237 	}
238 
239 	// Destroy previous cache (if managed by timeline)
240 	if (managed_cache && final_cache) {
241 		delete final_cache;
242 		final_cache = NULL;
243 	}
244 }
245 
246 // Add to the tracked_objects map a pointer to a tracked object (TrackedObjectBBox)
AddTrackedObject(std::shared_ptr<openshot::TrackedObjectBase> trackedObject)247 void Timeline::AddTrackedObject(std::shared_ptr<openshot::TrackedObjectBase> trackedObject){
248 
249 	// Search for the tracked object on the map
250 	auto iterator = tracked_objects.find(trackedObject->Id());
251 
252 	if (iterator != tracked_objects.end()){
253 		// Tracked object's id already present on the map, overwrite it
254 		iterator->second = trackedObject;
255 	}
256 	else{
257 		// Tracked object's id not present -> insert it on the map
258 		tracked_objects[trackedObject->Id()] = trackedObject;
259 	}
260 
261 	return;
262 }
263 
264 // Return tracked object pointer by it's id
GetTrackedObject(std::string id) const265 std::shared_ptr<openshot::TrackedObjectBase> Timeline::GetTrackedObject(std::string id) const{
266 
267 	// Search for the tracked object on the map
268 	auto iterator = tracked_objects.find(id);
269 
270 	if (iterator != tracked_objects.end()){
271 		// Id found, return the pointer to the tracked object
272 		std::shared_ptr<openshot::TrackedObjectBase> trackedObject = iterator->second;
273 		return trackedObject;
274 	}
275 	else {
276 		// Id not found, return a null pointer
277 		return nullptr;
278 	}
279 }
280 
281 // Return the ID's of the tracked objects as a list of strings
GetTrackedObjectsIds() const282 std::list<std::string> Timeline::GetTrackedObjectsIds() const{
283 
284 	// Create a list of strings
285 	std::list<std::string> trackedObjects_ids;
286 
287 	// Iterate through the tracked_objects map
288 	for (auto const& it: tracked_objects){
289 		// Add the IDs to the list
290 		trackedObjects_ids.push_back(it.first);
291 	}
292 
293 	return trackedObjects_ids;
294 }
295 
296 #ifdef USE_OPENCV
297 // Return the trackedObject's properties as a JSON string
GetTrackedObjectValues(std::string id,int64_t frame_number) const298 std::string Timeline::GetTrackedObjectValues(std::string id, int64_t frame_number) const {
299 
300 	// Initialize the JSON object
301 	Json::Value trackedObjectJson;
302 
303 	// Search for the tracked object on the map
304 	auto iterator = tracked_objects.find(id);
305 
306 	if (iterator != tracked_objects.end())
307 	{
308 		// Id found, Get the object pointer and cast it as a TrackedObjectBBox
309 		std::shared_ptr<TrackedObjectBBox> trackedObject = std::static_pointer_cast<TrackedObjectBBox>(iterator->second);
310 
311 		// Get the trackedObject values for it's first frame
312 		if (trackedObject->ExactlyContains(frame_number)){
313 			BBox box = trackedObject->GetBox(frame_number);
314 			float x1 = box.cx - (box.width/2);
315 			float y1 = box.cy - (box.height/2);
316 			float x2 = box.cx + (box.width/2);
317 			float y2 = box.cy + (box.height/2);
318 			float rotation = box.angle;
319 
320 			trackedObjectJson["x1"] = x1;
321 			trackedObjectJson["y1"] = y1;
322 			trackedObjectJson["x2"] = x2;
323 			trackedObjectJson["y2"] = y2;
324 			trackedObjectJson["rotation"] = rotation;
325 
326 		} else {
327 			BBox box = trackedObject->BoxVec.begin()->second;
328 			float x1 = box.cx - (box.width/2);
329 			float y1 = box.cy - (box.height/2);
330 			float x2 = box.cx + (box.width/2);
331 			float y2 = box.cy + (box.height/2);
332 			float rotation = box.angle;
333 
334 			trackedObjectJson["x1"] = x1;
335 			trackedObjectJson["y1"] = y1;
336 			trackedObjectJson["x2"] = x2;
337 			trackedObjectJson["y2"] = y2;
338 			trackedObjectJson["rotation"] = rotation;
339 		}
340 
341 	}
342 	else {
343 		// Id not found, return all 0 values
344 		trackedObjectJson["x1"] = 0;
345 		trackedObjectJson["y1"] = 0;
346 		trackedObjectJson["x2"] = 0;
347 		trackedObjectJson["y2"] = 0;
348 		trackedObjectJson["rotation"] = 0;
349 	}
350 
351 	return trackedObjectJson.toStyledString();
352 }
353 #endif
354 
355 // Add an openshot::Clip to the timeline
AddClip(Clip * clip)356 void Timeline::AddClip(Clip* clip)
357 {
358 	// Assign timeline to clip
359 	clip->ParentTimeline(this);
360 
361 	// Clear cache of clip and nested reader (if any)
362 	clip->cache.Clear();
363 	if (clip->Reader() && clip->Reader()->GetCache())
364 		clip->Reader()->GetCache()->Clear();
365 
366 	// All clips should be converted to the frame rate of this timeline
367 	if (auto_map_clips)
368 		// Apply framemapper (or update existing framemapper)
369 		apply_mapper_to_clip(clip);
370 
371 	// Add clip to list
372 	clips.push_back(clip);
373 
374 	// Sort clips
375 	sort_clips();
376 }
377 
378 // Add an effect to the timeline
AddEffect(EffectBase * effect)379 void Timeline::AddEffect(EffectBase* effect)
380 {
381 	// Assign timeline to effect
382 	effect->ParentTimeline(this);
383 
384 	// Add effect to list
385 	effects.push_back(effect);
386 
387 	// Sort effects
388 	sort_effects();
389 }
390 
391 // Remove an effect from the timeline
RemoveEffect(EffectBase * effect)392 void Timeline::RemoveEffect(EffectBase* effect)
393 {
394 	effects.remove(effect);
395 }
396 
397 // Remove an openshot::Clip to the timeline
RemoveClip(Clip * clip)398 void Timeline::RemoveClip(Clip* clip)
399 {
400 	clips.remove(clip);
401 }
402 
403 // Look up a clip
GetClip(const std::string & id)404 openshot::Clip* Timeline::GetClip(const std::string& id)
405 {
406 	// Find the matching clip (if any)
407 	for (const auto& clip : clips) {
408 		if (clip->Id() == id) {
409 			return clip;
410 		}
411 	}
412 	return nullptr;
413 }
414 
415 // Look up a timeline effect
GetEffect(const std::string & id)416 openshot::EffectBase* Timeline::GetEffect(const std::string& id)
417 {
418 	// Find the matching effect (if any)
419 	for (const auto& effect : effects) {
420 		if (effect->Id() == id) {
421 			return effect;
422 		}
423 	}
424 	return nullptr;
425 }
426 
GetClipEffect(const std::string & id)427 openshot::EffectBase* Timeline::GetClipEffect(const std::string& id)
428 {
429 	// Search all clips for matching effect ID
430 	for (const auto& clip : clips) {
431 		const auto e = clip->GetEffect(id);
432 		if (e != nullptr) {
433 			return e;
434 		}
435 	}
436 	return nullptr;
437 }
438 
439 // Return the list of effects on all clips
ClipEffects() const440 std::list<openshot::EffectBase*> Timeline::ClipEffects() const {
441 
442 	// Initialize the list
443 	std::list<EffectBase*> timelineEffectsList;
444 
445 	// Loop through all clips
446 	for (const auto& clip : clips) {
447 
448 		// Get the clip's list of effects
449 		std::list<EffectBase*> clipEffectsList = clip->Effects();
450 
451 		// Append the clip's effects to the list
452 		timelineEffectsList.insert(timelineEffectsList.end(), clipEffectsList.begin(), clipEffectsList.end());
453 	}
454 
455 	return timelineEffectsList;
456 }
457 
458 // Compute the end time of the latest timeline element
GetMaxTime()459 double Timeline::GetMaxTime() {
460 	double last_clip = 0.0;
461 	double last_effect = 0.0;
462 
463 	if (!clips.empty()) {
464 		const auto max_clip = std::max_element(
465 				clips.begin(), clips.end(), CompareClipEndFrames());
466 		last_clip = (*max_clip)->Position() + (*max_clip)->Duration();
467 	}
468 	if (!effects.empty()) {
469 		const auto max_effect = std::max_element(
470 				effects.begin(), effects.end(), CompareEffectEndFrames());
471 		last_effect = (*max_effect)->Position() + (*max_effect)->Duration();
472 	}
473 	return std::max(last_clip, last_effect);
474 }
475 
476 // Compute the highest frame# based on the latest time and FPS
GetMaxFrame()477 int64_t Timeline::GetMaxFrame() {
478 	double fps = info.fps.ToDouble();
479 	auto max_time = GetMaxTime();
480 	return std::round(max_time * fps) + 1;
481 }
482 
483 // Apply a FrameMapper to a clip which matches the settings of this timeline
apply_mapper_to_clip(Clip * clip)484 void Timeline::apply_mapper_to_clip(Clip* clip)
485 {
486     // Get lock (prevent getting frames while this happens)
487     const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
488 
489 	// Determine type of reader
490 	ReaderBase* clip_reader = NULL;
491 	if (clip->Reader()->Name() == "FrameMapper")
492 	{
493 		// Get the existing reader
494 		clip_reader = (ReaderBase*) clip->Reader();
495 
496 	} else {
497 
498 		// Create a new FrameMapper to wrap the current reader
499 		FrameMapper* mapper = new FrameMapper(clip->Reader(), info.fps, PULLDOWN_NONE, info.sample_rate, info.channels, info.channel_layout);
500 		allocated_frame_mappers.insert(mapper);
501 		clip_reader = (ReaderBase*) mapper;
502 	}
503 
504 	// Update the mapping
505 	FrameMapper* clip_mapped_reader = (FrameMapper*) clip_reader;
506 	clip_mapped_reader->ChangeMapping(info.fps, PULLDOWN_NONE, info.sample_rate, info.channels, info.channel_layout);
507 
508 	// Update clip reader
509 	clip->Reader(clip_reader);
510 }
511 
512 // Apply the timeline's framerate and samplerate to all clips
ApplyMapperToClips()513 void Timeline::ApplyMapperToClips()
514 {
515 	// Clear all cached frames
516 	ClearAllCache();
517 
518 	// Loop through all clips
519 	for (auto clip : clips)
520 	{
521 		// Apply framemapper (or update existing framemapper)
522 		apply_mapper_to_clip(clip);
523 	}
524 }
525 
526 // Calculate time of a frame number, based on a framerate
calculate_time(int64_t number,Fraction rate)527 double Timeline::calculate_time(int64_t number, Fraction rate)
528 {
529 	// Get float version of fps fraction
530     double raw_fps = rate.ToFloat();
531 
532 	// Return the time (in seconds) of this frame
533 	return double(number - 1) / raw_fps;
534 }
535 
536 // Apply effects to the source frame (if any)
apply_effects(std::shared_ptr<Frame> frame,int64_t timeline_frame_number,int layer)537 std::shared_ptr<Frame> Timeline::apply_effects(std::shared_ptr<Frame> frame, int64_t timeline_frame_number, int layer)
538 {
539 	// Debug output
540 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::apply_effects", "frame->number", frame->number, "timeline_frame_number", timeline_frame_number, "layer", layer);
541 
542 	// Find Effects at this position and layer
543 	for (auto effect : effects)
544 	{
545 		// Does clip intersect the current requested time
546 		long effect_start_position = round(effect->Position() * info.fps.ToDouble()) + 1;
547 		long effect_end_position = round((effect->Position() + (effect->Duration())) * info.fps.ToDouble()) + 1;
548 
549 		bool does_effect_intersect = (effect_start_position <= timeline_frame_number && effect_end_position >= timeline_frame_number && effect->Layer() == layer);
550 
551 		// Debug output
552 		ZmqLogger::Instance()->AppendDebugMethod("Timeline::apply_effects (Does effect intersect)", "effect->Position()", effect->Position(), "does_effect_intersect", does_effect_intersect, "timeline_frame_number", timeline_frame_number, "layer", layer);
553 
554 		// Clip is visible
555 		if (does_effect_intersect)
556 		{
557 			// Determine the frame needed for this clip (based on the position on the timeline)
558             long effect_start_frame = (effect->Start() * info.fps.ToDouble()) + 1;
559 			long effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame;
560 
561 			// Debug output
562 			ZmqLogger::Instance()->AppendDebugMethod("Timeline::apply_effects (Process Effect)", "effect_frame_number", effect_frame_number, "does_effect_intersect", does_effect_intersect);
563 
564 			// Apply the effect to this frame
565 			frame = effect->GetFrame(frame, effect_frame_number);
566 		}
567 
568 	} // end effect loop
569 
570 	// Return modified frame
571 	return frame;
572 }
573 
574 // Get or generate a blank frame
GetOrCreateFrame(std::shared_ptr<Frame> background_frame,Clip * clip,int64_t number,openshot::TimelineInfoStruct * options)575 std::shared_ptr<Frame> Timeline::GetOrCreateFrame(std::shared_ptr<Frame> background_frame, Clip* clip, int64_t number, openshot::TimelineInfoStruct* options)
576 {
577 	std::shared_ptr<Frame> new_frame;
578 
579 	// Init some basic properties about this frame
580 	int samples_in_frame = Frame::GetSamplesPerFrame(number, info.fps, info.sample_rate, info.channels);
581 
582 	try {
583 		// Debug output
584 		ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame);
585 
586 		// Attempt to get a frame (but this could fail if a reader has just been closed)
587 		new_frame = std::shared_ptr<Frame>(clip->GetFrame(background_frame, number, options));
588 
589 		// Return real frame
590 		return new_frame;
591 
592 	} catch (const ReaderClosed & e) {
593 		// ...
594 	} catch (const OutOfBoundsFrame & e) {
595 		// ...
596 	}
597 
598 	// Debug output
599 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetOrCreateFrame (create blank)", "number", number, "samples_in_frame", samples_in_frame);
600 
601 	// Create blank frame
602 	return new_frame;
603 }
604 
605 // Process a new layer of video or audio
add_layer(std::shared_ptr<Frame> new_frame,Clip * source_clip,int64_t clip_frame_number,bool is_top_clip,float max_volume)606 void Timeline::add_layer(std::shared_ptr<Frame> new_frame, Clip* source_clip, int64_t clip_frame_number, bool is_top_clip, float max_volume)
607 {
608     // Create timeline options (with details about this current frame request)
609     TimelineInfoStruct* options = new TimelineInfoStruct();
610     options->is_top_clip = is_top_clip;
611 
612     // Get the clip's frame, composited on top of the current timeline frame
613 	std::shared_ptr<Frame> source_frame;
614 	source_frame = GetOrCreateFrame(new_frame, source_clip, clip_frame_number, options);
615     delete options;
616 
617 	// No frame found... so bail
618 	if (!source_frame)
619 		return;
620 
621 	// Debug output
622 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer", "new_frame->number", new_frame->number, "clip_frame_number", clip_frame_number);
623 
624 	/* COPY AUDIO - with correct volume */
625 	if (source_clip->Reader()->info.has_audio) {
626 		// Debug output
627 		ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Copy Audio)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number);
628 
629 		if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0)
630 			for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++)
631 			{
632 				// Get volume from previous frame and this frame
633 				float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1);
634 				float volume = source_clip->volume.GetValue(clip_frame_number);
635 				int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1)
636 				int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1)
637 
638 				// Apply volume mixing strategy
639 				if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) {
640 					// Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume
641 					previous_volume = previous_volume / max_volume;
642 					volume = volume / max_volume;
643 				}
644 				else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) {
645 					// Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will)
646 					previous_volume = previous_volume * 0.77;
647 					volume = volume * 0.77;
648 				}
649 
650 				// If channel filter enabled, check for correct channel (and skip non-matching channels)
651 				if (channel_filter != -1 && channel_filter != channel)
652 					continue; // skip to next channel
653 
654 				// If no volume on this frame or previous frame, do nothing
655 				if (previous_volume == 0.0 && volume == 0.0)
656 					continue; // skip to next channel
657 
658 				// If channel mapping disabled, just use the current channel
659 				if (channel_mapping == -1)
660 					channel_mapping = channel;
661 
662 				// Apply ramp to source frame (if needed)
663 				if (!isEqual(previous_volume, 1.0) || !isEqual(volume, 1.0))
664 					source_frame->ApplyGainRamp(channel_mapping, 0, source_frame->GetAudioSamplesCount(), previous_volume, volume);
665 
666 				// TODO: Improve FrameMapper (or Timeline) to always get the correct number of samples per frame.
667 				// Currently, the ResampleContext sometimes leaves behind a few samples for the next call, and the
668 				// number of samples returned is variable... and does not match the number expected.
669 				// This is a crude solution at best. =)
670 				if (new_frame->GetAudioSamplesCount() != source_frame->GetAudioSamplesCount()){
671 					// Force timeline frame to match the source frame
672 					new_frame->ResizeAudio(info.channels, source_frame->GetAudioSamplesCount(), info.sample_rate, info.channel_layout);
673 				}
674 				// Copy audio samples (and set initial volume).  Mix samples with existing audio samples.  The gains are added together, to
675 				// be sure to set the gain's correctly, so the sum does not exceed 1.0 (of audio distortion will happen).
676 				new_frame->AddAudio(false, channel_mapping, 0, source_frame->GetAudioSamples(channel), source_frame->GetAudioSamplesCount(), 1.0);
677 			}
678 		else
679 			// Debug output
680 			ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (No Audio Copied - Wrong # of Channels)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), "info.channels", info.channels, "clip_frame_number", clip_frame_number);
681 	}
682 
683 	// Debug output
684 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Transform: Composite Image Layer: Completed)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetImage()->width());
685 }
686 
687 // Update the list of 'opened' clips
update_open_clips(Clip * clip,bool does_clip_intersect)688 void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect)
689 {
690 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::update_open_clips (before)", "does_clip_intersect", does_clip_intersect, "closing_clips.size()", closing_clips.size(), "open_clips.size()", open_clips.size());
691 
692 	// is clip already in list?
693 	bool clip_found = open_clips.count(clip);
694 
695 	if (clip_found && !does_clip_intersect)
696 	{
697 		// Remove clip from 'opened' list, because it's closed now
698 		open_clips.erase(clip);
699 
700 		// Close clip
701 		clip->Close();
702 	}
703 	else if (!clip_found && does_clip_intersect)
704 	{
705 		// Add clip to 'opened' list, because it's missing
706 		open_clips[clip] = clip;
707 
708 		try {
709 			// Open the clip
710 			clip->Open();
711 
712 		} catch (const InvalidFile & e) {
713 			// ...
714 		}
715 	}
716 
717 	// Debug output
718 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::update_open_clips (after)", "does_clip_intersect", does_clip_intersect, "clip_found", clip_found, "closing_clips.size()", closing_clips.size(), "open_clips.size()", open_clips.size());
719 }
720 
721 // Sort clips by position on the timeline
sort_clips()722 void Timeline::sort_clips()
723 {
724 	// Debug output
725 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::SortClips", "clips.size()", clips.size());
726 
727 	// sort clips
728 	clips.sort(CompareClips());
729 }
730 
731 // Sort effects by position on the timeline
sort_effects()732 void Timeline::sort_effects()
733 {
734 	// sort clips
735 	effects.sort(CompareEffects());
736 }
737 
738 // Close the reader (and any resources it was consuming)
Close()739 void Timeline::Close()
740 {
741 	ZmqLogger::Instance()->AppendDebugMethod("Timeline::Close");
742 
743 	// Close all open clips
744 	for (auto clip : clips)
745 	{
746 		// Open or Close this clip, based on if it's intersecting or not
747 		update_open_clips(clip, false);
748 	}
749 
750 	// Mark timeline as closed
751 	is_open = false;
752 
753 	// Clear cache
754 	if (final_cache)
755 		final_cache->Clear();
756 }
757 
758 // Open the reader (and start consuming resources)
Open()759 void Timeline::Open()
760 {
761 	is_open = true;
762 }
763 
764 // Compare 2 floating point numbers for equality
isEqual(double a,double b)765 bool Timeline::isEqual(double a, double b)
766 {
767 	return fabs(a - b) < 0.000001;
768 }
769 
770 // Get an openshot::Frame object for a specific frame number of this reader.
GetFrame(int64_t requested_frame)771 std::shared_ptr<Frame> Timeline::GetFrame(int64_t requested_frame)
772 {
773 
774 	// Adjust out of bounds frame number
775 	if (requested_frame < 1)
776 		requested_frame = 1;
777 
778 	// Check cache
779 	std::shared_ptr<Frame> frame;
780 	std::lock_guard<std::mutex> guard(get_frame_mutex);
781 	frame = final_cache->GetFrame(requested_frame);
782 	if (frame) {
783 		// Debug output
784 		ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Cached frame found)", "requested_frame", requested_frame);
785 
786 		// Return cached frame
787 		return frame;
788 	}
789 	else
790 	{
791 		// Create a scoped lock, allowing only a single thread to run the following code at one time
792 		const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
793 
794 		// Check for open reader (or throw exception)
795 		if (!is_open)
796 			throw ReaderClosed("The Timeline is closed.  Call Open() before calling this method.");
797 
798 		// Check cache again (due to locking)
799 		frame = final_cache->GetFrame(requested_frame);
800 		if (frame) {
801 			// Debug output
802 			ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Cached frame found on 2nd look)", "requested_frame", requested_frame);
803 
804 			// Return cached frame
805 			return frame;
806 		}
807 
808 		// Check if previous frame was cached? (if not, assume we are seeking somewhere else on the Timeline, and need
809 		// to clear all cache (for continuity sake). For example, jumping back to a previous spot can cause issues with audio
810 		// data where the new jump location doesn't match up with the previously cached audio data.
811 		std::shared_ptr<Frame> previous_frame = final_cache->GetFrame(requested_frame - 1);
812 		if (!previous_frame) {
813 			// Seeking to new place on timeline (destroy cache)
814 			ClearAllCache();
815 		}
816 
817 		// Minimum number of frames to process (for performance reasons)
818 		int minimum_frames = OPEN_MP_NUM_PROCESSORS;
819 
820 		// Get a list of clips that intersect with the requested section of timeline
821 		// This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
822 		std::vector<Clip*> nearby_clips;
823 		nearby_clips = find_intersecting_clips(requested_frame, 1, true);
824 
825         // Debug output
826         ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (processing frame)", "requested_frame", requested_frame, "omp_get_thread_num()", omp_get_thread_num());
827 
828         // Init some basic properties about this frame
829         int samples_in_frame = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
830 
831         // Create blank frame (which will become the requested frame)
832         std::shared_ptr<Frame> new_frame(std::make_shared<Frame>(requested_frame, preview_width, preview_height, "#000000", samples_in_frame, info.channels));
833         new_frame->AddAudioSilence(samples_in_frame);
834         new_frame->SampleRate(info.sample_rate);
835         new_frame->ChannelsLayout(info.channel_layout);
836 
837         // Debug output
838         ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Adding solid color)", "requested_frame", requested_frame, "info.width", info.width, "info.height", info.height);
839 
840         // Add Background Color to 1st layer (if animated or not black)
841         if ((color.red.GetCount() > 1 || color.green.GetCount() > 1 || color.blue.GetCount() > 1) ||
842             (color.red.GetValue(requested_frame) != 0.0 || color.green.GetValue(requested_frame) != 0.0 || color.blue.GetValue(requested_frame) != 0.0))
843         new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame));
844 
845         // Debug output
846         ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Loop through clips)", "requested_frame", requested_frame, "clips.size()", clips.size(), "nearby_clips.size()", nearby_clips.size());
847 
848         // Find Clips near this time
849         for (auto clip : nearby_clips)
850         {
851             long clip_start_position = round(clip->Position() * info.fps.ToDouble()) + 1;
852             long clip_end_position = round((clip->Position() + clip->Duration()) * info.fps.ToDouble()) + 1;
853 
854             bool does_clip_intersect = (clip_start_position <= requested_frame && clip_end_position >= requested_frame);
855 
856             // Debug output
857             ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Does clip intersect)", "requested_frame", requested_frame, "clip->Position()", clip->Position(), "clip->Duration()", clip->Duration(), "does_clip_intersect", does_clip_intersect);
858 
859             // Clip is visible
860             if (does_clip_intersect)
861             {
862                 // Determine if clip is "top" clip on this layer (only happens when multiple clips are overlapping)
863                 bool is_top_clip = true;
864                 float max_volume = 0.0;
865                 for (auto nearby_clip : nearby_clips)
866                 {
867                     long nearby_clip_start_position = round(nearby_clip->Position() * info.fps.ToDouble()) + 1;
868                     long nearby_clip_end_position = round((nearby_clip->Position() + nearby_clip->Duration()) * info.fps.ToDouble()) + 1;
869                     long nearby_clip_start_frame = (nearby_clip->Start() * info.fps.ToDouble()) + 1;
870                     long nearby_clip_frame_number = requested_frame - nearby_clip_start_position + nearby_clip_start_frame;
871 
872                     // Determine if top clip
873                     if (clip->Id() != nearby_clip->Id() && clip->Layer() == nearby_clip->Layer() &&
874                             nearby_clip_start_position <= requested_frame && nearby_clip_end_position >= requested_frame &&
875                             nearby_clip_start_position > clip_start_position && is_top_clip == true) {
876                         is_top_clip = false;
877                     }
878 
879                     // Determine max volume of overlapping clips
880                     if (nearby_clip->Reader() && nearby_clip->Reader()->info.has_audio &&
881                             nearby_clip->has_audio.GetInt(nearby_clip_frame_number) != 0 &&
882                             nearby_clip_start_position <= requested_frame && nearby_clip_end_position >= requested_frame) {
883                             max_volume += nearby_clip->volume.GetValue(nearby_clip_frame_number);
884                     }
885                 }
886 
887                 // Determine the frame needed for this clip (based on the position on the timeline)
888                 long clip_start_frame = (clip->Start() * info.fps.ToDouble()) + 1;
889                 long clip_frame_number = requested_frame - clip_start_position + clip_start_frame;
890 
891                 // Debug output
892                 ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Calculate clip's frame #)", "clip->Position()", clip->Position(), "clip->Start()", clip->Start(), "info.fps.ToFloat()", info.fps.ToFloat(), "clip_frame_number", clip_frame_number);
893 
894                 // Add clip's frame as layer
895                 add_layer(new_frame, clip, clip_frame_number, is_top_clip, max_volume);
896 
897             } else {
898                 // Debug output
899                 ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (clip does not intersect)",
900                                                          "requested_frame", requested_frame, "does_clip_intersect",
901                                                          does_clip_intersect);
902             }
903 
904         } // end clip loop
905 
906         // Debug output
907         ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Add frame to cache)", "requested_frame", requested_frame, "info.width", info.width, "info.height", info.height);
908 
909         // Set frame # on mapped frame
910         new_frame->SetFrameNumber(requested_frame);
911 
912         // Add final frame to cache
913         final_cache->Add(new_frame);
914 
915 		// Return frame (or blank frame)
916 		return final_cache->GetFrame(requested_frame);
917 	}
918 }
919 
920 
921 // Find intersecting clips (or non intersecting clips)
find_intersecting_clips(int64_t requested_frame,int number_of_frames,bool include)922 std::vector<Clip*> Timeline::find_intersecting_clips(int64_t requested_frame, int number_of_frames, bool include)
923 {
924 	// Find matching clips
925 	std::vector<Clip*> matching_clips;
926 
927 	// Calculate time of frame
928 	float min_requested_frame = requested_frame;
929 	float max_requested_frame = requested_frame + (number_of_frames - 1);
930 
931 	// Re-Sort Clips (since they likely changed)
932 	sort_clips();
933 
934 	// Find Clips at this time
935 	for (auto clip : clips)
936 	{
937 		// Does clip intersect the current requested time
938 		long clip_start_position = round(clip->Position() * info.fps.ToDouble()) + 1;
939 		long clip_end_position = round((clip->Position() + clip->Duration()) * info.fps.ToDouble()) + 1;
940 
941 		bool does_clip_intersect =
942                 (clip_start_position <= min_requested_frame || clip_start_position <= max_requested_frame) &&
943                 (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame);
944 
945 		// Debug output
946 		ZmqLogger::Instance()->AppendDebugMethod("Timeline::find_intersecting_clips (Is clip near or intersecting)", "requested_frame", requested_frame, "min_requested_frame", min_requested_frame, "max_requested_frame", max_requested_frame, "clip->Position()", clip->Position(), "does_clip_intersect", does_clip_intersect);
947 
948 		// Open (or schedule for closing) this clip, based on if it's intersecting or not
949 		update_open_clips(clip, does_clip_intersect);
950 
951 		// Clip is visible
952 		if (does_clip_intersect && include)
953 			// Add the intersecting clip
954 			matching_clips.push_back(clip);
955 
956 		else if (!does_clip_intersect && !include)
957 			// Add the non-intersecting clip
958 			matching_clips.push_back(clip);
959 
960 	} // end clip loop
961 
962 	// return list
963 	return matching_clips;
964 }
965 
966 // Set the cache object used by this reader
SetCache(CacheBase * new_cache)967 void Timeline::SetCache(CacheBase* new_cache) {
968 	// Destroy previous cache (if managed by timeline)
969 	if (managed_cache && final_cache) {
970 		delete final_cache;
971 		final_cache = NULL;
972 		managed_cache = false;
973 	}
974 
975 	// Set new cache
976 	final_cache = new_cache;
977 }
978 
979 // Generate JSON string of this object
Json() const980 std::string Timeline::Json() const {
981 
982 	// Return formatted string
983 	return JsonValue().toStyledString();
984 }
985 
986 // Generate Json::Value for this object
JsonValue() const987 Json::Value Timeline::JsonValue() const {
988 
989 	// Create root json object
990 	Json::Value root = ReaderBase::JsonValue(); // get parent properties
991 	root["type"] = "Timeline";
992 	root["viewport_scale"] = viewport_scale.JsonValue();
993 	root["viewport_x"] = viewport_x.JsonValue();
994 	root["viewport_y"] = viewport_y.JsonValue();
995 	root["color"] = color.JsonValue();
996 	root["path"] = path;
997 
998 	// Add array of clips
999 	root["clips"] = Json::Value(Json::arrayValue);
1000 
1001 	// Find Clips at this time
1002 	for (const auto existing_clip : clips)
1003 	{
1004 		root["clips"].append(existing_clip->JsonValue());
1005 	}
1006 
1007 	// Add array of effects
1008 	root["effects"] = Json::Value(Json::arrayValue);
1009 
1010 	// loop through effects
1011 	for (const auto existing_effect: effects)
1012 	{
1013 		root["effects"].append(existing_effect->JsonValue());
1014 	}
1015 
1016 	// return JsonValue
1017 	return root;
1018 }
1019 
1020 // Load JSON string into this object
SetJson(const std::string value)1021 void Timeline::SetJson(const std::string value) {
1022 
1023 	// Get lock (prevent getting frames while this happens)
1024 	const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
1025 
1026 	// Parse JSON string into JSON objects
1027 	try
1028 	{
1029 		const Json::Value root = openshot::stringToJson(value);
1030 		// Set all values that match
1031 		SetJsonValue(root);
1032 	}
1033 	catch (const std::exception& e)
1034 	{
1035 		// Error parsing JSON (or missing keys)
1036 		throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1037 	}
1038 }
1039 
1040 // Load Json::Value into this object
SetJsonValue(const Json::Value root)1041 void Timeline::SetJsonValue(const Json::Value root) {
1042 
1043 	// Close timeline before we do anything (this also removes all open and closing clips)
1044 	bool was_open = is_open;
1045 	Close();
1046 
1047 	// Set parent data
1048 	ReaderBase::SetJsonValue(root);
1049 
1050 	// Set data from Json (if key is found)
1051 	if (!root["path"].isNull())
1052 		path = root["path"].asString();
1053 
1054 	if (!root["clips"].isNull()) {
1055 		// Clear existing clips
1056 		clips.clear();
1057 
1058 		// loop through clips
1059 		for (const Json::Value existing_clip : root["clips"]) {
1060 			// Create Clip
1061 			Clip *c = new Clip();
1062 
1063 			// When a clip is attached to an object, it searches for the object
1064 			// on it's parent timeline. Setting the parent timeline of the clip here
1065 			// allows attaching it to an object when exporting the project (because)
1066 			// the exporter script initializes the clip and it's effects
1067 			// before setting it's parent timeline.
1068 			c->ParentTimeline(this);
1069 
1070 			// Load Json into Clip
1071 			c->SetJsonValue(existing_clip);
1072 
1073 			// Add Clip to Timeline
1074 			AddClip(c);
1075 		}
1076 	}
1077 
1078 	if (!root["effects"].isNull()) {
1079 		// Clear existing effects
1080 		effects.clear();
1081 
1082 		// loop through effects
1083 		for (const Json::Value existing_effect :root["effects"]) {
1084 			// Create Effect
1085 			EffectBase *e = NULL;
1086 
1087 			if (!existing_effect["type"].isNull()) {
1088 				// Create instance of effect
1089 				if ( (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) ) {
1090 
1091 					// Load Json into Effect
1092 					e->SetJsonValue(existing_effect);
1093 
1094 					// Add Effect to Timeline
1095 					AddEffect(e);
1096 				}
1097 			}
1098 		}
1099 	}
1100 
1101 	if (!root["duration"].isNull()) {
1102 		// Update duration of timeline
1103 		info.duration = root["duration"].asDouble();
1104 		info.video_length = info.fps.ToFloat() * info.duration;
1105 	}
1106 
1107 	// Update preview settings
1108 	preview_width = info.width;
1109 	preview_height = info.height;
1110 
1111 	// Re-open if needed
1112 	if (was_open)
1113 		Open();
1114 }
1115 
1116 // Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
ApplyJsonDiff(std::string value)1117 void Timeline::ApplyJsonDiff(std::string value) {
1118 
1119     // Get lock (prevent getting frames while this happens)
1120     const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
1121 
1122 	// Parse JSON string into JSON objects
1123 	try
1124 	{
1125 		const Json::Value root = openshot::stringToJson(value);
1126 		// Process the JSON change array, loop through each item
1127 		for (const Json::Value change : root) {
1128 			std::string change_key = change["key"][(uint)0].asString();
1129 
1130 			// Process each type of change
1131 			if (change_key == "clips")
1132 				// Apply to CLIPS
1133 				apply_json_to_clips(change);
1134 
1135 			else if (change_key == "effects")
1136 				// Apply to EFFECTS
1137 				apply_json_to_effects(change);
1138 
1139 			else
1140 				// Apply to TIMELINE
1141 				apply_json_to_timeline(change);
1142 
1143 		}
1144 	}
1145 	catch (const std::exception& e)
1146 	{
1147 		// Error parsing JSON (or missing keys)
1148 		throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1149 	}
1150 }
1151 
1152 // Apply JSON diff to clips
apply_json_to_clips(Json::Value change)1153 void Timeline::apply_json_to_clips(Json::Value change) {
1154 
1155 	// Get key and type of change
1156 	std::string change_type = change["type"].asString();
1157 	std::string clip_id = "";
1158 	Clip *existing_clip = NULL;
1159 
1160 	// Find id of clip (if any)
1161 	for (auto key_part : change["key"]) {
1162 		// Get each change
1163 		if (key_part.isObject()) {
1164 			// Check for id
1165 			if (!key_part["id"].isNull()) {
1166 				// Set the id
1167 				clip_id = key_part["id"].asString();
1168 
1169 				// Find matching clip in timeline (if any)
1170 				for (auto c : clips)
1171 				{
1172 					if (c->Id() == clip_id) {
1173 						existing_clip = c;
1174 						break; // clip found, exit loop
1175 					}
1176 				}
1177 				break; // id found, exit loop
1178 			}
1179 		}
1180 	}
1181 
1182 	// Check for a more specific key (targetting this clip's effects)
1183 	// For example: ["clips", {"id:123}, "effects", {"id":432}]
1184 	if (existing_clip && change["key"].size() == 4 && change["key"][2] == "effects")
1185 	{
1186 		// This change is actually targetting a specific effect under a clip (and not the clip)
1187 		Json::Value key_part = change["key"][3];
1188 
1189 		if (key_part.isObject()) {
1190 			// Check for id
1191 			if (!key_part["id"].isNull())
1192 			{
1193 				// Set the id
1194 				std::string effect_id = key_part["id"].asString();
1195 
1196 				// Find matching effect in timeline (if any)
1197 				std::list<EffectBase*> effect_list = existing_clip->Effects();
1198 				for (auto e : effect_list)
1199 				{
1200 					if (e->Id() == effect_id) {
1201 						// Apply the change to the effect directly
1202 						apply_json_to_effects(change, e);
1203 
1204 						// Calculate start and end frames that this impacts, and remove those frames from the cache
1205                         int64_t new_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1206                         int64_t new_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1207                         final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1208 
1209 						return; // effect found, don't update clip
1210 					}
1211 				}
1212 			}
1213 		}
1214 	}
1215 
1216 	// Calculate start and end frames that this impacts, and remove those frames from the cache
1217 	if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1218 		int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1219 		int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1220 		final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1221 	}
1222 
1223 	// Determine type of change operation
1224 	if (change_type == "insert") {
1225 
1226 		// Create new clip
1227 		Clip *clip = new Clip();
1228 		clip->SetJsonValue(change["value"]); // Set properties of new clip from JSON
1229 		AddClip(clip); // Add clip to timeline
1230 
1231 		// Apply framemapper (or update existing framemapper)
1232 		apply_mapper_to_clip(clip);
1233 
1234 	} else if (change_type == "update") {
1235 
1236 		// Update existing clip
1237 		if (existing_clip) {
1238 
1239 			// Calculate start and end frames that this impacts, and remove those frames from the cache
1240 			int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1241 			int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1242 			final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1243 
1244             // Remove cache on clip's Reader (if found)
1245             if (existing_clip->Reader() && existing_clip->Reader()->GetCache())
1246                 existing_clip->Reader()->GetCache()->Remove(old_starting_frame - 8, old_ending_frame + 8);
1247 
1248 			// Update clip properties from JSON
1249 			existing_clip->SetJsonValue(change["value"]);
1250 
1251 			// Apply framemapper (or update existing framemapper)
1252 			apply_mapper_to_clip(existing_clip);
1253 		}
1254 
1255 	} else if (change_type == "delete") {
1256 
1257 		// Remove existing clip
1258 		if (existing_clip) {
1259 
1260 			// Calculate start and end frames that this impacts, and remove those frames from the cache
1261 			int64_t old_starting_frame = (existing_clip->Position() * info.fps.ToDouble()) + 1;
1262 			int64_t old_ending_frame = ((existing_clip->Position() + existing_clip->Duration()) * info.fps.ToDouble()) + 1;
1263 			final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1264 
1265 			// Remove clip from timeline
1266 			RemoveClip(existing_clip);
1267 		}
1268 
1269 	}
1270 
1271 }
1272 
1273 // Apply JSON diff to effects
apply_json_to_effects(Json::Value change)1274 void Timeline::apply_json_to_effects(Json::Value change) {
1275 
1276 	// Get key and type of change
1277 	std::string change_type = change["type"].asString();
1278 	EffectBase *existing_effect = NULL;
1279 
1280 	// Find id of an effect (if any)
1281 	for (auto key_part : change["key"]) {
1282 
1283 		if (key_part.isObject()) {
1284 			// Check for id
1285 			if (!key_part["id"].isNull())
1286 			{
1287 				// Set the id
1288 				std::string effect_id = key_part["id"].asString();
1289 
1290 				// Find matching effect in timeline (if any)
1291 				for (auto e : effects)
1292 				{
1293 					if (e->Id() == effect_id) {
1294 						existing_effect = e;
1295 						break; // effect found, exit loop
1296 					}
1297 				}
1298 				break; // id found, exit loop
1299 			}
1300 		}
1301 	}
1302 
1303 	// Now that we found the effect, apply the change to it
1304 	if (existing_effect || change_type == "insert")
1305 		// Apply change to effect
1306 		apply_json_to_effects(change, existing_effect);
1307 }
1308 
1309 // Apply JSON diff to effects (if you already know which effect needs to be updated)
apply_json_to_effects(Json::Value change,EffectBase * existing_effect)1310 void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_effect) {
1311 
1312 	// Get key and type of change
1313 	std::string change_type = change["type"].asString();
1314 
1315 	// Calculate start and end frames that this impacts, and remove those frames from the cache
1316 	if (!change["value"].isArray() && !change["value"]["position"].isNull()) {
1317 		int64_t new_starting_frame = (change["value"]["position"].asDouble() * info.fps.ToDouble()) + 1;
1318 		int64_t new_ending_frame = ((change["value"]["position"].asDouble() + change["value"]["end"].asDouble() - change["value"]["start"].asDouble()) * info.fps.ToDouble()) + 1;
1319 		final_cache->Remove(new_starting_frame - 8, new_ending_frame + 8);
1320 	}
1321 
1322 	// Determine type of change operation
1323 	if (change_type == "insert") {
1324 
1325 		// Determine type of effect
1326 		std::string effect_type = change["value"]["type"].asString();
1327 
1328 		// Create Effect
1329 		EffectBase *e = NULL;
1330 
1331 		// Init the matching effect object
1332 		if ( (e = EffectInfo().CreateEffect(effect_type)) ) {
1333 
1334 			// Load Json into Effect
1335 			e->SetJsonValue(change["value"]);
1336 
1337 			// Add Effect to Timeline
1338 			AddEffect(e);
1339 		}
1340 
1341 	} else if (change_type == "update") {
1342 
1343 		// Update existing effect
1344 		if (existing_effect) {
1345 
1346 			// Calculate start and end frames that this impacts, and remove those frames from the cache
1347 			int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1348 			int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1349 			final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1350 
1351 			// Update effect properties from JSON
1352 			existing_effect->SetJsonValue(change["value"]);
1353 		}
1354 
1355 	} else if (change_type == "delete") {
1356 
1357 		// Remove existing effect
1358 		if (existing_effect) {
1359 
1360 			// Calculate start and end frames that this impacts, and remove those frames from the cache
1361 			int64_t old_starting_frame = (existing_effect->Position() * info.fps.ToDouble()) + 1;
1362 			int64_t old_ending_frame = ((existing_effect->Position() + existing_effect->Duration()) * info.fps.ToDouble()) + 1;
1363 			final_cache->Remove(old_starting_frame - 8, old_ending_frame + 8);
1364 
1365 			// Remove effect from timeline
1366 			RemoveEffect(existing_effect);
1367 		}
1368 
1369 	}
1370 }
1371 
1372 // Apply JSON diff to timeline properties
apply_json_to_timeline(Json::Value change)1373 void Timeline::apply_json_to_timeline(Json::Value change) {
1374 
1375 	// Get key and type of change
1376 	std::string change_type = change["type"].asString();
1377 	std::string root_key = change["key"][(uint)0].asString();
1378 	std::string sub_key = "";
1379 	if (change["key"].size() >= 2)
1380 		sub_key = change["key"][(uint)1].asString();
1381 
1382 	// Clear entire cache
1383 	ClearAllCache();
1384 
1385 	// Determine type of change operation
1386 	if (change_type == "insert" || change_type == "update") {
1387 
1388 		// INSERT / UPDATE
1389 		// Check for valid property
1390 		if (root_key == "color")
1391 			// Set color
1392 			color.SetJsonValue(change["value"]);
1393 		else if (root_key == "viewport_scale")
1394 			// Set viewport scale
1395 			viewport_scale.SetJsonValue(change["value"]);
1396 		else if (root_key == "viewport_x")
1397 			// Set viewport x offset
1398 			viewport_x.SetJsonValue(change["value"]);
1399 		else if (root_key == "viewport_y")
1400 			// Set viewport y offset
1401 			viewport_y.SetJsonValue(change["value"]);
1402 		else if (root_key == "duration") {
1403 			// Update duration of timeline
1404 			info.duration = change["value"].asDouble();
1405 			info.video_length = info.fps.ToFloat() * info.duration;
1406 		}
1407 		else if (root_key == "width") {
1408 			// Set width
1409 			info.width = change["value"].asInt();
1410 			preview_width = info.width;
1411 		}
1412 		else if (root_key == "height") {
1413 			// Set height
1414 			info.height = change["value"].asInt();
1415 			preview_height = info.height;
1416 		}
1417 		else if (root_key == "fps" && sub_key == "" && change["value"].isObject()) {
1418 			// Set fps fraction
1419 			if (!change["value"]["num"].isNull())
1420 				info.fps.num = change["value"]["num"].asInt();
1421 			if (!change["value"]["den"].isNull())
1422 				info.fps.den = change["value"]["den"].asInt();
1423 		}
1424 		else if (root_key == "fps" && sub_key == "num")
1425 			// Set fps.num
1426 			info.fps.num = change["value"].asInt();
1427 		else if (root_key == "fps" && sub_key == "den")
1428 			// Set fps.den
1429 			info.fps.den = change["value"].asInt();
1430 		else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) {
1431 			// Set display_ratio fraction
1432 			if (!change["value"]["num"].isNull())
1433 				info.display_ratio.num = change["value"]["num"].asInt();
1434 			if (!change["value"]["den"].isNull())
1435 				info.display_ratio.den = change["value"]["den"].asInt();
1436 		}
1437 		else if (root_key == "display_ratio" && sub_key == "num")
1438 			// Set display_ratio.num
1439 			info.display_ratio.num = change["value"].asInt();
1440 		else if (root_key == "display_ratio" && sub_key == "den")
1441 			// Set display_ratio.den
1442 			info.display_ratio.den = change["value"].asInt();
1443 		else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) {
1444 			// Set pixel_ratio fraction
1445 			if (!change["value"]["num"].isNull())
1446 				info.pixel_ratio.num = change["value"]["num"].asInt();
1447 			if (!change["value"]["den"].isNull())
1448 				info.pixel_ratio.den = change["value"]["den"].asInt();
1449 		}
1450 		else if (root_key == "pixel_ratio" && sub_key == "num")
1451 			// Set pixel_ratio.num
1452 			info.pixel_ratio.num = change["value"].asInt();
1453 		else if (root_key == "pixel_ratio" && sub_key == "den")
1454 			// Set pixel_ratio.den
1455 			info.pixel_ratio.den = change["value"].asInt();
1456 
1457 		else if (root_key == "sample_rate")
1458 			// Set sample rate
1459 			info.sample_rate = change["value"].asInt();
1460 		else if (root_key == "channels")
1461 			// Set channels
1462 			info.channels = change["value"].asInt();
1463 		else if (root_key == "channel_layout")
1464 			// Set channel layout
1465 			info.channel_layout = (ChannelLayout) change["value"].asInt();
1466 		else
1467 			// Error parsing JSON (or missing keys)
1468 			throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1469 
1470 
1471 	} else if (change["type"].asString() == "delete") {
1472 
1473 		// DELETE / RESET
1474 		// Reset the following properties (since we can't delete them)
1475 		if (root_key == "color") {
1476 			color = Color();
1477 			color.red = Keyframe(0.0);
1478 			color.green = Keyframe(0.0);
1479 			color.blue = Keyframe(0.0);
1480 		}
1481 		else if (root_key == "viewport_scale")
1482 			viewport_scale = Keyframe(1.0);
1483 		else if (root_key == "viewport_x")
1484 			viewport_x = Keyframe(0.0);
1485 		else if (root_key == "viewport_y")
1486 			viewport_y = Keyframe(0.0);
1487 		else
1488 			// Error parsing JSON (or missing keys)
1489 			throw InvalidJSONKey("JSON change key is invalid", change.toStyledString());
1490 
1491 	}
1492 
1493 }
1494 
1495 // Clear all caches
ClearAllCache()1496 void Timeline::ClearAllCache() {
1497 
1498 	// Get lock (prevent getting frames while this happens)
1499 	const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
1500 
1501     // Clear primary cache
1502     final_cache->Clear();
1503 
1504     // Loop through all clips
1505     for (auto clip : clips)
1506     {
1507         // Clear cache on clip
1508         clip->Reader()->GetCache()->Clear();
1509 
1510         // Clear nested Reader (if any)
1511         if (clip->Reader()->Name() == "FrameMapper") {
1512 			FrameMapper* nested_reader = (FrameMapper*) clip->Reader();
1513 			if (nested_reader->Reader() && nested_reader->Reader()->GetCache())
1514 				nested_reader->Reader()->GetCache()->Clear();
1515 		}
1516 
1517     }
1518 }
1519 
1520 // Set Max Image Size (used for performance optimization). Convenience function for setting
1521 // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT.
SetMaxSize(int width,int height)1522 void Timeline::SetMaxSize(int width, int height) {
1523 	// Maintain aspect ratio regardless of what size is passed in
1524 	QSize display_ratio_size = QSize(info.display_ratio.num * info.pixel_ratio.ToFloat(), info.display_ratio.den * info.pixel_ratio.ToFloat());
1525 	QSize proposed_size = QSize(std::min(width, info.width), std::min(height, info.height));
1526 
1527 	// Scale QSize up to proposed size
1528 	display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio);
1529 
1530 	// Update preview settings
1531 	preview_width = display_ratio_size.width();
1532 	preview_height = display_ratio_size.height();
1533 
1534 	// Update timeline cache size
1535     final_cache->SetMaxBytesFromInfo(max_concurrent_frames * 4, preview_width, preview_height, info.sample_rate, info.channels);
1536 }