]> git.sesse.net Git - mlt/blob - src/modules/xml/producer_xml.c
Fix parsing mixed XML documents and svg.mlt example.
[mlt] / src / modules / xml / producer_xml.c
1 /*
2  * producer_xml.c -- a libxml2 parser of mlt service networks
3  * Copyright (C) 2003-2009 Ushodaya Enterprises Limited
4  * Author: Dan Dennedy <dan@dennedy.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, 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 <framework/mlt.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <ctype.h>
29 #include <unistd.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 #define _x (const xmlChar*)
39 #define _s (const char*)
40
41 #undef DEBUG
42 #ifdef DEBUG
43 extern xmlDocPtr xml_make_doc( mlt_service service );
44 #endif
45
46 enum service_type
47 {
48         mlt_invalid_type,
49         mlt_unknown_type,
50         mlt_producer_type,
51         mlt_playlist_type,
52         mlt_entry_type,
53         mlt_tractor_type,
54         mlt_multitrack_type,
55         mlt_filter_type,
56         mlt_transition_type,
57         mlt_consumer_type,
58         mlt_field_type,
59         mlt_services_type,
60         mlt_dummy_filter_type,
61         mlt_dummy_transition_type,
62         mlt_dummy_producer_type,
63 };
64
65 struct deserialise_context_s
66 {
67         enum service_type stack_types[ STACK_SIZE ];
68         mlt_service stack_service[ STACK_SIZE ];
69         int stack_service_size;
70         mlt_properties producer_map;
71         mlt_properties destructors;
72         char *property;
73         int is_value;
74         xmlDocPtr value_doc;
75         xmlNodePtr stack_node[ STACK_SIZE ];
76         int stack_node_size;
77         xmlDocPtr entity_doc;
78         int entity_is_replace;
79         int depth;
80         int branch[ STACK_SIZE ];
81         const xmlChar *publicId;
82         const xmlChar *systemId;
83         mlt_properties params;
84         mlt_profile profile;
85         int pass;
86 };
87 typedef struct deserialise_context_s *deserialise_context;
88
89 /** Convert the numerical current branch address to a dot-delimited string.
90 */
91 static char *serialise_branch( deserialise_context this, char *s )
92 {
93         int i;
94         
95         s[0] = 0;
96         for ( i = 0; i < this->depth; i++ )
97         {
98                 int len = strlen( s );
99                 snprintf( s + len, BRANCH_SIG_LEN - len, "%d.", this->branch[ i ] );
100         }
101         return s;
102 }
103
104 /** Push a service.
105 */
106
107 static int context_push_service( deserialise_context this, mlt_service that, enum service_type type )
108 {
109         int ret = this->stack_service_size >= STACK_SIZE - 1;
110         if ( ret == 0 )
111         {
112                 this->stack_service[ this->stack_service_size ] = that;
113                 this->stack_types[ this->stack_service_size++ ] = type;
114                 
115                 // Record the tree branch on which this service lives
116                 if ( that != NULL && mlt_properties_get( MLT_SERVICE_PROPERTIES( that ), "_xml_branch" ) == NULL )
117                 {
118                         char s[ BRANCH_SIG_LEN ];
119                         mlt_properties_set( MLT_SERVICE_PROPERTIES( that ), "_xml_branch", serialise_branch( this, s ) );
120                 }
121         }
122         return ret;
123 }
124
125 /** Pop a service.
126 */
127
128 static mlt_service context_pop_service( deserialise_context this, enum service_type *type )
129 {
130         mlt_service result = NULL;
131         
132         *type = invalid_type;
133         if ( this->stack_service_size > 0 )
134         {
135                 result = this->stack_service[ -- this->stack_service_size ];
136                 if ( type != NULL )
137                         *type = this->stack_types[ this->stack_service_size ];
138         }
139         return result;
140 }
141
142 /** Push a node.
143 */
144
145 static int context_push_node( deserialise_context this, xmlNodePtr node )
146 {
147         int ret = this->stack_node_size >= STACK_SIZE - 1;
148         if ( ret == 0 )
149                 this->stack_node[ this->stack_node_size ++ ] = node;
150         return ret;
151 }
152
153 /** Pop a node.
154 */
155
156 static xmlNodePtr context_pop_node( deserialise_context this )
157 {
158         xmlNodePtr result = NULL;
159         if ( this->stack_node_size > 0 )
160                 result = this->stack_node[ -- this->stack_node_size ];
161         return result;
162 }
163
164
165 // Set the destructor on a new service
166 static void track_service( mlt_properties properties, void *service, mlt_destructor destructor )
167 {
168         int registered = mlt_properties_get_int( properties, "registered" );
169         char *key = mlt_properties_get( properties, "registered" );
170         mlt_properties_set_data( properties, key, service, 0, destructor, NULL );
171         mlt_properties_set_int( properties, "registered", ++ registered );
172 }
173
174
175 // Prepend the property value with the document root
176 static inline void qualify_property( deserialise_context context, mlt_properties properties, const char *name )
177 {
178         char *resource = mlt_properties_get( properties, name );
179         if ( resource != NULL && resource[0] )
180         {
181                 // Qualify file name properties 
182                 char *root = mlt_properties_get( context->producer_map, "root" );
183                 if ( root != NULL && strcmp( root, "" ) )
184                 {
185                         char *full_resource = malloc( strlen( root ) + strlen( resource ) + 2 );
186                         if ( resource[ 0 ] != '/' && strchr( resource, ':' ) == NULL )
187                         {
188                                 strcpy( full_resource, root );
189                                 strcat( full_resource, "/" );
190                                 strcat( full_resource, resource );
191                         }
192                         else
193                         {
194                                 strcpy( full_resource, resource );
195                         }
196                         mlt_properties_set( properties, name, full_resource );
197                         free( full_resource );
198                 }
199         }
200 }
201
202
203 /** This function adds a producer to a playlist or multitrack when
204     there is no entry or track element.
205 */
206
207 static int add_producer( deserialise_context context, mlt_service service, mlt_position in, mlt_position out )
208 {
209         // Return value (0 = service remains top of stack, 1 means it can be removed)
210         int result = 0;
211
212         // Get the parent producer
213         enum service_type type = mlt_invalid_type;
214         mlt_service container = context_pop_service( context, &type );
215         int contained = 0;
216
217         if ( service != NULL && container != NULL )
218         {
219                 char *container_branch = mlt_properties_get( MLT_SERVICE_PROPERTIES( container ), "_xml_branch" );
220                 char *service_branch = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "_xml_branch" );
221                 contained = !strncmp( container_branch, service_branch, strlen( container_branch ) );
222         }
223
224         if ( contained )
225         {
226                 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
227                 char *hide_s = mlt_properties_get( properties, "hide" );
228
229                 // Indicate that this service is no longer top of stack
230                 result = 1;
231
232                 switch( type )
233                 {
234                         case mlt_tractor_type: 
235                                 {
236                                         mlt_multitrack multitrack = mlt_tractor_multitrack( MLT_TRACTOR( container ) );
237                                         mlt_multitrack_connect( multitrack, MLT_PRODUCER( service ), mlt_multitrack_count( multitrack ) );
238                                 }
239                                 break;
240                         case mlt_multitrack_type:
241                                 {
242                                         mlt_multitrack_connect( MLT_MULTITRACK( container ),
243                                                 MLT_PRODUCER( service ),
244                                                 mlt_multitrack_count( MLT_MULTITRACK( container ) ) );
245                                 }
246                                 break;
247                         case mlt_playlist_type:
248                                 {
249                                         mlt_playlist_append_io( MLT_PLAYLIST( container ), MLT_PRODUCER( service ), in, out );
250                                 }
251                                 break;
252                         default:
253                                 result = 0;
254                                 fprintf( stderr, "Producer defined inside something that isn't a container\n" );
255                                 break;
256                 };
257
258                 // Set the hide state of the track producer
259                 if ( hide_s != NULL )
260                 {
261                         if ( strcmp( hide_s, "video" ) == 0 )
262                                 mlt_properties_set_int( properties, "hide", 1 );
263                         else if ( strcmp( hide_s, "audio" ) == 0 )
264                                 mlt_properties_set_int( properties, "hide", 2 );
265                         else if ( strcmp( hide_s, "both" ) == 0 )
266                                 mlt_properties_set_int( properties, "hide", 3 );
267                 }
268         }
269
270         // Put the parent producer back
271         if ( container != NULL )
272                 context_push_service( context, container, type );
273
274         return result;
275 }
276
277 /** Attach filters defined on that to this.
278 */
279
280 static void attach_filters( mlt_service this, mlt_service that )
281 {
282         if ( that != NULL )
283         {
284                 int i = 0;
285                 mlt_filter filter = NULL;
286                 for ( i = 0; ( filter = mlt_service_filter( that, i ) ) != NULL; i ++ )
287                 {
288                         mlt_service_attach( this, filter );
289                         attach_filters( MLT_FILTER_SERVICE( filter ), MLT_FILTER_SERVICE( filter ) );
290                 }
291         }
292 }
293
294 static void on_start_profile( deserialise_context context, const xmlChar *name, const xmlChar **atts)
295 {
296         mlt_profile p = context->profile;
297         for ( ; atts != NULL && *atts != NULL; atts += 2 )
298         {
299                 if ( xmlStrcmp( atts[ 0 ], _x("name") ) == 0 || xmlStrcmp( atts[ 0 ], _x("profile") ) == 0 )
300                 {
301                         mlt_profile my_profile = mlt_profile_init( _s(atts[ 1 ]) );
302                         if ( my_profile )
303                         {
304                                 p->description = strdup( my_profile->description );
305                                 p->display_aspect_den = my_profile->display_aspect_den;
306                                 p->display_aspect_num = my_profile->display_aspect_num;
307                                 p->frame_rate_den = my_profile->frame_rate_den;
308                                 p->frame_rate_num = my_profile->frame_rate_num;
309                                 p->width = my_profile->width;
310                                 p->height = my_profile->height;
311                                 p->progressive = my_profile->progressive;
312                                 p->sample_aspect_den = my_profile->sample_aspect_den;
313                                 p->sample_aspect_num = my_profile->sample_aspect_num;
314                                 p->colorspace = my_profile->colorspace;
315                                 p->is_explicit = 1;
316                                 mlt_profile_close( my_profile );
317                         }
318                 }
319                 else if ( xmlStrcmp( atts[ 0 ], _x("description") ) == 0 )
320                 {
321                         if ( p->description )
322                                 free( p->description );
323                         p->description = strdup( _s(atts[ 1 ]) );
324                         p->is_explicit = 1;
325                 }
326                 else if ( xmlStrcmp( atts[ 0 ], _x("display_aspect_den") ) == 0 )
327                         p->display_aspect_den = strtol( _s(atts[ 1 ]), NULL, 0 );
328                 else if ( xmlStrcmp( atts[ 0 ], _x("display_aspect_num") ) == 0 )
329                         p->display_aspect_num = strtol( _s(atts[ 1 ]), NULL, 0 );
330                 else if ( xmlStrcmp( atts[ 0 ], _x("sample_aspect_num") ) == 0 )
331                         p->sample_aspect_num = strtol( _s(atts[ 1 ]), NULL, 0 );
332                 else if ( xmlStrcmp( atts[ 0 ], _x("sample_aspect_den") ) == 0 )
333                         p->sample_aspect_den = strtol( _s(atts[ 1 ]), NULL, 0 );
334                 else if ( xmlStrcmp( atts[ 0 ], _x("width") ) == 0 )
335                         p->width = strtol( _s(atts[ 1 ]), NULL, 0 );
336                 else if ( xmlStrcmp( atts[ 0 ], _x("height") ) == 0 )
337                         p->height = strtol( _s(atts[ 1 ]), NULL, 0 );
338                 else if ( xmlStrcmp( atts[ 0 ], _x("progressive") ) == 0 )
339                         p->progressive = strtol( _s(atts[ 1 ]), NULL, 0 );
340                 else if ( xmlStrcmp( atts[ 0 ], _x("frame_rate_num") ) == 0 )
341                         p->frame_rate_num = strtol( _s(atts[ 1 ]), NULL, 0 );
342                 else if ( xmlStrcmp( atts[ 0 ], _x("frame_rate_den") ) == 0 )
343                         p->frame_rate_den = strtol( _s(atts[ 1 ]), NULL, 0 );
344                 else if ( xmlStrcmp( atts[ 0 ], _x("colorspace") ) == 0 )
345                         p->colorspace = strtol( _s(atts[ 1 ]), NULL, 0 );
346         }
347 }
348
349 static void on_start_tractor( deserialise_context context, const xmlChar *name, const xmlChar **atts)
350 {
351         mlt_tractor tractor = mlt_tractor_new( );
352         mlt_service service = MLT_TRACTOR_SERVICE( tractor );
353         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
354
355         track_service( context->destructors, service, (mlt_destructor) mlt_tractor_close );
356
357         for ( ; atts != NULL && *atts != NULL; atts += 2 )
358                 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
359
360         mlt_properties_set_int( MLT_TRACTOR_PROPERTIES( tractor ), "global_feed", 1 );
361
362         if ( mlt_properties_get( properties, "id" ) != NULL )
363                 mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL );
364         
365         context_push_service( context, service, mlt_tractor_type );
366 }
367
368 static void on_end_tractor( deserialise_context context, const xmlChar *name )
369 {
370         // Get the tractor
371         enum service_type type;
372         mlt_service tractor = context_pop_service( context, &type );
373
374         if ( tractor != NULL && type == mlt_tractor_type )
375         {
376                 // See if the tractor should be added to a playlist or multitrack
377                 if ( add_producer( context, tractor, 0, mlt_producer_get_out( MLT_PRODUCER( tractor ) ) ) == 0 )
378                         context_push_service( context, tractor, type );
379         }
380         else
381         {
382                 fprintf( stderr, "Invalid state for tractor\n" );
383         }
384 }
385
386 static void on_start_multitrack( deserialise_context context, const xmlChar *name, const xmlChar **atts)
387 {
388         enum service_type type;
389         mlt_service parent = context_pop_service( context, &type );
390
391         // If we don't have a parent, then create one now, providing we're in a state where we can
392         if ( parent == NULL || ( type == mlt_playlist_type || type == mlt_multitrack_type ) )
393         {
394                 mlt_tractor tractor = NULL;
395                 // Push the parent back
396                 if ( parent != NULL )
397                         context_push_service( context, parent, type );
398
399                 // Create a tractor to contain the multitrack
400                 tractor = mlt_tractor_new( );
401                 parent = MLT_TRACTOR_SERVICE( tractor );
402                 track_service( context->destructors, parent, (mlt_destructor) mlt_tractor_close );
403                 type = mlt_tractor_type;
404
405                 // Flag it as a synthesised tractor for clean up later
406                 mlt_properties_set_int( MLT_SERVICE_PROPERTIES( parent ), "loader_synth", 1 );
407         }
408
409         if ( type == mlt_tractor_type )
410         {
411                 mlt_service service = MLT_SERVICE( mlt_tractor_multitrack( MLT_TRACTOR( parent ) ) );
412                 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
413                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
414                         mlt_properties_set( properties, (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
415
416                 if ( mlt_properties_get( properties, "id" ) != NULL )
417                         mlt_properties_set_data( context->producer_map, mlt_properties_get( properties,"id" ), service, 0, NULL, NULL );
418
419                 context_push_service( context, parent, type );
420                 context_push_service( context, service, mlt_multitrack_type );
421         }
422         else
423         {
424                 fprintf( stderr, "Invalid multitrack position\n" );
425         }
426 }
427
428 static void on_end_multitrack( deserialise_context context, const xmlChar *name )
429 {
430         // Get the multitrack from the stack
431         enum service_type type;
432         mlt_service service = context_pop_service( context, &type );
433
434         if ( service == NULL || type != mlt_multitrack_type )
435                 fprintf( stderr, "End multitrack in the wrong state...\n" );
436 }
437
438 static void on_start_playlist( deserialise_context context, const xmlChar *name, const xmlChar **atts)
439 {
440         mlt_playlist playlist = mlt_playlist_init( );
441         mlt_service service = MLT_PLAYLIST_SERVICE( playlist );
442         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
443
444         track_service( context->destructors, service, (mlt_destructor) mlt_playlist_close );
445
446         for ( ; atts != NULL && *atts != NULL; atts += 2 )
447         {
448                 mlt_properties_set( properties, (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
449
450                 // Out will be overwritten later as we append, so we need to save it
451                 if ( xmlStrcmp( atts[ 0 ], _x("out") ) == 0 )
452                         mlt_properties_set( properties, "_xml.out", ( const char* )atts[ 1 ] );
453         }
454
455         if ( mlt_properties_get( properties, "id" ) != NULL )
456                 mlt_properties_set_data( context->producer_map, mlt_properties_get( properties, "id" ), service, 0, NULL, NULL );
457
458         context_push_service( context, service, mlt_playlist_type );
459 }
460
461 static void on_end_playlist( deserialise_context context, const xmlChar *name )
462 {
463         // Get the playlist from the stack
464         enum service_type type;
465         mlt_service service = context_pop_service( context, &type );
466
467         if ( service != NULL && type == mlt_playlist_type )
468         {
469                 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
470                 mlt_position in = -1;
471                 mlt_position out = -1;
472
473                 if ( mlt_properties_get( properties, "in" ) )
474                         in = mlt_properties_get_position( properties, "in" );
475                 if ( mlt_properties_get( properties, "out" ) )
476                         out = mlt_properties_get_position( properties, "out" );
477
478                 // See if the playlist should be added to a playlist or multitrack
479                 if ( add_producer( context, service, in, out ) == 0 )
480                         context_push_service( context, service, type );
481         }
482         else
483         {
484                 fprintf( stderr, "Invalid state of playlist end %d\n", type );
485         }
486 }
487
488 static void on_start_producer( deserialise_context context, const xmlChar *name, const xmlChar **atts)
489 {
490         // use a dummy service to hold properties to allow arbitrary nesting
491         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
492         mlt_service_init( service, NULL );
493
494         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
495
496         context_push_service( context, service, mlt_dummy_producer_type );
497
498         for ( ; atts != NULL && *atts != NULL; atts += 2 )
499                 mlt_properties_set( properties, (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
500 }
501
502 // Parse a SMIL clock value (as produced by Kino 0.9.1) and return position in frames
503 static mlt_position parse_clock_value( char *value, double fps )
504 {
505         // This implementation expects a fully specified clock value - no optional
506         // parts (e.g. 1:05)
507         char *pos, *copy = strdup( value );
508         int hh, mm, ss, ms;
509         mlt_position result = -1;
510
511         value = copy;
512         pos = strchr( value, ':' );
513         if ( !pos )
514                 return result;
515         *pos = '\0';
516         hh = atoi( value );
517         value = pos + 1;
518
519         pos = strchr( value, ':' );
520         if ( !pos )
521                 return result;
522         *pos = '\0';
523         mm = atoi( value );
524         value = pos + 1;
525         
526         pos = strchr( value, '.' );
527         if ( !pos )
528                 return result;
529         *pos = '\0';
530         ss = atoi( value );
531         value = pos + 1;
532         
533         ms = atoi( value );
534         free( copy );
535         result = ( fps * ( ( (hh * 3600) + (mm * 60) + ss ) * 1000  + ms ) / 1000 + 0.5 );
536         
537         return result;
538 }
539
540 static void on_end_producer( deserialise_context context, const xmlChar *name )
541 {
542         enum service_type type;
543         mlt_service service = context_pop_service( context, &type );
544         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
545
546         if ( service != NULL && type == mlt_dummy_producer_type )
547         {
548                 mlt_service producer = NULL;
549
550                 qualify_property( context, properties, "resource" );
551                 char *resource = mlt_properties_get( properties, "resource" );
552
553                 // Let Kino-SMIL src be a synonym for resource
554                 if ( resource == NULL )
555                 {
556                         qualify_property( context, properties, "src" );
557                         resource = mlt_properties_get( properties, "src" );
558                 }
559
560                 // Instantiate the producer
561                 if ( mlt_properties_get( properties, "mlt_service" ) != NULL )
562                 {
563                         char *service_name = mlt_properties_get( properties, "mlt_service" );
564                         if ( resource )
565                         {
566                                 char *temp = calloc( 1, strlen( service_name ) + strlen( resource ) + 2 );
567                                 strcat( temp, service_name );
568                                 strcat( temp, ":" );
569                                 strcat( temp, resource );
570                                 producer = MLT_SERVICE( mlt_factory_producer( context->profile, NULL, temp ) );
571                                 free( temp );
572                         }
573                         else
574                         {
575                                 producer = MLT_SERVICE( mlt_factory_producer( context->profile, NULL, service_name ) );
576                         }
577                 }
578
579                 // Just in case the plugin requested doesn't exist...
580                 if ( producer == NULL && resource != NULL )
581                         producer = MLT_SERVICE( mlt_factory_producer( context->profile, NULL, resource ) );
582         
583                 if ( producer == NULL )
584                         producer = MLT_SERVICE( mlt_factory_producer( context->profile, NULL, "+INVALID.txt" ) );
585
586                 if ( producer == NULL )
587                         producer = MLT_SERVICE( mlt_factory_producer( context->profile, NULL, "colour:red" ) );
588
589                 // Track this producer
590                 track_service( context->destructors, producer, (mlt_destructor) mlt_producer_close );
591
592                 // Propogate the properties
593                 qualify_property( context, properties, "resource" );
594                 qualify_property( context, properties, "luma" );
595                 qualify_property( context, properties, "luma.resource" );
596                 qualify_property( context, properties, "composite.luma" );
597                 qualify_property( context, properties, "producer.resource" );
598
599                 // Handle in/out properties separately
600                 mlt_position in = -1;
601                 mlt_position out = -1;
602         
603                 // Get in
604                 if ( mlt_properties_get( properties, "in" ) != NULL )
605                         in = mlt_properties_get_position( properties, "in" );
606                 // Let Kino-SMIL clipBegin be a synonym for in
607                 if ( mlt_properties_get( properties, "clipBegin" ) != NULL )
608                 {
609                         if ( strchr( mlt_properties_get( properties, "clipBegin" ), ':' ) )
610                                 // Parse clock value
611                                 in = parse_clock_value( mlt_properties_get( properties, "clipBegin" ),
612                                         mlt_producer_get_fps( MLT_PRODUCER(  producer ) ) );
613                         else
614                                 // Parse frames value
615                                 in = mlt_properties_get_position( properties, "clipBegin" );
616                 }
617                 // Get out
618                 if ( mlt_properties_get( properties, "out" ) != NULL )
619                         out = mlt_properties_get_position( properties, "out" );
620                 // Let Kino-SMIL clipEnd be a synonym for out
621                 if ( mlt_properties_get( properties, "clipEnd" ) != NULL )
622                 {
623                         if ( strchr( mlt_properties_get( properties, "clipEnd" ), ':' ) )
624                                 // Parse clock value
625                                 out = parse_clock_value( mlt_properties_get( properties, "clipEnd" ),
626                                         mlt_producer_get_fps( MLT_PRODUCER( producer ) ) );
627                         else
628                                 // Parse frames value
629                                 out = mlt_properties_get_position( properties, "clipEnd" );
630                 }
631                 // Remove in and out
632                 mlt_properties_set( properties, "in", NULL );
633                 mlt_properties_set( properties, "out", NULL );
634
635                 // Inherit the properties
636                 mlt_properties_inherit( MLT_SERVICE_PROPERTIES( producer ), properties );
637
638                 // Attach all filters from service onto producer
639                 attach_filters( producer, service );
640
641                 // Add the producer to the producer map
642                 if ( mlt_properties_get( properties, "id" ) != NULL )
643                         mlt_properties_set_data( context->producer_map, mlt_properties_get(properties, "id"), producer, 0, NULL, NULL );
644
645                 // See if the producer should be added to a playlist or multitrack
646                 if ( add_producer( context, producer, in, out ) == 0 )
647                 {
648                         // Otherwise, set in and out on...
649                         if ( in != -1 || out != -1 )
650                         {
651                                 // Get the parent service
652                                 enum service_type type;
653                                 mlt_service parent = context_pop_service( context, &type );
654                                 if ( parent != NULL )
655                                 {
656                                         // Get the parent properties
657                                         properties = MLT_SERVICE_PROPERTIES( parent );
658                                 
659                                         char *resource = mlt_properties_get( properties, "resource" );
660                                 
661                                         // Put the parent producer back
662                                         context_push_service( context, parent, type );
663                                         
664                                         // If the parent is a track or entry
665                                         if ( resource && ( strcmp( resource, "<entry>" ) == 0 ) )
666                                         {
667                                                 if ( in > -1 ) mlt_properties_set_position( properties, "in", in );
668                                                 if ( out > -1 ) mlt_properties_set_position( properties, "out", out );
669                                         }
670                                         else
671                                         {
672                                                 // Otherwise, set in and out on producer directly
673                                                 mlt_producer_set_in_and_out( MLT_PRODUCER( producer ), in, out );
674                                         }
675                                 }
676                                 else
677                                 {
678                                         // Otherwise, set in and out on producer directly
679                                         mlt_producer_set_in_and_out( MLT_PRODUCER( producer ), in, out );
680                                 }
681                         }
682
683                         // Push the producer onto the stack
684                         context_push_service( context, producer, mlt_producer_type );
685                 }
686
687                 mlt_service_close( service );
688         }
689 }
690
691 static void on_start_blank( deserialise_context context, const xmlChar *name, const xmlChar **atts)
692 {
693         // Get the playlist from the stack
694         enum service_type type;
695         mlt_service service = context_pop_service( context, &type );
696         mlt_position length = 0;
697         
698         if ( type == mlt_playlist_type && service != NULL )
699         {
700                 // Look for the length attribute
701                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
702                 {
703                         if ( xmlStrcmp( atts[0], _x("length") ) == 0 )
704                         {
705                                 length = atoll( _s(atts[1]) );
706                                 break;
707                         }
708                 }
709
710                 // Append a blank to the playlist
711                 mlt_playlist_blank( MLT_PLAYLIST( service ), length - 1 );
712
713                 // Push the playlist back onto the stack
714                 context_push_service( context, service, type );
715         }
716         else
717         {
718                 fprintf( stderr, "blank without a playlist - a definite no no\n" );
719         }
720 }
721
722 static void on_start_entry( deserialise_context context, const xmlChar *name, const xmlChar **atts)
723 {
724         mlt_producer entry = NULL;
725         mlt_properties temp = mlt_properties_new( );
726
727         for ( ; atts != NULL && *atts != NULL; atts += 2 )
728         {
729                 mlt_properties_set( temp, (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
730                 
731                 // Look for the producer attribute
732                 if ( xmlStrcmp( atts[ 0 ], _x("producer") ) == 0 )
733                 {
734                         mlt_producer producer = mlt_properties_get_data( context->producer_map, (const char*) atts[1], NULL );
735                         if ( producer !=  NULL )
736                                 mlt_properties_set_data( temp, "producer", producer, 0, NULL, NULL );
737                 }
738         }
739
740         // If we have a valid entry
741         if ( mlt_properties_get_data( temp, "producer", NULL ) != NULL )
742         {
743                 mlt_playlist_clip_info info;
744                 enum service_type parent_type = invalid_type;
745                 mlt_service parent = context_pop_service( context, &parent_type );
746                 mlt_producer producer = mlt_properties_get_data( temp, "producer", NULL );
747
748                 if ( parent_type == mlt_playlist_type )
749                 {
750                         // Append the producer to the playlist
751                         mlt_position in = -1;
752                         mlt_position out = -1;
753                         if ( mlt_properties_get( temp, "in" ) )
754                                 in = mlt_properties_get_position( temp, "in" );
755                         if ( mlt_properties_get( temp, "out" ) )
756                                 out = mlt_properties_get_position( temp, "out" );
757                         mlt_playlist_append_io( MLT_PLAYLIST( parent ), producer, in, out );
758
759                         // Handle the repeat property
760                         if ( mlt_properties_get_int( temp, "repeat" ) > 0 )
761                         {
762                                 mlt_playlist_repeat_clip( MLT_PLAYLIST( parent ),
763                                                                                   mlt_playlist_count( MLT_PLAYLIST( parent ) ) - 1,
764                                                                                   mlt_properties_get_int( temp, "repeat" ) );
765                         }
766
767                         mlt_playlist_get_clip_info( MLT_PLAYLIST( parent ), &info, mlt_playlist_count( MLT_PLAYLIST( parent ) ) - 1 );
768                         entry = info.cut;
769                 }
770                 else
771                 {
772                         fprintf( stderr, "Entry not part of a playlist...\n" );
773                 }
774
775                 context_push_service( context, parent, parent_type );
776         }
777
778         // Push the cut onto the stack
779         context_push_service( context, MLT_PRODUCER_SERVICE( entry ), mlt_entry_type );
780
781         mlt_properties_close( temp );
782 }
783
784 static void on_end_entry( deserialise_context context, const xmlChar *name )
785 {
786         // Get the entry from the stack
787         enum service_type entry_type = invalid_type;
788         mlt_service entry = context_pop_service( context, &entry_type );
789
790         if ( entry == NULL && entry_type != mlt_entry_type )
791         {
792                 fprintf( stderr, "Invalid state at end of entry\n" );
793         }
794 }
795
796 static void on_start_track( deserialise_context context, const xmlChar *name, const xmlChar **atts)
797 {
798         // use a dummy service to hold properties to allow arbitrary nesting
799         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
800         mlt_service_init( service, NULL );
801
802         // Push the dummy service onto the stack
803         context_push_service( context, service, mlt_entry_type );
804         
805         mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "resource", "<track>" );
806         
807         for ( ; atts != NULL && *atts != NULL; atts += 2 )
808         {
809                 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), (const char*) atts[0], atts[1] == NULL ? "" : (const char*) atts[1] );
810                 
811                 // Look for the producer attribute
812                 if ( xmlStrcmp( atts[ 0 ], _x("producer") ) == 0 )
813                 {
814                         mlt_producer producer = mlt_properties_get_data( context->producer_map, (const char*) atts[1], NULL );
815                         if ( producer !=  NULL )
816                                 mlt_properties_set_data( MLT_SERVICE_PROPERTIES( service ), "producer", producer, 0, NULL, NULL );
817                 }
818         }
819 }
820
821 static void on_end_track( deserialise_context context, const xmlChar *name )
822 {
823         // Get the track from the stack
824         enum service_type track_type;
825         mlt_service track = context_pop_service( context, &track_type );
826
827         if ( track != NULL && track_type == mlt_entry_type )
828         {
829                 mlt_properties track_props = MLT_SERVICE_PROPERTIES( track );
830                 enum service_type parent_type = invalid_type;
831                 mlt_service parent = context_pop_service( context, &parent_type );
832                 mlt_multitrack multitrack = NULL;
833
834                 mlt_producer producer = mlt_properties_get_data( track_props, "producer", NULL );
835                 mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer );
836
837                 if ( parent_type == mlt_tractor_type )
838                         multitrack = mlt_tractor_multitrack( MLT_TRACTOR( parent ) );
839                 else if ( parent_type == mlt_multitrack_type )
840                         multitrack = MLT_MULTITRACK( parent );
841                 else
842                         fprintf( stderr, "track contained in an invalid container\n" );
843
844                 if ( multitrack != NULL )
845                 {
846                         // Set producer i/o if specified
847                         if ( mlt_properties_get( track_props, "in" ) != NULL ||
848                                  mlt_properties_get( track_props, "out" ) != NULL )
849                         {
850                                 mlt_position in = -1;
851                                 mlt_position out = -1;
852                                 if ( mlt_properties_get( track_props, "in" ) )
853                                         in = mlt_properties_get_position( track_props, "in" );
854                                 if ( mlt_properties_get( track_props, "out" ) )
855                                         out = mlt_properties_get_position( track_props, "out" );
856                                 mlt_producer cut = mlt_producer_cut( MLT_PRODUCER( producer ), in, out );
857                                 mlt_multitrack_connect( multitrack, cut, mlt_multitrack_count( multitrack ) );
858                                 mlt_properties_inherit( MLT_PRODUCER_PROPERTIES( cut ), track_props );
859                                 track_props = MLT_PRODUCER_PROPERTIES( cut );
860                                 mlt_producer_close( cut );
861                         }
862                         else
863                         {
864                                 mlt_multitrack_connect( multitrack, producer, mlt_multitrack_count( multitrack ) );
865                         }
866
867                         // Set the hide state of the track producer
868                         char *hide_s = mlt_properties_get( track_props, "hide" );
869                         if ( hide_s != NULL )
870                         {
871                                 if ( strcmp( hide_s, "video" ) == 0 )
872                                         mlt_properties_set_int( producer_props, "hide", 1 );
873                                 else if ( strcmp( hide_s, "audio" ) == 0 )
874                                         mlt_properties_set_int( producer_props, "hide", 2 );
875                                 else if ( strcmp( hide_s, "both" ) == 0 )
876                                         mlt_properties_set_int( producer_props, "hide", 3 );
877                         }
878                 }
879
880                 if ( parent != NULL )
881                         context_push_service( context, parent, parent_type );
882
883                 mlt_service_close( track );
884         }
885         else
886         {
887                 fprintf( stderr, "Invalid state at end of track\n" );
888         }
889 }
890
891 static void on_start_filter( deserialise_context context, const xmlChar *name, const xmlChar **atts)
892 {
893         // use a dummy service to hold properties to allow arbitrary nesting
894         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
895         mlt_service_init( service, NULL );
896
897         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
898
899         context_push_service( context, service, mlt_dummy_filter_type );
900
901         // Set the properties
902         for ( ; atts != NULL && *atts != NULL; atts += 2 )
903                 mlt_properties_set( properties, (const char*) atts[0], (const char*) atts[1] );
904 }
905
906 static void on_end_filter( deserialise_context context, const xmlChar *name )
907 {
908         enum service_type type;
909         mlt_service service = context_pop_service( context, &type );
910         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
911
912         enum service_type parent_type = invalid_type;
913         mlt_service parent = context_pop_service( context, &parent_type );
914
915         if ( service != NULL && type == mlt_dummy_filter_type )
916         {
917                 mlt_service filter = MLT_SERVICE( mlt_factory_filter( context->profile, mlt_properties_get( properties, "mlt_service" ), NULL ) );
918                 mlt_properties filter_props = MLT_SERVICE_PROPERTIES( filter );
919
920                 track_service( context->destructors, filter, (mlt_destructor) mlt_filter_close );
921
922                 // Propogate the properties
923                 qualify_property( context, properties, "resource" );
924                 qualify_property( context, properties, "luma" );
925                 qualify_property( context, properties, "luma.resource" );
926                 qualify_property( context, properties, "composite.luma" );
927                 qualify_property( context, properties, "producer.resource" );
928                 mlt_properties_inherit( filter_props, properties );
929
930                 // Attach all filters from service onto filter
931                 attach_filters( filter, service );
932
933                 // Associate the filter with the parent
934                 if ( parent != NULL )
935                 {
936                         if ( parent_type == mlt_tractor_type )
937                         {
938                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
939                                 mlt_field_plant_filter( field, MLT_FILTER( filter ), mlt_properties_get_int( properties, "track" ) );
940                                 mlt_filter_set_in_and_out( MLT_FILTER( filter ), 
941                                                                                    mlt_properties_get_int( properties, "in" ),
942                                                                                    mlt_properties_get_int( properties, "out" ) );
943                         }
944                         else
945                         {
946                                 mlt_service_attach( parent, MLT_FILTER( filter ) );
947                         }
948
949                         // Put the parent back on the stack
950                         context_push_service( context, parent, parent_type );
951                 }
952                 else
953                 {
954                         fprintf( stderr, "filter closed with invalid parent...\n" );
955                 }
956
957                 // Close the dummy filter service
958                 mlt_service_close( service );
959         }
960         else
961         {
962                 fprintf( stderr, "Invalid top of stack on filter close\n" );
963         }
964 }
965
966 static void on_start_transition( deserialise_context context, const xmlChar *name, const xmlChar **atts)
967 {
968         // use a dummy service to hold properties to allow arbitrary nesting
969         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
970         mlt_service_init( service, NULL );
971
972         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
973
974         context_push_service( context, service, mlt_dummy_transition_type );
975
976         // Set the properties
977         for ( ; atts != NULL && *atts != NULL; atts += 2 )
978                 mlt_properties_set( properties, (const char*) atts[0], (const char*) atts[1] );
979 }
980
981 static void on_end_transition( deserialise_context context, const xmlChar *name )
982 {
983         enum service_type type;
984         mlt_service service = context_pop_service( context, &type );
985         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
986
987         enum service_type parent_type = invalid_type;
988         mlt_service parent = context_pop_service( context, &parent_type );
989
990         if ( service != NULL && type == mlt_dummy_transition_type )
991         {
992                 char *id = mlt_properties_get( properties, "mlt_service" );
993                 mlt_service effect = MLT_SERVICE( mlt_factory_transition( context->profile, id, NULL ) );
994                 mlt_properties effect_props = MLT_SERVICE_PROPERTIES( effect );
995
996                 track_service( context->destructors, effect, (mlt_destructor) mlt_transition_close );
997
998                 // Propogate the properties
999                 qualify_property( context, properties, "resource" );
1000                 qualify_property( context, properties, "luma" );
1001                 qualify_property( context, properties, "luma.resource" );
1002                 qualify_property( context, properties, "composite.luma" );
1003                 qualify_property( context, properties, "producer.resource" );
1004                 mlt_properties_inherit( effect_props, properties );
1005
1006                 // Attach all filters from service onto effect
1007                 attach_filters( effect, service );
1008
1009                 // Associate the filter with the parent
1010                 if ( parent != NULL )
1011                 {
1012                         if ( parent_type == mlt_tractor_type )
1013                         {
1014                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
1015                                 if ( mlt_properties_get_int( properties, "a_track" ) == mlt_properties_get_int( properties, "b_track" ) )
1016                                         mlt_properties_set_int( properties, "b_track", mlt_properties_get_int( properties, "a_track" ) + 1 );
1017                                 mlt_field_plant_transition( field, MLT_TRANSITION( effect ), 
1018                                                                                         mlt_properties_get_int( properties, "a_track" ),
1019                                                                                         mlt_properties_get_int( properties, "b_track" ) );
1020                                 mlt_transition_set_in_and_out( MLT_TRANSITION( effect ), 
1021                                                                                    mlt_properties_get_int( properties, "in" ),
1022                                                                                    mlt_properties_get_int( properties, "out" ) );
1023                         }
1024                         else
1025                         {
1026                                 fprintf( stderr, "Misplaced transition - ignoring\n" );
1027                         }
1028
1029                         // Put the parent back on the stack
1030                         context_push_service( context, parent, parent_type );
1031                 }
1032                 else
1033                 {
1034                         fprintf( stderr, "transition closed with invalid parent...\n" );
1035                 }
1036
1037                 // Close the dummy filter service
1038                 mlt_service_close( service );
1039         }
1040         else
1041         {
1042                 fprintf( stderr, "Invalid top of stack on transition close\n" );
1043         }
1044 }
1045
1046 static void on_start_property( deserialise_context context, const xmlChar *name, const xmlChar **atts)
1047 {
1048         enum service_type type;
1049         mlt_service service = context_pop_service( context, &type );
1050         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1051         const char *value = NULL;
1052
1053         if ( service != NULL )
1054         {
1055                 // Set the properties
1056                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1057                 {
1058                         if ( xmlStrcmp( atts[ 0 ], _x("name") ) == 0 )
1059                                 context->property = strdup( _s(atts[ 1 ]) );
1060                         else if ( xmlStrcmp( atts[ 0 ], _x("value") ) == 0 )
1061                                 value = _s(atts[ 1 ]);
1062                 }
1063
1064                 if ( context->property != NULL )
1065                         mlt_properties_set( properties, context->property, value == NULL ? "" : value );
1066
1067                 // Tell parser to collect any further nodes for serialisation
1068                 context->is_value = 1;
1069
1070                 context_push_service( context, service, type );
1071         }
1072         else
1073         {
1074                 fprintf( stderr, "Property without a service '%s'?\n", ( const char * )name );
1075         }
1076 }
1077
1078 static void on_end_property( deserialise_context context, const xmlChar *name )
1079 {
1080         enum service_type type;
1081         mlt_service service = context_pop_service( context, &type );
1082         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1083
1084         if ( service != NULL )
1085         {
1086                 // Tell parser to stop building a tree
1087                 context->is_value = 0;
1088         
1089                 // See if there is a xml tree for the value
1090                 if ( context->property != NULL && context->value_doc != NULL )
1091                 {
1092                         xmlChar *value;
1093                         int size;
1094                 
1095                         // Serialise the tree to get value
1096                         xmlDocDumpMemory( context->value_doc, &value, &size );
1097                         mlt_properties_set( properties, context->property, _s(value) );
1098                         xmlFree( value );
1099                         xmlFreeDoc( context->value_doc );
1100                         context->value_doc = NULL;
1101                 }
1102
1103                 // Close this property handling
1104                 free( context->property );
1105                 context->property = NULL;
1106
1107                 context_push_service( context, service, type );
1108         }
1109         else
1110         {
1111                 fprintf( stderr, "Property without a service '%s'??\n", (const char *)name );
1112         }
1113 }
1114
1115 static void on_start_element( void *ctx, const xmlChar *name, const xmlChar **atts)
1116 {
1117         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1118         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1119         
1120 //printf("on_start_element: %s\n", name );
1121         if ( context->pass == 0 )
1122         {
1123                 if ( xmlStrcmp( name, _x("mlt") ) == 0 ||
1124                      xmlStrcmp( name, _x("profile") ) == 0 ||
1125                      xmlStrcmp( name, _x("profileinfo") ) == 0 )
1126                         on_start_profile( context, name, atts );
1127                 return;
1128         }
1129         context->branch[ context->depth ] ++;
1130         context->depth ++;
1131         
1132         // Build a tree from nodes within a property value
1133         if ( context->is_value == 1 && context->pass == 1 )
1134         {
1135                 xmlNodePtr node = xmlNewNode( NULL, name );
1136                 
1137                 if ( context->value_doc == NULL )
1138                 {
1139                         // Start a new tree
1140                         context->value_doc = xmlNewDoc( _x("1.0") );
1141                         xmlDocSetRootElement( context->value_doc, node );
1142                 }
1143                 else
1144                 {
1145                         // Append child to tree
1146                         xmlAddChild( context->stack_node[ context->stack_node_size - 1 ], node );
1147                 }
1148                 context_push_node( context, node );
1149                 
1150                 // Set the attributes
1151                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1152                         xmlSetProp( node, atts[ 0 ], atts[ 1 ] );
1153         }
1154         else if ( xmlStrcmp( name, _x("tractor") ) == 0 )
1155                 on_start_tractor( context, name, atts );
1156         else if ( xmlStrcmp( name, _x("multitrack") ) == 0 )
1157                 on_start_multitrack( context, name, atts );
1158         else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 )
1159                 on_start_playlist( context, name, atts );
1160         else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 )
1161                 on_start_producer( context, name, atts );
1162         else if ( xmlStrcmp( name, _x("blank") ) == 0 )
1163                 on_start_blank( context, name, atts );
1164         else if ( xmlStrcmp( name, _x("entry") ) == 0 )
1165                 on_start_entry( context, name, atts );
1166         else if ( xmlStrcmp( name, _x("track") ) == 0 )
1167                 on_start_track( context, name, atts );
1168         else if ( xmlStrcmp( name, _x("filter") ) == 0 )
1169                 on_start_filter( context, name, atts );
1170         else if ( xmlStrcmp( name, _x("transition") ) == 0 )
1171                 on_start_transition( context, name, atts );
1172         else if ( xmlStrcmp( name, _x("property") ) == 0 )
1173                 on_start_property( context, name, atts );
1174         else if ( xmlStrcmp( name, _x("westley") ) == 0 || xmlStrcmp( name, _x("mlt") ) == 0 )
1175                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1176                         mlt_properties_set( context->producer_map, ( const char * )atts[ 0 ], ( const char * )atts[ 1 ] );
1177 }
1178
1179 static void on_end_element( void *ctx, const xmlChar *name )
1180 {
1181         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1182         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1183         
1184 //printf("on_end_element: %s\n", name );
1185         if ( context->is_value == 1 && context->pass == 1 && xmlStrcmp( name, _x("property") ) != 0 )
1186                 context_pop_node( context );
1187         else if ( xmlStrcmp( name, _x("multitrack") ) == 0 )
1188                 on_end_multitrack( context, name );
1189         else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 )
1190                 on_end_playlist( context, name );
1191         else if ( xmlStrcmp( name, _x("track") ) == 0 )
1192                 on_end_track( context, name );
1193         else if ( xmlStrcmp( name, _x("entry") ) == 0 )
1194                 on_end_entry( context, name );
1195         else if ( xmlStrcmp( name, _x("tractor") ) == 0 )
1196                 on_end_tractor( context, name );
1197         else if ( xmlStrcmp( name, _x("property") ) == 0 )
1198                 on_end_property( context, name );
1199         else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 )
1200                 on_end_producer( context, name );
1201         else if ( xmlStrcmp( name, _x("filter") ) == 0 )
1202                 on_end_filter( context, name );
1203         else if ( xmlStrcmp( name, _x("transition") ) == 0 )
1204                 on_end_transition( context, name );
1205
1206         context->branch[ context->depth ] = 0;
1207         context->depth --;
1208 }
1209
1210 static void on_characters( void *ctx, const xmlChar *ch, int len )
1211 {
1212         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1213         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1214         char *value = calloc( len + 1, 1 );
1215         enum service_type type;
1216         mlt_service service = context_pop_service( context, &type );
1217         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1218
1219         if ( service != NULL )
1220                 context_push_service( context, service, type );
1221
1222         value[ len ] = 0;
1223         strncpy( value, (const char*) ch, len );
1224
1225         if ( context->stack_node_size > 0 )
1226                 xmlNodeAddContent( context->stack_node[ context->stack_node_size - 1 ], ( xmlChar* )value );
1227
1228         // libxml2 generates an on_characters immediately after a get_entity within
1229         // an element value, and we ignore it because it is called again during
1230         // actual substitution.
1231         else if ( context->property != NULL && context->entity_is_replace == 0 )
1232         {
1233                 char *s = mlt_properties_get( properties, context->property );
1234                 if ( s != NULL )
1235                 {
1236                         // Append new text to existing content
1237                         char *new = calloc( strlen( s ) + len + 1, 1 );
1238                         strcat( new, s );
1239                         strcat( new, value );
1240                         mlt_properties_set( properties, context->property, new );
1241                         free( new );
1242                 }
1243                 else
1244                         mlt_properties_set( properties, context->property, value );
1245         }
1246         context->entity_is_replace = 0;
1247         
1248         free( value);
1249 }
1250
1251 /** Convert parameters parsed from resource into entity declarations.
1252 */
1253 static void params_to_entities( deserialise_context context )
1254 {
1255         if ( context->params != NULL )
1256         {       
1257                 int i;
1258                 
1259                 // Add our params as entitiy declarations
1260                 for ( i = 0; i < mlt_properties_count( context->params ); i++ )
1261                 {
1262                         xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i );
1263                         xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY,
1264                                 context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, _s(name) ) );
1265                 }
1266
1267                 // Flag completion
1268                 mlt_properties_close( context->params );
1269                 context->params = NULL;
1270         }
1271 }
1272
1273 // The following 3 facilitate entity substitution in the SAX parser
1274 static void on_internal_subset( void *ctx, const xmlChar* name,
1275         const xmlChar* publicId, const xmlChar* systemId )
1276 {
1277         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1278         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1279         
1280         context->publicId = publicId;
1281         context->systemId = systemId;
1282         xmlCreateIntSubset( context->entity_doc, name, publicId, systemId );
1283         
1284         // Override default entities with our parameters
1285         params_to_entities( context );
1286 }
1287
1288 // TODO: Check this with Dan... I think this is for parameterisation
1289 // but it's breaking standard escaped entities (like &lt; etc).
1290 static void on_entity_declaration( void *ctx, const xmlChar* name, int type, 
1291         const xmlChar* publicId, const xmlChar* systemId, xmlChar* content)
1292 {
1293         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1294         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1295         
1296         xmlAddDocEntity( context->entity_doc, name, type, publicId, systemId, content );
1297 }
1298
1299 // TODO: Check this functionality (see on_entity_declaration)
1300 static xmlEntityPtr on_get_entity( void *ctx, const xmlChar* name )
1301 {
1302         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1303         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1304         xmlEntityPtr e = NULL;
1305
1306         // Setup for entity declarations if not ready
1307         if ( xmlGetIntSubset( context->entity_doc ) == NULL )
1308         {
1309                 xmlCreateIntSubset( context->entity_doc, _x("mlt"), _x(""), _x("") );
1310                 context->publicId = _x("");
1311                 context->systemId = _x("");
1312         }
1313
1314         // Add our parameters if not already
1315         params_to_entities( context );
1316         
1317         e = xmlGetPredefinedEntity( name );
1318         
1319         // Send signal to on_characters that an entity substitutin is pending
1320         if ( e == NULL )
1321         {
1322                 e = xmlGetDocEntity( context->entity_doc, name );
1323                 if ( e != NULL )
1324                         context->entity_is_replace = 1;
1325         }
1326         
1327         return e;
1328 }
1329
1330 /** Convert a hexadecimal character to its value.
1331 */
1332 static int tohex( char p )
1333 {
1334         return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10;
1335 }
1336
1337 /** Decode a url-encoded string containing hexadecimal character sequences.
1338 */
1339 static char *url_decode( char *dest, char *src )
1340 {
1341         char *p = dest;
1342         
1343         while ( *src )
1344         {
1345                 if ( *src == '%' )
1346                 {
1347                         *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) );
1348                         src += 3;
1349                 }
1350                 else
1351                 {
1352                         *p ++ = *src ++;
1353                 }
1354         }
1355
1356         *p = *src;
1357         return dest;
1358 }
1359
1360 /** Extract the filename from a URL attaching parameters to a properties list.
1361 */
1362 static void parse_url( mlt_properties properties, char *url )
1363 {
1364         int i;
1365         int n = strlen( url );
1366         char *name = NULL;
1367         char *value = NULL;
1368         
1369         for ( i = 0; i < n; i++ )
1370         {
1371                 switch ( url[ i ] )
1372                 {
1373                         case '?':
1374                                 url[ i++ ] = '\0';
1375                                 name = &url[ i ];
1376                                 break;
1377                         
1378                         case ':':
1379                         case '=':
1380                                 url[ i++ ] = '\0';
1381                                 value = &url[ i ];
1382                                 break;
1383                         
1384                         case '&':
1385                                 url[ i++ ] = '\0';
1386                                 if ( name != NULL && value != NULL )
1387                                         mlt_properties_set( properties, name, value );
1388                                 name = &url[ i ];
1389                                 value = NULL;
1390                                 break;
1391                 }
1392         }
1393         if ( name != NULL && value != NULL )
1394                 mlt_properties_set( properties, name, value );
1395 }
1396
1397 // Quick workaround to avoid unecessary libxml2 warnings
1398 static int file_exists( char *file )
1399 {
1400         char *name = strdup( file );
1401         int exists = 0;
1402         if ( name != NULL && strchr( name, '?' ) )
1403                 *( strchr( name, '?' ) ) = '\0';
1404         if ( name != NULL )
1405         {
1406                 FILE *f = fopen( name, "r" );
1407                 exists = f != NULL;
1408                 if ( exists ) fclose( f );
1409         }
1410         free( name );
1411         return exists;
1412 }
1413
1414 mlt_producer producer_xml_init( mlt_profile profile, mlt_service_type servtype, const char *id, char *data )
1415 {
1416         xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) );
1417         struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) );
1418         mlt_properties properties = NULL;
1419         int i = 0;
1420         struct _xmlParserCtxt *xmlcontext;
1421         int well_formed = 0;
1422         char *filename = NULL;
1423         int info = strcmp( id, "xml-string" ) ? 0 : 1;
1424
1425         if ( data == NULL || !strcmp( data, "" ) || ( info == 0 && !file_exists( data ) ) )
1426                 return NULL;
1427
1428         context = calloc( 1, sizeof( struct deserialise_context_s ) );
1429         if ( context == NULL )
1430                 return NULL;
1431
1432         context->producer_map = mlt_properties_new();
1433         context->destructors = mlt_properties_new();
1434         context->params = mlt_properties_new();
1435         context->profile = profile;
1436
1437         // Decode URL and parse parameters
1438         mlt_properties_set( context->producer_map, "root", "" );
1439         if ( info == 0 )
1440         {
1441                 filename = strdup( data );
1442                 parse_url( context->params, url_decode( filename, data ) );
1443
1444                 // We need the directory prefix which was used for the xml
1445                 if ( strchr( filename, '/' ) )
1446                 {
1447                         char *root = NULL;
1448                         mlt_properties_set( context->producer_map, "root", filename );
1449                         root = mlt_properties_get( context->producer_map, "root" );
1450                         *( strrchr( root, '/' ) ) = '\0';
1451
1452                         // If we don't have an absolute path here, we're heading for disaster...
1453                         if ( root[ 0 ] != '/' )
1454                         {
1455                                 char *cwd = getcwd( NULL, 0 );
1456                                 char *real = malloc( strlen( cwd ) + strlen( root ) + 2 );
1457                                 sprintf( real, "%s/%s", cwd, root );
1458                                 mlt_properties_set( context->producer_map, "root", real );
1459                                 free( real );
1460                                 free( cwd );
1461                         }
1462                 }
1463         }
1464
1465         // We need to track the number of registered filters
1466         mlt_properties_set_int( context->destructors, "registered", 0 );
1467
1468         // Setup SAX callbacks
1469         sax->startElement = on_start_element;
1470
1471         // Setup libxml2 SAX parsing
1472         xmlInitParser(); 
1473         xmlSubstituteEntitiesDefault( 1 );
1474         // This is used to facilitate entity substitution in the SAX parser
1475         context->entity_doc = xmlNewDoc( _x("1.0") );
1476         if ( info == 0 )
1477                 xmlcontext = xmlCreateFileParserCtxt( filename );
1478         else
1479                 xmlcontext = xmlCreateMemoryParserCtxt( data, strlen( data ) );
1480
1481         // Invalid context - clean up and return NULL
1482         if ( xmlcontext == NULL )
1483         {
1484                 mlt_properties_close( context->producer_map );
1485                 mlt_properties_close( context->destructors );
1486                 mlt_properties_close( context->params );
1487                 free( context );
1488                 free( sax );
1489                 free( filename );
1490                 return NULL;
1491         }
1492
1493         // Parse
1494         xmlcontext->sax = sax;
1495         xmlcontext->_private = ( void* )context;        
1496         xmlParseDocument( xmlcontext );
1497         
1498         // Cleanup after parsing
1499         xmlcontext->sax = NULL;
1500         xmlcontext->_private = NULL;
1501         xmlFreeParserCtxt( xmlcontext );
1502         context->stack_node_size = 0;
1503         context->stack_service_size = 0;
1504
1505         // Setup the second pass
1506         context->pass ++;
1507         if ( info == 0 )
1508                 xmlcontext = xmlCreateFileParserCtxt( filename );
1509         else
1510                 xmlcontext = xmlCreateMemoryParserCtxt( data, strlen( data ) );
1511
1512         // Invalid context - clean up and return NULL
1513         if ( xmlcontext == NULL )
1514         {
1515                 mlt_properties_close( context->producer_map );
1516                 mlt_properties_close( context->destructors );
1517                 mlt_properties_close( context->params );
1518                 xmlFreeDoc( context->entity_doc );
1519                 free( context );
1520                 free( sax );
1521                 free( filename );
1522                 return NULL;
1523         }
1524
1525         // Setup SAX callbacks
1526         sax->endElement = on_end_element;
1527         sax->characters = on_characters;
1528         sax->cdataBlock = on_characters;
1529         sax->internalSubset = on_internal_subset;
1530         sax->entityDecl = on_entity_declaration;
1531         sax->getEntity = on_get_entity;
1532
1533         // Parse
1534         xmlcontext->sax = sax;
1535         xmlcontext->_private = ( void* )context;
1536         xmlParseDocument( xmlcontext );
1537         well_formed = xmlcontext->wellFormed;
1538
1539         // Cleanup after parsing
1540         xmlFreeDoc( context->entity_doc );
1541         free( sax );
1542         xmlMemoryDump( ); // for debugging
1543
1544         // Get the last producer on the stack
1545         enum service_type type;
1546         mlt_service service = context_pop_service( context, &type );
1547         if ( well_formed && service != NULL )
1548         {
1549                 // Verify it is a producer service (mlt_type="mlt_producer")
1550                 // (producer, playlist, multitrack)
1551                 char *type = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "mlt_type" );
1552                 if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 && strcmp( type, "producer" ) != 0 ) )
1553                         service = NULL;
1554         }
1555
1556 #ifdef DEBUG
1557         xmlDocPtr doc = xml_make_doc( service );
1558         xmlDocFormatDump( stdout, doc, 1 );
1559         xmlFreeDoc( doc );
1560         service = NULL;
1561 #endif
1562         
1563         if ( well_formed && service != NULL )
1564         {
1565                 char *title = mlt_properties_get( context->producer_map, "title" );
1566                 
1567                 // Need the complete producer list for various reasons
1568                 properties = context->destructors;
1569
1570                 // Now make sure we don't have a reference to the service in the properties
1571                 for ( i = mlt_properties_count( properties ) - 1; i >= 1; i -- )
1572                 {
1573                         char *name = mlt_properties_get_name( properties, i );
1574                         if ( mlt_properties_get_data( properties, name, NULL ) == service )
1575                         {
1576                                 mlt_properties_set_data( properties, name, service, 0, NULL, NULL );
1577                                 break;
1578                         }
1579                 }
1580
1581                 // We are done referencing destructor property list
1582                 // Set this var to service properties for convenience
1583                 properties = MLT_SERVICE_PROPERTIES( service );
1584         
1585                 // Assign the title
1586                 mlt_properties_set( properties, "title", title );
1587
1588                 // Optimise for overlapping producers
1589                 mlt_producer_optimise( MLT_PRODUCER( service ) );
1590
1591                 // Handle deep copies
1592                 if ( getenv( "MLT_XML_DEEP" ) == NULL )
1593                 {
1594                         // Now assign additional properties
1595                         if ( info == 0 )
1596                                 mlt_properties_set( properties, "resource", data );
1597
1598                         // This tells consumer_xml not to deep copy
1599                         mlt_properties_set( properties, "xml", "was here" );
1600                 }
1601                 else
1602                 {
1603                         // Allow the project to be edited
1604                         mlt_properties_set( properties, "_xml", "was here" );
1605                         mlt_properties_set_int( properties, "_mlt_service_hidden", 1 );
1606                 }
1607         }
1608         else
1609         {
1610                 // Return null if not well formed
1611                 service = NULL;
1612         }
1613
1614         // Clean up
1615         mlt_properties_close( context->producer_map );
1616         if ( context->params != NULL )
1617                 mlt_properties_close( context->params );
1618         mlt_properties_close( context->destructors );
1619         free( context );
1620         free( filename );
1621
1622         return MLT_PRODUCER( service );
1623 }