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)
2014-01-05 22:37:11 -06:00
Timeline : : Timeline ( int width , int height , Fraction fps , int sample_rate , int channels ) : is_open ( false )
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-12 17:21:21 -06:00
// Init cache
int64 bytes = height * width * 4 + ( 44100 * 2 * 4 ) ;
2013-02-15 00:23:55 -06:00
final_cache = Cache ( 2 * bytes ) ; // 20 frames, 4 colors of chars, 2 audio channels of 4 byte floats
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 ;
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
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
{
2012-10-14 02:36:05 -05:00
// All clips must be converted to the frame rate of this timeline,
// so assign the same frame rate to each clip.
2014-01-05 22:37:11 -06:00
clip - > Reader ( ) - > info . fps = info . fps ;
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
SortClips ( ) ;
}
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
SortEffects ( ) ;
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 ) ;
}
2012-10-05 17:05:33 -05:00
// Calculate time of a frame number, based on a framerate
2014-01-05 22:37:11 -06:00
float Timeline : : calculate_time ( 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)
tr1 : : shared_ptr < Frame > Timeline : : apply_effects ( tr1 : : shared_ptr < Frame > frame , int timeline_frame_number , int layer )
{
// 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
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::apply_effects " , " requested_time " , requested_time , " frame->number " , frame - > number , " timeline_frame_number " , timeline_frame_number , " layer " , layer , " " , - 1 , " " , - 1 ) ;
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 )
{
// Get clip object from the iterator
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 ) ;
// 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
# pragma omp critical (debug_output)
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 ) ;
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 ;
}
2012-11-07 17:45:13 -06:00
// Process a new layer of video or audio
2012-11-29 17:28:22 -06:00
void Timeline : : add_layer ( tr1 : : shared_ptr < Frame > new_frame , Clip * source_clip , int clip_frame_number , int timeline_frame_number )
2012-11-07 17:45:13 -06:00
{
// Get the clip's frame & image
2012-11-12 17:21:21 -06:00
tr1 : : shared_ptr < Frame > source_frame ;
2013-02-12 01:28:48 -06:00
2012-11-12 17:21:21 -06:00
# pragma omp critical (reader_lock)
2013-02-19 00:51:07 -06:00
source_frame = tr1 : : shared_ptr < Frame > ( source_clip - > GetFrame ( 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
# pragma omp critical (debug_output)
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 ) ;
2013-10-06 18:11:33 -05:00
/* Apply effects to the source frame (if any) */
source_frame = apply_effects ( source_frame , timeline_frame_number , source_clip - > Layer ( ) ) ;
2015-02-19 15:59:57 -06:00
// Declare an image to hold the source frame's image
2012-11-29 16:32:48 -06:00
tr1 : : shared_ptr < Magick : : Image > 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
# pragma omp critical (debug_output)
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)
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 ) )
source_frame - > ApplyGainRamp ( channel , 0 , source_frame - > GetAudioSamplesCount ( ) , previous_volume , volume ) ;
// 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).
new_frame - > AddAudio ( false , channel , 0 , source_frame - > GetAudioSamples ( channel ) , source_frame - > GetAudioSamplesCount ( ) , initial_volume ) ;
}
else
// Debug output
# pragma omp critical (debug_output)
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 ) ;
}
2012-11-29 16:32:48 -06:00
/* GET IMAGE DATA - OR GENERATE IT */
if ( ! source_clip - > Waveform ( ) )
2015-02-19 15:59:57 -06:00
{
// Debug output
# pragma omp critical (debug_output)
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 ) ;
2012-11-29 16:32:48 -06:00
// Get actual frame image data
source_image = source_frame - > GetImage ( ) ;
2015-02-19 15:59:57 -06:00
}
2012-11-29 16:32:48 -06:00
else
2012-11-29 23:11:50 -06:00
{
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
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 ) ;
2012-11-29 23:11:50 -06:00
// Get the color of the waveform
2015-02-19 15:59:57 -06:00
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 ) ;
2012-11-29 23:11:50 -06:00
2012-11-29 16:32:48 -06:00
// Generate Waveform Dynamically (the size of the timeline)
2014-01-05 22:37:11 -06:00
source_image = source_frame - > GetWaveform ( info . width , info . height , red , green , blue ) ;
2012-11-29 23:11:50 -06:00
}
2012-11-29 16:32:48 -06:00
// Get some basic image properties
int source_width = source_image - > columns ( ) ;
int source_height = source_image - > rows ( ) ;
2012-11-12 01:25:35 -06:00
/* ALPHA & OPACITY */
2012-11-08 04:35:21 -06:00
if ( source_clip - > alpha . GetValue ( clip_frame_number ) ! = 0 )
{
2012-11-13 00:11:20 -06:00
float alpha = 1.0 - source_clip - > alpha . GetValue ( clip_frame_number ) ;
source_image - > quantumOperator ( Magick : : OpacityChannel , Magick : : MultiplyEvaluateOperator , alpha ) ;
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
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 */
2014-01-05 22:37:11 -06:00
Magick : : Geometry new_size ( info . width , info . height ) ;
2012-11-12 01:25:35 -06:00
switch ( source_clip - > scale )
{
case ( SCALE_FIT ) :
new_size . aspect ( false ) ; // respect aspect ratio
source_image - > resize ( new_size ) ;
source_width = source_image - > size ( ) . width ( ) ;
source_height = source_image - > size ( ) . height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_FIT) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " new_size.aspect() " , new_size . aspect ( ) , " " , - 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 ) :
new_size . aspect ( true ) ; // ignore aspect ratio
source_image - > resize ( new_size ) ;
source_width = source_image - > size ( ) . width ( ) ;
source_height = source_image - > size ( ) . height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_STRETCH) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " new_size.aspect() " , new_size . aspect ( ) , " " , - 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 ) :
2014-01-05 22:37:11 -06:00
Magick : : Geometry width_size ( info . width , round ( info . width / ( float ( source_width ) / float ( source_height ) ) ) ) ;
Magick : : Geometry height_size ( round ( info . height / ( float ( source_height ) / float ( source_width ) ) ) , info . height ) ;
2012-11-12 01:25:35 -06:00
new_size . aspect ( false ) ; // respect aspect ratio
2014-01-05 22:37:11 -06:00
if ( width_size . width ( ) > = info . width & & width_size . height ( ) > = info . height )
2012-11-12 01:25:35 -06:00
source_image - > resize ( width_size ) ; // width is larger, so resize to it
else
source_image - > resize ( height_size ) ; // height is larger, so resize to it
source_width = source_image - > size ( ) . width ( ) ;
source_height = source_image - > size ( ) . height ( ) ;
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_CROP) " , " source_frame->number " , source_frame - > number , " source_width " , source_width , " source_height " , source_height , " new_size.aspect() " , new_size . aspect ( ) , " " , - 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
int scaled_source_width = source_width * sx ;
int scaled_source_height = source_height * sy ;
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
# pragma omp critical (debug_output)
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 ) ;
2012-11-12 01:25:35 -06:00
/* LOCATION, ROTATION, AND SCALE */
float r = source_clip - > rotation . GetValue ( clip_frame_number ) ; // rotate in degrees
2014-01-05 22:37:11 -06: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 ;
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
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Transform: SIMPLE) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
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)
offset_x = round ( x ) ;
offset_y = round ( y ) ;
2013-02-10 21:16:46 -06:00
transformed = true ;
2012-11-13 22:07:49 -06:00
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
# pragma omp critical (debug_output)
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
/* RESIZE SOURCE CANVAS - to the same size as timeline canvas */
2014-01-05 22:37:11 -06:00
if ( source_width ! = info . width | | source_height ! = info . height )
2013-02-12 02:42:18 -06:00
{
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Transform: COMPLEX: Resize Source Canvas) " , " source_frame->number " , source_frame - > number , " source_frame->GetWidth() " , source_frame - > GetWidth ( ) , " info.width " , info . width , " source_frame->GetHeight() " , source_frame - > GetHeight ( ) , " info.height " , info . height , " " , - 1 ) ;
2013-02-12 02:42:18 -06:00
source_image - > borderColor ( Magick : : Color ( " none " ) ) ;
source_image - > border ( Magick : : Geometry ( 1 , 1 , 0 , 0 , false , false ) ) ; // prevent stretching of edge pixels (during the canvas resize)
2014-01-05 22:37:11 -06:00
source_image - > size ( Magick : : Geometry ( info . width , info . height , 0 , 0 , false , false ) ) ; // resize the canvas (to prevent clipping)
2013-02-12 02:42:18 -06:00
}
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Transform: COMPLEX: Prepare for ScaleRotateTranslateDistortion) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
2012-11-13 22:07:49 -06:00
// Use the distort operator, which is very CPU intensive
// origin X,Y Scale Angle NewX,NewY
2015-02-19 15:59:57 -06:00
double distort_args [ 7 ] = { ( source_width / 2.0 ) , ( source_height / 2.0 ) , sx , sy , r , x + ( scaled_source_width / 2.0 ) , y + ( scaled_source_height / 2.0 ) } ;
2012-11-12 17:21:21 -06:00
source_image - > distort ( Magick : : ScaleRotateTranslateDistortion , 7 , distort_args , false ) ;
2013-02-10 21:16:46 -06:00
transformed = true ;
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
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-19 15:59:57 -06:00
2015-02-21 00:12:21 -06:00
/* Is this the 1st layer? If so, add a background color below the image */
if ( new_frame - > GetImage ( ) - > columns ( ) = = 1 )
2013-02-10 21:16:46 -06:00
{
2015-02-19 15:59:57 -06:00
// Debug output
# pragma omp critical (debug_output)
2015-02-21 00:12:21 -06:00
AppendDebugMethod ( " Timeline::add_layer (Transform: 1st Layer, Generate Solid Color Image " , " source_frame->number " , source_frame - > number , " offset_x " , offset_x , " offset_y " , offset_y , " new_frame->GetImage()->columns() " , new_frame - > GetImage ( ) - > columns ( ) , " transformed " , transformed , " " , - 1 ) ;
2015-02-19 15:59:57 -06:00
2013-02-12 01:28:48 -06:00
/* CREATE BACKGROUND COLOR - needed if this is the 1st layer */
2013-02-10 21:16:46 -06:00
int red = color . red . GetInt ( timeline_frame_number ) ;
int green = color . green . GetInt ( timeline_frame_number ) ;
int blue = color . blue . GetInt ( timeline_frame_number ) ;
2014-07-03 12:26:02 -05:00
new_frame - > AddColor ( info . width , info . height , Magick : : Color ( ( Magick : : Quantum ) red , ( Magick : : Quantum ) green , ( Magick : : Quantum ) blue , 0 ) ) ;
2013-02-10 21:16:46 -06:00
}
2015-02-19 15:59:57 -06:00
2015-02-21 00:12:21 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::add_layer (Transform: Composite Image Layer " , " source_frame->number " , source_frame - > number , " offset_x " , offset_x , " offset_y " , offset_y , " new_frame->GetImage()->columns() " , new_frame - > GetImage ( ) - > columns ( ) , " transformed " , transformed , " " , - 1 ) ;
/* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */
tr1 : : shared_ptr < Magick : : Image > new_image = new_frame - > GetImage ( ) ;
new_image - > composite ( * source_image . get ( ) , offset_x , offset_y , Magick : : OverCompositeOp ) ;
2012-11-08 18:02:20 -06:00
2012-11-07 17:45:13 -06:00
}
2012-10-09 02:09:44 -05:00
// Update the list of 'opened' clips
void Timeline : : update_open_clips ( Clip * clip , bool is_open )
{
// is clip already in list?
bool clip_found = open_clips . count ( clip ) ;
if ( clip_found & & ! is_open )
{
2012-12-03 22:55:46 -06:00
// Mark clip "to be removed"
closing_clips . push_back ( clip ) ;
2012-10-09 02:09:44 -05:00
}
else if ( ! clip_found & & is_open )
{
// Add clip to 'opened' list, because it's missing
open_clips [ clip ] = clip ;
// Open the clip's reader
clip - > Open ( ) ;
}
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::update_open_clips " , " clip_found " , clip_found , " is_open " , is_open , " closing_clips.size() " , closing_clips . size ( ) , " open_clips.size() " , open_clips . size ( ) , " " , - 1 , " " , - 1 ) ;
2012-10-09 02:09:44 -05:00
}
2012-12-03 22:55:46 -06:00
// Update the list of 'closed' clips
void Timeline : : update_closed_clips ( )
{
// Close all "to be closed" clips
list < Clip * > : : iterator clip_itr ;
for ( clip_itr = closing_clips . begin ( ) ; clip_itr ! = closing_clips . end ( ) ; + + clip_itr )
{
// Get clip object from the iterator
Clip * clip = ( * clip_itr ) ;
// Close the clip's reader
clip - > Close ( ) ;
// Remove clip from 'opened' list, because it's closed now
open_clips . erase ( clip ) ;
}
// Clear list
closing_clips . clear ( ) ;
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::update_closed_clips " , " closing_clips.size() " , closing_clips . size ( ) , " open_clips.size() " , open_clips . size ( ) , " " , - 1 , " " , - 1 , " " , - 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
void Timeline : : SortClips ( )
{
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::SortClips " , " clips.size() " , clips . size ( ) , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
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
void Timeline : : SortEffects ( )
{
// 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 ( )
{
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
// Actually close the clips
update_closed_clips ( ) ;
2013-12-18 21:55:43 -06:00
is_open = false ;
2014-01-05 22:37:11 -06:00
// Clear cache
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.
2012-10-14 03:43:52 -05:00
tr1 : : shared_ptr < Frame > Timeline : : GetFrame ( int requested_frame ) throw ( ReaderClosed )
2012-10-05 01:58:27 -05:00
{
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
2015-02-07 18:06:11 -06:00
if ( final_cache . Exists ( requested_frame ) ) {
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::GetFrame (Cached frame found) " , " requested_frame " , requested_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 17:21:21 -06:00
return final_cache . GetFrame ( requested_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-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::GetFrame (Generating frame) " , " requested_frame " , requested_frame , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
// Minimum number of frames to process (for performance reasons)
2015-02-19 15:59:57 -06:00
int minimum_frames = 1 ;
2015-02-07 18:06:11 -06:00
// Set the number of threads in OpenMP
omp_set_num_threads ( OPEN_MP_NUM_PROCESSORS ) ;
// Allow nested OpenMP sections
2012-11-12 17:21:21 -06:00
omp_set_nested ( true ) ;
2014-04-02 16:48:27 -05:00
2015-02-19 15:59:57 -06:00
# pragma xxx omp parallel
2012-10-10 02:36:53 -05:00
{
2015-02-19 15:59:57 -06:00
# pragma xxx omp single
2012-11-12 17:21:21 -06:00
{
2015-02-07 18:06:11 -06:00
// Debug output
2015-02-19 15:59:57 -06:00
# pragma xxx omp critical (debug_output)
2015-02-07 18:06:11 -06:00
AppendDebugMethod ( " Timeline::GetFrame (Loop through frames) " , " requested_frame " , requested_frame , " minimum_frames " , minimum_frames , " " , - 1 , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-19 01:03:22 -06: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'
list < Clip * > nearby_clips = find_intersecting_clips ( requested_frame , minimum_frames , true ) ;
2012-11-12 17:21:21 -06:00
// Loop through all requested frames
for ( int frame_number = requested_frame ; frame_number < requested_frame + minimum_frames ; frame_number + + )
{
2015-02-19 15:59:57 -06:00
# pragma xxx omp task firstprivate(frame_number)
2012-11-12 17:21:21 -06:00
{
// Create blank frame (which will become the requested frame)
2014-01-28 17:17:38 -06:00
tr1 : : shared_ptr < Frame > new_frame ( tr1 : : shared_ptr < Frame > ( new Frame ( frame_number , info . width , info . height , " #000000 " , 0 , info . channels ) ) ) ;
2012-11-07 17:45:13 -06:00
2012-11-12 17:21:21 -06:00
// Calculate time of frame
2014-01-05 22:37:11 -06:00
float requested_time = calculate_time ( frame_number , info . fps ) ;
2012-11-07 17:45:13 -06:00
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::GetFrame (Loop through clips) " , " frame_number " , frame_number , " requested_time " , requested_time , " clips.size() " , clips . size ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-19 01:03:22 -06:00
// Find Clips near this time
2012-11-12 17:21:21 -06:00
list < Clip * > : : iterator clip_itr ;
2015-02-19 01:03:22 -06:00
for ( clip_itr = nearby_clips . begin ( ) ; clip_itr ! = nearby_clips . end ( ) ; + + clip_itr )
2012-11-12 17:21:21 -06:00
{
// 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 ( ) < = requested_time & & clip - > Position ( ) + clip_duration > = requested_time ) ;
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
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 ) ;
2012-11-12 17:21:21 -06:00
// Clip is visible
if ( does_clip_intersect )
{
// Determine the frame needed for this clip (based on the position on the timeline)
float time_diff = ( requested_time - clip - > Position ( ) ) + clip - > Start ( ) ;
2014-01-05 22:37:11 -06:00
int clip_frame_number = round ( time_diff * info . fps . ToFloat ( ) ) + 1 ;
2012-11-12 17:21:21 -06:00
// Add clip's frame as layer
2012-11-29 17:28:22 -06:00
add_layer ( new_frame , clip , clip_frame_number , frame_number ) ;
2012-11-12 17:21:21 -06:00
} else
2015-02-07 18:06:11 -06:00
// Debug output
# pragma omp critical (debug_output)
AppendDebugMethod ( " Timeline::GetFrame (clip does not intersect) " , " frame_number " , frame_number , " requested_time " , requested_time , " does_clip_intersect " , does_clip_intersect , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2012-11-12 17:21:21 -06:00
} // end clip loop
2014-05-14 23:04:35 -05:00
// Check for empty frame image (and fill with color)
if ( new_frame - > GetImage ( ) - > columns ( ) = = 1 )
{
int red = color . red . GetInt ( frame_number ) ;
int green = color . green . GetInt ( frame_number ) ;
int blue = color . blue . GetInt ( frame_number ) ;
2014-07-03 12:26:02 -05:00
new_frame - > AddColor ( info . width , info . height , Magick : : Color ( ( Magick : : Quantum ) red , ( Magick : : Quantum ) green , ( Magick : : Quantum ) blue ) ) ;
2014-05-14 23:04:35 -05:00
}
// Add final frame to cache
# pragma omp critical (timeline_cache)
final_cache . Add ( frame_number , new_frame ) ;
2012-11-12 17:21:21 -06:00
} // end omp task
} // end frame loop
2012-12-03 22:55:46 -06:00
// Actually close all clips no longer needed
# pragma omp critical (reader_lock)
update_closed_clips ( ) ;
2012-11-12 17:21:21 -06:00
} // end omp single
} // end omp parallel
// Return frame (or blank frame)
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)
list < Clip * > Timeline : : find_intersecting_clips ( int requested_frame , int number_of_frames , bool include )
{
// Find matching clips
list < Clip * > matching_clips ;
// 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)
SortClips ( ) ;
// 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
# pragma omp critical (debug_output)
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 ) ;
// 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 ) ;
// 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 ;
}
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 ) ;
2014-01-05 22:37:11 -06:00
// Clear existing clips
clips . clear ( ) ;
2014-01-27 23:31:38 -06:00
if ( ! root [ " clips " ] . isNull ( ) )
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 ) ;
}
// Clear existing effects
effects . clear ( ) ;
2014-01-27 23:31:38 -06:00
if ( ! root [ " effects " ] . isNull ( ) )
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 ;
2014-01-08 01:43:58 -06:00
if ( ! existing_effect [ " type " ] . isNull ( ) )
2014-01-05 22:37:11 -06:00
// Init the matching effect object
if ( existing_effect [ " type " ] . asString ( ) = = " ChromaKey " )
e = new ChromaKey ( ) ;
else if ( existing_effect [ " type " ] . asString ( ) = = " Deinterlace " )
e = new Deinterlace ( ) ;
else if ( existing_effect [ " type " ] . asString ( ) = = " Mask " )
e = new Mask ( ) ;
else if ( existing_effect [ " type " ] . asString ( ) = = " Negate " )
e = new Negate ( ) ;
// Load Json into Effect
e - > SetJsonValue ( existing_effect ) ;
// Add Effect to Timeline
AddEffect ( e ) ;
}
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 ) {
2014-05-14 23:04:35 -05:00
// Clear internal cache (since things are about to change)
final_cache . Clear ( ) ;
2014-01-08 01:43:58 -06:00
// 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
}
}
}
// 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
if ( existing_clip )
existing_clip - > SetJsonValue ( change [ " value " ] ) ; // Update clip properties from JSON
} else if ( change_type = = " delete " ) {
// Remove existing clip
if ( existing_clip )
RemoveClip ( existing_clip ) ; // Remove clip from timeline
}
}
// 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 ( ) ;
string effect_id = " " ;
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
effect_id = key_part [ " id " ] . asString ( ) ;
// 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
}
}
}
// 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
if ( effect_type = = " ChromaKey " )
e = new ChromaKey ( ) ;
else if ( effect_type = = " Deinterlace " )
e = new Deinterlace ( ) ;
else if ( effect_type = = " Mask " )
e = new Mask ( ) ;
else if ( effect_type = = " Negate " )
e = new Negate ( ) ;
// Load Json into Effect
e - > SetJsonValue ( change [ " value " ] ) ;
// Add Effect to Timeline
AddEffect ( e ) ;
} else if ( change_type = = " update " ) {
// Update existing effect
if ( existing_effect )
existing_effect - > SetJsonValue ( change [ " value " ] ) ; // Update effect properties from JSON
} else if ( change_type = = " delete " ) {
// Remove existing effect
if ( existing_effect )
RemoveEffect ( existing_effect ) ; // Remove effect from timeline
}
}
// 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 ( ) ;
// 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 " ] ) ;
else
// 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 ( ) ) ;
}
}