2013-09-12 17:52:10 -05:00
/**
* @ file
* @ brief Source file for Timeline class
* @ author Jonathan Thomas < jonathan @ openshot . org >
*
* @ section LICENSE
*
2014-03-29 18:49:22 -05:00
* Copyright ( c ) 2008 - 2014 OpenShot Studios , LLC
* < http : //www.openshotstudios.com/>. This file is part of
* OpenShot Library ( libopenshot ) , an open - source project dedicated to
* delivering high quality video editing and animation solutions to the
* world . For more information visit < http : //www.openshot.org/>.
2013-09-12 17:52:10 -05:00
*
2014-03-29 18:49:22 -05:00
* OpenShot Library ( libopenshot ) is free software : you can redistribute it
2014-07-11 16:52:14 -05:00
* and / or modify it under the terms of the GNU Lesser General Public License
2014-03-29 18:49:22 -05:00
* as published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
2013-09-12 17:52:10 -05:00
*
2014-03-29 18:49:22 -05:00
* OpenShot Library ( libopenshot ) is distributed in the hope that it will be
* useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2014-07-11 16:52:14 -05:00
* GNU Lesser General Public License for more details .
2013-09-12 17:52:10 -05:00
*
2014-07-11 16:52:14 -05:00
* You should have received a copy of the GNU Lesser General Public License
2014-03-29 18:49:22 -05:00
* along with OpenShot Library . If not , see < http : //www.gnu.org/licenses/>.
2013-09-12 17:52:10 -05:00
*/
2012-10-03 01:55:24 -05:00
# include "../include/Timeline.h"
using namespace openshot ;
// Default Constructor for the timeline (which sets the canvas width and height)
2015-06-01 00:20:14 -07:00
Timeline : : Timeline ( int width , int height , Fraction fps , int sample_rate , int channels , ChannelLayout channel_layout ) :
is_open ( false ) , auto_map_clips ( true )
2012-10-03 01:55:24 -05:00
{
// Init viewport size (curve based, because it can be animated)
viewport_scale = Keyframe ( 100.0 ) ;
viewport_x = Keyframe ( 0.0 ) ;
viewport_y = Keyframe ( 0.0 ) ;
2012-11-12 17:21:21 -06:00
2012-11-29 17:28:22 -06:00
// Init background color
color . red = Keyframe ( 0.0 ) ;
color . green = Keyframe ( 0.0 ) ;
color . blue = Keyframe ( 0.0 ) ;
2012-11-20 16:22:50 -06:00
// Init FileInfo struct (clear all values)
info . width = width ;
info . height = height ;
2014-01-05 22:37:11 -06:00
info . fps = fps ;
2012-11-20 16:22:50 -06:00
info . sample_rate = sample_rate ;
info . channels = channels ;
2015-06-01 00:20:14 -07:00
info . channel_layout = channel_layout ;
2014-01-05 22:37:11 -06:00
info . video_timebase = fps . Reciprocal ( ) ;
2014-01-27 23:31:38 -06:00
info . duration = 60 * 30 ; // 30 minute default duration
2015-06-01 00:20:14 -07:00
info . has_audio = true ;
info . has_video = true ;
info . video_length = info . fps . ToFloat ( ) * info . duration ;
// Init cache
2016-09-07 00:40:01 -05:00
final_cache = new CacheMemory ( ) ;
final_cache - > SetMaxBytesFromInfo ( OPEN_MP_NUM_PROCESSORS * 2 , info . width , info . height , info . sample_rate , info . channels ) ;
2012-10-05 01:58:27 -05:00
}
2012-10-05 17:05:33 -05:00
// Add an openshot::Clip to the timeline
2013-10-01 17:19:53 -05:00
void Timeline : : AddClip ( Clip * clip ) throw ( ReaderClosed )
2012-10-05 17:05:33 -05:00
{
2015-06-01 00:20:14 -07:00
// All clips should be converted to the frame rate of this timeline
if ( auto_map_clips )
// Apply framemapper (or update existing framemapper)
apply_mapper_to_clip ( clip ) ;
2012-10-14 02:36:05 -05:00
2012-10-05 17:05:33 -05:00
// Add clip to list
clips . push_back ( clip ) ;
// Sort clips
2015-03-14 01:36:13 -05:00
sort_clips ( ) ;
2012-10-05 17:05:33 -05:00
}
2013-09-28 22:00:52 -05:00
// Add an effect to the timeline
void Timeline : : AddEffect ( EffectBase * effect )
{
// Add effect to list
effects . push_back ( effect ) ;
2013-10-01 17:19:53 -05:00
// Sort effects
2015-03-14 01:36:13 -05:00
sort_effects ( ) ;
2013-09-28 22:00:52 -05:00
}
// Remove an effect from the timeline
void Timeline : : RemoveEffect ( EffectBase * effect )
{
effects . remove ( effect ) ;
}
2013-02-13 02:46:55 -06:00
// Remove an openshot::Clip to the timeline
void Timeline : : RemoveClip ( Clip * clip )
{
clips . remove ( clip ) ;
}
2015-06-01 00:20:14 -07:00
// Apply a FrameMapper to a clip which matches the settings of this timeline
void Timeline : : apply_mapper_to_clip ( Clip * clip )
{
// Determine type of reader
ReaderBase * clip_reader = NULL ;
2016-01-05 01:59:50 -06:00
if ( clip - > Reader ( ) - > Name ( ) = = " FrameMapper " )
2015-06-01 00:20:14 -07:00
{
// Get the existing reader
clip_reader = ( ReaderBase * ) clip - > Reader ( ) ;
} else {
// Create a new FrameMapper to wrap the current reader
clip_reader = ( ReaderBase * ) new FrameMapper ( clip - > Reader ( ) , info . fps , PULLDOWN_NONE , info . sample_rate , info . channels , info . channel_layout ) ;
}
// Update the mapping
FrameMapper * clip_mapped_reader = ( FrameMapper * ) clip_reader ;
clip_mapped_reader - > ChangeMapping ( info . fps , PULLDOWN_NONE , info . sample_rate , info . channels , info . channel_layout ) ;
// Update clip reader
clip - > Reader ( clip_reader ) ;
}
// Apply the timeline's framerate and samplerate to all clips
void Timeline : : ApplyMapperToClips ( )
{
// Clear all cached frames
2016-09-07 00:40:01 -05:00
final_cache - > Clear ( ) ;
2015-06-01 00:20:14 -07:00
// Loop through all clips
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = clips . begin ( ) ; clip_itr ! = clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * clip = ( * clip_itr ) ;
// Apply framemapper (or update existing framemapper)
apply_mapper_to_clip ( clip ) ;
}
}
2012-10-05 17:05:33 -05:00
// Calculate time of a frame number, based on a framerate
2015-08-24 01:05:48 -05:00
float Timeline : : calculate_time ( long int number , Fraction rate )
2012-10-05 17:05:33 -05:00
{
// Get float version of fps fraction
2014-01-05 22:37:11 -06:00
float raw_fps = rate . ToFloat ( ) ;
2012-10-05 17:05:33 -05:00
// Return the time (in seconds) of this frame
return float ( number - 1 ) / raw_fps ;
}
2013-10-06 18:11:33 -05:00
// Apply effects to the source frame (if any)
2015-08-24 01:05:48 -05:00
tr1 : : shared_ptr < Frame > Timeline : : apply_effects ( tr1 : : shared_ptr < Frame > frame , long int timeline_frame_number , int layer )
2013-10-06 18:11:33 -05:00
{
// Calculate time of frame
2014-01-05 22:37:11 -06:00
float requested_time = calculate_time ( timeline_frame_number , info . fps ) ;
2013-10-06 18:11:33 -05:00
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects " , " requested_time " , requested_time , " frame->number " , frame - > number , " timeline_frame_number " , timeline_frame_number , " layer " , layer , " " , - 1 , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
2013-10-06 18:11:33 -05:00
// Find Effects at this position and layer
list < EffectBase * > : : iterator effect_itr ;
for ( effect_itr = effects . begin ( ) ; effect_itr ! = effects . end ( ) ; + + effect_itr )
{
2015-08-07 23:11:03 -05:00
// Get effect object from the iterator
2013-10-06 18:11:33 -05:00
EffectBase * effect = ( * effect_itr ) ;
// Does clip intersect the current requested time
float effect_duration = effect - > End ( ) - effect - > Start ( ) ;
bool does_effect_intersect = ( effect - > Position ( ) < = requested_time & & effect - > Position ( ) + effect_duration > = requested_time & & effect - > Layer ( ) = = layer ) ;
2015-02-25 17:39:59 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects (Does effect intersect) " , " effect->Position() " , effect - > Position ( ) , " requested_time " , requested_time , " does_effect_intersect " , does_effect_intersect , " timeline_frame_number " , timeline_frame_number , " layer " , layer , " effect_duration " , effect_duration ) ;
2015-02-25 17:39:59 -06:00
2013-10-06 18:11:33 -05:00
// Clip is visible
if ( does_effect_intersect )
{
// Determine the frame needed for this clip (based on the position on the timeline)
float time_diff = ( requested_time - effect - > Position ( ) ) + effect - > Start ( ) ;
2014-01-05 22:37:11 -06:00
int effect_frame_number = round ( time_diff * info . fps . ToFloat ( ) ) + 1 ;
2013-10-06 18:11:33 -05:00
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects (Process Effect) " , " time_diff " , time_diff , " effect_frame_number " , effect_frame_number , " effect_duration " , effect_duration , " does_effect_intersect " , does_effect_intersect , " " , - 1 , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
2013-10-06 18:11:33 -05:00
// Apply the effect to this frame
frame = effect - > GetFrame ( frame , effect_frame_number ) ;
}
} // end effect loop
// Return modified frame
return frame ;
}
2016-01-30 17:12:41 -06:00
// Get or generate a blank frame
tr1 : : shared_ptr < Frame > Timeline : : GetOrCreateFrame ( Clip * clip , long int number )
{
tr1 : : shared_ptr < Frame > new_frame ;
// Init some basic properties about this frame
int samples_in_frame = Frame : : GetSamplesPerFrame ( number , info . fps , info . sample_rate , info . channels ) ;
try {
2016-04-24 15:37:47 -05:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetOrCreateFrame (from reader) " , " number " , number , " samples_in_frame " , samples_in_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2016-09-14 04:11:12 -05:00
// Set max image size (used for performance optimization)
clip - > SetMaxSize ( info . width , info . height ) ;
2016-01-30 17:12:41 -06:00
// Attempt to get a frame (but this could fail if a reader has just been closed)
new_frame = tr1 : : shared_ptr < Frame > ( clip - > GetFrame ( number ) ) ;
// Return real frame
return new_frame ;
} catch ( const ReaderClosed & e ) {
// ...
} catch ( const TooManySeeks & e ) {
// ...
} catch ( const OutOfBoundsFrame & e ) {
// ...
}
2016-04-24 15:37:47 -05:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetOrCreateFrame (create blank) " , " number " , number , " samples_in_frame " , samples_in_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2016-01-30 17:12:41 -06:00
// Create blank frame
new_frame = tr1 : : shared_ptr < Frame > ( new Frame ( number , info . width , info . height , " #000000 " , samples_in_frame , info . channels ) ) ;
new_frame - > SampleRate ( info . sample_rate ) ;
new_frame - > ChannelsLayout ( info . channel_layout ) ;
return new_frame ;
}
2012-11-07 17:45:13 -06:00
// Process a new layer of video or audio
2015-08-24 01:05:48 -05:00
void Timeline : : add_layer ( tr1 : : shared_ptr < Frame > new_frame , Clip * source_clip , long int clip_frame_number , long int timeline_frame_number , bool is_top_clip )
2012-11-07 17:45:13 -06:00
{
// Get the clip's frame & image
2016-01-30 17:12:41 -06:00
tr1 : : shared_ptr < Frame > source_frame = GetOrCreateFrame ( source_clip , clip_frame_number ) ;
2013-02-12 01:28:48 -06:00
// No frame found... so bail
if ( ! source_frame )
return ;
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer " , " new_frame->number " , new_frame - > number , " clip_frame_number " , clip_frame_number , " timeline_frame_number " , timeline_frame_number , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
2015-11-25 23:54:10 -06:00
/* REPLACE IMAGE WITH WAVEFORM IMAGE (IF NEEDED) */
if ( source_clip - > Waveform ( ) )
{
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Generate Waveform Image) " , " source_frame->number " , source_frame - > number , " source_clip->Waveform() " , source_clip - > Waveform ( ) , " clip_frame_number " , clip_frame_number , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-11-25 23:54:10 -06:00
// Get the color of the waveform
int red = source_clip - > wave_color . red . GetInt ( clip_frame_number ) ;
int green = source_clip - > wave_color . green . GetInt ( clip_frame_number ) ;
int blue = source_clip - > wave_color . blue . GetInt ( clip_frame_number ) ;
int alpha = source_clip - > wave_color . alpha . GetInt ( clip_frame_number ) ;
// Generate Waveform Dynamically (the size of the timeline)
tr1 : : shared_ptr < QImage > source_image = source_frame - > GetWaveform ( info . width , info . height , red , green , blue , alpha ) ;
source_frame - > AddImage ( tr1 : : shared_ptr < QImage > ( source_image ) ) ;
}
2015-02-26 02:31:36 -06:00
/* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the
* effects on the top clip . */
if ( is_top_clip )
source_frame = apply_effects ( source_frame , timeline_frame_number , source_clip - > Layer ( ) ) ;
2013-10-06 18:11:33 -05:00
2015-02-19 15:59:57 -06:00
// Declare an image to hold the source frame's image
2015-06-01 00:20:14 -07:00
tr1 : : shared_ptr < QImage > source_image ;
2012-11-12 01:25:35 -06:00
2012-11-29 16:32:48 -06:00
/* COPY AUDIO - with correct volume */
2015-02-19 15:59:57 -06:00
if ( source_clip - > Reader ( ) - > info . has_audio ) {
2012-11-29 16:32:48 -06:00
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
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 , " timeline_frame_number " , timeline_frame_number , " " , - 1 ) ;
2012-11-29 16:32:48 -06:00
2015-02-19 15:59:57 -06:00
if ( source_frame - > GetAudioChannelsCount ( ) = = info . channels )
for ( int channel = 0 ; channel < source_frame - > GetAudioChannelsCount ( ) ; channel + + )
{
float initial_volume = 1.0f ;
float previous_volume = source_clip - > volume . GetValue ( clip_frame_number - 1 ) ; // previous frame's percentage of volume (0 to 1)
float volume = source_clip - > volume . GetValue ( clip_frame_number ) ; // percentage of volume (0 to 1)
2016-04-24 15:37:47 -05:00
int channel_filter = source_clip - > channel_filter . GetInt ( clip_frame_number ) ; // optional channel to filter (if not -1)
int channel_mapping = source_clip - > channel_mapping . GetInt ( clip_frame_number ) ; // optional channel to map this channel to (if not -1)
// If channel filter enabled, check for correct channel (and skip non-matching channels)
if ( channel_filter ! = - 1 & & channel_filter ! = channel )
continue ; // skip to next channel
// If channel mapping disabled, just use the current channel
if ( channel_mapping = = - 1 )
channel_mapping = channel ;
2012-11-29 16:32:48 -06:00
2015-02-19 15:59:57 -06:00
// If no ramp needed, set initial volume = clip's volume
if ( isEqual ( previous_volume , volume ) )
initial_volume = volume ;
// Apply ramp to source frame (if needed)
if ( ! isEqual ( previous_volume , volume ) )
2016-04-24 15:37:47 -05:00
source_frame - > ApplyGainRamp ( channel_mapping , 0 , source_frame - > GetAudioSamplesCount ( ) , previous_volume , volume ) ;
2015-02-19 15:59:57 -06:00
2015-06-01 00:20:14 -07:00
// TODO: Improve FrameMapper (or Timeline) to always get the correct number of samples per frame.
// Currently, the ResampleContext sometimes leaves behind a few samples for the next call, and the
// number of samples returned is variable... and does not match the number expected.
// This is a crude solution at best. =)
if ( new_frame - > GetAudioSamplesCount ( ) ! = source_frame - > GetAudioSamplesCount ( ) )
// Force timeline frame to match the source frame
new_frame - > ResizeAudio ( info . channels , source_frame - > GetAudioSamplesCount ( ) , info . sample_rate , info . channel_layout ) ;
2015-02-19 15:59:57 -06:00
// Copy audio samples (and set initial volume). Mix samples with existing audio samples. The gains are added together, to
// be sure to set the gain's correctly, so the sum does not exceed 1.0 (of audio distortion will happen).
2016-04-24 15:37:47 -05:00
new_frame - > AddAudio ( false , channel_mapping , 0 , source_frame - > GetAudioSamples ( channel ) , source_frame - > GetAudioSamplesCount ( ) , initial_volume ) ;
2015-06-01 00:20:14 -07:00
2015-02-19 15:59:57 -06:00
}
else
// Debug output
2016-04-21 01:39:17 -05:00
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 , " timeline_frame_number " , timeline_frame_number , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
}
2012-11-29 16:32:48 -06:00
2016-01-09 15:50:53 -06:00
// Skip out if only an audio frame
if ( ! source_clip - > Waveform ( ) & & ! source_clip - > Reader ( ) - > info . has_video )
// Skip the rest of the image processing for performance reasons
return ;
2015-11-25 23:54:10 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Get Source Image) " , " source_frame->number " , source_frame - > number , " source_clip->Waveform() " , source_clip - > Waveform ( ) , " clip_frame_number " , clip_frame_number , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
2015-11-25 23:54:10 -06:00
// Get actual frame image data
source_image = source_frame - > GetImage ( ) ;
2012-11-29 16:32:48 -06:00
// Get some basic image properties
2015-06-01 00:20:14 -07:00
int source_width = source_image - > width ( ) ;
int source_height = source_image - > height ( ) ;
2012-11-12 01:25:35 -06:00
/* ALPHA & OPACITY */
2016-01-16 21:53:07 -06:00
if ( source_clip - > alpha . GetValue ( clip_frame_number ) ! = 1.0 )
2012-11-08 04:35:21 -06:00
{
2015-06-01 00:20:14 -07:00
float alpha = source_clip - > alpha . GetValue ( clip_frame_number ) ;
// Get source image's pixels
unsigned char * pixels = ( unsigned char * ) source_image - > bits ( ) ;
// Loop through pixels
for ( int pixel = 0 , byte_index = 0 ; pixel < source_image - > width ( ) * source_image - > height ( ) ; pixel + + , byte_index + = 4 )
{
// Get the alpha values from the pixel
int A = pixels [ byte_index + 3 ] ;
// Apply alpha to pixel
2016-01-16 21:53:07 -06:00
pixels [ byte_index + 3 ] * = alpha ;
2015-06-01 00:20:14 -07:00
}
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Set Alpha & Opacity) " , " alpha " , alpha , " source_frame->number " , source_frame - > number , " clip_frame_number " , clip_frame_number , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-08 04:35:21 -06:00
}
2012-11-07 17:45:13 -06:00
2012-11-12 01:25:35 -06:00
/* RESIZE SOURCE IMAGE - based on scale type */
switch ( source_clip - > scale )
{
case ( SCALE_FIT ) :
2015-06-01 00:20:14 -07:00
// keep aspect ratio
source_image = tr1 : : shared_ptr < QImage > ( new QImage ( source_image - > scaled ( info . width , info . height , Qt : : KeepAspectRatio , Qt : : SmoothTransformation ) ) ) ;
source_width = source_image - > width ( ) ;
source_height = source_image - > height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_FIT) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 01:25:35 -06:00
break ;
2015-02-19 15:59:57 -06:00
2012-11-12 01:25:35 -06:00
case ( SCALE_STRETCH ) :
2015-06-01 00:20:14 -07:00
// ignore aspect ratio
source_image = tr1 : : shared_ptr < QImage > ( new QImage ( source_image - > scaled ( info . width , info . height , Qt : : IgnoreAspectRatio , Qt : : SmoothTransformation ) ) ) ;
source_width = source_image - > width ( ) ;
source_height = source_image - > height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_STRETCH) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 01:25:35 -06:00
break ;
2015-02-19 15:59:57 -06:00
2012-11-12 01:25:35 -06:00
case ( SCALE_CROP ) :
2016-02-23 00:27:03 -06:00
QSize width_size ( info . width , round ( info . width / ( float ( source_width ) / float ( source_height ) ) ) ) ;
QSize height_size ( round ( info . height / ( float ( source_height ) / float ( source_width ) ) ) , info . height ) ;
2015-06-01 00:20:14 -07:00
// respect aspect ratio
2014-01-05 22:37:11 -06:00
if ( width_size . width ( ) > = info . width & & width_size . height ( ) > = info . height )
2015-06-01 00:20:14 -07:00
source_image = tr1 : : shared_ptr < QImage > ( new QImage ( source_image - > scaled ( width_size . width ( ) , width_size . height ( ) , Qt : : KeepAspectRatio , Qt : : SmoothTransformation ) ) ) ;
2012-11-12 01:25:35 -06:00
else
2015-06-01 00:20:14 -07:00
source_image = tr1 : : shared_ptr < QImage > ( new QImage ( source_image - > scaled ( height_size . width ( ) , height_size . height ( ) , Qt : : KeepAspectRatio , Qt : : SmoothTransformation ) ) ) ; // height is larger, so resize to it
source_width = source_image - > width ( ) ;
source_height = source_image - > height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_CROP) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 01:25:35 -06:00
break ;
}
2012-11-07 17:45:13 -06:00
2012-11-12 01:25:35 -06:00
/* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */
float x = 0.0 ; // left
float y = 0.0 ; // top
2015-02-19 15:59:57 -06:00
// Adjust size for scale x and scale y
float sx = source_clip - > scale_x . GetValue ( clip_frame_number ) ; // percentage X scale
float sy = source_clip - > scale_y . GetValue ( clip_frame_number ) ; // percentage Y scale
2016-02-03 00:43:27 -06:00
float scaled_source_width = source_width * sx ;
float scaled_source_height = source_height * sy ;
2015-02-19 15:59:57 -06:00
2012-11-12 01:25:35 -06:00
switch ( source_clip - > gravity )
{
case ( GRAVITY_TOP ) :
2015-02-19 15:59:57 -06:00
x = ( info . width - scaled_source_width ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_TOP_RIGHT ) :
2015-02-19 15:59:57 -06:00
x = info . width - scaled_source_width ; // right
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_LEFT ) :
2015-02-19 15:59:57 -06:00
y = ( info . height - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_CENTER ) :
2015-02-19 15:59:57 -06:00
x = ( info . width - scaled_source_width ) / 2.0 ; // center
y = ( info . height - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_RIGHT ) :
2015-02-19 15:59:57 -06:00
x = info . width - scaled_source_width ; // right
y = ( info . height - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM_LEFT ) :
2015-02-19 15:59:57 -06:00
y = ( info . height - scaled_source_height ) ; // bottom
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM ) :
2015-02-19 15:59:57 -06:00
x = ( info . width - scaled_source_width ) / 2.0 ; // center
y = ( info . height - scaled_source_height ) ; // bottom
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM_RIGHT ) :
2015-02-19 15:59:57 -06:00
x = info . width - scaled_source_width ; // right
y = ( info . height - scaled_source_height ) ; // bottom
2012-11-12 01:25:35 -06:00
break ;
}
2012-11-08 18:02:20 -06:00
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Gravity) " , " source_frame->number " , source_frame - > number , " source_clip->gravity " , source_clip - > gravity , " info.width " , info . width , " source_width " , source_width , " info.height " , info . height , " source_height " , source_height ) ;
2015-02-19 15:59:57 -06:00
2012-11-12 01:25:35 -06:00
/* LOCATION, ROTATION, AND SCALE */
float r = source_clip - > rotation . GetValue ( clip_frame_number ) ; // rotate in degrees
2015-06-01 00:20:14 -07:00
x + = ( info . width * source_clip - > location_x . GetValue ( clip_frame_number ) ) ; // move in percentage of final width
y + = ( info . height * source_clip - > location_y . GetValue ( clip_frame_number ) ) ; // move in percentage of final height
2015-02-19 15:59:57 -06:00
bool is_x_animated = source_clip - > location_x . Points . size ( ) > 1 ;
bool is_y_animated = source_clip - > location_y . Points . size ( ) > 1 ;
2012-11-12 01:25:35 -06:00
2012-12-06 17:58:51 -06:00
int offset_x = - 1 ;
int offset_y = - 1 ;
2013-02-10 21:16:46 -06:00
bool transformed = false ;
2015-06-01 00:20:14 -07:00
QTransform transform ;
2012-11-16 17:29:12 -06:00
if ( ( ! isEqual ( x , 0 ) | | ! isEqual ( y , 0 ) ) & & ( isEqual ( r , 0 ) & & isEqual ( sx , 1 ) & & isEqual ( sy , 1 ) & & ! is_x_animated & & ! is_y_animated ) )
2012-11-12 17:21:21 -06:00
{
2015-02-19 15:59:57 -06:00
// SIMPLE OFFSET
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: SIMPLE) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
2015-02-19 15:59:57 -06:00
2012-11-13 22:07:49 -06:00
// If only X and Y are different, and no animation is being used (just set the offset for speed)
2013-02-10 21:16:46 -06:00
transformed = true ;
2012-11-13 22:07:49 -06:00
2015-06-01 00:20:14 -07:00
// Set QTransform
transform . translate ( x , y ) ;
2012-11-16 17:29:12 -06:00
} else if ( ! isEqual ( r , 0 ) | | ! isEqual ( x , 0 ) | | ! isEqual ( y , 0 ) | | ! isEqual ( sx , 1 ) | | ! isEqual ( sy , 1 ) )
2012-11-13 22:07:49 -06:00
{
2015-02-19 15:59:57 -06:00
// COMPLEX DISTORTION
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: COMPLEX) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
2013-02-12 02:42:18 -06:00
2015-06-01 00:20:14 -07:00
// Use the QTransform object, which can be very CPU intensive
transformed = true ;
2015-02-19 15:59:57 -06:00
2015-06-01 00:20:14 -07:00
// Set QTransform
if ( ! isEqual ( r , 0 ) ) {
// ROTATE CLIP
float origin_x = x + ( source_width / 2.0 ) ;
float origin_y = y + ( source_height / 2.0 ) ;
transform . translate ( origin_x , origin_y ) ;
transform . rotate ( r ) ;
transform . translate ( - origin_x , - origin_y ) ;
2013-02-12 02:42:18 -06:00
}
2015-06-01 00:20:14 -07:00
// Set QTransform
if ( ! isEqual ( x , 0 ) | | ! isEqual ( y , 0 ) ) {
// TRANSLATE/MOVE CLIP
transform . translate ( x , y ) ;
}
2015-02-19 15:59:57 -06:00
2015-06-01 00:20:14 -07:00
if ( ! isEqual ( sx , 0 ) | | ! isEqual ( sy , 0 ) ) {
// TRANSLATE/MOVE CLIP
transform . scale ( sx , sy ) ;
}
2015-02-19 15:59:57 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: COMPLEX: Completed ScaleRotateTranslateDistortion) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
2013-02-10 21:16:46 -06:00
}
2015-02-21 00:12:21 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: Composite Image Layer: Prepare) " , " source_frame->number " , source_frame - > number , " offset_x " , offset_x , " offset_y " , offset_y , " new_frame->GetImage()->width() " , new_frame - > GetImage ( ) - > width ( ) , " transformed " , transformed , " " , - 1 ) ;
2015-02-21 00:12:21 -06:00
/* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */
2015-06-01 00:20:14 -07:00
tr1 : : shared_ptr < QImage > new_image = new_frame - > GetImage ( ) ;
// Load timeline's new frame image into a QPainter
QPainter painter ( new_image . get ( ) ) ;
2016-02-03 00:43:27 -06:00
painter . setRenderHints ( QPainter : : Antialiasing | QPainter : : SmoothPixmapTransform | QPainter : : TextAntialiasing , true ) ;
2015-06-01 00:20:14 -07:00
// Apply transform (translate, rotate, scale)... if any
if ( transformed )
painter . setTransform ( transform ) ;
// Composite a new layer onto the image
painter . setCompositionMode ( QPainter : : CompositionMode_SourceOver ) ;
painter . drawImage ( 0 , 0 , * source_image ) ;
painter . end ( ) ;
2012-11-08 18:02:20 -06:00
2015-03-15 02:28:28 -05:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: Composite Image Layer: Completed) " , " source_frame->number " , source_frame - > number , " offset_x " , offset_x , " offset_y " , offset_y , " new_frame->GetImage()->width() " , new_frame - > GetImage ( ) - > width ( ) , " transformed " , transformed , " " , - 1 ) ;
2012-11-07 17:45:13 -06:00
}
2012-10-09 02:09:44 -05:00
// Update the list of 'opened' clips
2015-03-15 02:28:28 -05:00
void Timeline : : update_open_clips ( Clip * clip , bool does_clip_intersect )
2012-10-09 02:09:44 -05:00
{
2016-04-21 01:39:17 -05:00
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 ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
2012-10-09 02:09:44 -05:00
// is clip already in list?
bool clip_found = open_clips . count ( clip ) ;
2015-03-15 02:28:28 -05:00
if ( clip_found & & ! does_clip_intersect )
2012-10-09 02:09:44 -05:00
{
2015-03-15 02:28:28 -05:00
// Remove clip from 'opened' list, because it's closed now
open_clips . erase ( clip ) ;
// Close clip
clip - > Close ( ) ;
2012-10-09 02:09:44 -05:00
}
2015-03-15 02:28:28 -05:00
else if ( ! clip_found & & does_clip_intersect )
2012-10-09 02:09:44 -05:00
{
// Add clip to 'opened' list, because it's missing
open_clips [ clip ] = clip ;
2015-03-15 02:28:28 -05:00
// Open the clip
2012-10-09 02:09:44 -05:00
clip - > Open ( ) ;
}
2015-02-07 18:06:11 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
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 ( ) , " " , - 1 , " " , - 1 ) ;
2012-12-03 22:55:46 -06:00
}
2012-10-05 17:05:33 -05:00
// Sort clips by position on the timeline
2015-03-14 01:36:13 -05:00
void Timeline : : sort_clips ( )
2012-10-05 17:05:33 -05:00
{
2015-02-07 18:06:11 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::SortClips " , " clips.size() " , clips . size ( ) , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-07 18:06:11 -06:00
2012-10-05 17:05:33 -05:00
// sort clips
2013-09-10 12:59:06 -05:00
clips . sort ( CompareClips ( ) ) ;
2012-10-05 17:05:33 -05:00
}
2013-10-01 17:19:53 -05:00
// Sort effects by position on the timeline
2015-03-14 01:36:13 -05:00
void Timeline : : sort_effects ( )
2013-10-01 17:19:53 -05:00
{
// sort clips
effects . sort ( CompareEffects ( ) ) ;
}
2012-10-08 16:22:18 -05:00
// Close the reader (and any resources it was consuming)
void Timeline : : Close ( )
{
2016-04-24 15:37:47 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::Close " , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-10-10 00:52:47 -05:00
// Close all open clips
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = clips . begin ( ) ; clip_itr ! = clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * clip = ( * clip_itr ) ;
2012-10-08 16:22:18 -05:00
2012-10-10 00:52:47 -05:00
// Open or Close this clip, based on if it's intersecting or not
update_open_clips ( clip , false ) ;
}
2012-12-03 22:55:46 -06:00
2015-03-15 02:28:28 -05:00
// Mark timeline as closed
2013-12-18 21:55:43 -06:00
is_open = false ;
2014-01-05 22:37:11 -06:00
// Clear cache
2016-09-07 00:40:01 -05:00
final_cache - > Clear ( ) ;
2012-10-08 16:22:18 -05:00
}
// Open the reader (and start consuming resources)
void Timeline : : Open ( )
{
2013-12-18 21:55:43 -06:00
is_open = true ;
2012-10-08 16:22:18 -05:00
}
2012-11-16 17:29:12 -06:00
// Compare 2 floating point numbers for equality
bool Timeline : : isEqual ( double a , double b )
{
return fabs ( a - b ) < 0.000001 ;
}
2012-11-07 17:45:13 -06:00
2012-10-05 01:58:27 -05:00
// Get an openshot::Frame object for a specific frame number of this reader.
2015-08-24 01:05:48 -05:00
tr1 : : shared_ptr < Frame > Timeline : : GetFrame ( long int requested_frame ) throw ( ReaderClosed , OutOfBoundsFrame )
2012-10-05 01:58:27 -05:00
{
2015-06-01 00:20:14 -07:00
// Check for open reader (or throw exception)
if ( ! is_open )
throw ReaderClosed ( " The Timeline is closed. Call Open() before calling this method . " , " " ) ;
2012-10-05 17:05:33 -05:00
// Adjust out of bounds frame number
if ( requested_frame < 1 )
requested_frame = 1 ;
2012-11-12 17:21:21 -06:00
// Check cache
2016-09-07 00:40:01 -05:00
tr1 : : shared_ptr < Frame > frame = final_cache - > GetFrame ( requested_frame ) ;
2015-08-05 23:40:58 -05:00
if ( frame ) {
2015-02-07 18:06:11 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Cached frame found) " , " requested_frame " , requested_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-07 18:06:11 -06:00
2015-03-15 02:28:28 -05:00
// Return cached frame
2015-08-05 23:40:58 -05:00
return frame ;
2015-02-07 18:06:11 -06:00
}
2012-11-12 17:21:21 -06:00
else
2012-10-05 17:05:33 -05:00
{
2015-06-01 00:20:14 -07:00
// Create a scoped lock, allowing only a single thread to run the following code at one time
const GenericScopedLock < CriticalSection > lock ( getFrameCriticalSection ) ;
// Check cache again (due to locking)
2016-09-07 00:40:01 -05:00
frame = final_cache - > GetFrame ( requested_frame ) ;
2015-08-05 23:40:58 -05:00
if ( frame ) {
2015-06-01 00:20:14 -07:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Cached frame found on 2nd look) " , " requested_frame " , requested_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-06-01 00:20:14 -07:00
// Return cached frame
2015-08-05 23:40:58 -05:00
return frame ;
2015-06-01 00:20:14 -07:00
}
2015-02-07 18:06:11 -06:00
// Minimum number of frames to process (for performance reasons)
2015-06-01 00:20:14 -07:00
int minimum_frames = OPEN_MP_NUM_PROCESSORS ;
2015-02-07 18:06:11 -06:00
2015-03-15 02:28:28 -05:00
// Get a list of clips that intersect with the requested section of timeline
// This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing'
vector < Clip * > nearby_clips = find_intersecting_clips ( requested_frame , minimum_frames , true ) ;
2014-04-02 16:48:27 -05:00
2015-06-01 00:20:14 -07:00
omp_set_num_threads ( OPEN_MP_NUM_PROCESSORS ) ;
2015-03-15 02:28:28 -05:00
// Allow nested OpenMP sections
2015-06-01 00:20:14 -07:00
omp_set_nested ( true ) ;
2015-03-15 02:28:28 -05:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame " , " requested_frame " , requested_frame , " minimum_frames " , minimum_frames , " OPEN_MP_NUM_PROCESSORS " , OPEN_MP_NUM_PROCESSORS , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
2016-01-30 17:12:41 -06:00
// GENERATE CACHE FOR CLIPS (IN FRAME # SEQUENCE)
// Determine all clip frames, and request them in order (to keep resampled audio in sequence)
for ( long int frame_number = requested_frame ; frame_number < requested_frame + minimum_frames ; frame_number + + )
{
// Calculate time of timeline frame
float requested_time = calculate_time ( frame_number , info . fps ) ;
// Loop through clips
for ( int clip_index = 0 ; clip_index < nearby_clips . size ( ) ; clip_index + + )
{
// Get clip object from the iterator
Clip * clip = nearby_clips [ clip_index ] ;
bool does_clip_intersect = ( clip - > Position ( ) < = requested_time & & clip - > Position ( ) + clip - > Duration ( ) > = requested_time ) ;
if ( does_clip_intersect )
{
// Get clip frame #
float time_diff = ( requested_time - clip - > Position ( ) ) + clip - > Start ( ) ;
int clip_frame_number = round ( time_diff * info . fps . ToFloat ( ) ) + 1 ;
// Cache clip object
clip - > GetFrame ( clip_frame_number ) ;
}
}
}
2015-06-01 00:20:14 -07:00
# pragma omp parallel
{
2015-03-15 02:28:28 -05:00
// Loop through all requested frames
2015-08-24 01:05:48 -05:00
# pragma omp for ordered firstprivate(nearby_clips, requested_frame, minimum_frames)
for ( long int frame_number = requested_frame ; frame_number < requested_frame + minimum_frames ; frame_number + + )
2012-11-12 17:21:21 -06:00
{
2015-02-07 18:06:11 -06:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (processing frame) " , " frame_number " , frame_number , " omp_get_thread_num() " , omp_get_thread_num ( ) , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-07 18:06:11 -06:00
2015-06-01 00:20:14 -07:00
// Init some basic properties about this frame
int samples_in_frame = Frame : : GetSamplesPerFrame ( frame_number , info . fps , info . sample_rate , info . channels ) ;
2015-03-15 02:28:28 -05:00
// Create blank frame (which will become the requested frame)
2015-06-01 00:20:14 -07:00
tr1 : : shared_ptr < Frame > new_frame ( tr1 : : shared_ptr < Frame > ( new Frame ( frame_number , info . width , info . height , " #000000 " , samples_in_frame , info . channels ) ) ) ;
2016-09-07 00:40:01 -05:00
new_frame - > AddAudioSilence ( samples_in_frame ) ;
2015-06-01 00:20:14 -07:00
new_frame - > SampleRate ( info . sample_rate ) ;
new_frame - > ChannelsLayout ( info . channel_layout ) ;
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Adding solid color) " , " frame_number " , frame_number , " info.width " , info . width , " info.height " , info . height , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-06-01 00:20:14 -07:00
2016-01-18 15:20:55 -06:00
// Add Background Color to 1st layer (if animated or not black)
if ( ( color . red . Points . size ( ) > 1 | | color . green . Points . size ( ) > 1 | | color . blue . Points . size ( ) > 1 ) | |
( color . red . GetValue ( frame_number ) ! = 0.0 | | color . green . GetValue ( frame_number ) ! = 0.0 | | color . blue . GetValue ( frame_number ) ! = 0.0 ) )
2015-06-01 00:20:14 -07:00
new_frame - > AddColor ( info . width , info . height , color . GetColorHex ( frame_number ) ) ;
2015-03-15 02:28:28 -05:00
// Calculate time of frame
float requested_time = calculate_time ( frame_number , info . fps ) ;
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Loop through clips) " , " frame_number " , frame_number , " requested_time " , requested_time , " clips.size() " , clips . size ( ) , " nearby_clips.size() " , nearby_clips . size ( ) , " " , - 1 , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
// Find Clips near this time
for ( int clip_index = 0 ; clip_index < nearby_clips . size ( ) ; clip_index + + )
2012-11-12 17:21:21 -06:00
{
2015-03-15 02:28:28 -05:00
// Get clip object from the iterator
Clip * clip = nearby_clips [ clip_index ] ;
// Does clip intersect the current requested time
bool does_clip_intersect = ( clip - > Position ( ) < = requested_time & & clip - > Position ( ) + clip - > Duration ( ) > = requested_time ) ;
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Does clip intersect) " , " frame_number " , frame_number , " requested_time " , requested_time , " clip->Position() " , clip - > Position ( ) , " clip->Duration() " , clip - > Duration ( ) , " does_clip_intersect " , does_clip_intersect , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
// Clip is visible
if ( does_clip_intersect )
2012-11-12 17:21:21 -06:00
{
2015-03-15 02:28:28 -05:00
// Determine if clip is "top" clip on this layer (only happens when multiple clips are overlapping)
bool is_top_clip = true ;
for ( int top_clip_index = 0 ; top_clip_index < nearby_clips . size ( ) ; top_clip_index + + )
2012-11-12 17:21:21 -06:00
{
2015-03-15 02:28:28 -05:00
Clip * nearby_clip = nearby_clips [ top_clip_index ] ;
if ( clip - > Id ( ) ! = nearby_clip - > Id ( ) & & clip - > Layer ( ) = = nearby_clip - > Layer ( ) & &
nearby_clip - > Position ( ) < = requested_time & & nearby_clip - > Position ( ) + nearby_clip - > Duration ( ) > = requested_time & &
nearby_clip - > Position ( ) > clip - > Position ( ) ) {
is_top_clip = false ;
break ;
}
2014-05-14 23:04:35 -05:00
}
2015-03-15 02:28:28 -05:00
// Determine the frame needed for this clip (based on the position on the timeline)
float time_diff = ( requested_time - clip - > Position ( ) ) + clip - > Start ( ) ;
2016-01-30 02:10:40 -06:00
int clip_frame_number = round ( time_diff * info . fps . ToFloat ( ) ) + 1 ;
2014-05-14 23:04:35 -05:00
2015-03-15 02:28:28 -05:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Calculate clip's frame #) " , " time_diff " , time_diff , " requested_time " , requested_time , " clip->Position() " , clip - > Position ( ) , " clip->Start() " , clip - > Start ( ) , " info.fps.ToFloat() " , info . fps . ToFloat ( ) , " clip_frame_number " , clip_frame_number ) ;
2012-11-12 17:21:21 -06:00
2015-03-15 02:28:28 -05:00
// Add clip's frame as layer
add_layer ( new_frame , clip , clip_frame_number , frame_number , is_top_clip ) ;
2012-12-03 22:55:46 -06:00
2015-03-15 02:28:28 -05:00
} else
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (clip does not intersect) " , " frame_number " , frame_number , " requested_time " , requested_time , " does_clip_intersect " , does_clip_intersect , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
} // end clip loop
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Add frame to cache) " , " frame_number " , frame_number , " info.width " , info . width , " info.height " , info . height , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-03-15 02:28:28 -05:00
2016-09-14 04:11:12 -05:00
// Set frame # on mapped frame
new_frame - > SetFrameNumber ( frame_number ) ;
2015-03-15 02:28:28 -05:00
// Add final frame to cache
2016-09-07 00:40:01 -05:00
final_cache - > Add ( new_frame ) ;
2015-03-15 02:28:28 -05:00
} // end frame loop
2015-06-01 00:20:14 -07:00
} // end parallel
2015-03-15 02:28:28 -05:00
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (end parallel region) " , " requested_frame " , requested_frame , " omp_get_thread_num() " , omp_get_thread_num ( ) , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 17:21:21 -06:00
// Return frame (or blank frame)
2016-09-07 00:40:01 -05:00
return final_cache - > GetFrame ( requested_frame ) ;
2012-10-05 17:05:33 -05:00
}
2012-10-03 01:55:24 -05:00
}
2013-12-07 21:09:55 -06:00
2015-02-19 01:03:22 -06:00
// Find intersecting clips (or non intersecting clips)
2015-08-24 01:05:48 -05:00
vector < Clip * > Timeline : : find_intersecting_clips ( long int requested_frame , int number_of_frames , bool include )
2015-02-19 01:03:22 -06:00
{
// Find matching clips
2015-03-15 02:28:28 -05:00
vector < Clip * > matching_clips ;
2015-02-19 01:03:22 -06:00
// Calculate time of frame
float min_requested_time = calculate_time ( requested_frame , info . fps ) ;
float max_requested_time = calculate_time ( requested_frame + ( number_of_frames - 1 ) , info . fps ) ;
// Re-Sort Clips (since they likely changed)
2015-03-14 01:36:13 -05:00
sort_clips ( ) ;
2015-02-19 01:03:22 -06:00
// Find Clips at this time
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = clips . begin ( ) ; clip_itr ! = clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * clip = ( * clip_itr ) ;
// Does clip intersect the current requested time
float clip_duration = clip - > End ( ) - clip - > Start ( ) ;
bool does_clip_intersect = ( clip - > Position ( ) < = min_requested_time & & clip - > Position ( ) + clip_duration > = min_requested_time ) | |
( clip - > Position ( ) > min_requested_time & & clip - > Position ( ) < = max_requested_time ) ;
// Debug output
2016-04-21 01:39:17 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::find_intersecting_clips (Is clip near or intersecting) " , " requested_frame " , requested_frame , " min_requested_time " , min_requested_time , " max_requested_time " , max_requested_time , " clip->Position() " , clip - > Position ( ) , " clip_duration " , clip_duration , " does_clip_intersect " , does_clip_intersect ) ;
2015-02-19 01:03:22 -06:00
// Open (or schedule for closing) this clip, based on if it's intersecting or not
# pragma omp critical (reader_lock)
update_open_clips ( clip , does_clip_intersect ) ;
2015-03-15 02:28:28 -05:00
2015-02-19 01:03:22 -06:00
// Clip is visible
if ( does_clip_intersect & & include )
// Add the intersecting clip
matching_clips . push_back ( clip ) ;
else if ( ! does_clip_intersect & & ! include )
// Add the non-intersecting clip
matching_clips . push_back ( clip ) ;
} // end clip loop
// return list
return matching_clips ;
}
2016-09-07 00:40:01 -05:00
// Get the cache object used by this reader
void Timeline : : SetCache ( CacheBase * new_cache ) {
// Set new cache
final_cache = new_cache ;
}
2013-12-07 21:09:55 -06:00
// Generate JSON string of this object
string Timeline : : Json ( ) {
// Return formatted string
return JsonValue ( ) . toStyledString ( ) ;
}
// Generate Json::JsonValue for this object
Json : : Value Timeline : : JsonValue ( ) {
// Create root json object
Json : : Value root = ReaderBase : : JsonValue ( ) ; // get parent properties
root [ " type " ] = " Timeline " ;
2014-01-05 22:37:11 -06:00
root [ " viewport_scale " ] = viewport_scale . JsonValue ( ) ;
root [ " viewport_x " ] = viewport_x . JsonValue ( ) ;
root [ " viewport_y " ] = viewport_y . JsonValue ( ) ;
root [ " color " ] = color . JsonValue ( ) ;
// Add array of clips
2014-01-27 23:31:38 -06:00
root [ " clips " ] = Json : : Value ( Json : : arrayValue ) ;
2014-01-05 22:37:11 -06:00
// Find Clips at this time
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = clips . begin ( ) ; clip_itr ! = clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * existing_clip = ( * clip_itr ) ;
2014-01-27 23:31:38 -06:00
root [ " clips " ] . append ( existing_clip - > JsonValue ( ) ) ;
2014-01-05 22:37:11 -06:00
}
// Add array of effects
2014-01-27 23:31:38 -06:00
root [ " effects " ] = Json : : Value ( Json : : arrayValue ) ;
2014-01-05 22:37:11 -06:00
// loop through effects
list < EffectBase * > : : iterator effect_itr ;
for ( effect_itr = effects . begin ( ) ; effect_itr ! = effects . end ( ) ; + + effect_itr )
{
// Get clip object from the iterator
EffectBase * existing_effect = ( * effect_itr ) ;
2014-01-27 23:31:38 -06:00
root [ " effects " ] . append ( existing_effect - > JsonValue ( ) ) ;
2014-01-05 22:37:11 -06:00
}
2013-12-07 21:09:55 -06:00
// return JsonValue
return root ;
}
// Load JSON string into this object
void Timeline : : SetJson ( string value ) throw ( InvalidJSON ) {
// Parse JSON string into JSON objects
Json : : Value root ;
Json : : Reader reader ;
bool success = reader . parse ( value , root ) ;
if ( ! success )
// Raise exception
throw InvalidJSON ( " JSON could not be parsed (or is invalid) " , " " ) ;
try
{
// Set all values that match
SetJsonValue ( root ) ;
}
catch ( exception e )
{
// Error parsing JSON (or missing keys)
throw InvalidJSON ( " JSON is invalid (missing keys or invalid data types) " , " " ) ;
}
}
// Load Json::JsonValue into this object
2014-01-05 22:37:11 -06:00
void Timeline : : SetJsonValue ( Json : : Value root ) throw ( InvalidFile , ReaderClosed ) {
// Close timeline before we do anything (this also removes all open and closing clips)
Close ( ) ;
2013-12-07 21:09:55 -06:00
// Set parent data
ReaderBase : : SetJsonValue ( root ) ;
2015-03-14 01:36:13 -05:00
if ( ! root [ " clips " ] . isNull ( ) ) {
// Clear existing clips
clips . clear ( ) ;
2014-01-05 22:37:11 -06:00
// loop through clips
2014-01-27 23:31:38 -06:00
for ( int x = 0 ; x < root [ " clips " ] . size ( ) ; x + + ) {
2014-01-05 22:37:11 -06:00
// Get each clip
2014-01-27 23:31:38 -06:00
Json : : Value existing_clip = root [ " clips " ] [ x ] ;
2014-01-05 22:37:11 -06:00
// Create Clip
Clip * c = new Clip ( ) ;
// Load Json into Clip
c - > SetJsonValue ( existing_clip ) ;
// Add Clip to Timeline
AddClip ( c ) ;
}
2015-03-14 01:36:13 -05:00
}
2014-01-05 22:37:11 -06:00
2015-03-14 01:36:13 -05:00
if ( ! root [ " effects " ] . isNull ( ) ) {
// Clear existing effects
effects . clear ( ) ;
2014-01-05 22:37:11 -06:00
// loop through effects
2014-01-27 23:31:38 -06:00
for ( int x = 0 ; x < root [ " effects " ] . size ( ) ; x + + ) {
2014-01-05 22:37:11 -06:00
// Get each effect
2014-01-27 23:31:38 -06:00
Json : : Value existing_effect = root [ " effects " ] [ x ] ;
2014-01-05 22:37:11 -06:00
// Create Effect
EffectBase * e = NULL ;
2016-08-16 22:40:51 -05:00
if ( ! existing_effect [ " type " ] . isNull ( ) ) {
// Create instance of effect
e = EffectInfo ( ) . CreateEffect ( existing_effect [ " type " ] . asString ( ) ) ;
2014-01-05 22:37:11 -06:00
2016-08-16 22:40:51 -05:00
// Load Json into Effect
e - > SetJsonValue ( existing_effect ) ;
2014-01-05 22:37:11 -06:00
2016-08-16 22:40:51 -05:00
// Add Effect to Timeline
AddEffect ( e ) ;
}
2014-01-05 22:37:11 -06:00
}
2015-03-14 01:36:13 -05:00
}
2016-08-15 00:44:51 -05:00
if ( ! root [ " duration " ] . isNull ( ) ) {
// Update duration of timeline
info . duration = root [ " duration " ] . asDouble ( ) ;
info . video_length = info . fps . ToFloat ( ) * info . duration ;
}
2013-12-07 21:09:55 -06:00
}
2014-01-08 01:43:58 -06:00
// Apply a special formatted JSON object, which represents a change to the timeline (insert, update, delete)
void Timeline : : ApplyJsonDiff ( string value ) throw ( InvalidJSON , InvalidJSONKey ) {
// Parse JSON string into JSON objects
Json : : Value root ;
Json : : Reader reader ;
bool success = reader . parse ( value , root ) ;
if ( ! success | | ! root . isArray ( ) )
// Raise exception
throw InvalidJSON ( " JSON could not be parsed (or is invalid) . " , " " ) ;
try
{
// Process the JSON change array, loop through each item
for ( int x = 0 ; x < root . size ( ) ; x + + ) {
// Get each change
Json : : Value change = root [ x ] ;
string root_key = change [ " key " ] [ ( uint ) 0 ] . asString ( ) ;
// Process each type of change
if ( root_key = = " clips " )
// Apply to CLIPS
apply_json_to_clips ( change ) ;
else if ( root_key = = " effects " )
// Apply to EFFECTS
apply_json_to_effects ( change ) ;
else
// Apply to TIMELINE
apply_json_to_timeline ( change ) ;
}
}
catch ( exception e )
{
// Error parsing JSON (or missing keys)
throw InvalidJSON ( " JSON is invalid (missing keys or invalid data types) " , " " ) ;
}
}
// Apply JSON diff to clips
void Timeline : : apply_json_to_clips ( Json : : Value change ) throw ( InvalidJSONKey ) {
// Get key and type of change
string change_type = change [ " type " ] . asString ( ) ;
string clip_id = " " ;
Clip * existing_clip = NULL ;
// Find id of clip (if any)
for ( int x = 0 ; x < change [ " key " ] . size ( ) ; x + + ) {
// Get each change
Json : : Value key_part = change [ " key " ] [ x ] ;
if ( key_part . isObject ( ) ) {
// Check for id
if ( ! key_part [ " id " ] . isNull ( ) ) {
// Set the id
clip_id = key_part [ " id " ] . asString ( ) ;
// Find matching clip in timeline (if any)
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = clips . begin ( ) ; clip_itr ! = clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * c = ( * clip_itr ) ;
if ( c - > Id ( ) = = clip_id ) {
existing_clip = c ;
break ; // clip found, exit loop
}
}
break ; // id found, exit loop
}
}
}
2015-08-07 23:11:03 -05:00
// Check for a more specific key (targetting this clip's effects)
// For example: ["clips", {"id:123}, "effects", {"id":432}]
if ( existing_clip & & change [ " key " ] . size ( ) = = 4 & & change [ " key " ] [ 2 ] = = " effects " )
{
// This change is actually targetting a specific effect under a clip (and not the clip)
Json : : Value key_part = change [ " key " ] [ 3 ] ;
if ( key_part . isObject ( ) ) {
// Check for id
if ( ! key_part [ " id " ] . isNull ( ) )
{
// Set the id
string effect_id = key_part [ " id " ] . asString ( ) ;
// Find matching effect in timeline (if any)
2016-08-16 22:40:51 -05:00
list < EffectBase * > effect_list = existing_clip - > Effects ( ) ;
2015-08-07 23:11:03 -05:00
list < EffectBase * > : : iterator effect_itr ;
2016-08-16 22:40:51 -05:00
for ( effect_itr = effect_list . begin ( ) ; effect_itr ! = effect_list . end ( ) ; + + effect_itr )
2015-08-07 23:11:03 -05:00
{
// Get effect object from the iterator
EffectBase * e = ( * effect_itr ) ;
if ( e - > Id ( ) = = effect_id ) {
// Apply the change to the effect directly
2016-08-16 22:40:51 -05:00
apply_json_to_effects ( change , e ) ;
2015-08-07 23:11:03 -05:00
return ; // effect found, don't update clip
}
}
}
}
}
2016-09-07 00:40:01 -05:00
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int new_starting_frame = change [ " value " ] [ " position " ] . asDouble ( ) * info . fps . ToDouble ( ) ;
long int new_ending_frame = ( change [ " value " ] [ " position " ] . asDouble ( ) + change [ " value " ] [ " end " ] . asDouble ( ) - change [ " value " ] [ " start " ] . asDouble ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( new_starting_frame - 1 , new_ending_frame + 1 ) ;
2014-01-08 01:43:58 -06:00
// Determine type of change operation
if ( change_type = = " insert " ) {
// Create new clip
Clip * clip = new Clip ( ) ;
clip - > SetJsonValue ( change [ " value " ] ) ; // Set properties of new clip from JSON
AddClip ( clip ) ; // Add clip to timeline
} else if ( change_type = = " update " ) {
// Update existing clip
2016-09-07 00:40:01 -05:00
if ( existing_clip ) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_clip - > Position ( ) * info . fps . ToDouble ( ) ;
long int old_ending_frame = ( existing_clip - > Position ( ) + existing_clip - > End ( ) - existing_clip - > Start ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( old_starting_frame - 1 , old_ending_frame + 1 ) ;
// Update clip properties from JSON
existing_clip - > SetJsonValue ( change [ " value " ] ) ;
}
2014-01-08 01:43:58 -06:00
} else if ( change_type = = " delete " ) {
// Remove existing clip
2016-09-07 00:40:01 -05:00
if ( existing_clip ) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_clip - > Position ( ) * info . fps . ToDouble ( ) ;
long int old_ending_frame = ( existing_clip - > Position ( ) + existing_clip - > End ( ) - existing_clip - > Start ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( old_starting_frame - 1 , old_ending_frame + 1 ) ;
// Remove clip from timeline
RemoveClip ( existing_clip ) ;
}
2014-01-08 01:43:58 -06:00
}
}
// Apply JSON diff to effects
void Timeline : : apply_json_to_effects ( Json : : Value change ) throw ( InvalidJSONKey ) {
// Get key and type of change
string change_type = change [ " type " ] . asString ( ) ;
EffectBase * existing_effect = NULL ;
// Find id of an effect (if any)
for ( int x = 0 ; x < change [ " key " ] . size ( ) ; x + + ) {
// Get each change
Json : : Value key_part = change [ " key " ] [ x ] ;
if ( key_part . isObject ( ) ) {
// Check for id
if ( ! key_part [ " id " ] . isNull ( ) )
{
// Set the id
2015-08-07 23:11:03 -05:00
string effect_id = key_part [ " id " ] . asString ( ) ;
2014-01-08 01:43:58 -06:00
// Find matching effect in timeline (if any)
list < EffectBase * > : : iterator effect_itr ;
for ( effect_itr = effects . begin ( ) ; effect_itr ! = effects . end ( ) ; + + effect_itr )
{
// Get effect object from the iterator
EffectBase * e = ( * effect_itr ) ;
if ( e - > Id ( ) = = effect_id ) {
existing_effect = e ;
break ; // effect found, exit loop
}
}
break ; // id found, exit loop
}
}
}
2015-08-07 23:11:03 -05:00
// Now that we found the effect, apply the change to it
if ( existing_effect | | change_type = = " insert " )
// Apply change to effect
apply_json_to_effects ( change , existing_effect ) ;
}
// Apply JSON diff to effects (if you already know which effect needs to be updated)
void Timeline : : apply_json_to_effects ( Json : : Value change , EffectBase * existing_effect ) throw ( InvalidJSONKey ) {
// Get key and type of change
string change_type = change [ " type " ] . asString ( ) ;
2016-09-07 00:40:01 -05:00
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int new_starting_frame = change [ " value " ] [ " position " ] . asDouble ( ) * info . fps . ToDouble ( ) ;
long int new_ending_frame = ( change [ " value " ] [ " position " ] . asDouble ( ) + change [ " value " ] [ " end " ] . asDouble ( ) - change [ " value " ] [ " start " ] . asDouble ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( new_starting_frame - 1 , new_ending_frame + 1 ) ;
2014-01-08 01:43:58 -06:00
// Determine type of change operation
if ( change_type = = " insert " ) {
// Determine type of effect
string effect_type = change [ " value " ] [ " type " ] . asString ( ) ;
// Create Effect
EffectBase * e = NULL ;
// Init the matching effect object
2016-08-16 22:40:51 -05:00
e = EffectInfo ( ) . CreateEffect ( effect_type ) ;
2015-11-25 23:54:10 -06:00
2014-01-08 01:43:58 -06:00
// Load Json into Effect
e - > SetJsonValue ( change [ " value " ] ) ;
// Add Effect to Timeline
AddEffect ( e ) ;
} else if ( change_type = = " update " ) {
// Update existing effect
2016-09-07 00:40:01 -05:00
if ( existing_effect ) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_effect - > Position ( ) * info . fps . ToDouble ( ) ;
long int old_ending_frame = ( existing_effect - > Position ( ) + existing_effect - > End ( ) - existing_effect - > Start ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( old_starting_frame - 1 , old_ending_frame + 1 ) ;
// Update effect properties from JSON
existing_effect - > SetJsonValue ( change [ " value " ] ) ;
}
2014-01-08 01:43:58 -06:00
} else if ( change_type = = " delete " ) {
// Remove existing effect
2016-09-07 00:40:01 -05:00
if ( existing_effect ) {
// Calculate start and end frames that this impacts, and remove those frames from the cache
long int old_starting_frame = existing_effect - > Position ( ) * info . fps . ToDouble ( ) ;
long int old_ending_frame = ( existing_effect - > Position ( ) + existing_effect - > End ( ) - existing_effect - > Start ( ) ) * info . fps . ToDouble ( ) ;
final_cache - > Remove ( old_starting_frame - 1 , old_ending_frame + 1 ) ;
// Remove effect from timeline
RemoveEffect ( existing_effect ) ;
}
2014-01-08 01:43:58 -06:00
}
}
// Apply JSON diff to timeline properties
void Timeline : : apply_json_to_timeline ( Json : : Value change ) throw ( InvalidJSONKey ) {
// Get key and type of change
string change_type = change [ " type " ] . asString ( ) ;
string root_key = change [ " key " ] [ ( uint ) 0 ] . asString ( ) ;
2016-01-10 16:50:54 -06:00
string sub_key = " " ;
if ( change [ " key " ] . size ( ) > = 2 )
sub_key = change [ " key " ] [ ( uint ) 1 ] . asString ( ) ;
2014-01-08 01:43:58 -06:00
2016-09-07 00:40:01 -05:00
// Clear entire cache
final_cache - > Clear ( ) ;
2014-01-08 01:43:58 -06:00
// Determine type of change operation
if ( change_type = = " insert " | | change_type = = " update " ) {
// INSERT / UPDATE
// Check for valid property
if ( root_key = = " color " )
// Set color
color . SetJsonValue ( change [ " value " ] ) ;
else if ( root_key = = " viewport_scale " )
// Set viewport scale
viewport_scale . SetJsonValue ( change [ " value " ] ) ;
else if ( root_key = = " viewport_x " )
// Set viewport x offset
viewport_x . SetJsonValue ( change [ " value " ] ) ;
else if ( root_key = = " viewport_y " )
// Set viewport y offset
viewport_y . SetJsonValue ( change [ " value " ] ) ;
2016-08-15 00:44:51 -05:00
else if ( root_key = = " duration " ) {
// Update duration of timeline
info . duration = change [ " value " ] . asDouble ( ) ;
info . video_length = info . fps . ToFloat ( ) * info . duration ;
}
2015-06-04 17:28:39 -05:00
else if ( root_key = = " width " )
// Set width
info . width = change [ " value " ] . asInt ( ) ;
else if ( root_key = = " height " )
// Set height
info . height = change [ " value " ] . asInt ( ) ;
2016-01-10 16:50:54 -06:00
else if ( root_key = = " fps " & & sub_key = = " " & & change [ " value " ] . isObject ( ) ) {
// Set fps fraction
if ( ! change [ " value " ] [ " num " ] . isNull ( ) )
info . fps . num = change [ " value " ] [ " num " ] . asInt ( ) ;
if ( ! change [ " value " ] [ " den " ] . isNull ( ) )
info . fps . den = change [ " value " ] [ " den " ] . asInt ( ) ;
}
else if ( root_key = = " fps " & & sub_key = = " num " )
2015-06-04 17:28:39 -05:00
// Set fps.num
info . fps . num = change [ " value " ] . asInt ( ) ;
2016-01-10 16:50:54 -06:00
else if ( root_key = = " fps " & & sub_key = = " den " )
2015-06-04 17:28:39 -05:00
// Set fps.den
info . fps . den = change [ " value " ] . asInt ( ) ;
2016-01-10 16:50:54 -06:00
else if ( root_key = = " sample_rate " )
// Set sample rate
info . sample_rate = change [ " value " ] . asInt ( ) ;
else if ( root_key = = " channels " )
// Set channels
info . channels = change [ " value " ] . asInt ( ) ;
else if ( root_key = = " channel_layout " )
// Set channel layout
info . channel_layout = ( ChannelLayout ) change [ " value " ] . asInt ( ) ;
2015-06-04 17:28:39 -05:00
2014-01-08 01:43:58 -06:00
else
2015-06-04 17:28:39 -05:00
2014-01-08 01:43:58 -06:00
// Error parsing JSON (or missing keys)
throw InvalidJSONKey ( " JSON change key is invalid " , change . toStyledString ( ) ) ;
} else if ( change [ " type " ] . asString ( ) = = " delete " ) {
// DELETE / RESET
// Reset the following properties (since we can't delete them)
if ( root_key = = " color " ) {
color = Color ( ) ;
color . red = Keyframe ( 0.0 ) ;
color . green = Keyframe ( 0.0 ) ;
color . blue = Keyframe ( 0.0 ) ;
}
else if ( root_key = = " viewport_scale " )
viewport_scale = Keyframe ( 1.0 ) ;
else if ( root_key = = " viewport_x " )
viewport_x = Keyframe ( 0.0 ) ;
else if ( root_key = = " viewport_y " )
viewport_y = Keyframe ( 0.0 ) ;
else
// Error parsing JSON (or missing keys)
throw InvalidJSONKey ( " JSON change key is invalid " , change . toStyledString ( ) ) ;
}
}