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