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