]> git.sesse.net Git - mlt/blobdiff - src/framework/mlt_playlist.c
Restrict auto-profile frame rate to a sane value.
[mlt] / src / framework / mlt_playlist.c
index 356b0d79de09543136165e8c1b8adfe05e41980c..62a1f1643b3ce4a25a44fb4926285f108178aeeb 100644 (file)
@@ -53,6 +53,7 @@ struct playlist_entry_s
 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index );
 static int mlt_playlist_unmix( mlt_playlist self, int clip );
 static int mlt_playlist_resize_mix( mlt_playlist self, int clip, int in, int out );
+static void mlt_playlist_next( mlt_listener listener, mlt_properties owner, mlt_service self, void **args );
 
 /** Construct a playlist.
  *
@@ -64,13 +65,13 @@ static int mlt_playlist_resize_mix( mlt_playlist self, int clip, int in, int out
 
 mlt_playlist mlt_playlist_init( )
 {
-       mlt_playlist self = calloc( sizeof( struct mlt_playlist_s ), 1 );
+       mlt_playlist self = calloc( 1, sizeof( struct mlt_playlist_s ) );
        if ( self != NULL )
        {
                mlt_producer producer = &self->parent;
 
                // Construct the producer
-               mlt_producer_init( producer, self );
+               if ( mlt_producer_init( producer, self ) != 0 ) goto error1;
 
                // Override the producer get_frame
                producer->get_frame = producer_get_frame;
@@ -80,7 +81,7 @@ mlt_playlist mlt_playlist_init( )
                producer->close_object = self;
 
                // Initialise blank
-               mlt_producer_init( &self->blank, NULL );
+               if ( mlt_producer_init( &self->blank, NULL ) != 0 ) goto error1;
                mlt_properties_set( MLT_PRODUCER_PROPERTIES( &self->blank ), "mlt_service", "blank" );
                mlt_properties_set( MLT_PRODUCER_PROPERTIES( &self->blank ), "resource", "blank" );
 
@@ -96,10 +97,35 @@ mlt_playlist mlt_playlist_init( )
                mlt_properties_set_position( MLT_PLAYLIST_PROPERTIES( self ), "length", 0 );
 
                self->size = 10;
-               self->list = malloc( self->size * sizeof( playlist_entry * ) );
+               self->list = calloc( self->size, sizeof( playlist_entry * ) );
+               if ( self->list == NULL ) goto error2;
+               
+               mlt_events_register( MLT_PLAYLIST_PROPERTIES( self ), "playlist-next", (mlt_transmitter) mlt_playlist_next );
        }
 
        return self;
+error2:
+       free( self->list );
+error1:
+       free( self );
+       return NULL;
+}
+
+/** Construct a playlist with a profile.
+ *
+ * Sets the resource property to "<playlist>".
+ * Set the mlt_type to property to "mlt_producer".
+ * \public \memberof mlt_playlist_s
+ * \param profile the profile to use with the profile
+ * \return a new playlist
+ */
+
+mlt_playlist mlt_playlist_new( mlt_profile profile )
+{
+    mlt_playlist self = mlt_playlist_init();
+    if ( self )
+        mlt_properties_set_data( MLT_PLAYLIST_PROPERTIES( self ), "_profile", profile, 0, NULL, NULL );
+    return self;
 }
 
 /** Get the producer associated to this playlist.
@@ -312,7 +338,7 @@ static int mlt_playlist_virtual_append( mlt_playlist self, mlt_producer source,
        }
 
        // Create the entry
-       self->list[ self->count ] = calloc( sizeof( playlist_entry ), 1 );
+       self->list[ self->count ] = calloc( 1, sizeof( playlist_entry ) );
        if ( self->list[ self->count ] != NULL )
        {
                self->list[ self->count ]->producer = producer;
@@ -370,6 +396,24 @@ static mlt_producer mlt_playlist_locate( mlt_playlist self, mlt_position *positi
        return producer;
 }
 
+/** The transmitter for the producer-next event
+ *
+ * Invokes the listener.
+ *
+ * \private \memberof mlt_playlist_s
+ * \param listener a function pointer that will be invoked
+ * \param owner the events object that will be passed to \p listener
+ * \param self a service that will be passed to \p listener
+ * \param args an array of pointers.
+ */
+
+static void mlt_playlist_next( mlt_listener listener, mlt_properties owner, mlt_service self, void **args )
+{
+       if ( listener )
+               listener( owner, self, args[ 0 ] );
+}
+
+
 /** Seek in the virtual playlist.
  *
  * This gets the producer at the current position and seeks on the producer
@@ -457,6 +501,10 @@ static mlt_service mlt_playlist_virtual_seek( mlt_playlist self, int *progressiv
                producer = &self->blank;
        }
 
+       // Determine if we have moved to the next entry in the playlist.
+       if ( original == total - 2 )
+               mlt_events_fire( properties, "playlist-next", i, NULL );
+
        return MLT_PRODUCER_SERVICE( producer );
 }
 
@@ -702,15 +750,36 @@ int mlt_playlist_append_io( mlt_playlist self, mlt_producer producer, mlt_positi
  *
  * \public \memberof mlt_playlist_s
  * \param self a playlist
- * \param length the ending time of the blank entry, not its duration
+ * \param out the ending time of the blank entry, not its duration
  * \return true if there was an error
  */
 
-int mlt_playlist_blank( mlt_playlist self, mlt_position length )
+int mlt_playlist_blank( mlt_playlist self, mlt_position out )
 {
        // Append to the virtual list
-       if (length >= 0)
-               return mlt_playlist_virtual_append( self, &self->blank, 0, length );
+       if ( out >= 0 )
+               return mlt_playlist_virtual_append( self, &self->blank, 0, out );
+       else
+               return 1;
+}
+
+/** Append a blank item to the playlist with duration as a time string.
+ *
+ * \public \memberof mlt_playlist_s
+ * \param self a playlist
+ * \param length the duration of the blank entry as a time string
+ * \return true if there was an error
+ */
+
+int mlt_playlist_blank_time( mlt_playlist self, const char* length )
+{
+       if ( self && length )
+       {
+               mlt_properties properties = MLT_PLAYLIST_PROPERTIES( self );
+               mlt_properties_set( properties , "_blank_time", length );
+               mlt_position duration = mlt_properties_get_position( properties, "_blank_time" );
+               return mlt_playlist_blank( self, duration - 1 );
+       }
        else
                return 1;
 }
@@ -767,12 +836,6 @@ int mlt_playlist_remove( mlt_playlist self, int where )
                // Get the clip info
                mlt_playlist_get_clip_info( self, &where_info, where );
 
-               // Make sure the clip to be removed is valid and correct if necessary
-               if ( where < 0 )
-                       where = 0;
-               if ( where >= self->count )
-                       where = self->count - 1;
-
                // Reorganise the list
                for ( i = where + 1; i < self->count; i ++ )
                        self->list[ i - 1 ] = self->list[ i ];
@@ -1067,7 +1130,7 @@ int mlt_playlist_join( mlt_playlist self, int clip, int count, int merge )
        if ( error == 0 )
        {
                int i = clip;
-               mlt_playlist new_clip = mlt_playlist_init( );
+               mlt_playlist new_clip = mlt_playlist_new( mlt_service_profile( MLT_PLAYLIST_SERVICE(self) ) );
                mlt_events_block( MLT_PLAYLIST_PROPERTIES( self ), self );
                if ( clip + count >= self->count )
                        count = self->count - clip - 1;
@@ -1088,6 +1151,10 @@ int mlt_playlist_join( mlt_playlist self, int clip, int count, int merge )
 
 /** Mix consecutive clips for a specified length and apply transition if specified.
  *
+ * This version of the mix function does not utilize any frames beyond the out of
+ * clip A or before the in point of clip B. It takes the frames needed for the length
+ * of the transition by adjusting the duration of both clips - the out point for clip A
+ * and the in point for clip B.
  * \public \memberof mlt_playlist_s
  * \param self a playlist
  * \param clip the index of the playlist entry
@@ -1106,6 +1173,11 @@ int mlt_playlist_mix( mlt_playlist self, int clip, int length, mlt_transition tr
                mlt_producer track_a = NULL;
                mlt_producer track_b = NULL;
                mlt_tractor tractor = mlt_tractor_new( );
+
+               mlt_service_set_profile( MLT_TRACTOR_SERVICE( tractor ),
+                       mlt_service_profile( MLT_PLAYLIST_SERVICE( self ) ) );
+               mlt_properties_set_lcnumeric( MLT_TRACTOR_PROPERTIES( tractor ),
+                       mlt_properties_get_lcnumeric( MLT_PLAYLIST_PROPERTIES( self ) ) );
                mlt_events_block( MLT_PLAYLIST_PROPERTIES( self ), self );
 
                // Check length is valid for both clips and resize if necessary.
@@ -1189,6 +1261,210 @@ int mlt_playlist_mix( mlt_playlist self, int clip, int length, mlt_transition tr
        return error;
 }
 
+/** Mix consecutive clips for a specified length.
+ *
+ * This version of the mix function maintains the out point of the clip A by occupying the
+ * beginning of clip B before its current in point. Therefore, it ends up adjusting the in
+ * point and duration of clip B without affecting the duration of clip A.
+ * Also, therefore, there must be enough frames after the out point of clip A.
+ * \public \memberof mlt_playlist_s
+ * \param self a playlist
+ * \param clip the index of the playlist entry
+ * \param length the number of frames over which to create the mix
+ * \return true if there was an error
+ */
+
+int mlt_playlist_mix_in( mlt_playlist self, int clip, int length )
+{
+       int error = ( clip < 0 || clip + 1 >= self->count );
+       if ( error == 0 )
+       {
+               playlist_entry *clip_a = self->list[ clip ];
+               playlist_entry *clip_b = self->list[ clip + 1 ];
+               mlt_producer track_a = NULL;
+               mlt_producer track_b = NULL;
+               mlt_tractor tractor = mlt_tractor_new( );
+
+               mlt_service_set_profile( MLT_TRACTOR_SERVICE( tractor ),
+                       mlt_service_profile( MLT_PLAYLIST_SERVICE( self ) ) );
+               mlt_properties_set_lcnumeric( MLT_TRACTOR_PROPERTIES( tractor ),
+                       mlt_properties_get_lcnumeric( MLT_PLAYLIST_PROPERTIES( self ) ) );
+               mlt_events_block( MLT_PLAYLIST_PROPERTIES( self ), self );
+
+               // Check length is valid for both clips and resize if necessary.
+               int max_size = ( clip_a->frame_out + 1 ) > clip_b->frame_count ? ( clip_a->frame_out +  1 ) : clip_b->frame_count;
+               length = length > max_size ? max_size : length;
+
+               // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches
+               if ( length != clip_a->frame_out + 1 )
+                       track_a = mlt_producer_cut( clip_a->producer, clip_a->frame_out + 1, clip_a->frame_out + length );
+               else
+                       track_a = clip_a->producer;
+
+               if ( length != clip_b->frame_count )
+                       track_b = mlt_producer_cut( clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1 );
+               else
+                       track_b = clip_b->producer;
+
+               // Set the tracks on the tractor
+               mlt_tractor_set_track( tractor, track_a, 0 );
+               mlt_tractor_set_track( tractor, track_b, 1 );
+
+               // Insert the mix object into the playlist
+               mlt_playlist_insert( self, MLT_TRACTOR_PRODUCER( tractor ), clip + 1, -1, -1 );
+               mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mlt_mix", tractor, 0, NULL, NULL );
+
+               // Close our references to the tracks if we created new cuts above (the tracks can still be used here)
+               if ( track_a != clip_a->producer )
+                       mlt_producer_close( track_a );
+               if ( track_b != clip_b->producer )
+                       mlt_producer_close( track_b );
+
+               // Check if we have anything left on the right hand clip
+               if ( track_b == clip_b->producer )
+               {
+                       clip_b->preservation_hack = 1;
+                       mlt_playlist_remove( self, clip + 2 );
+               }
+               else if ( clip_b->frame_out - clip_b->frame_in >= length )
+               {
+                       mlt_playlist_resize_clip( self, clip + 2, clip_b->frame_in + length, clip_b->frame_out );
+                       mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_b->producer ), "mix_in", tractor, 0, NULL, NULL );
+                       mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_out", clip_b->producer, 0, NULL, NULL );
+               }
+               else
+               {
+                       mlt_producer_clear( clip_b->producer );
+                       mlt_playlist_remove( self, clip + 2 );
+               }
+
+               // Check if we have anything left on the left hand clip
+               if ( track_a == clip_a->producer )
+               {
+                       clip_a->preservation_hack = 1;
+                       mlt_playlist_remove( self, clip );
+               }
+               else if ( clip_a->frame_out - clip_a->frame_in > 0 )
+               {
+                       mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_a->producer ), "mix_out", tractor, 0, NULL, NULL );
+                       mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_in", clip_a->producer, 0, NULL, NULL );
+               }
+               else
+               {
+                       mlt_producer_clear( clip_a->producer );
+                       mlt_playlist_remove( self, clip );
+               }
+
+               // Unblock and force a fire off of change events to listeners
+               mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( self ), self );
+               mlt_playlist_virtual_refresh( self );
+               mlt_tractor_close( tractor );
+       }
+       return error;
+}
+
+/** Mix consecutive clips for a specified length.
+ *
+ * This version of the mix function maintains the in point of the B clip by occupying the
+ * end of clip A before its current out point. Therefore, it ends up adjusting the out
+ * point and duration of clip A without affecting the duration or starting frame of clip B.
+ * Also, therefore, there must be enough frames before the in point of clip B.
+ * \public \memberof mlt_playlist_s
+ * \param self a playlist
+ * \param clip the index of the playlist entry
+ * \param length the number of frames over which to create the mix
+ * \return true if there was an error
+ */
+
+int mlt_playlist_mix_out( mlt_playlist self, int clip, int length )
+{
+       int error = ( clip < 0 || clip + 1 >= self->count );
+       if ( error == 0 )
+       {
+               playlist_entry *clip_a = self->list[ clip ];
+               playlist_entry *clip_b = self->list[ clip + 1 ];
+               mlt_producer track_a = NULL;
+               mlt_producer track_b = NULL;
+               mlt_tractor tractor = mlt_tractor_new( );
+
+               mlt_service_set_profile( MLT_TRACTOR_SERVICE( tractor ),
+                       mlt_service_profile( MLT_PLAYLIST_SERVICE( self ) ) );
+               mlt_properties_set_lcnumeric( MLT_TRACTOR_PROPERTIES( tractor ),
+                       mlt_properties_get_lcnumeric( MLT_PLAYLIST_PROPERTIES( self ) ) );
+               mlt_events_block( MLT_PLAYLIST_PROPERTIES( self ), self );
+
+               // Check length is valid for both clips and resize if necessary.
+               int max_size = clip_a->frame_count > clip_b->frame_in ? clip_a->frame_count : clip_b->frame_in;
+               length = length > max_size ? max_size : length;
+
+               // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches
+               if ( length != clip_a->frame_count )
+                       track_a = mlt_producer_cut( clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out );
+               else
+                       track_a = clip_a->producer;
+
+               if ( length != clip_b->frame_in )
+                       track_b = mlt_producer_cut( clip_b->producer, clip_b->frame_in - length + 1, clip_b->frame_in );
+               else
+                       track_b = clip_b->producer;
+
+               // Set the tracks on the tractor
+               mlt_tractor_set_track( tractor, track_a, 0 );
+               mlt_tractor_set_track( tractor, track_b, 1 );
+
+               // Insert the mix object into the playlist
+               mlt_playlist_insert( self, MLT_TRACTOR_PRODUCER( tractor ), clip + 1, -1, -1 );
+               mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mlt_mix", tractor, 0, NULL, NULL );
+
+               // Close our references to the tracks if we created new cuts above (the tracks can still be used here)
+               if ( track_a != clip_a->producer )
+                       mlt_producer_close( track_a );
+               if ( track_b != clip_b->producer )
+                       mlt_producer_close( track_b );
+
+               // Check if we have anything left on the right hand clip
+               if ( track_b == clip_b->producer )
+               {
+                       clip_b->preservation_hack = 1;
+                       mlt_playlist_remove( self, clip + 2 );
+               }
+               else if ( clip_b->frame_out - clip_b->frame_in > 0 )
+               {
+                       mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_b->producer ), "mix_in", tractor, 0, NULL, NULL );
+                       mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_out", clip_b->producer, 0, NULL, NULL );
+               }
+               else
+               {
+                       mlt_producer_clear( clip_b->producer );
+                       mlt_playlist_remove( self, clip + 2 );
+               }
+
+               // Check if we have anything left on the left hand clip
+               if ( track_a == clip_a->producer )
+               {
+                       clip_a->preservation_hack = 1;
+                       mlt_playlist_remove( self, clip );
+               }
+               else if ( clip_a->frame_out - clip_a->frame_in >= length )
+               {
+                       mlt_playlist_resize_clip( self, clip, clip_a->frame_in, clip_a->frame_out - length );
+                       mlt_properties_set_data( MLT_PRODUCER_PROPERTIES( clip_a->producer ), "mix_out", tractor, 0, NULL, NULL );
+                       mlt_properties_set_data( MLT_TRACTOR_PROPERTIES( tractor ), "mix_in", clip_a->producer, 0, NULL, NULL );
+               }
+               else
+               {
+                       mlt_producer_clear( clip_a->producer );
+                       mlt_playlist_remove( self, clip );
+               }
+
+               // Unblock and force a fire off of change events to listeners
+               mlt_events_unblock( MLT_PLAYLIST_PROPERTIES( self ), self );
+               mlt_playlist_virtual_refresh( self );
+               mlt_tractor_close( tractor );
+       }
+       return error;
+}
+
 /** Add a transition to an existing mix.
  *
  * \public \memberof mlt_playlist_s
@@ -1432,7 +1708,7 @@ void mlt_playlist_consolidate_blanks( mlt_playlist self, int keep_length )
  * \public \memberof mlt_playlist_s
  * \param self a playlist
  * \param clip the index of the playlist entry
- * \return true if there was an error
+ * \return true if \p clip is a "blank" producer
  */
 
 int mlt_playlist_is_blank( mlt_playlist self, int clip )
@@ -1488,16 +1764,16 @@ mlt_producer mlt_playlist_replace_with_blank( mlt_playlist self, int clip )
  * \public \memberof mlt_playlist_s
  * \param self a playlist
  * \param clip the index of the new blank section
- * \param length the ending time of the new blank section (duration - 1)
+ * \param out the ending time of the new blank section (duration - 1)
  */
 
-void mlt_playlist_insert_blank( mlt_playlist self, int clip, int length )
+void mlt_playlist_insert_blank( mlt_playlist self, int clip, int out )
 {
-       if ( self != NULL && length >= 0 )
+       if ( self != NULL && out >= 0 )
        {
                mlt_properties properties = MLT_PLAYLIST_PROPERTIES( self );
                mlt_events_block( properties, properties );
-               mlt_playlist_blank( self, length );
+               mlt_playlist_blank( self, out );
                mlt_playlist_move( self, self->count - 1, clip );
                mlt_events_unblock( properties, properties );
                mlt_playlist_virtual_refresh( self );