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