]> git.sesse.net Git - mlt/blob - src/modules/westley/producer_westley.c
Major westley rewrite - allows attachable filters
[mlt] / src / modules / westley / producer_westley.c
1 /*
2  * producer_westley.c -- a libxml2 parser of mlt service networks
3  * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4  * Author: Dan Dennedy <dan@dennedy.org>
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 // TODO: destroy unreferenced producers (they are currently destroyed
22 //       when the returned producer is closed).
23
24 #include "producer_westley.h"
25 #include <framework/mlt.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <ctype.h>
30
31 #include <libxml/parser.h>
32 #include <libxml/parserInternals.h> // for xmlCreateFileParserCtxt
33 #include <libxml/tree.h>
34
35 #define STACK_SIZE 1000
36 #define BRANCH_SIG_LEN 4000
37
38 #undef DEBUG
39 #ifdef DEBUG
40 extern xmlDocPtr westley_make_doc( mlt_service service );
41 #endif
42
43 enum service_type
44 {
45         mlt_invalid_type,
46         mlt_unknown_type,
47         mlt_producer_type,
48         mlt_playlist_type,
49         mlt_entry_type,
50         mlt_tractor_type,
51         mlt_multitrack_type,
52         mlt_filter_type,
53         mlt_transition_type,
54         mlt_consumer_type,
55         mlt_field_type,
56         mlt_service_type,
57         mlt_dummy_filter_type,
58         mlt_dummy_transition_type,
59         mlt_dummy_producer_type,
60 };
61
62 struct deserialise_context_s
63 {
64         enum service_type stack_types[ STACK_SIZE ];
65         mlt_service stack_service[ STACK_SIZE ];
66         int stack_service_size;
67         mlt_properties producer_map;
68         mlt_properties destructors;
69         char *property;
70         int is_value;
71         xmlDocPtr value_doc;
72         xmlNodePtr stack_node[ STACK_SIZE ];
73         int stack_node_size;
74         xmlDocPtr entity_doc;
75         int entity_is_replace;
76         int depth;
77         int branch[ STACK_SIZE ];
78         const xmlChar *publicId;
79         const xmlChar *systemId;
80         mlt_properties params;
81 };
82 typedef struct deserialise_context_s *deserialise_context;
83
84 /** Convert the numerical current branch address to a dot-delimited string.
85 */
86 static char *serialise_branch( deserialise_context this, char *s )
87 {
88         int i;
89         
90         s[0] = 0;
91         for ( i = 0; i < this->depth; i++ )
92         {
93                 int len = strlen( s );
94                 snprintf( s + len, BRANCH_SIG_LEN - len, "%d.", this->branch[ i ] );
95         }
96         return s;
97 }
98
99 /** Push a service.
100 */
101
102 static int context_push_service( deserialise_context this, mlt_service that, enum service_type type )
103 {
104         int ret = this->stack_service_size >= STACK_SIZE - 1;
105         if ( ret == 0 )
106         {
107                 this->stack_service[ this->stack_service_size ] = that;
108                 this->stack_types[ this->stack_service_size++ ] = type;
109                 
110                 // Record the tree branch on which this service lives
111                 if ( that != NULL && mlt_properties_get( mlt_service_properties( that ), "_westley_branch" ) == NULL )
112                 {
113                         char s[ BRANCH_SIG_LEN ];
114                         mlt_properties_set( mlt_service_properties( that ), "_westley_branch", serialise_branch( this, s ) );
115                 }
116         }
117         return ret;
118 }
119
120 /** Pop a service.
121 */
122
123 static mlt_service context_pop_service( deserialise_context this, enum service_type *type )
124 {
125         mlt_service result = NULL;
126         if ( this->stack_service_size > 0 )
127         {
128                 result = this->stack_service[ -- this->stack_service_size ];
129                 if ( type != NULL )
130                         *type = this->stack_types[ this->stack_service_size ];
131         }
132         return result;
133 }
134
135 /** Push a node.
136 */
137
138 static int context_push_node( deserialise_context this, xmlNodePtr node )
139 {
140         int ret = this->stack_node_size >= STACK_SIZE - 1;
141         if ( ret == 0 )
142                 this->stack_node[ this->stack_node_size ++ ] = node;
143         return ret;
144 }
145
146 /** Pop a node.
147 */
148
149 static xmlNodePtr context_pop_node( deserialise_context this )
150 {
151         xmlNodePtr result = NULL;
152         if ( this->stack_node_size > 0 )
153                 result = this->stack_node[ -- this->stack_node_size ];
154         return result;
155 }
156
157
158 // Set the destructor on a new service
159 static void track_service( mlt_properties properties, void *service, mlt_destructor destructor )
160 {
161         int registered = mlt_properties_get_int( properties, "registered" );
162         char *key = mlt_properties_get( properties, "registered" );
163         mlt_properties_set_data( properties, key, service, 0, destructor, NULL );
164         mlt_properties_set_int( properties, "registered", ++ registered );
165 }
166
167
168 // Prepend the property value with the document root
169 static inline void qualify_property( deserialise_context context, mlt_properties properties, char *name )
170 {
171         char *resource = mlt_properties_get( properties, name );
172         if ( resource != NULL )
173         {
174                 // Qualify file name properties 
175                 char *root = mlt_properties_get( context->producer_map, "_root" );
176                 if ( root != NULL )
177                 {
178                         char *full_resource = malloc( strlen( root ) + strlen( resource ) + 1 );
179                         if ( resource[ 0 ] != '/' )
180                         {
181                                 strcpy( full_resource, root );
182                                 strcat( full_resource, resource );
183                         }
184                         else
185                         {
186                                 strcpy( full_resource, resource );
187                         }
188                         mlt_properties_set( properties, name, full_resource );
189                         free( full_resource );
190                 }
191         }
192 }
193
194
195 /** This function adds a producer to a playlist or multitrack when
196     there is no entry or track element.
197 */
198
199 static int add_producer( deserialise_context context, mlt_service service, mlt_position in, mlt_position out )
200 {
201         // Return value (0 = service remains top of stack, 1 means it can be removed)
202         int result = 0;
203
204         // Get the parent producer
205         enum service_type type;
206         mlt_service container = context_pop_service( context, &type );
207         int contained = 0;
208
209         if ( service != NULL && container != NULL )
210         {
211                 char *container_branch = mlt_properties_get( mlt_service_properties( container ), "_westley_branch" );
212                 char *service_branch = mlt_properties_get( mlt_service_properties( service ), "_westley_branch" );
213                 contained = !strncmp( container_branch, service_branch, strlen( container_branch ) );
214         }
215
216         if ( contained )
217         {
218                 mlt_properties properties = mlt_service_properties( service );
219                 char *hide_s = mlt_properties_get( properties, "hide" );
220
221                 // Indicate that this service is no longer top of stack
222                 result = 1;
223
224                 switch( type )
225                 {
226                         case mlt_tractor_type: 
227                                 {
228                                         mlt_multitrack multitrack = mlt_tractor_multitrack( MLT_TRACTOR( container ) );
229                                         mlt_multitrack_connect( multitrack, MLT_PRODUCER( service ), mlt_multitrack_count( multitrack ) );
230                                 }
231                                 break;
232                         case mlt_multitrack_type:
233                                 {
234                                         mlt_multitrack_connect( MLT_MULTITRACK( container ),
235                                                 MLT_PRODUCER( service ),
236                                                 mlt_multitrack_count( MLT_MULTITRACK( container ) ) );
237                                 }
238                                 break;
239                         case mlt_playlist_type:
240                                 {
241                                         mlt_playlist_append_io( MLT_PLAYLIST( container ), MLT_PRODUCER( service ), in, out );
242                                 }
243                                 break;
244                         default:
245                                 result = 0;
246                                 fprintf( stderr, "Producer defined inside something that isn't a container\n" );
247                                 break;
248                 };
249
250                 // Set the hide state of the track producer
251                 if ( hide_s != NULL )
252                 {
253                         if ( strcmp( hide_s, "video" ) == 0 )
254                                 mlt_properties_set_int( properties, "hide", 1 );
255                         else if ( strcmp( hide_s, "audio" ) == 0 )
256                                 mlt_properties_set_int( properties, "hide", 2 );
257                         else if ( strcmp( hide_s, "both" ) == 0 )
258                                 mlt_properties_set_int( properties, "hide", 3 );
259                 }
260         }
261
262         // Put the parent producer back
263         if ( container != NULL )
264                 context_push_service( context, container, type );
265
266         return result;
267 }
268
269 /** Attach filters defined on that to this.
270 */
271
272 static void attach_filters( mlt_service this, mlt_service that )
273 {
274         if ( that != NULL )
275         {
276                 int i = 0;
277                 mlt_filter filter = NULL;
278                 for ( i = 0; ( filter = mlt_service_filter( that, i ) ) != NULL; i ++ )
279                 {
280                         mlt_service_attach( this, filter );
281                         attach_filters( mlt_filter_service( filter ), mlt_filter_service( filter ) );
282                 }
283         }
284 }
285
286 static void on_start_tractor( deserialise_context context, const xmlChar *name, const xmlChar **atts)
287 {
288         mlt_service service = mlt_tractor_service( mlt_tractor_new( ) );
289         mlt_properties properties = mlt_service_properties( service );
290
291         track_service( context->destructors, service, (mlt_destructor) mlt_tractor_close );
292
293         mlt_properties_set_position( properties, "length", 0 );
294
295         for ( ; atts != NULL && *atts != NULL; atts += 2 )
296                 mlt_properties_set( mlt_service_properties( service ), (char*) atts[0], (char*) atts[1] );
297
298         if ( mlt_properties_get_position( properties, "length" ) < mlt_properties_get_position( properties, "out" ) )
299         {
300                 mlt_position length = mlt_properties_get_position( properties, "out" ) + 1;
301                 mlt_properties_set_position( properties, "length", length );
302         }
303
304         if ( mlt_properties_get( properties, "id" ) != NULL )
305                 mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL );
306         
307         context_push_service( context, service, mlt_tractor_type );
308 }
309
310 static void on_end_tractor( deserialise_context context, const xmlChar *name )
311 {
312         // Get the tractor
313         enum service_type type;
314         mlt_service tractor = context_pop_service( context, &type );
315
316         if ( tractor != NULL && type == mlt_tractor_type )
317         {
318                 // See if the tractor should be added to a playlist or multitrack
319                 if ( add_producer( context, tractor, 0, mlt_producer_get_out( MLT_PRODUCER( tractor ) ) ) == 0 )
320                         context_push_service( context, tractor, type );
321         }
322 }
323
324 static void on_start_multitrack( deserialise_context context, const xmlChar *name, const xmlChar **atts)
325 {
326         enum service_type type;
327         mlt_service parent = context_pop_service( context, &type );
328
329         // If we don't have a parent, then create one now, providing we're in a state where we can
330         if ( parent == NULL || ( type == mlt_playlist_type || type == mlt_multitrack_type ) )
331         {
332                 // Push the parent back
333                 if ( parent != NULL )
334                         context_push_service( context, parent, type );
335
336                 // Create a tractor to contain the multitrack
337                 parent = mlt_tractor_service( mlt_tractor_new( ) );
338                 track_service( context->destructors, parent, (mlt_destructor) mlt_tractor_close );
339                 type = mlt_tractor_type;
340
341                 // Flag it as a synthesised tractor for clean up later
342                 mlt_properties_set_int( mlt_service_properties( parent ), "fezzik_synth", 1 );
343         }
344
345         if ( type == mlt_tractor_type )
346         {
347                 mlt_service service = MLT_SERVICE( mlt_tractor_multitrack( MLT_TRACTOR( parent ) ) );
348                 mlt_properties properties = mlt_service_properties( service );
349                 mlt_properties_set_position( properties, "length", 0 );
350                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
351                         mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] );
352
353                 if ( mlt_properties_get( properties, "id" ) != NULL )
354                         mlt_properties_set_data( context->producer_map, mlt_properties_get( properties,"id" ), service, 0, NULL, NULL );
355
356                 context_push_service( context, parent, type );
357                 context_push_service( context, service, mlt_multitrack_type );
358         }
359         else
360         {
361                 fprintf( stderr, "Invalid multitrack position\n" );
362         }
363 }
364
365 static void on_end_multitrack( deserialise_context context, const xmlChar *name )
366 {
367         // Get the multitrack from the stack
368         enum service_type type;
369         mlt_service service = context_pop_service( context, &type );
370
371         if ( service == NULL || type != mlt_multitrack_type )
372                 fprintf( stderr, "End multitrack in the wrong state...\n" );
373 }
374
375 static void on_start_playlist( deserialise_context context, const xmlChar *name, const xmlChar **atts)
376 {
377         mlt_service service = mlt_playlist_service( mlt_playlist_init() );
378         mlt_properties properties = mlt_service_properties( service );
379
380         track_service( context->destructors, service, (mlt_destructor) mlt_playlist_close );
381
382         mlt_properties_set_position( properties, "length", 0 );
383
384         for ( ; atts != NULL && *atts != NULL; atts += 2 )
385         {
386                 mlt_properties_set( properties, ( char* )atts[0], ( char* )atts[1] );
387
388                 // Out will be overwritten later as we append, so we need to save it
389                 if ( strcmp( atts[ 0 ], "out" ) == 0 )
390                         mlt_properties_set( properties, "_westley.out", ( char* )atts[ 1 ] );
391         }
392
393         if ( mlt_properties_get( properties, "id" ) != NULL )
394                 mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL );
395
396         context_push_service( context, service, mlt_playlist_type );
397 }
398
399 static void on_end_playlist( deserialise_context context, const xmlChar *name )
400 {
401         // Get the playlist from the stack
402         enum service_type type;
403         mlt_service service = context_pop_service( context, &type );
404
405         if ( service != NULL && type == mlt_playlist_type )
406         {
407                 mlt_properties properties = mlt_service_properties( service );
408                 mlt_position in = mlt_properties_get_position( properties, "in" );
409                 mlt_position out;
410
411                 if ( mlt_properties_get( properties, "_westley.out" ) != NULL )
412                         out = mlt_properties_get_position( properties, "_westley.out" );
413                 else
414                         out = mlt_properties_get_position( properties, "length" ) - 1;
415
416                 if ( mlt_properties_get_position( properties, "length" ) < out )
417                         mlt_properties_set_position( properties, "length", out  + 1 );
418
419                 mlt_producer_set_in_and_out( MLT_PRODUCER( service ), in, out );
420         
421                 // See if the playlist should be added to a playlist or multitrack
422                 if ( add_producer( context, service, in, out ) == 0 )
423                         context_push_service( context, service, type );
424         }
425         else
426         {
427                 fprintf( stderr, "Invalid state of playlist end\n" );
428         }
429 }
430
431 static void on_start_producer( deserialise_context context, const xmlChar *name, const xmlChar **atts)
432 {
433         // use a dummy service to hold properties to allow arbitrary nesting
434         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
435         mlt_service_init( service, NULL );
436
437         mlt_properties properties = mlt_service_properties( service );
438
439         context_push_service( context, service, mlt_dummy_producer_type );
440
441         for ( ; atts != NULL && *atts != NULL; atts += 2 )
442                 mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] );
443 }
444
445 static void on_end_producer( deserialise_context context, const xmlChar *name )
446 {
447         enum service_type type;
448         mlt_service service = context_pop_service( context, &type );
449         mlt_properties properties = mlt_service_properties( service );
450
451         if ( service != NULL && type == mlt_dummy_producer_type )
452         {
453                 mlt_service producer = NULL;
454
455                 qualify_property( context, properties, "resource" );
456                 char *resource = mlt_properties_get( properties, "resource" );
457
458                 // Let Kino-SMIL src be a synonym for resource
459                 if ( resource == NULL )
460                 {
461                         qualify_property( context, properties, "src" );
462                         resource = mlt_properties_get( properties, "src" );
463                 }
464
465                 // Instantiate the producer
466                 if ( mlt_properties_get( properties, "mlt_service" ) != NULL )
467                 {
468                         char temp[ 1024 ];
469                         strncpy( temp, mlt_properties_get( properties, "mlt_service" ), 1024 );
470                         if ( resource != NULL )
471                         {
472                                 strcat( temp, ":" );
473                                 strncat( temp, resource, 1023 - strlen( temp ) );
474                         }
475                         producer = MLT_SERVICE( mlt_factory_producer( "fezzik", temp ) );
476                 }
477
478                 // Just in case the plugin requested doesn't exist...
479                 if ( producer == NULL && resource != NULL )
480                         producer = MLT_SERVICE( mlt_factory_producer( "fezzik", resource ) );
481         
482                 // Track this producer
483                 track_service( context->destructors, producer, (mlt_destructor) mlt_producer_close );
484
485                 // Propogate the properties
486                 qualify_property( context, properties, "resource" );
487                 qualify_property( context, properties, "luma" );
488                 qualify_property( context, properties, "luma.resource" );
489                 qualify_property( context, properties, "composite.luma" );
490                 qualify_property( context, properties, "producer.resource" );
491
492                 // Handle in/out properties separately
493                 mlt_position in = -1;
494                 mlt_position out = -1;
495         
496                 // Get in
497                 if ( mlt_properties_get( properties, "in" ) != NULL )
498                         in = mlt_properties_get_position( properties, "in" );
499                 // Let Kino-SMIL clipBegin be a synonym for in
500                 if ( mlt_properties_get( properties, "clipBegin" ) != NULL )
501                         in = mlt_properties_get_position( properties, "clipBegin" );
502                 // Get out
503                 if ( mlt_properties_get( properties, "out" ) != NULL )
504                         out = mlt_properties_get_position( properties, "out" );
505                 // Let Kino-SMIL clipEnd be a synonym for out
506                 if ( mlt_properties_get( properties, "clipEnd" ) != NULL )
507                         out = mlt_properties_get_position( properties, "clipEnd" );
508         
509                 // Remove in and out
510                 mlt_properties_set( properties, "in", NULL );
511                 mlt_properties_set( properties, "out", NULL );
512
513                 // Inherit the properties
514                 mlt_properties_inherit( mlt_service_properties( producer ), properties );
515
516                 // Attach all filters from service onto producer
517                 attach_filters( producer, service );
518
519                 // Add the producer to the producer map
520                 if ( mlt_properties_get( properties, "id" ) != NULL )
521                         mlt_properties_set_data( context->producer_map, mlt_properties_get(properties, "id"), producer, 0, NULL, NULL );
522
523                 // See if the producer should be added to a playlist or multitrack
524                 if ( add_producer( context, producer, in, out ) == 0 )
525                 {
526                         // Otherwise, set in and out on...
527                         if ( in != -1 || out != -1 )
528                         {
529                                 // Get the parent service
530                                 enum service_type type;
531                                 mlt_service parent = context_pop_service( context, &type );
532                                 if ( parent != NULL )
533                                 {
534                                         // Get the parent properties
535                                         properties = mlt_service_properties( parent );
536                                 
537                                         char *resource = mlt_properties_get( properties, "resource" );
538                                 
539                                         // Put the parent producer back
540                                         context_push_service( context, parent, type );
541                                         
542                                         // If the parent is a track or entry
543                                         if ( resource && ( strcmp( resource, "<entry>" ) == 0 ) )
544                                         {
545                                                 mlt_properties_set_position( properties, "in", in );
546                                                 mlt_properties_set_position( properties, "out", out );
547                                         }
548                                         else
549                                         {
550                                                 // Otherwise, set in and out on producer directly
551                                                 mlt_producer_set_in_and_out( MLT_PRODUCER( service ), in, out );
552                                         }
553                                 }
554                                 else
555                                 {
556                                         // Otherwise, set in and out on producer directly
557                                         mlt_producer_set_in_and_out( MLT_PRODUCER( producer ), in, out );
558                                 }
559                         }
560         
561                         // Push the producer onto the stack
562                         context_push_service( context, producer, mlt_producer_type );
563                 }
564
565                 mlt_service_close( service );
566         }
567 }
568
569 static void on_start_blank( deserialise_context context, const xmlChar *name, const xmlChar **atts)
570 {
571         // Get the playlist from the stack
572         enum service_type type;
573         mlt_service service = context_pop_service( context, &type );
574         mlt_position length = 0;
575         
576         if ( type == mlt_playlist_type && service != NULL )
577         {
578                 // Look for the length attribute
579                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
580                 {
581                         if ( strcmp( atts[0], "length" ) == 0 )
582                         {
583                                 length = atoll( atts[1] );
584                                 break;
585                         }
586                 }
587
588                 // Append a blank to the playlist
589                 mlt_playlist_blank( MLT_PLAYLIST( service ), length - 1 );
590
591                 // Push the playlist back onto the stack
592                 context_push_service( context, service, type );
593         }
594         else
595         {
596                 fprintf( stderr, "blank without a playlist - a definite no no\n" );
597         }
598 }
599
600 static void on_start_entry_track( deserialise_context context, const xmlChar *name, const xmlChar **atts)
601 {
602         // use a dummy service to hold properties to allow arbitrary nesting
603         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
604         mlt_service_init( service, NULL );
605
606         // Push the dummy service onto the stack
607         context_push_service( context, service, mlt_entry_type );
608         
609         if ( strcmp( name, "entry" ) == 0 )
610                 mlt_properties_set( mlt_service_properties( service ), "resource", "<entry>" );
611         else
612                 mlt_properties_set( mlt_service_properties( service ), "resource", "<track>" );
613         
614         for ( ; atts != NULL && *atts != NULL; atts += 2 )
615         {
616                 mlt_properties_set( mlt_service_properties( service ), (char*) atts[0], (char*) atts[1] );
617                 
618                 // Look for the producer attribute
619                 if ( strcmp( atts[ 0 ], "producer" ) == 0 )
620                 {
621                         mlt_producer producer = mlt_properties_get_data( context->producer_map, (char*) atts[1], NULL );
622                         if ( producer !=  NULL )
623                                 mlt_properties_set_data( mlt_service_properties( service ), "producer", producer, 0, NULL, NULL );
624                 }
625         }
626 }
627
628 static void on_end_track( deserialise_context context, const xmlChar *name )
629 {
630         // Get the track from the stack
631         enum service_type track_type;
632         mlt_service track = context_pop_service( context, &track_type );
633
634         if ( track != NULL && track_type == mlt_entry_type )
635         {
636                 mlt_properties track_props = mlt_service_properties( track );
637                 enum service_type parent_type;
638                 mlt_service parent = context_pop_service( context, &parent_type );
639                 mlt_multitrack multitrack = NULL;
640
641                 mlt_producer producer = mlt_properties_get_data( track_props, "producer", NULL );
642                 mlt_properties producer_props = mlt_producer_properties( producer );
643
644                 if ( parent_type == mlt_tractor_type )
645                         multitrack = mlt_tractor_multitrack( MLT_TRACTOR( parent ) );
646                 else if ( parent_type == mlt_multitrack_type )
647                         multitrack = MLT_MULTITRACK( parent );
648                 else
649                         fprintf( stderr, "track contained in an invalid container\n" );
650
651                 if ( multitrack != NULL )
652                 {
653                         // Set the track on the multitrack
654                         mlt_multitrack_connect( multitrack, producer, mlt_multitrack_count( multitrack ) );
655
656                         // Set producer i/o if specified
657                         if ( mlt_properties_get( track_props, "in" ) != NULL ||
658                                 mlt_properties_get( track_props, "out" ) != NULL )
659                         {
660                                 mlt_producer_set_in_and_out( MLT_PRODUCER( producer ),
661                                         mlt_properties_get_position( track_props, "in" ),
662                                         mlt_properties_get_position( track_props, "out" ) );
663                         }
664         
665                         // Set the hide state of the track producer
666                         char *hide_s = mlt_properties_get( track_props, "hide" );
667                         if ( hide_s != NULL )
668                         {
669                                 if ( strcmp( hide_s, "video" ) == 0 )
670                                         mlt_properties_set_int( producer_props, "hide", 1 );
671                                 else if ( strcmp( hide_s, "audio" ) == 0 )
672                                         mlt_properties_set_int( producer_props, "hide", 2 );
673                                 else if ( strcmp( hide_s, "both" ) == 0 )
674                                         mlt_properties_set_int( producer_props, "hide", 3 );
675                         }
676                 }
677
678                 if ( parent != NULL )
679                         context_push_service( context, parent, parent_type );
680
681                 mlt_service_close( track );
682         }
683         else
684         {
685                 fprintf( stderr, "Invalid state at end of track\n" );
686         }
687 }
688
689 static void on_end_entry( deserialise_context context, const xmlChar *name )
690 {
691         // Get the entry from the stack
692         enum service_type entry_type;
693         mlt_service entry = context_pop_service( context, &entry_type );
694
695         if ( entry != NULL && entry_type == mlt_entry_type )
696         {
697                 mlt_properties entry_props = mlt_service_properties( entry );
698                 enum service_type parent_type;
699                 mlt_service parent = context_pop_service( context, &parent_type );
700                 mlt_producer producer = mlt_properties_get_data( entry_props, "producer", NULL );
701
702                 if ( parent_type == mlt_playlist_type )
703                 {
704                         // Append the producer to the playlist
705                         if ( mlt_properties_get( mlt_service_properties( entry ), "in" ) != NULL ||
706                                 mlt_properties_get( mlt_service_properties( entry ), "out" ) != NULL )
707                         {
708                                 mlt_playlist_append_io( MLT_PLAYLIST( parent ), producer,
709                                         mlt_properties_get_position( mlt_service_properties( entry ), "in" ), 
710                                         mlt_properties_get_position( mlt_service_properties( entry ), "out" ) );
711                         }
712                         else
713                         {
714                                 mlt_playlist_append( MLT_PLAYLIST( parent ), producer );
715                         }
716                 }
717                 else
718                 {
719                         fprintf( stderr, "Invalid position for an entry...\n" );
720                 }
721
722                 if ( parent != NULL )
723                         context_push_service( context, parent, parent_type );
724
725                 mlt_service_close( entry );
726         }
727         else
728         {
729                 fprintf( stderr, "Invalid state at end of entry\n" );
730         }
731 }
732
733 static void on_start_filter( deserialise_context context, const xmlChar *name, const xmlChar **atts)
734 {
735         // use a dummy service to hold properties to allow arbitrary nesting
736         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
737         mlt_service_init( service, NULL );
738
739         mlt_properties properties = mlt_service_properties( service );
740
741         context_push_service( context, service, mlt_dummy_filter_type );
742
743         // Set the properties
744         for ( ; atts != NULL && *atts != NULL; atts += 2 )
745                 mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] );
746 }
747
748 static void on_end_filter( deserialise_context context, const xmlChar *name )
749 {
750         enum service_type type;
751         mlt_service service = context_pop_service( context, &type );
752         mlt_properties properties = mlt_service_properties( service );
753
754         enum service_type parent_type;
755         mlt_service parent = context_pop_service( context, &parent_type );
756
757         if ( service != NULL && type == mlt_dummy_filter_type )
758         {
759                 mlt_service filter = MLT_SERVICE( mlt_factory_filter( mlt_properties_get( properties, "mlt_service" ), NULL ) );
760                 mlt_properties filter_props = mlt_service_properties( filter );
761
762                 track_service( context->destructors, filter, (mlt_destructor) mlt_filter_close );
763
764                 // Propogate the properties
765                 qualify_property( context, properties, "resource" );
766                 qualify_property( context, properties, "luma" );
767                 qualify_property( context, properties, "luma.resource" );
768                 qualify_property( context, properties, "composite.luma" );
769                 qualify_property( context, properties, "producer.resource" );
770                 mlt_properties_inherit( filter_props, properties );
771
772                 // Attach all filters from service onto filter
773                 attach_filters( filter, service );
774
775                 // Associate the filter with the parent
776                 if ( parent != NULL )
777                 {
778                         if ( parent_type == mlt_tractor_type )
779                         {
780                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
781                                 mlt_field_plant_filter( field, MLT_FILTER( filter ), mlt_properties_get_int( properties, "track" ) );
782                                 mlt_filter_set_in_and_out( MLT_FILTER( filter ), 
783                                                                                    mlt_properties_get_int( properties, "in" ),
784                                                                                    mlt_properties_get_int( properties, "out" ) );
785                         }
786                         else
787                         {
788                                 mlt_service_attach( parent, MLT_FILTER( filter ) );
789                         }
790
791                         // Put the parent back on the stack
792                         context_push_service( context, parent, parent_type );
793                 }
794                 else
795                 {
796                         fprintf( stderr, "filter closed with invalid parent...\n" );
797                 }
798
799                 // Close the dummy filter service
800                 mlt_service_close( service );
801         }
802         else
803         {
804                 fprintf( stderr, "Invalid top of stack on filter close\n" );
805         }
806 }
807
808 static void on_start_transition( deserialise_context context, const xmlChar *name, const xmlChar **atts)
809 {
810         // use a dummy service to hold properties to allow arbitrary nesting
811         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
812         mlt_service_init( service, NULL );
813
814         mlt_properties properties = mlt_service_properties( service );
815
816         context_push_service( context, service, mlt_dummy_transition_type );
817
818         // Set the properties
819         for ( ; atts != NULL && *atts != NULL; atts += 2 )
820                 mlt_properties_set( properties, (char*) atts[0], (char*) atts[1] );
821 }
822
823 static void on_end_transition( deserialise_context context, const xmlChar *name )
824 {
825         enum service_type type;
826         mlt_service service = context_pop_service( context, &type );
827         mlt_properties properties = mlt_service_properties( service );
828
829         enum service_type parent_type;
830         mlt_service parent = context_pop_service( context, &parent_type );
831
832         if ( service != NULL && type == mlt_dummy_transition_type )
833         {
834                 mlt_service effect = MLT_SERVICE( mlt_factory_transition(mlt_properties_get(properties,"mlt_service"), NULL ) );
835                 mlt_properties effect_props = mlt_service_properties( effect );
836
837                 track_service( context->destructors, effect, (mlt_destructor) mlt_transition_close );
838
839                 // Propogate the properties
840                 qualify_property( context, properties, "resource" );
841                 qualify_property( context, properties, "luma" );
842                 qualify_property( context, properties, "luma.resource" );
843                 qualify_property( context, properties, "composite.luma" );
844                 qualify_property( context, properties, "producer.resource" );
845                 mlt_properties_inherit( effect_props, properties );
846
847                 // Attach all filters from service onto effect
848                 attach_filters( effect, service );
849
850                 // Associate the filter with the parent
851                 if ( parent != NULL )
852                 {
853                         if ( parent_type == mlt_tractor_type )
854                         {
855                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
856                                 mlt_field_plant_transition( field, MLT_TRANSITION( effect ), 
857                                                                                         mlt_properties_get_int( properties, "a_track" ),
858                                                                                         mlt_properties_get_int( properties, "b_track" ) );
859                                 mlt_transition_set_in_and_out( MLT_TRANSITION( effect ), 
860                                                                                    mlt_properties_get_int( properties, "in" ),
861                                                                                    mlt_properties_get_int( properties, "out" ) );
862                         }
863                         else
864                         {
865                                 fprintf( stderr, "Misplaced transition - ignoring\n" );
866                         }
867
868                         // Put the parent back on the stack
869                         context_push_service( context, parent, parent_type );
870                 }
871                 else
872                 {
873                         fprintf( stderr, "transition closed with invalid parent...\n" );
874                 }
875
876                 // Close the dummy filter service
877                 mlt_service_close( service );
878         }
879         else
880         {
881                 fprintf( stderr, "Invalid top of stack on transition close\n" );
882         }
883 }
884
885 static void on_start_property( deserialise_context context, const xmlChar *name, const xmlChar **atts)
886 {
887         enum service_type type;
888         mlt_service service = context_pop_service( context, &type );
889         mlt_properties properties = mlt_service_properties( service );
890         char *value = NULL;
891
892         if ( service != NULL )
893         {
894                 // Set the properties
895                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
896                 {
897                         if ( strcmp( atts[ 0 ], "name" ) == 0 )
898                                 context->property = strdup( atts[ 1 ] );
899                         else if ( strcmp( atts[ 0 ], "value" ) == 0 )
900                                 value = (char*) atts[ 1 ];
901                 }
902
903                 if ( context->property != NULL && value != NULL )
904                         mlt_properties_set( properties, context->property, value );
905         
906                 // Tell parser to collect any further nodes for serialisation
907                 context->is_value = 1;
908
909                 context_push_service( context, service, type );
910         }
911         else
912         {
913                 fprintf( stderr, "Property without a service '%s'?\n", ( char * )name );
914         }
915 }
916
917 static void on_end_property( deserialise_context context, const xmlChar *name )
918 {
919         enum service_type type;
920         mlt_service service = context_pop_service( context, &type );
921         mlt_properties properties = mlt_service_properties( service );
922
923         if ( service != NULL )
924         {
925                 // Tell parser to stop building a tree
926                 context->is_value = 0;
927         
928                 // See if there is a xml tree for the value
929                 if ( context->property != NULL && context->value_doc != NULL )
930                 {
931                         xmlChar *value;
932                         int size;
933                 
934                         // Serialise the tree to get value
935                         xmlDocDumpMemory( context->value_doc, &value, &size );
936                         mlt_properties_set( properties, context->property, value );
937                         xmlFree( value );
938                         xmlFreeDoc( context->value_doc );
939                         context->value_doc = NULL;
940                 }
941         
942                 // Close this property handling
943                 free( context->property );
944                 context->property = NULL;
945
946                 context_push_service( context, service, type );
947         }
948         else
949         {
950                 fprintf( stderr, "Property without a service '%s'??\n", (char *)name );
951         }
952 }
953
954 static void on_start_element( void *ctx, const xmlChar *name, const xmlChar **atts)
955 {
956         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
957         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
958         
959 //printf("on_start_element: %s\n", name );
960         context->branch[ context->depth ] ++;
961         context->depth ++;
962         
963         // Build a tree from nodes within a property value
964         if ( context->is_value == 1 )
965         {
966                 xmlNodePtr node = xmlNewNode( NULL, name );
967                 
968                 if ( context->value_doc == NULL )
969                 {
970                         // Start a new tree
971                         context->value_doc = xmlNewDoc( "1.0" );
972                         xmlDocSetRootElement( context->value_doc, node );
973                 }
974                 else
975                 {
976                         // Append child to tree
977                         xmlAddChild( context->stack_node[ context->stack_node_size - 1 ], node );
978                 }
979                 context_push_node( context, node );
980                 
981                 // Set the attributes
982                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
983                         xmlSetProp( node, atts[ 0 ], atts[ 1 ] );
984         }
985         else if ( strcmp( name, "tractor" ) == 0 )
986                 on_start_tractor( context, name, atts );
987         else if ( strcmp( name, "multitrack" ) == 0 )
988                 on_start_multitrack( context, name, atts );
989         else if ( strcmp( name, "playlist" ) == 0 || strcmp( name, "seq" ) == 0 || strcmp( name, "smil" ) == 0 )
990                 on_start_playlist( context, name, atts );
991         else if ( strcmp( name, "producer" ) == 0 || strcmp( name, "video" ) == 0 )
992                 on_start_producer( context, name, atts );
993         else if ( strcmp( name, "blank" ) == 0 )
994                 on_start_blank( context, name, atts );
995         else if ( strcmp( name, "entry" ) == 0 || strcmp( name, "track" ) == 0 )
996                 on_start_entry_track( context, name, atts );
997         else if ( strcmp( name, "filter" ) == 0 )
998                 on_start_filter( context, name, atts );
999         else if ( strcmp( name, "transition" ) == 0 )
1000                 on_start_transition( context, name, atts );
1001         else if ( strcmp( name, "property" ) == 0 )
1002                 on_start_property( context, name, atts );
1003 }
1004
1005 static void on_end_element( void *ctx, const xmlChar *name )
1006 {
1007         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1008         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1009         
1010 //printf("on_end_element: %s\n", name );
1011         if ( context->is_value == 1 && strcmp( name, "property" ) != 0 )
1012                 context_pop_node( context );
1013         else if ( strcmp( name, "multitrack" ) == 0 )
1014                 on_end_multitrack( context, name );
1015         else if ( strcmp( name, "playlist" ) == 0 || strcmp( name, "seq" ) == 0 || strcmp( name, "smil" ) == 0 )
1016                 on_end_playlist( context, name );
1017         else if ( strcmp( name, "track" ) == 0 )
1018                 on_end_track( context, name );
1019         else if ( strcmp( name, "entry" ) == 0 )
1020                 on_end_entry( context, name );
1021         else if ( strcmp( name, "tractor" ) == 0 )
1022                 on_end_tractor( context, name );
1023         else if ( strcmp( name, "property" ) == 0 )
1024                 on_end_property( context, name );
1025         else if ( strcmp( name, "producer" ) == 0 || strcmp( name, "video" ) == 0 )
1026                 on_end_producer( context, name );
1027         else if ( strcmp( name, "filter" ) == 0 )
1028                 on_end_filter( context, name );
1029         else if ( strcmp( name, "transition" ) == 0 )
1030                 on_end_transition( context, name );
1031
1032         context->branch[ context->depth ] = 0;
1033         context->depth --;
1034 }
1035
1036 static void on_characters( void *ctx, const xmlChar *ch, int len )
1037 {
1038         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1039         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1040         char *value = calloc( len + 1, 1 );
1041         enum service_type type;
1042         mlt_service service = context_pop_service( context, &type );
1043         mlt_properties properties = mlt_service_properties( service );
1044
1045         if ( service != NULL )
1046                 context_push_service( context, service, type );
1047
1048         value[ len ] = 0;
1049         strncpy( value, (const char*) ch, len );
1050
1051         if ( context->stack_node_size > 0 )
1052                 xmlNodeAddContent( context->stack_node[ context->stack_node_size - 1 ], ( xmlChar* )value );
1053
1054         // libxml2 generates an on_characters immediately after a get_entity within
1055         // an element value, and we ignore it because it is called again during
1056         // actual substitution.
1057         else if ( context->property != NULL && context->entity_is_replace == 0 )
1058         {
1059                 char *s = mlt_properties_get( properties, context->property );
1060                 if ( s != NULL )
1061                 {
1062                         // Append new text to existing content
1063                         char *new = calloc( strlen( s ) + len + 1, 1 );
1064                         strcat( new, s );
1065                         strcat( new, value );
1066                         mlt_properties_set( properties, context->property, new );
1067                         free( new );
1068                 }
1069                 else
1070                         mlt_properties_set( properties, context->property, value );
1071         }
1072         context->entity_is_replace = 0;
1073         
1074         free( value);
1075 }
1076
1077 /** Convert parameters parsed from resource into entity declarations.
1078 */
1079 static void params_to_entities( deserialise_context context )
1080 {
1081         if ( context->params != NULL )
1082         {       
1083                 int i;
1084                 
1085                 // Add our params as entitiy declarations
1086                 for ( i = 0; i < mlt_properties_count( context->params ); i++ )
1087                 {
1088                         xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i );
1089                         xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY,
1090                                 context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, name ) );
1091                 }
1092
1093                 // Flag completion
1094                 mlt_properties_close( context->params );
1095                 context->params = NULL;
1096         }
1097 }
1098
1099 // The following 3 facilitate entity substitution in the SAX parser
1100 static void on_internal_subset( void *ctx, const xmlChar* name,
1101         const xmlChar* publicId, const xmlChar* systemId )
1102 {
1103         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1104         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1105         
1106         context->publicId = publicId;
1107         context->systemId = systemId;
1108         xmlCreateIntSubset( context->entity_doc, name, publicId, systemId );
1109         
1110         // Override default entities with our parameters
1111         params_to_entities( context );
1112 }
1113
1114 static void on_entity_declaration( void *ctx, const xmlChar* name, int type, 
1115         const xmlChar* publicId, const xmlChar* systemId, xmlChar* content)
1116 {
1117         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1118         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1119         
1120         xmlAddDocEntity( context->entity_doc, name, type, publicId, systemId, content );
1121 }
1122
1123 xmlEntityPtr on_get_entity( void *ctx, const xmlChar* name )
1124 {
1125         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1126         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1127         xmlEntityPtr e = NULL;
1128
1129         // Setup for entity declarations if not ready
1130         if ( xmlGetIntSubset( context->entity_doc ) == NULL )
1131         {
1132                 xmlCreateIntSubset( context->entity_doc, "westley", "", "" );
1133                 context->publicId = "";
1134                 context->systemId = "";
1135         }
1136
1137         // Add our parameters if not already
1138         params_to_entities( context );
1139         
1140         e = xmlGetDocEntity( context->entity_doc, name );
1141         
1142         // Send signal to on_characters that an entity substitutin is pending
1143         if ( e != NULL )
1144                 context->entity_is_replace = 1;
1145         
1146         return e;
1147 }
1148
1149 /** Convert a hexadecimal character to its value.
1150 */
1151 static int tohex( char p )
1152 {
1153         return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10;
1154 }
1155
1156 /** Decode a url-encoded string containing hexadecimal character sequences.
1157 */
1158 static char *url_decode( char *dest, char *src )
1159 {
1160         char *p = dest;
1161         
1162         while ( *src )
1163         {
1164                 if ( *src == '%' )
1165                 {
1166                         *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) );
1167                         src += 3;
1168                 }
1169                 else
1170                 {
1171                         *p ++ = *src ++;
1172                 }
1173         }
1174
1175         *p = *src;
1176         return dest;
1177 }
1178
1179 /** Extract the filename from a URL attaching parameters to a properties list.
1180 */
1181 static void parse_url( mlt_properties properties, char *url )
1182 {
1183         int i;
1184         int n = strlen( url );
1185         char *name = NULL;
1186         char *value = NULL;
1187         
1188         for ( i = 0; i < n; i++ )
1189         {
1190                 switch ( url[ i ] )
1191                 {
1192                         case '?':
1193                                 url[ i++ ] = '\0';
1194                                 name = &url[ i ];
1195                                 break;
1196                         
1197                         case ':':
1198                         case '=':
1199                                 url[ i++ ] = '\0';
1200                                 value = &url[ i ];
1201                                 break;
1202                         
1203                         case '&':
1204                                 url[ i++ ] = '\0';
1205                                 if ( name != NULL && value != NULL )
1206                                         mlt_properties_set( properties, name, value );
1207                                 name = &url[ i ];
1208                                 value = NULL;
1209                                 break;
1210                 }
1211         }
1212         if ( name != NULL && value != NULL )
1213                 mlt_properties_set( properties, name, value );
1214 }
1215
1216 mlt_producer producer_westley_init( char *url )
1217 {
1218         if ( url == NULL )
1219                 return NULL;
1220         xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) );
1221         struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) );
1222         mlt_properties properties = NULL;
1223         int i = 0;
1224         struct _xmlParserCtxt *xmlcontext;
1225         int well_formed = 0;
1226         char *filename = strdup( url );
1227         
1228         context->producer_map = mlt_properties_new();
1229         context->destructors = mlt_properties_new();
1230         context->params = mlt_properties_new();
1231
1232         // Decode URL and parse parameters      
1233         parse_url( context->params, url_decode( filename, url ) );
1234
1235         // We need to track the number of registered filters
1236         mlt_properties_set_int( context->destructors, "registered", 0 );
1237
1238         // We need the directory prefix which was used for the westley
1239         mlt_properties_set( context->producer_map, "_root", "" );
1240         if ( strchr( filename, '/' ) )
1241         {
1242                 char *root = NULL;
1243                 mlt_properties_set( context->producer_map, "_root", filename );
1244                 root = mlt_properties_get( context->producer_map, "_root" );
1245                 *( strrchr( root, '/' ) + 1 ) = '\0';
1246         }
1247
1248         // Setup SAX callbacks
1249         sax->startElement = on_start_element;
1250         sax->endElement = on_end_element;
1251         sax->characters = on_characters;
1252         sax->cdataBlock = on_characters;
1253         sax->internalSubset = on_internal_subset;
1254         sax->entityDecl = on_entity_declaration;
1255         sax->getEntity = on_get_entity;
1256
1257         // Setup libxml2 SAX parsing
1258         xmlInitParser(); 
1259         xmlSubstituteEntitiesDefault( 1 );
1260         // This is used to facilitate entity substitution in the SAX parser
1261         context->entity_doc = xmlNewDoc( "1.0" );
1262         xmlcontext = xmlCreateFileParserCtxt( filename );
1263         xmlcontext->sax = sax;
1264         xmlcontext->_private = ( void* )context;
1265         
1266         // Parse
1267         xmlParseDocument( xmlcontext );
1268         well_formed = xmlcontext->wellFormed;
1269         
1270         // Cleanup after parsing
1271         xmlFreeDoc( context->entity_doc );
1272         free( sax );
1273         xmlcontext->sax = NULL;
1274         xmlcontext->_private = NULL;
1275         xmlFreeParserCtxt( xmlcontext );
1276         xmlMemoryDump( ); // for debugging
1277
1278         // Get the last producer on the stack
1279         enum service_type type;
1280         mlt_service service = context_pop_service( context, &type );
1281         if ( well_formed && service != NULL )
1282         {
1283                 // Verify it is a producer service (mlt_type="mlt_producer")
1284                 // (producer, playlist, multitrack)
1285                 char *type = mlt_properties_get( mlt_service_properties( service ), "mlt_type" );
1286                 if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 && strcmp( type, "producer" ) != 0 ) )
1287                         service = NULL;
1288         }
1289
1290 #ifdef DEBUG
1291         xmlDocPtr doc = westley_make_doc( service );
1292         xmlDocFormatDump( stdout, doc, 1 );
1293         xmlFreeDoc( doc );
1294         service = NULL;
1295 #endif
1296         
1297         if ( well_formed && service != NULL )
1298         {
1299                 
1300                 // Need the complete producer list for various reasons
1301                 properties = context->destructors;
1302
1303                 // Now make sure we don't have a reference to the service in the properties
1304                 for ( i = mlt_properties_count( properties ) - 1; i >= 1; i -- )
1305                 {
1306                         char *name = mlt_properties_get_name( properties, i );
1307                         if ( mlt_properties_get_data( properties, name, NULL ) == service )
1308                         {
1309                                 mlt_properties_set_data( properties, name, service, 0, NULL, NULL );
1310                                 break;
1311                         }
1312                 }
1313
1314                 // We are done referencing destructor property list
1315                 // Set this var to service properties for convenience
1316                 properties = mlt_service_properties( service );
1317         
1318                 // make the returned service destroy the connected services
1319                 mlt_properties_set_data( properties, "__destructors__", context->destructors, 0, (mlt_destructor) mlt_properties_close, NULL );
1320
1321                 // Now assign additional properties
1322                 mlt_properties_set( properties, "resource", url );
1323
1324                 // This tells consumer_westley not to deep copy
1325                 mlt_properties_set( properties, "westley", "was here" );
1326         }
1327         else
1328         {
1329                 // Return null if not well formed
1330                 service = NULL;
1331                 
1332                 // Clean up
1333                 mlt_properties_close( context->destructors );
1334         }
1335
1336         // Clean up
1337         mlt_properties_close( context->producer_map );
1338         if ( context->params != NULL )
1339                 mlt_properties_close( context->params );
1340         free( context );
1341         free( filename );
1342
1343         return MLT_PRODUCER( service );
1344 }