]> git.sesse.net Git - mlt/blob - src/framework/mlt_playlist.c
Checkpoint for current managed cuts (prototype on mix)
[mlt] / src / framework / mlt_playlist.c
1 /*
2  * mlt_playlist.c -- playlist service class
3  * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4  * Author: Charles Yates <charles.yates@pandora.be>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20
21 #include "config.h"
22
23 #include "mlt_playlist.h"
24 #include "mlt_tractor.h"
25 #include "mlt_multitrack.h"
26 #include "mlt_field.h"
27 #include "mlt_frame.h"
28 #include "mlt_transition.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 /** Virtual playlist entry.
35 */
36
37 typedef struct playlist_entry_s
38 {
39         mlt_producer producer;
40         mlt_position frame_in;
41         mlt_position frame_out;
42         mlt_position frame_count;
43         int repeat;
44         mlt_position producer_length;
45         mlt_event event;
46 }
47 playlist_entry;
48
49 /** Private definition.
50 */
51
52 struct mlt_playlist_s
53 {
54         struct mlt_producer_s parent;
55         struct mlt_producer_s blank;
56
57         int size;
58         int count;
59         playlist_entry **list;
60 };
61
62 /** Forward declarations
63 */
64
65 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index );
66 static int mlt_playlist_unmix( mlt_playlist this, int clip );
67 static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out );
68
69 /** Constructor.
70 */
71
72 mlt_playlist mlt_playlist_init( )
73 {
74         mlt_playlist this = calloc( sizeof( struct mlt_playlist_s ), 1 );
75         if ( this != NULL )
76         {
77                 mlt_producer producer = &this->parent;
78
79                 // Construct the producer
80                 mlt_producer_init( producer, this );
81
82                 // Override the producer get_frame
83                 producer->get_frame = producer_get_frame;
84
85                 // Define the destructor
86                 producer->close = ( mlt_destructor )mlt_playlist_close;
87                 producer->close_object = this;
88
89                 // Initialise blank
90                 mlt_producer_init( &this->blank, NULL );
91                 mlt_properties_set( mlt_producer_properties( &this->blank ), "mlt_service", "blank" );
92                 mlt_properties_set( mlt_producer_properties( &this->blank ), "resource", "blank" );
93
94                 // Indicate that this producer is a playlist
95                 mlt_properties_set_data( mlt_playlist_properties( this ), "playlist", this, 0, NULL, NULL );
96
97                 // Specify the eof condition
98                 mlt_properties_set( mlt_playlist_properties( this ), "eof", "pause" );
99                 mlt_properties_set( mlt_playlist_properties( this ), "resource", "<playlist>" );
100                 mlt_properties_set( mlt_playlist_properties( this ), "mlt_type", "mlt_producer" );
101                 mlt_properties_set_position( mlt_playlist_properties( this ), "in", 0 );
102                 mlt_properties_set_position( mlt_playlist_properties( this ), "out", 0 );
103                 mlt_properties_set_position( mlt_playlist_properties( this ), "length", 0 );
104
105                 this->size = 10;
106                 this->list = malloc( this->size * sizeof( playlist_entry * ) );
107         }
108         
109         return this;
110 }
111
112 /** Get the producer associated to this playlist.
113 */
114
115 mlt_producer mlt_playlist_producer( mlt_playlist this )
116 {
117         return this != NULL ? &this->parent : NULL;
118 }
119
120 /** Get the service associated to this playlist.
121 */
122
123 mlt_service mlt_playlist_service( mlt_playlist this )
124 {
125         return mlt_producer_service( &this->parent );
126 }
127
128 /** Get the propertues associated to this playlist.
129 */
130
131 mlt_properties mlt_playlist_properties( mlt_playlist this )
132 {
133         return mlt_producer_properties( &this->parent );
134 }
135
136 /** Refresh the playlist after a clip has been changed.
137 */
138
139 static int mlt_playlist_virtual_refresh( mlt_playlist this )
140 {
141         // Obtain the properties
142         mlt_properties properties = mlt_playlist_properties( this );
143
144         // Get the fps of the first producer
145         double fps = mlt_properties_get_double( properties, "first_fps" );
146         int i = 0;
147         mlt_position frame_count = 0;
148
149         for ( i = 0; i < this->count; i ++ )
150         {
151                 // Get the producer
152                 mlt_producer producer = this->list[ i ]->producer;
153                 int current_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1;
154
155                 // If fps is 0
156                 if ( fps == 0 )
157                 {
158                         // Inherit it from the producer
159                         fps = mlt_producer_get_fps( producer );
160                 }
161                 else if ( fps != mlt_properties_get_double( mlt_producer_properties( producer ), "fps" ) )
162                 {
163                         // Generate a warning for now - the following attempt to fix may fail
164                         fprintf( stderr, "Warning: fps mismatch on playlist producer %d\n", this->count );
165
166                         // It should be safe to impose fps on an image producer, but not necessarily safe for video
167                         mlt_properties_set_double( mlt_producer_properties( producer ), "fps", fps );
168                 }
169
170                 // Check if the length of the producer has changed
171                 if ( this->list[ i ]->producer != &this->blank &&
172                          ( this->list[ i ]->frame_in != mlt_producer_get_in( producer ) ||
173                            this->list[ i ]->frame_out != mlt_producer_get_out( producer ) ) )
174                 {
175                         // This clip should be removed...
176                         if ( current_length < 1 )
177                         {
178                                 this->list[ i ]->frame_in = 0;
179                                 this->list[ i ]->frame_out = -1;
180                                 this->list[ i ]->frame_count = 0;
181                         }
182                         else 
183                         {
184                                 this->list[ i ]->frame_in = mlt_producer_get_in( producer );
185                                 this->list[ i ]->frame_out = mlt_producer_get_out( producer );
186                                 this->list[ i ]->frame_count = current_length;
187                         }
188
189                         // Update the producer_length
190                         this->list[ i ]->producer_length = current_length;
191                 }
192
193                 // Calculate the frame_count
194                 this->list[ i ]->frame_count = ( this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1 ) * this->list[ i ]->repeat;
195
196                 // Update the frame_count for this clip
197                 frame_count += this->list[ i ]->frame_count;
198         }
199
200         // Refresh all properties
201         mlt_properties_set_double( properties, "first_fps", fps );
202         mlt_properties_set_double( properties, "fps", fps == 0 ? 25 : fps );
203         mlt_events_block( properties, properties );
204         mlt_properties_set_position( properties, "length", frame_count );
205         mlt_events_unblock( properties, properties );
206         mlt_properties_set_position( properties, "out", frame_count - 1 );
207
208         return 0;
209 }
210
211 /** Listener for producers on the playlist.
212 */
213
214 static void mlt_playlist_listener( mlt_producer producer, mlt_playlist this )
215 {
216         mlt_playlist_virtual_refresh( this );
217 }
218
219 /** Append to the virtual playlist.
220 */
221
222 static int mlt_playlist_virtual_append( mlt_playlist this, mlt_producer source, mlt_position in, mlt_position out )
223 {
224         char *resource = source != NULL ? mlt_properties_get( mlt_producer_properties( source ), "resource" ) : NULL;
225         mlt_producer producer = NULL;
226         mlt_properties properties = NULL;
227         mlt_properties parent = NULL;
228
229         // If we have a cut, then use the in/out points from the cut
230         if ( resource == NULL || !strcmp( resource, "blank" ) )
231         {
232                 producer = &this->blank;
233                 properties = mlt_producer_properties( producer );
234                 mlt_properties_inc_ref( properties );
235         }
236         else if ( mlt_producer_is_cut( source ) )
237         {
238                 producer = source;
239                 if ( in == -1 )
240                         in = mlt_producer_get_in( producer );
241                 if ( out == -1 || out > mlt_producer_get_out( producer ) )
242                         out = mlt_producer_get_out( producer );
243                 properties = mlt_producer_properties( producer );
244                 mlt_properties_inc_ref( properties );
245         }
246         else
247         {
248                 producer = mlt_producer_cut( source, in, out );
249                 if ( in == -1 || in < mlt_producer_get_in( producer ) )
250                         in = mlt_producer_get_in( producer );
251                 if ( out == -1 || out > mlt_producer_get_out( producer ) )
252                         out = mlt_producer_get_out( producer );
253                 properties = mlt_producer_properties( producer );
254         }
255
256         // Fetch the cuts parent properties
257         parent = mlt_producer_properties( mlt_producer_cut_parent( producer ) );
258
259         // Check that we have room
260         if ( this->count >= this->size )
261         {
262                 int i;
263                 this->list = realloc( this->list, ( this->size + 10 ) * sizeof( playlist_entry * ) );
264                 for ( i = this->size; i < this->size + 10; i ++ ) this->list[ i ] = NULL;
265                 this->size += 10;
266         }
267
268         // Create the entry
269         this->list[ this->count ] = calloc( sizeof( playlist_entry ), 1 );
270         if ( this->list[ this->count ] != NULL )
271         {
272                 this->list[ this->count ]->producer = producer;
273                 this->list[ this->count ]->frame_in = in;
274                 this->list[ this->count ]->frame_out = out;
275                 this->list[ this->count ]->frame_count = out - in + 1;
276                 this->list[ this->count ]->repeat = 1;
277                 this->list[ this->count ]->producer_length = mlt_producer_get_out( producer ) - mlt_producer_get_in( producer ) + 1;
278                 this->list[ this->count ]->event = mlt_events_listen( parent, this, "producer-changed", ( mlt_listener )mlt_playlist_listener );
279                 mlt_event_inc_ref( this->list[ this->count ]->event );
280                 mlt_properties_set( properties, "eof", "pause" );
281                 mlt_producer_set_speed( producer, 0 );
282                 this->count ++;
283         }
284
285         return mlt_playlist_virtual_refresh( this );
286 }
287
288 /** Seek in the virtual playlist.
289 */
290
291 static mlt_service mlt_playlist_virtual_seek( mlt_playlist this )
292 {
293         // Default producer to blank
294         mlt_producer producer = NULL;
295
296         // Map playlist position to real producer in virtual playlist
297         mlt_position position = mlt_producer_frame( &this->parent );
298
299         mlt_position original = position;
300
301         // Total number of frames
302         int64_t total = 0;
303
304         // Get the properties
305         mlt_properties properties = mlt_playlist_properties( this );
306
307         // Get the eof handling
308         char *eof = mlt_properties_get( properties, "eof" );
309
310         // Index for the main loop
311         int i = 0;
312
313         // Loop for each producer until found
314         for ( i = 0; i < this->count; i ++ )
315         {
316                 // Increment the total
317                 total += this->list[ i ]->frame_count;
318
319                 // Check if the position indicates that we have found the clip
320                 // Note that 0 length clips get skipped automatically
321                 if ( position < this->list[ i ]->frame_count )
322                 {
323                         // Found it, now break
324                         producer = this->list[ i ]->producer;
325                         break;
326                 }
327                 else
328                 {
329                         // Decrement position by length of this entry
330                         position -= this->list[ i ]->frame_count;
331                 }
332         }
333
334         // Seek in real producer to relative position
335         if ( producer != NULL )
336         {
337                 int count = this->list[ i ]->frame_count / this->list[ i ]->repeat;
338                 mlt_producer_seek( producer, position % count );
339         }
340         else if ( !strcmp( eof, "pause" ) && total > 0 )
341         {
342                 playlist_entry *entry = this->list[ this->count - 1 ];
343                 mlt_producer this_producer = mlt_playlist_producer( this );
344                 mlt_producer_seek( this_producer, original - 1 );
345                 producer = entry->producer;
346                 mlt_producer_seek( producer, entry->frame_out );
347                 mlt_producer_set_speed( this_producer, 0 );
348                 mlt_producer_set_speed( producer, 0 );
349         }
350         else if ( !strcmp( eof, "loop" ) && total > 0 )
351         {
352                 playlist_entry *entry = this->list[ 0 ];
353                 mlt_producer this_producer = mlt_playlist_producer( this );
354                 mlt_producer_seek( this_producer, 0 );
355                 producer = entry->producer;
356                 mlt_producer_seek( producer, 0 );
357         }
358         else
359         {
360                 producer = &this->blank;
361         }
362
363         return mlt_producer_service( producer );
364 }
365
366 /** Invoked when a producer indicates that it has prematurely reached its end.
367 */
368
369 static mlt_producer mlt_playlist_virtual_set_out( mlt_playlist this )
370 {
371         // Default producer to blank
372         mlt_producer producer = &this->blank;
373
374         // Map playlist position to real producer in virtual playlist
375         mlt_position position = mlt_producer_frame( &this->parent );
376
377         // Loop through the virtual playlist
378         int i = 0;
379
380         for ( i = 0; i < this->count; i ++ )
381         {
382                 if ( position < this->list[ i ]->frame_count )
383                 {
384                         // Found it, now break
385                         producer = this->list[ i ]->producer;
386                         break;
387                 }
388                 else
389                 {
390                         // Decrement position by length of this entry
391                         position -= this->list[ i ]->frame_count;
392                 }
393         }
394
395         // Seek in real producer to relative position
396         if ( i < this->count && this->list[ i ]->frame_out != position )
397         {
398                 // Update the frame_count for the changed clip (hmmm)
399                 this->list[ i ]->frame_out = position;
400                 this->list[ i ]->frame_count = this->list[ i ]->frame_out - this->list[ i ]->frame_in + 1;
401
402                 // Refresh the playlist
403                 mlt_playlist_virtual_refresh( this );
404         }
405
406         return producer;
407 }
408
409 /** Obtain the current clips index.
410 */
411
412 int mlt_playlist_current_clip( mlt_playlist this )
413 {
414         // Map playlist position to real producer in virtual playlist
415         mlt_position position = mlt_producer_frame( &this->parent );
416
417         // Loop through the virtual playlist
418         int i = 0;
419
420         for ( i = 0; i < this->count; i ++ )
421         {
422                 if ( position < this->list[ i ]->frame_count )
423                 {
424                         // Found it, now break
425                         break;
426                 }
427                 else
428                 {
429                         // Decrement position by length of this entry
430                         position -= this->list[ i ]->frame_count;
431                 }
432         }
433
434         return i;
435 }
436
437 /** Obtain the current clips producer.
438 */
439
440 mlt_producer mlt_playlist_current( mlt_playlist this )
441 {
442         int i = mlt_playlist_current_clip( this );
443         if ( i < this->count )
444                 return this->list[ i ]->producer;
445         else
446                 return &this->blank;
447 }
448
449 /** Get the position which corresponds to the start of the next clip.
450 */
451
452 mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index )
453 {
454         mlt_position position = 0;
455         int absolute_clip = index;
456         int i = 0;
457
458         // Determine the absolute clip
459         switch ( whence )
460         {
461                 case mlt_whence_relative_start:
462                         absolute_clip = index;
463                         break;
464
465                 case mlt_whence_relative_current:
466                         absolute_clip = mlt_playlist_current_clip( this ) + index;
467                         break;
468
469                 case mlt_whence_relative_end:
470                         absolute_clip = this->count - index;
471                         break;
472         }
473
474         // Check that we're in a valid range
475         if ( absolute_clip < 0 )
476                 absolute_clip = 0;
477         else if ( absolute_clip > this->count )
478                 absolute_clip = this->count;
479
480         // Now determine the position
481         for ( i = 0; i < absolute_clip; i ++ )
482                 position += this->list[ i ]->frame_count;
483
484         return position;
485 }
486
487 /** Get all the info about the clip specified.
488 */
489
490 int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index )
491 {
492         int error = index < 0 || index >= this->count;
493         memset( info, 0, sizeof( mlt_playlist_clip_info ) );
494         if ( !error )
495         {
496                 mlt_producer producer = mlt_producer_cut_parent( this->list[ index ]->producer );
497                 mlt_properties properties = mlt_producer_properties( producer );
498                 info->clip = index;
499                 info->producer = producer;
500                 info->cut = this->list[ index ]->producer;
501                 info->start = mlt_playlist_clip( this, mlt_whence_relative_start, index );
502                 info->resource = mlt_properties_get( properties, "resource" );
503                 info->frame_in = this->list[ index ]->frame_in;
504                 info->frame_out = this->list[ index ]->frame_out;
505                 info->frame_count = this->list[ index ]->frame_count;
506                 info->repeat = this->list[ index ]->repeat;
507                 info->length = mlt_producer_get_length( producer );
508                 info->fps = mlt_producer_get_fps( producer );
509         }
510
511         return error;
512 }
513
514 /** Get number of clips in the playlist.
515 */
516
517 int mlt_playlist_count( mlt_playlist this )
518 {
519         return this->count;
520 }
521
522 /** Clear the playlist.
523 */
524
525 int mlt_playlist_clear( mlt_playlist this )
526 {
527         int i;
528         for ( i = 0; i < this->count; i ++ )
529         {
530                 mlt_event_close( this->list[ i ]->event );
531                 mlt_producer_close( this->list[ i ]->producer );
532         }
533         this->count = 0;
534         mlt_properties_set_double( mlt_playlist_properties( this ), "first_fps", 0 );
535         return mlt_playlist_virtual_refresh( this );
536 }
537
538 /** Append a producer to the playlist.
539 */
540
541 int mlt_playlist_append( mlt_playlist this, mlt_producer producer )
542 {
543         // Append to virtual list
544         return mlt_playlist_virtual_append( this, producer, 0, mlt_producer_get_playtime( producer ) - 1 );
545 }
546
547 /** Append a producer to the playlist with in/out points.
548 */
549
550 int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out )
551 {
552         // Append to virtual list
553         if ( in != -1 && out != -1 )
554                 return mlt_playlist_virtual_append( this, producer, in, out );
555         else
556                 return mlt_playlist_append( this, producer );
557 }
558
559 /** Append a blank to the playlist of a given length.
560 */
561
562 int mlt_playlist_blank( mlt_playlist this, mlt_position length )
563 {
564         // Append to the virtual list
565         return mlt_playlist_virtual_append( this, &this->blank, 0, length );
566 }
567
568 /** Insert a producer into the playlist.
569 */
570
571 int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out )
572 {
573         // Append to end
574         mlt_events_block( mlt_playlist_properties( this ), this );
575         mlt_playlist_append_io( this, producer, in, out );
576
577         // Move to the position specified
578         mlt_playlist_move( this, this->count - 1, where );
579         mlt_events_unblock( mlt_playlist_properties( this ), this );
580
581         return mlt_playlist_virtual_refresh( this );
582 }
583
584 /** Remove an entry in the playlist.
585 */
586
587 int mlt_playlist_remove( mlt_playlist this, int where )
588 {
589         int error = where < 0 || where >= this->count;
590         if ( error == 0 && mlt_playlist_unmix( this, where ) != 0 )
591         {
592                 // We need to know the current clip and the position within the playlist
593                 int current = mlt_playlist_current_clip( this );
594                 mlt_position position = mlt_producer_position( mlt_playlist_producer( this ) );
595
596                 // We need all the details about the clip we're removing
597                 mlt_playlist_clip_info where_info;
598                 playlist_entry *entry = this->list[ where ];
599                 mlt_properties properties = mlt_producer_properties( entry->producer );
600
601                 // Loop variable
602                 int i = 0;
603
604                 // Get the clip info 
605                 mlt_playlist_get_clip_info( this, &where_info, where );
606
607                 // Make sure the clip to be removed is valid and correct if necessary
608                 if ( where < 0 ) 
609                         where = 0;
610                 if ( where >= this->count )
611                         where = this->count - 1;
612
613                 // Reorganise the list
614                 for ( i = where + 1; i < this->count; i ++ )
615                         this->list[ i - 1 ] = this->list[ i ];
616                 this->count --;
617
618                 // Decouple from mix_in/out if necessary
619                 if ( mlt_properties_get_data( properties, "mix_in", NULL ) != NULL )
620                 {
621                         mlt_properties mix = mlt_properties_get_data( properties, "mix_in", NULL );
622                         mlt_properties_set_data( mix, "mix_out", NULL, 0, NULL, NULL );
623                 }
624                 if ( mlt_properties_get_data( properties, "mix_out", NULL ) != NULL )
625                 {
626                         mlt_properties mix = mlt_properties_get_data( properties, "mix_out", NULL );
627                         mlt_properties_set_data( mix, "mix_in", NULL, 0, NULL, NULL );
628                 }
629
630                 // Close the producer associated to the clip info
631                 mlt_event_close( entry->event );
632                 mlt_producer_clear( entry->producer );
633                 mlt_producer_close( entry->producer );
634
635                 // Correct position
636                 if ( where == current )
637                         mlt_producer_seek( mlt_playlist_producer( this ), where_info.start );
638                 else if ( where < current && this->count > 0 )
639                         mlt_producer_seek( mlt_playlist_producer( this ), position - where_info.frame_count );
640                 else if ( this->count == 0 )
641                         mlt_producer_seek( mlt_playlist_producer( this ), 0 );
642
643                 // Free the entry
644                 free( entry );
645
646                 // Refresh the playlist
647                 mlt_playlist_virtual_refresh( this );
648         }
649
650         return error;
651 }
652
653 /** Move an entry in the playlist.
654 */
655
656 int mlt_playlist_move( mlt_playlist this, int src, int dest )
657 {
658         int i;
659
660         /* We need to ensure that the requested indexes are valid and correct it as necessary */
661         if ( src < 0 ) 
662                 src = 0;
663         if ( src >= this->count )
664                 src = this->count - 1;
665
666         if ( dest < 0 ) 
667                 dest = 0;
668         if ( dest >= this->count )
669                 dest = this->count - 1;
670         
671         if ( src != dest && this->count > 1 )
672         {
673                 int current = mlt_playlist_current_clip( this );
674                 mlt_position position = mlt_producer_position( mlt_playlist_producer( this ) );
675                 playlist_entry *src_entry = NULL;
676
677                 // We need all the details about the current clip
678                 mlt_playlist_clip_info current_info;
679
680                 mlt_playlist_get_clip_info( this, &current_info, current );
681                 position -= current_info.start;
682
683                 if ( current == src )
684                         current = dest;
685                 else if ( current > src && current < dest )
686                         current ++;
687                 else if ( current == dest )
688                         current = src;
689
690                 src_entry = this->list[ src ];
691                 if ( src > dest )
692                 {
693                         for ( i = src; i > dest; i -- )
694                                 this->list[ i ] = this->list[ i - 1 ];
695                 }
696                 else
697                 {
698                         for ( i = src; i < dest; i ++ )
699                                 this->list[ i ] = this->list[ i + 1 ];
700                 }
701                 this->list[ dest ] = src_entry;
702
703                 mlt_playlist_get_clip_info( this, &current_info, current );
704                 mlt_producer_seek( mlt_playlist_producer( this ), current_info.start + position );
705                 mlt_playlist_virtual_refresh( this );
706         }
707
708         return 0;
709 }
710
711 /** Repeat the specified clip n times.
712 */
713
714 int mlt_playlist_repeat_clip( mlt_playlist this, int clip, int repeat )
715 {
716         int error = repeat < 1 || clip < 0 || clip >= this->count;
717         if ( error == 0 )
718         {
719                 playlist_entry *entry = this->list[ clip ];
720                 entry->repeat = repeat;
721                 mlt_playlist_virtual_refresh( this );
722         }
723         return error;
724 }
725
726 /** Resize the specified clip.
727 */
728
729 int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out )
730 {
731         int error = clip < 0 || clip >= this->count;
732         if ( error == 0 && mlt_playlist_resize_mix( this, clip, in, out ) != 0 )
733         {
734                 playlist_entry *entry = this->list[ clip ];
735                 mlt_producer producer = entry->producer;
736
737                 if ( in <= -1 )
738                         in = 0;
739                 if ( out <= -1 || out >= mlt_producer_get_length( producer ) )
740                         out = mlt_producer_get_length( producer ) - 1;
741
742                 if ( out < in )
743                 {
744                         mlt_position t = in;
745                         in = out;
746                         out = t;
747                 }
748
749                 mlt_producer_set_in_and_out( producer, in, out );
750         }
751         return error;
752 }
753
754 /** Split a clip on the playlist at the given position.
755 */
756
757 int mlt_playlist_split( mlt_playlist this, int clip, mlt_position position )
758 {
759         int error = clip < 0 || clip >= this->count;
760         if ( error == 0 )
761         {
762                 playlist_entry *entry = this->list[ clip ];
763                 if ( position >= 0 && position < entry->frame_count )
764                 {
765                         mlt_producer split = NULL;
766                         int in = entry->frame_in;
767                         int out = entry->frame_out;
768                         mlt_events_block( mlt_playlist_properties( this ), this );
769                         mlt_playlist_resize_clip( this, clip, in, in + position );
770                         split = mlt_producer_cut( entry->producer, in + position + 1, out );
771                         mlt_playlist_insert( this, split, clip + 1, 0, -1 );
772                         mlt_producer_close( split );
773                         mlt_events_unblock( mlt_playlist_properties( this ), this );
774                         mlt_events_fire( mlt_playlist_properties( this ), "producer-changed", NULL );
775                 }
776                 else
777                 {
778                         error = 1;
779                 }
780         }
781         return error;
782 }
783
784 /** Join 1 or more consecutive clips.
785 */
786
787 int mlt_playlist_join( mlt_playlist this, int clip, int count, int merge )
788 {
789         int error = clip < 0 || ( clip ) >= this->count;
790         if ( error == 0 )
791         {
792                 int i = clip;
793                 mlt_playlist new_clip = mlt_playlist_init( );
794                 mlt_events_block( mlt_playlist_properties( this ), this );
795                 if ( clip + count >= this->count )
796                         count = this->count - clip;
797                 for ( i = 0; i <= count; i ++ )
798                 {
799                         playlist_entry *entry = this->list[ clip ];
800                         mlt_playlist_append( new_clip, entry->producer );
801                         mlt_playlist_repeat_clip( new_clip, i, entry->repeat );
802                         mlt_playlist_remove( this, clip );
803                 }
804                 mlt_events_unblock( mlt_playlist_properties( this ), this );
805                 mlt_playlist_insert( this, mlt_playlist_producer( new_clip ), clip, 0, -1 );
806                 mlt_playlist_close( new_clip );
807         }
808         return error;
809 }
810
811 /** Mix consecutive clips for a specified length and apply transition if specified.
812 */
813
814 int mlt_playlist_mix( mlt_playlist this, int clip, int length, mlt_transition transition )
815 {
816         int error = ( clip < 0 || clip + 1 >= this->count );
817         if ( error == 0 )
818         {
819                 playlist_entry *clip_a = this->list[ clip ];
820                 playlist_entry *clip_b = this->list[ clip + 1 ];
821                 mlt_producer track_a;
822                 mlt_producer track_b;
823                 mlt_tractor tractor = mlt_tractor_new( );
824                 mlt_events_block( mlt_playlist_properties( this ), this );
825
826                 // TODO: Check length is valid for both clips and resize if necessary.
827
828                 // Create the a and b tracks/cuts
829                 track_a = mlt_producer_cut( clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out );
830                 track_b = mlt_producer_cut( clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1 );
831
832                 // Temporary - for the benefit of westley serialisation
833                 mlt_properties_set_int( mlt_producer_properties( track_a ), "cut", 1 );
834                 mlt_properties_set_int( mlt_producer_properties( track_b ), "cut", 1 );
835
836                 // Set the tracks on the tractor
837                 mlt_tractor_set_track( tractor, track_a, 0 );
838                 mlt_tractor_set_track( tractor, track_b, 1 );
839
840                 // Insert the mix object into the playlist
841                 mlt_playlist_insert( this, mlt_tractor_producer( tractor ), clip + 1, -1, -1 );
842                 mlt_properties_set_data( mlt_tractor_properties( tractor ), "mlt_mix", tractor, 0, NULL, NULL );
843
844                 // Attach the transition
845                 if ( transition != NULL )
846                 {
847                         mlt_field field = mlt_tractor_field( tractor );
848                         mlt_field_plant_transition( field, transition, 0, 1 );
849                         mlt_transition_set_in_and_out( transition, 0, length - 1 );
850                 }
851
852                 // Check if we have anything left on the right hand clip
853                 if ( clip_b->frame_out - clip_b->frame_in > length )
854                 {
855                         mlt_playlist_resize_clip( this, clip + 2, clip_b->frame_in + length, clip_b->frame_out );
856                         mlt_properties_set_data( mlt_producer_properties( clip_b->producer ), "mix_in", tractor, 0, NULL, NULL );
857                         mlt_properties_set_data( mlt_tractor_properties( tractor ), "mix_out", clip_b->producer, 0, NULL, NULL );
858                 }
859                 else
860                 {
861                         mlt_producer_clear( clip_b->producer );
862                         mlt_playlist_remove( this, clip + 2 );
863                 }
864
865                 // Check if we have anything left on the left hand clip
866                 if ( clip_a->frame_out - clip_a->frame_in > length )
867                 {
868                         mlt_playlist_resize_clip( this, clip, clip_a->frame_in, clip_a->frame_out - length );
869                         mlt_properties_set_data( mlt_producer_properties( clip_a->producer ), "mix_out", tractor, 0, NULL, NULL );
870                         mlt_properties_set_data( mlt_tractor_properties( tractor ), "mix_in", clip_a->producer, 0, NULL, NULL );
871                 }
872                 else
873                 {
874                         mlt_producer_clear( clip_a->producer );
875                         mlt_playlist_remove( this, clip );
876                 }
877
878                 mlt_events_unblock( mlt_playlist_properties( this ), this );
879                 mlt_events_fire( mlt_playlist_properties( this ), "producer-changed", NULL );
880                 mlt_producer_close( track_a );
881                 mlt_producer_close( track_b );
882                 mlt_tractor_close( tractor );
883         }
884         return error;
885 }
886
887 /** Remove a mixed clip - ensure that the cuts included in the mix find their way
888         back correctly on to the playlist.
889 */
890
891 static int mlt_playlist_unmix( mlt_playlist this, int clip )
892 {
893         int error = ( clip < 0 || clip >= this->count ); 
894
895         // Ensure that the clip request is actually a mix
896         if ( error == 0 )
897         {
898                 mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer );
899                 mlt_properties properties = mlt_producer_properties( producer );
900                 error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL;
901         }
902
903         if ( error == 0 )
904         {
905                 playlist_entry *mix = this->list[ clip ];
906                 mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer );
907                 mlt_properties properties = mlt_tractor_properties( tractor );
908                 mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL );
909                 mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL );
910                 int length = mlt_producer_get_playtime( mlt_tractor_producer( tractor ) );
911                 mlt_events_block( mlt_playlist_properties( this ), this );
912
913                 if ( clip_a != NULL )
914                 {
915                         mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) + length );
916                 }
917                 else
918                 {
919                         mlt_producer cut = mlt_tractor_get_track( tractor, 0 );
920                         mlt_playlist_insert( this, cut, clip, -1, -1 );
921                         clip ++;
922                 }
923
924                 if ( clip_b != NULL )
925                 {
926                         mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) - length, mlt_producer_get_out( clip_b ) );
927                 }
928                 else
929                 {
930                         mlt_producer cut = mlt_tractor_get_track( tractor, 1 );
931                         mlt_playlist_insert( this, cut, clip + 1, -1, -1 );
932                 }
933
934                 mlt_properties_set_data( properties, "mlt_mix", NULL, 0, NULL, NULL );
935                 mlt_playlist_remove( this, clip );
936                 mlt_events_unblock( mlt_playlist_properties( this ), this );
937                 mlt_events_fire( mlt_playlist_properties( this ), "producer-changed", NULL );
938         }
939         return error;
940 }
941
942 static int mlt_playlist_resize_mix( mlt_playlist this, int clip, int in, int out )
943 {
944         int error = ( clip < 0 || clip >= this->count ); 
945
946         // Ensure that the clip request is actually a mix
947         if ( error == 0 )
948         {
949                 mlt_producer producer = mlt_producer_cut_parent( this->list[ clip ]->producer );
950                 mlt_properties properties = mlt_producer_properties( producer );
951                 error = mlt_properties_get_data( properties, "mlt_mix", NULL ) == NULL;
952         }
953
954         if ( error == 0 )
955         {
956                 playlist_entry *mix = this->list[ clip ];
957                 mlt_tractor tractor = ( mlt_tractor )mlt_producer_cut_parent( mix->producer );
958                 mlt_properties properties = mlt_tractor_properties( tractor );
959                 mlt_producer clip_a = mlt_properties_get_data( properties, "mix_in", NULL );
960                 mlt_producer clip_b = mlt_properties_get_data( properties, "mix_out", NULL );
961                 mlt_producer track_a = mlt_tractor_get_track( tractor, 0 );
962                 mlt_producer track_b = mlt_tractor_get_track( tractor, 1 );
963                 int length = out - in + 1;
964                 int length_diff = length - mlt_producer_get_playtime( mlt_tractor_producer( tractor ) );
965                 mlt_events_block( mlt_playlist_properties( this ), this );
966
967                 if ( clip_a != NULL )
968                         mlt_producer_set_in_and_out( clip_a, mlt_producer_get_in( clip_a ), mlt_producer_get_out( clip_a ) - length_diff );
969
970                 if ( clip_b != NULL )
971                         mlt_producer_set_in_and_out( clip_b, mlt_producer_get_in( clip_b ) + length_diff, mlt_producer_get_out( clip_b ) );
972
973                 mlt_producer_set_in_and_out( track_a, mlt_producer_get_in( track_a ) - length_diff, mlt_producer_get_out( track_a ) );
974                 mlt_producer_set_in_and_out( track_b, mlt_producer_get_in( track_b ), mlt_producer_get_out( track_b ) + length_diff );
975                 mlt_producer_set_in_and_out( mlt_multitrack_producer( mlt_tractor_multitrack( tractor ) ), in, out );
976                 mlt_producer_set_in_and_out( mlt_tractor_producer( tractor ), in, out );
977                 mlt_properties_set_position( mlt_producer_properties( mix->producer ), "length", out - in + 1 );
978                 mlt_producer_set_in_and_out( mix->producer, in, out );
979
980                 mlt_events_unblock( mlt_playlist_properties( this ), this );
981                 mlt_playlist_virtual_refresh( this );
982         }
983         return error;
984 }
985
986 /** Get the current frame.
987 */
988
989 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
990 {
991         // Get this mlt_playlist
992         mlt_playlist this = producer->child;
993
994         // Get the real producer
995         mlt_service real = mlt_playlist_virtual_seek( this );
996
997         // Get the frame
998         mlt_service_get_frame( real, frame, index );
999
1000         // Check if we're at the end of the clip
1001         mlt_properties properties = mlt_frame_properties( *frame );
1002         if ( mlt_properties_get_int( properties, "end_of_clip" ) )
1003                 mlt_playlist_virtual_set_out( this );
1004
1005         // Check for notifier and call with appropriate argument
1006         mlt_properties playlist_properties = mlt_producer_properties( producer );
1007         void ( *notifier )( void * ) = mlt_properties_get_data( playlist_properties, "notifier", NULL );
1008         if ( notifier != NULL )
1009         {
1010                 void *argument = mlt_properties_get_data( playlist_properties, "notifier_arg", NULL );
1011                 notifier( argument );
1012         }
1013
1014         // Update position on the frame we're creating
1015         mlt_frame_set_position( *frame, mlt_producer_frame( producer ) );
1016
1017         // Position ourselves on the next frame
1018         mlt_producer_prepare_next( producer );
1019
1020         return 0;
1021 }
1022
1023 /** Close the playlist.
1024 */
1025
1026 void mlt_playlist_close( mlt_playlist this )
1027 {
1028         if ( this != NULL && mlt_properties_dec_ref( mlt_playlist_properties( this ) ) <= 0 )
1029         {
1030                 int i = 0;
1031                 this->parent.close = NULL;
1032                 for ( i = 0; i < this->count; i ++ )
1033                 {
1034                         mlt_event_close( this->list[ i ]->event );
1035                         mlt_producer_close( this->list[ i ]->producer );
1036                         free( this->list[ i ] );
1037                 }
1038                 mlt_producer_close( &this->blank );
1039                 mlt_producer_close( &this->parent );
1040                 free( this->list );
1041                 free( this );
1042         }
1043 }