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