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
{
2016-11-03 02:19:48 -05:00
// Create CrashHandler and Attach (incase of errors)
CrashHandler : : Instance ( ) ;
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 ;
2017-03-10 00:51:08 -06:00
// Init max image size
2019-01-19 02:18:52 -06:00
SetMaxSize ( info . width , info . height ) ;
2017-03-10 00:51:08 -06:00
2015-06-01 00:20:14 -07:00
// 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
2017-10-26 18:44:35 -05:00
void Timeline : : AddClip ( Clip * clip )
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 )
{
2017-03-05 04:34:32 -06:00
// Get lock (prevent getting frames while this happens)
const GenericScopedLock < CriticalSection > lock ( getFrameCriticalSection ) ;
2015-06-01 00:20:14 -07:00
// 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
2017-03-14 11:42:05 -05:00
ClearAllCache ( ) ;
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
2017-09-28 16:03:01 -05:00
double Timeline : : calculate_time ( int64_t number , Fraction rate )
2012-10-05 17:05:33 -05:00
{
// Get float version of fps fraction
2017-05-26 01:08:20 -05:00
double raw_fps = rate . ToFloat ( ) ;
2012-10-05 17:05:33 -05:00
// Return the time (in seconds) of this frame
2017-05-26 01:08:20 -05:00
return double ( number - 1 ) / raw_fps ;
2012-10-05 17:05:33 -05:00
}
2013-10-06 18:11:33 -05:00
// Apply effects to the source frame (if any)
2017-09-28 16:03:01 -05:00
std : : shared_ptr < Frame > Timeline : : apply_effects ( std : : shared_ptr < Frame > frame , int64_t timeline_frame_number , int layer )
2013-10-06 18:11:33 -05:00
{
2015-02-19 15:59:57 -06:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects " , " frame->number " , frame - > number , " timeline_frame_number " , timeline_frame_number , " layer " , layer , " " , - 1 , " " , - 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
2017-03-10 00:51:08 -06:00
long effect_start_position = round ( effect - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-11 00:51:43 -06:00
long effect_end_position = round ( ( effect - > Position ( ) + ( effect - > Duration ( ) ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-10 00:51:08 -06:00
bool does_effect_intersect = ( effect_start_position < = timeline_frame_number & & effect_end_position > = timeline_frame_number & & effect - > Layer ( ) = = layer ) ;
2013-10-06 18:11:33 -05:00
2015-02-25 17:39:59 -06:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects (Does effect intersect) " , " effect->Position() " , effect - > Position ( ) , " does_effect_intersect " , does_effect_intersect , " timeline_frame_number " , timeline_frame_number , " layer " , layer , " " , - 1 , " " , - 1 ) ;
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)
2017-03-11 00:51:43 -06:00
long effect_start_frame = ( effect - > Start ( ) * info . fps . ToDouble ( ) ) + 1 ;
long effect_frame_number = timeline_frame_number - effect_start_position + effect_start_frame ;
2013-10-06 18:11:33 -05:00
2015-02-19 15:59:57 -06:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::apply_effects (Process Effect) " , " effect_frame_number " , effect_frame_number , " does_effect_intersect " , does_effect_intersect , " " , - 1 , " " , - 1 , " " , - 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
2017-09-28 16:03:01 -05:00
std : : shared_ptr < Frame > Timeline : : GetOrCreateFrame ( Clip * clip , int64_t number )
2016-01-30 17:12:41 -06:00
{
2017-08-20 17:37:39 -05:00
std : : shared_ptr < Frame > new_frame ;
2016-01-30 17:12:41 -06:00
// 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-01-30 17:12:41 -06:00
// Attempt to get a frame (but this could fail if a reader has just been closed)
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_GetOtCreateFrame)
2017-08-20 17:37:39 -05:00
new_frame = std : : shared_ptr < Frame > ( clip - > GetFrame ( number ) ) ;
2016-01-30 17:12:41 -06:00
// 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
2019-01-19 02:18:52 -06:00
new_frame = std : : make_shared < Frame > ( number , Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , " #000000 " , samples_in_frame , info . channels ) ;
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_GetOtCreateFrame)
{
new_frame - > SampleRate ( info . sample_rate ) ;
new_frame - > ChannelsLayout ( info . channel_layout ) ;
}
2016-01-30 17:12:41 -06:00
return new_frame ;
}
2012-11-07 17:45:13 -06:00
// Process a new layer of video or audio
2018-06-11 12:02:21 -07:00
void Timeline : : add_layer ( std : : shared_ptr < Frame > new_frame , Clip * source_clip , int64_t clip_frame_number , int64_t timeline_frame_number , bool is_top_clip , float max_volume )
2012-11-07 17:45:13 -06:00
{
// Get the clip's frame & image
2017-11-11 17:16:56 -06:00
std : : shared_ptr < Frame > source_frame ;
# pragma omp critical (T_addLayer)
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)
2017-11-11 17:16:56 -06:00
std : : shared_ptr < QImage > source_image ;
# pragma omp critical (T_addLayer)
2019-01-19 02:18:52 -06:00
source_image = source_frame - > GetWaveform ( Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , red , green , blue , alpha ) ;
2017-08-20 17:37:39 -05:00
source_frame - > AddImage ( std : : shared_ptr < QImage > ( source_image ) ) ;
2015-11-25 23:54:10 -06:00
}
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 . */
2018-09-23 10:09:20 -07:00
if ( is_top_clip & & source_frame ) {
2018-09-23 09:51:56 -07:00
# pragma omp critical (T_addLayer)
2015-02-26 02:31:36 -06:00
source_frame = apply_effects ( source_frame , timeline_frame_number , source_clip - > Layer ( ) ) ;
2018-09-23 10:09:20 -07:00
}
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
2017-08-20 17:37:39 -05:00
std : : 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 ) {
// 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
2018-01-06 01:55:42 -06:00
if ( source_frame - > GetAudioChannelsCount ( ) = = info . channels & & source_clip - > has_audio . GetInt ( clip_frame_number ) ! = 0 )
2015-02-19 15:59:57 -06:00
for ( int channel = 0 ; channel < source_frame - > GetAudioChannelsCount ( ) ; channel + + )
{
2018-06-27 01:35:38 -05:00
// Get volume from previous frame and this frame
float previous_volume = source_clip - > volume . GetValue ( clip_frame_number - 1 ) ;
float volume = source_clip - > volume . GetValue ( clip_frame_number ) ;
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)
2018-06-27 01:35:38 -05:00
// Apply volume mixing strategy
if ( source_clip - > mixing = = VOLUME_MIX_AVERAGE & & max_volume > 1.0 ) {
// Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume
previous_volume = previous_volume / max_volume ;
volume = volume / max_volume ;
}
else if ( source_clip - > mixing = = VOLUME_MIX_REDUCE & & max_volume > 1.0 ) {
// Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will)
previous_volume = previous_volume * 0.77 ;
volume = volume * 0.77 ;
}
2016-04-24 15:37:47 -05:00
// 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
2018-01-06 01:55:42 -06:00
// If no volume on this frame or previous frame, do nothing
if ( previous_volume = = 0.0 & & volume = = 0.0 )
continue ; // skip to next channel
2016-04-24 15:37:47 -05:00
// 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
// Apply ramp to source frame (if needed)
2018-06-11 12:02:21 -07:00
if ( ! isEqual ( previous_volume , 1.0 ) | | ! isEqual ( volume , 1.0 ) )
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
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_addLayer)
2015-06-01 00:20:14 -07:00
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).
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_addLayer)
2018-06-11 12:02:21 -07:00
new_frame - > AddAudio ( false , channel_mapping , 0 , source_frame - > GetAudioSamples ( channel ) , source_frame - > GetAudioSamplesCount ( ) , 1.0 ) ;
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
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 */
2017-10-26 18:15:16 -05:00
QSize source_size = source_image - > size ( ) ;
2012-11-12 01:25:35 -06:00
switch ( source_clip - > scale )
{
2019-01-09 00:56:49 -06:00
case ( SCALE_FIT ) : {
// keep aspect ratio
2019-01-19 02:18:52 -06:00
source_size . scale ( Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , Qt : : KeepAspectRatio ) ;
2015-02-19 15:59:57 -06:00
2019-01-09 00:56:49 -06:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_FIT) " , " source_frame->number " , source_frame - > number , " source_width " , source_size . width ( ) , " source_height " , source_size . height ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
break ;
}
case ( SCALE_STRETCH ) : {
// ignore aspect ratio
2019-01-19 02:18:52 -06:00
source_size . scale ( Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , Qt : : IgnoreAspectRatio ) ;
2015-02-19 15:59:57 -06:00
2019-01-09 00:56:49 -06:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_STRETCH) " , " source_frame->number " , source_frame - > number , " source_width " , source_size . width ( ) , " source_height " , source_size . height ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
break ;
}
case ( SCALE_CROP ) : {
2019-01-19 02:18:52 -06:00
QSize width_size ( Settings : : Instance ( ) - > MAX_WIDTH , round ( Settings : : Instance ( ) - > MAX_WIDTH / ( float ( source_size . width ( ) ) / float ( source_size . height ( ) ) ) ) ) ;
QSize height_size ( round ( Settings : : Instance ( ) - > MAX_HEIGHT / ( float ( source_size . height ( ) ) / float ( source_size . width ( ) ) ) ) , Settings : : Instance ( ) - > MAX_HEIGHT ) ;
2015-02-19 15:59:57 -06:00
2019-01-09 00:56:49 -06:00
// respect aspect ratio
2019-01-19 02:18:52 -06:00
if ( width_size . width ( ) > = Settings : : Instance ( ) - > MAX_WIDTH & & width_size . height ( ) > = Settings : : Instance ( ) - > MAX_HEIGHT )
2019-01-09 00:56:49 -06:00
source_size . scale ( width_size . width ( ) , width_size . height ( ) , Qt : : KeepAspectRatio ) ;
else
source_size . scale ( height_size . width ( ) , height_size . height ( ) , Qt : : KeepAspectRatio ) ;
2015-02-19 15:59:57 -06:00
2019-01-09 00:56:49 -06:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_CROP) " , " source_frame->number " , source_frame - > number , " source_width " , source_size . width ( ) , " source_height " , source_size . height ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
break ;
}
case ( SCALE_NONE ) : {
// Calculate ratio of source size to project size
// Even with no scaling, previews need to be adjusted correctly
// (otherwise NONE scaling draws the frame image outside of the preview)
float source_width_ratio = source_size . width ( ) / float ( info . width ) ;
float source_height_ratio = source_size . height ( ) / float ( info . height ) ;
2019-01-19 02:18:52 -06:00
source_size . scale ( Settings : : Instance ( ) - > MAX_WIDTH * source_width_ratio , Settings : : Instance ( ) - > MAX_HEIGHT * source_height_ratio , Qt : : KeepAspectRatio ) ;
2016-02-23 00:27:03 -06:00
2019-01-09 00:56:49 -06:00
// Debug output
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Scale: SCALE_NONE) " , " source_frame->number " , source_frame - > number , " source_width " , source_size . width ( ) , " source_height " , source_size . height ( ) , " " , - 1 , " " , - 1 , " " , - 1 ) ;
break ;
}
2012-11-12 01:25:35 -06:00
}
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
2017-10-26 18:15:16 -05:00
float scaled_source_width = source_size . width ( ) * sx ;
float scaled_source_height = source_size . 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 ) :
2019-01-19 02:18:52 -06:00
x = ( Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_TOP_RIGHT ) :
2019-01-19 02:18:52 -06:00
x = Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ; // right
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_LEFT ) :
2019-01-19 02:18:52 -06:00
y = ( Settings : : Instance ( ) - > MAX_HEIGHT - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_CENTER ) :
2019-01-19 02:18:52 -06:00
x = ( Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ) / 2.0 ; // center
y = ( Settings : : Instance ( ) - > MAX_HEIGHT - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_RIGHT ) :
2019-01-19 02:18:52 -06:00
x = Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ; // right
y = ( Settings : : Instance ( ) - > MAX_HEIGHT - scaled_source_height ) / 2.0 ; // center
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM_LEFT ) :
2019-01-19 02:18:52 -06:00
y = ( Settings : : Instance ( ) - > MAX_HEIGHT - scaled_source_height ) ; // bottom
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM ) :
2019-01-19 02:18:52 -06:00
x = ( Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ) / 2.0 ; // center
y = ( Settings : : Instance ( ) - > MAX_HEIGHT - scaled_source_height ) ; // bottom
2012-11-12 01:25:35 -06:00
break ;
case ( GRAVITY_BOTTOM_RIGHT ) :
2019-01-19 02:18:52 -06:00
x = Settings : : Instance ( ) - > MAX_WIDTH - scaled_source_width ; // right
y = ( Settings : : Instance ( ) - > MAX_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
2017-10-26 18:15:16 -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 , " scaled_source_width " , scaled_source_width , " info.height " , info . height , " scaled_source_height " , scaled_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
2019-01-19 02:18:52 -06:00
x + = ( Settings : : Instance ( ) - > MAX_WIDTH * source_clip - > location_x . GetValue ( clip_frame_number ) ) ; // move in percentage of final width
y + = ( Settings : : Instance ( ) - > MAX_HEIGHT * source_clip - > location_y . GetValue ( clip_frame_number ) ) ; // move in percentage of final height
2016-09-16 00:12:17 -05:00
float shear_x = source_clip - > shear_x . GetValue ( clip_frame_number ) ;
float shear_y = source_clip - > shear_y . GetValue ( clip_frame_number ) ;
2012-11-12 01:25:35 -06:00
2013-02-10 21:16:46 -06:00
bool transformed = false ;
2015-06-01 00:20:14 -07:00
QTransform transform ;
2015-02-19 15:59:57 -06:00
2016-09-16 00:12:17 -05:00
// Transform source image (if needed)
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Build QTransform - if needed) " , " source_frame->number " , source_frame - > number , " x " , x , " y " , y , " r " , r , " sx " , sx , " sy " , sy ) ;
2017-11-08 03:36:01 -06:00
if ( ! isEqual ( r , 0 ) ) {
// ROTATE CLIP
float origin_x = x + ( scaled_source_width / 2.0 ) ;
float origin_y = y + ( scaled_source_height / 2.0 ) ;
transform . translate ( origin_x , origin_y ) ;
transform . rotate ( r ) ;
transform . translate ( - origin_x , - origin_y ) ;
transformed = true ;
}
2017-02-04 22:01:06 -06:00
if ( ! isEqual ( x , 0 ) | | ! isEqual ( y , 0 ) ) {
// TRANSLATE/MOVE CLIP
transform . translate ( x , y ) ;
transformed = true ;
}
2017-10-26 18:15:16 -05:00
// SCALE CLIP (if needed)
float source_width_scale = ( float ( source_size . width ( ) ) / float ( source_image - > width ( ) ) ) * sx ;
float source_height_scale = ( float ( source_size . height ( ) ) / float ( source_image - > height ( ) ) ) * sy ;
if ( ! isEqual ( source_width_scale , 1.0 ) | | ! isEqual ( source_height_scale , 1.0 ) ) {
transform . scale ( source_width_scale , source_height_scale ) ;
transformed = true ;
}
2017-02-04 22:01:06 -06:00
if ( ! isEqual ( shear_x , 0 ) | | ! isEqual ( shear_y , 0 ) ) {
// SHEAR HEIGHT/WIDTH
transform . shear ( shear_x , shear_y ) ;
transformed = true ;
}
2015-02-21 00:12:21 -06:00
// Debug output
2017-10-26 18:15:16 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: Composite Image Layer: Prepare) " , " source_frame->number " , source_frame - > number , " new_frame->GetImage()->width() " , new_frame - > GetImage ( ) - > width ( ) , " transformed " , transformed , " " , - 1 , " " , - 1 , " " , - 1 ) ;
2015-02-21 00:12:21 -06:00
/* COMPOSITE SOURCE IMAGE (LAYER) ONTO FINAL IMAGE */
2017-11-11 17:16:56 -06:00
std : : shared_ptr < QImage > new_image ;
# pragma omp critical (T_addLayer)
new_image = new_frame - > GetImage ( ) ;
2015-06-01 00:20:14 -07:00
// 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 ) ;
2016-09-16 00:12:17 -05:00
2017-03-15 02:06:53 -05:00
// Draw frame #'s on top of image (if needed)
if ( source_clip - > display ! = FRAME_DISPLAY_NONE ) {
stringstream frame_number_str ;
switch ( source_clip - > display )
{
case ( FRAME_DISPLAY_CLIP ) :
frame_number_str < < clip_frame_number ;
break ;
case ( FRAME_DISPLAY_TIMELINE ) :
frame_number_str < < timeline_frame_number ;
break ;
case ( FRAME_DISPLAY_BOTH ) :
frame_number_str < < timeline_frame_number < < " ( " < < clip_frame_number < < " ) " ;
break ;
}
// Draw frame number on top of image
painter . setPen ( QColor ( " #ffffff " ) ) ;
painter . drawText ( 20 , 20 , QString ( frame_number_str . str ( ) . c_str ( ) ) ) ;
}
2015-06-01 00:20:14 -07:00
painter . end ( ) ;
2012-11-08 18:02:20 -06:00
2015-03-15 02:28:28 -05:00
// Debug output
2017-10-26 18:15:16 -05:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::add_layer (Transform: Composite Image Layer: Completed) " , " source_frame->number " , source_frame - > number , " new_frame->GetImage()->width() " , new_frame - > GetImage ( ) - > width ( ) , " transformed " , transformed , " " , - 1 , " " , - 1 , " " , - 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 ;
2018-09-11 00:40:31 -05:00
try {
// Open the clip
clip - > Open ( ) ;
} catch ( const InvalidFile & e ) {
// ...
}
2012-10-09 02:09:44 -05:00
}
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.
2017-10-26 18:44:35 -05:00
std : : shared_ptr < Frame > Timeline : : GetFrame ( int64_t requested_frame )
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
2017-11-11 17:16:56 -06:00
std : : shared_ptr < Frame > frame ;
# pragma omp critical (T_GetFrame)
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 ) ;
2017-05-18 17:04:34 -05:00
// Check for open reader (or throw exception)
if ( ! is_open )
throw ReaderClosed ( " The Timeline is closed. Call Open() before calling this method . " , " " ) ;
2015-06-01 00:20:14 -07:00
// Check cache again (due to locking)
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_GetFrame)
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'
2017-11-11 17:16:56 -06:00
vector < Clip * > nearby_clips ;
# pragma omp critical (T_GetFrame)
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)
2017-09-28 16:03:01 -05:00
for ( int64_t frame_number = requested_frame ; frame_number < requested_frame + minimum_frames ; frame_number + + )
2016-01-30 17:12:41 -06:00
{
// 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 ] ;
2017-03-10 00:51:08 -06:00
long clip_start_position = round ( clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-11 00:51:43 -06:00
long clip_end_position = round ( ( clip - > Position ( ) + clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-10 00:51:08 -06:00
bool does_clip_intersect = ( clip_start_position < = frame_number & & clip_end_position > = frame_number ) ;
2016-01-30 17:12:41 -06:00
if ( does_clip_intersect )
{
// Get clip frame #
2017-03-11 00:51:43 -06:00
long clip_start_frame = ( clip - > Start ( ) * info . fps . ToDouble ( ) ) + 1 ;
long clip_frame_number = frame_number - clip_start_position + clip_start_frame ;
2016-01-30 17:12:41 -06:00
// 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
2018-09-11 00:40:31 -05:00
# pragma omp for ordered firstprivate(nearby_clips, requested_frame, minimum_frames) schedule(static,1)
2017-09-28 16:03:01 -05:00
for ( int64_t 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)
2019-01-19 02:18:52 -06:00
std : : shared_ptr < Frame > new_frame ( std : : make_shared < Frame > ( frame_number , Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , " #000000 " , samples_in_frame , info . channels ) ) ;
2017-11-11 17:16:56 -06:00
# pragma omp critical (T_GetFrame)
{
new_frame - > AddAudioSilence ( samples_in_frame ) ;
new_frame - > SampleRate ( info . sample_rate ) ;
new_frame - > ChannelsLayout ( info . channel_layout ) ;
}
2015-06-01 00:20:14 -07:00
// 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 ) )
2019-01-19 02:18:52 -06:00
new_frame - > AddColor ( Settings : : Instance ( ) - > MAX_WIDTH , Settings : : Instance ( ) - > MAX_HEIGHT , color . GetColorHex ( frame_number ) ) ;
2015-06-01 00:20:14 -07:00
2015-03-15 02:28:28 -05:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Loop through clips) " , " frame_number " , frame_number , " clips.size() " , clips . size ( ) , " nearby_clips.size() " , nearby_clips . size ( ) , " " , - 1 , " " , - 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 ] ;
2017-03-10 00:51:08 -06:00
long clip_start_position = round ( clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-11 00:51:43 -06:00
long clip_end_position = round ( ( clip - > Position ( ) + clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2015-03-15 02:28:28 -05:00
2017-03-10 00:51:08 -06:00
bool does_clip_intersect = ( clip_start_position < = frame_number & & clip_end_position > = frame_number ) ;
2015-03-15 02:28:28 -05:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Does clip intersect) " , " frame_number " , frame_number , " clip->Position() " , clip - > Position ( ) , " clip->Duration() " , clip - > Duration ( ) , " does_clip_intersect " , does_clip_intersect , " " , - 1 , " " , - 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 ;
2018-06-11 12:02:21 -07:00
float max_volume = 0.0 ;
2015-03-15 02:28:28 -05:00
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 ] ;
2017-03-10 00:51:08 -06:00
long nearby_clip_start_position = round ( nearby_clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-11 00:51:43 -06:00
long nearby_clip_end_position = round ( ( nearby_clip - > Position ( ) + nearby_clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2018-06-11 12:02:21 -07:00
long nearby_clip_start_frame = ( nearby_clip - > Start ( ) * info . fps . ToDouble ( ) ) + 1 ;
long nearby_clip_frame_number = frame_number - nearby_clip_start_position + nearby_clip_start_frame ;
2017-03-10 00:51:08 -06:00
2018-06-27 01:35:38 -05:00
// Determine if top clip
2015-03-15 02:28:28 -05:00
if ( clip - > Id ( ) ! = nearby_clip - > Id ( ) & & clip - > Layer ( ) = = nearby_clip - > Layer ( ) & &
2017-03-10 00:51:08 -06:00
nearby_clip_start_position < = frame_number & & nearby_clip_end_position > = frame_number & &
2018-06-11 12:02:21 -07:00
nearby_clip_start_position > clip_start_position & & is_top_clip = = true ) {
2015-03-15 02:28:28 -05:00
is_top_clip = false ;
2018-06-11 12:02:21 -07:00
}
2018-06-27 01:35:38 -05:00
// Determine max volume of overlapping clips
if ( nearby_clip - > Reader ( ) & & nearby_clip - > Reader ( ) - > info . has_audio & &
nearby_clip - > has_audio . GetInt ( nearby_clip_frame_number ) ! = 0 & &
nearby_clip_start_position < = frame_number & & nearby_clip_end_position > = frame_number ) {
max_volume + = nearby_clip - > volume . GetValue ( nearby_clip_frame_number ) ;
2015-03-15 02:28:28 -05:00
}
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)
2017-03-11 00:51:43 -06:00
long clip_start_frame = ( clip - > Start ( ) * info . fps . ToDouble ( ) ) + 1 ;
long clip_frame_number = frame_number - clip_start_position + clip_start_frame ;
2014-05-14 23:04:35 -05:00
2015-03-15 02:28:28 -05:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (Calculate clip's frame #) " , " clip->Position() " , clip - > Position ( ) , " clip->Start() " , clip - > Start ( ) , " info.fps.ToFloat() " , info . fps . ToFloat ( ) , " clip_frame_number " , clip_frame_number , " " , - 1 , " " , - 1 ) ;
2012-11-12 17:21:21 -06:00
2015-03-15 02:28:28 -05:00
// Add clip's frame as layer
2018-06-11 12:02:21 -07:00
add_layer ( new_frame , clip , clip_frame_number , frame_number , is_top_clip , max_volume ) ;
2012-12-03 22:55:46 -06:00
2015-03-15 02:28:28 -05:00
} else
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::GetFrame (clip does not intersect) " , " frame_number " , frame_number , " does_clip_intersect " , does_clip_intersect , " " , - 1 , " " , - 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
2018-06-21 02:44:08 -05:00
# pragma omp ordered
2017-11-11 17:16:56 -06:00
{
new_frame - > SetFrameNumber ( frame_number ) ;
2016-09-14 04:11:12 -05:00
2017-11-11 17:16:56 -06:00
// Add final frame to cache
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)
2017-09-28 16:03:01 -05:00
vector < Clip * > Timeline : : find_intersecting_clips ( int64_t 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
2017-03-10 00:51:08 -06:00
float min_requested_frame = requested_frame ;
float max_requested_frame = requested_frame + ( number_of_frames - 1 ) ;
2015-02-19 01:03:22 -06:00
// 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
2017-03-10 00:51:08 -06:00
long clip_start_position = round ( clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-11 00:51:43 -06:00
long clip_end_position = round ( ( clip - > Position ( ) + clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-10 00:51:08 -06:00
bool does_clip_intersect =
( clip_start_position < = min_requested_frame | | clip_start_position < = max_requested_frame ) & &
( clip_end_position > = min_requested_frame | | clip_end_position > = max_requested_frame ) ;
2015-02-19 01:03:22 -06:00
// Debug output
2017-03-10 00:51:08 -06:00
ZmqLogger : : Instance ( ) - > AppendDebugMethod ( " Timeline::find_intersecting_clips (Is clip near or intersecting) " , " requested_frame " , requested_frame , " min_requested_frame " , min_requested_frame , " max_requested_frame " , max_requested_frame , " clip->Position() " , clip - > Position ( ) , " does_clip_intersect " , does_clip_intersect , " " , - 1 ) ;
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 ) ;
// 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
2017-10-26 18:44:35 -05:00
void Timeline : : SetJson ( string value ) {
2013-12-07 21:09:55 -06:00
2017-05-18 17:04:34 -05:00
// Get lock (prevent getting frames while this happens)
const GenericScopedLock < CriticalSection > lock ( getFrameCriticalSection ) ;
2013-12-07 21:09:55 -06:00
// 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
2017-10-26 18:44:35 -05:00
void Timeline : : SetJsonValue ( Json : : Value root ) {
2014-01-05 22:37:11 -06:00
// Close timeline before we do anything (this also removes all open and closing clips)
2017-05-18 17:04:34 -05:00
bool was_open = is_open ;
2014-01-05 22:37:11 -06:00
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
2018-08-11 18:22:18 -05:00
if ( e = EffectInfo ( ) . CreateEffect ( existing_effect [ " type " ] . asString ( ) ) ) {
2014-01-05 22:37:11 -06:00
2018-08-11 18:22:18 -05:00
// Load Json into Effect
e - > SetJsonValue ( existing_effect ) ;
2014-01-05 22:37:11 -06:00
2018-08-11 18:22:18 -05:00
// Add Effect to Timeline
AddEffect ( e ) ;
}
2016-08-16 22:40:51 -05:00
}
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 ;
}
2017-05-18 17:04:34 -05:00
// Re-open if needed
if ( was_open )
Open ( ) ;
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)
2017-10-26 18:44:35 -05:00
void Timeline : : ApplyJsonDiff ( string value ) {
2014-01-08 01:43:58 -06:00
2017-03-05 04:34:32 -06:00
// Get lock (prevent getting frames while this happens)
const GenericScopedLock < CriticalSection > lock ( getFrameCriticalSection ) ;
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
2017-10-26 18:44:35 -05:00
void Timeline : : apply_json_to_clips ( Json : : Value change ) {
2014-01-08 01:43:58 -06:00
// 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 ) ;
2017-03-10 00:51:08 -06:00
// Calculate start and end frames that this impacts, and remove those frames from the cache
2017-09-28 16:03:01 -05:00
int64_t new_starting_frame = ( existing_clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t new_ending_frame = ( ( existing_clip - > Position ( ) + existing_clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( new_starting_frame - 8 , new_ending_frame + 8 ) ;
2017-03-10 00:51:08 -06:00
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
2016-09-15 17:43:46 -05:00
if ( ! change [ " value " ] . isArray ( ) & & ! change [ " value " ] [ " position " ] . isNull ( ) ) {
2017-09-28 16:03:01 -05:00
int64_t new_starting_frame = ( change [ " value " ] [ " position " ] . asDouble ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t new_ending_frame = ( ( change [ " value " ] [ " position " ] . asDouble ( ) + change [ " value " ] [ " end " ] . asDouble ( ) - change [ " value " ] [ " start " ] . asDouble ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( new_starting_frame - 8 , new_ending_frame + 8 ) ;
2016-09-15 17:43:46 -05:00
}
2016-09-07 00:40:01 -05:00
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
2018-01-06 01:55:42 -06:00
// Apply framemapper (or update existing framemapper)
apply_mapper_to_clip ( clip ) ;
2014-01-08 01:43:58 -06:00
} 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
2017-09-28 16:03:01 -05:00
int64_t old_starting_frame = ( existing_clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t old_ending_frame = ( ( existing_clip - > Position ( ) + existing_clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( old_starting_frame - 8 , old_ending_frame + 8 ) ;
2016-09-07 00:40:01 -05:00
2017-07-27 02:25:20 -05:00
// Remove cache on clip's Reader (if found)
if ( existing_clip - > Reader ( ) & & existing_clip - > Reader ( ) - > GetCache ( ) )
existing_clip - > Reader ( ) - > GetCache ( ) - > Remove ( old_starting_frame - 8 , old_ending_frame + 8 ) ;
2016-09-07 00:40:01 -05:00
// Update clip properties from JSON
existing_clip - > SetJsonValue ( change [ " value " ] ) ;
2017-07-27 02:25:20 -05:00
2018-01-06 01:55:42 -06:00
// Apply framemapper (or update existing framemapper)
apply_mapper_to_clip ( existing_clip ) ;
2016-09-07 00:40:01 -05:00
}
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
2017-09-28 16:03:01 -05:00
int64_t old_starting_frame = ( existing_clip - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t old_ending_frame = ( ( existing_clip - > Position ( ) + existing_clip - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( old_starting_frame - 8 , old_ending_frame + 8 ) ;
2016-09-07 00:40:01 -05:00
// Remove clip from timeline
RemoveClip ( existing_clip ) ;
}
2014-01-08 01:43:58 -06:00
}
}
// Apply JSON diff to effects
2017-10-26 18:44:35 -05:00
void Timeline : : apply_json_to_effects ( Json : : Value change ) {
2014-01-08 01:43:58 -06:00
// 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)
2017-10-26 18:44:35 -05:00
void Timeline : : apply_json_to_effects ( Json : : Value change , EffectBase * existing_effect ) {
2015-08-07 23:11:03 -05:00
// 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
2016-09-15 17:43:46 -05:00
if ( ! change [ " value " ] . isArray ( ) & & ! change [ " value " ] [ " position " ] . isNull ( ) ) {
2017-09-28 16:03:01 -05:00
int64_t new_starting_frame = ( change [ " value " ] [ " position " ] . asDouble ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t new_ending_frame = ( ( change [ " value " ] [ " position " ] . asDouble ( ) + change [ " value " ] [ " end " ] . asDouble ( ) - change [ " value " ] [ " start " ] . asDouble ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( new_starting_frame - 8 , new_ending_frame + 8 ) ;
2016-09-15 17:43:46 -05:00
}
2016-09-07 00:40:01 -05:00
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
2018-08-11 18:22:18 -05:00
if ( e = EffectInfo ( ) . CreateEffect ( effect_type ) ) {
2015-11-25 23:54:10 -06:00
2018-08-11 18:22:18 -05:00
// Load Json into Effect
e - > SetJsonValue ( change [ " value " ] ) ;
2014-01-08 01:43:58 -06:00
2018-08-11 18:22:18 -05:00
// Add Effect to Timeline
AddEffect ( e ) ;
}
2014-01-08 01:43:58 -06:00
} 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
2017-09-28 16:03:01 -05:00
int64_t old_starting_frame = ( existing_effect - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t old_ending_frame = ( ( existing_effect - > Position ( ) + existing_effect - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( old_starting_frame - 8 , old_ending_frame + 8 ) ;
2016-09-07 00:40:01 -05:00
// 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
2017-09-28 16:03:01 -05:00
int64_t old_starting_frame = ( existing_effect - > Position ( ) * info . fps . ToDouble ( ) ) + 1 ;
int64_t old_ending_frame = ( ( existing_effect - > Position ( ) + existing_effect - > Duration ( ) ) * info . fps . ToDouble ( ) ) + 1 ;
2017-03-14 11:42:05 -05:00
final_cache - > Remove ( old_starting_frame - 8 , old_ending_frame + 8 ) ;
2016-09-07 00:40:01 -05:00
// Remove effect from timeline
RemoveEffect ( existing_effect ) ;
}
2014-01-08 01:43:58 -06:00
}
}
// Apply JSON diff to timeline properties
2017-10-26 18:44:35 -05:00
void Timeline : : apply_json_to_timeline ( Json : : Value change ) {
2014-01-08 01:43:58 -06:00
// 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 ( ) ) ;
}
}
2017-03-10 00:51:08 -06:00
// Clear all caches
void Timeline : : ClearAllCache ( ) {
2017-05-18 02:48:00 -05:00
// Get lock (prevent getting frames while this happens)
const GenericScopedLock < CriticalSection > lock ( getFrameCriticalSection ) ;
2017-03-10 00:51:08 -06:00
// Clear primary cache
final_cache - > Clear ( ) ;
2014-01-08 01:43:58 -06:00
2017-03-10 00:51:08 -06: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 ) ;
2014-01-08 01:43:58 -06:00
2017-03-10 00:51:08 -06:00
// Clear cache on clip
clip - > Reader ( ) - > GetCache ( ) - > Clear ( ) ;
2014-01-08 01:43:58 -06:00
2017-03-10 00:51:08 -06:00
// Clear nested Reader (if any)
if ( clip - > Reader ( ) - > Name ( ) = = " FrameMapper " ) {
FrameMapper * nested_reader = ( FrameMapper * ) clip - > Reader ( ) ;
if ( nested_reader - > Reader ( ) & & nested_reader - > Reader ( ) - > GetCache ( ) )
nested_reader - > Reader ( ) - > GetCache ( ) - > Clear ( ) ;
}
}
2017-11-11 17:16:56 -06:00
}
2019-01-19 02:18:52 -06:00
// Set Max Image Size (used for performance optimization). Convenience function for setting
// Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT.
void Timeline : : SetMaxSize ( int width , int height ) {
2019-03-09 13:19:07 -06:00
// Init max image size (choose the smallest one)
Settings : : Instance ( ) - > MAX_WIDTH = min ( width , info . width ) ;
Settings : : Instance ( ) - > MAX_HEIGHT = min ( height , info . height ) ;
2019-01-19 02:18:52 -06:00
}