]> git.sesse.net Git - mlt/blob - src/modules/xml/producer_xml.c
Change previous mods to use mlt_multitrack_count()
[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                         // Set the size of the producer_avformat cache to the number of tracks.
884                         if ( mlt_multitrack_count( multitrack ) > mlt_service_cache_get_size( MLT_MULTITRACK_SERVICE(multitrack), "producer_avformat" ) )
885                                 mlt_service_cache_set_size( MLT_MULTITRACK_SERVICE(multitrack), "producer_avformat", mlt_multitrack_count( multitrack ) + 2 );
886                 }
887
888                 if ( parent != NULL )
889                         context_push_service( context, parent, parent_type );
890
891                 mlt_service_close( track );
892         }
893         else
894         {
895                 fprintf( stderr, "Invalid state at end of track\n" );
896         }
897 }
898
899 static void on_start_filter( deserialise_context context, const xmlChar *name, const xmlChar **atts)
900 {
901         // use a dummy service to hold properties to allow arbitrary nesting
902         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
903         mlt_service_init( service, NULL );
904
905         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
906
907         context_push_service( context, service, mlt_dummy_filter_type );
908
909         // Set the properties
910         for ( ; atts != NULL && *atts != NULL; atts += 2 )
911                 mlt_properties_set( properties, (const char*) atts[0], (const char*) atts[1] );
912 }
913
914 static void on_end_filter( deserialise_context context, const xmlChar *name )
915 {
916         enum service_type type;
917         mlt_service service = context_pop_service( context, &type );
918         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
919
920         enum service_type parent_type = invalid_type;
921         mlt_service parent = context_pop_service( context, &parent_type );
922
923         if ( service != NULL && type == mlt_dummy_filter_type )
924         {
925                 mlt_service filter = MLT_SERVICE( mlt_factory_filter( context->profile, mlt_properties_get( properties, "mlt_service" ), NULL ) );
926                 mlt_properties filter_props = MLT_SERVICE_PROPERTIES( filter );
927
928                 track_service( context->destructors, filter, (mlt_destructor) mlt_filter_close );
929                 mlt_properties_set_lcnumeric( MLT_SERVICE_PROPERTIES( filter ), context->lc_numeric );
930
931                 // Propogate the properties
932                 qualify_property( context, properties, "resource" );
933                 qualify_property( context, properties, "luma" );
934                 qualify_property( context, properties, "luma.resource" );
935                 qualify_property( context, properties, "composite.luma" );
936                 qualify_property( context, properties, "producer.resource" );
937                 mlt_properties_inherit( filter_props, properties );
938
939                 // Attach all filters from service onto filter
940                 attach_filters( filter, service );
941
942                 // Associate the filter with the parent
943                 if ( parent != NULL )
944                 {
945                         if ( parent_type == mlt_tractor_type )
946                         {
947                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
948                                 mlt_field_plant_filter( field, MLT_FILTER( filter ), mlt_properties_get_int( properties, "track" ) );
949                                 mlt_filter_set_in_and_out( MLT_FILTER( filter ), 
950                                                                                    mlt_properties_get_int( properties, "in" ),
951                                                                                    mlt_properties_get_int( properties, "out" ) );
952                         }
953                         else
954                         {
955                                 mlt_service_attach( parent, MLT_FILTER( filter ) );
956                         }
957
958                         // Put the parent back on the stack
959                         context_push_service( context, parent, parent_type );
960                 }
961                 else
962                 {
963                         fprintf( stderr, "filter closed with invalid parent...\n" );
964                 }
965
966                 // Close the dummy filter service
967                 mlt_service_close( service );
968         }
969         else
970         {
971                 fprintf( stderr, "Invalid top of stack on filter close\n" );
972         }
973 }
974
975 static void on_start_transition( deserialise_context context, const xmlChar *name, const xmlChar **atts)
976 {
977         // use a dummy service to hold properties to allow arbitrary nesting
978         mlt_service service = calloc( 1, sizeof( struct mlt_service_s ) );
979         mlt_service_init( service, NULL );
980
981         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
982
983         context_push_service( context, service, mlt_dummy_transition_type );
984
985         // Set the properties
986         for ( ; atts != NULL && *atts != NULL; atts += 2 )
987                 mlt_properties_set( properties, (const char*) atts[0], (const char*) atts[1] );
988 }
989
990 static void on_end_transition( deserialise_context context, const xmlChar *name )
991 {
992         enum service_type type;
993         mlt_service service = context_pop_service( context, &type );
994         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
995
996         enum service_type parent_type = invalid_type;
997         mlt_service parent = context_pop_service( context, &parent_type );
998
999         if ( service != NULL && type == mlt_dummy_transition_type )
1000         {
1001                 char *id = mlt_properties_get( properties, "mlt_service" );
1002                 mlt_service effect = MLT_SERVICE( mlt_factory_transition( context->profile, id, NULL ) );
1003                 mlt_properties effect_props = MLT_SERVICE_PROPERTIES( effect );
1004
1005                 track_service( context->destructors, effect, (mlt_destructor) mlt_transition_close );
1006                 mlt_properties_set_lcnumeric( MLT_SERVICE_PROPERTIES( effect ), context->lc_numeric );
1007
1008                 // Propogate the properties
1009                 qualify_property( context, properties, "resource" );
1010                 qualify_property( context, properties, "luma" );
1011                 qualify_property( context, properties, "luma.resource" );
1012                 qualify_property( context, properties, "composite.luma" );
1013                 qualify_property( context, properties, "producer.resource" );
1014                 mlt_properties_inherit( effect_props, properties );
1015
1016                 // Attach all filters from service onto effect
1017                 attach_filters( effect, service );
1018
1019                 // Associate the filter with the parent
1020                 if ( parent != NULL )
1021                 {
1022                         if ( parent_type == mlt_tractor_type )
1023                         {
1024                                 mlt_field field = mlt_tractor_field( MLT_TRACTOR( parent ) );
1025                                 if ( mlt_properties_get_int( properties, "a_track" ) == mlt_properties_get_int( properties, "b_track" ) )
1026                                         mlt_properties_set_int( properties, "b_track", mlt_properties_get_int( properties, "a_track" ) + 1 );
1027                                 mlt_field_plant_transition( field, MLT_TRANSITION( effect ), 
1028                                                                                         mlt_properties_get_int( properties, "a_track" ),
1029                                                                                         mlt_properties_get_int( properties, "b_track" ) );
1030                                 mlt_transition_set_in_and_out( MLT_TRANSITION( effect ), 
1031                                                                                    mlt_properties_get_int( properties, "in" ),
1032                                                                                    mlt_properties_get_int( properties, "out" ) );
1033                         }
1034                         else
1035                         {
1036                                 fprintf( stderr, "Misplaced transition - ignoring\n" );
1037                         }
1038
1039                         // Put the parent back on the stack
1040                         context_push_service( context, parent, parent_type );
1041                 }
1042                 else
1043                 {
1044                         fprintf( stderr, "transition closed with invalid parent...\n" );
1045                 }
1046
1047                 // Close the dummy filter service
1048                 mlt_service_close( service );
1049         }
1050         else
1051         {
1052                 fprintf( stderr, "Invalid top of stack on transition close\n" );
1053         }
1054 }
1055
1056 static void on_start_property( deserialise_context context, const xmlChar *name, const xmlChar **atts)
1057 {
1058         enum service_type type;
1059         mlt_service service = context_pop_service( context, &type );
1060         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1061         const char *value = NULL;
1062
1063         if ( service != NULL )
1064         {
1065                 // Set the properties
1066                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1067                 {
1068                         if ( xmlStrcmp( atts[ 0 ], _x("name") ) == 0 )
1069                                 context->property = strdup( _s(atts[ 1 ]) );
1070                         else if ( xmlStrcmp( atts[ 0 ], _x("value") ) == 0 )
1071                                 value = _s(atts[ 1 ]);
1072                 }
1073
1074                 if ( context->property != NULL )
1075                         mlt_properties_set( properties, context->property, value == NULL ? "" : value );
1076
1077                 // Tell parser to collect any further nodes for serialisation
1078                 context->is_value = 1;
1079
1080                 context_push_service( context, service, type );
1081         }
1082         else
1083         {
1084                 fprintf( stderr, "Property without a service '%s'?\n", ( const char * )name );
1085         }
1086 }
1087
1088 static void on_end_property( deserialise_context context, const xmlChar *name )
1089 {
1090         enum service_type type;
1091         mlt_service service = context_pop_service( context, &type );
1092         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1093
1094         if ( service != NULL )
1095         {
1096                 // Tell parser to stop building a tree
1097                 context->is_value = 0;
1098         
1099                 // See if there is a xml tree for the value
1100                 if ( context->property != NULL && context->value_doc != NULL )
1101                 {
1102                         xmlChar *value;
1103                         int size;
1104                 
1105                         // Serialise the tree to get value
1106                         xmlDocDumpMemory( context->value_doc, &value, &size );
1107                         mlt_properties_set( properties, context->property, _s(value) );
1108 #ifdef WIN32
1109                         xmlFreeFunc xmlFree = NULL;
1110                         xmlMemGet( &xmlFree, NULL, NULL, NULL);
1111 #endif
1112                         xmlFree( value );
1113                         xmlFreeDoc( context->value_doc );
1114                         context->value_doc = NULL;
1115                 }
1116
1117                 // Close this property handling
1118                 free( context->property );
1119                 context->property = NULL;
1120
1121                 context_push_service( context, service, type );
1122         }
1123         else
1124         {
1125                 fprintf( stderr, "Property without a service '%s'??\n", (const char *)name );
1126         }
1127 }
1128
1129 static void on_start_element( void *ctx, const xmlChar *name, const xmlChar **atts)
1130 {
1131         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1132         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1133         
1134 //printf("on_start_element: %s\n", name );
1135         if ( context->pass == 0 )
1136         {
1137                 if ( xmlStrcmp( name, _x("mlt") ) == 0 ||
1138                      xmlStrcmp( name, _x("profile") ) == 0 ||
1139                      xmlStrcmp( name, _x("profileinfo") ) == 0 )
1140                         on_start_profile( context, name, atts );
1141                 return;
1142         }
1143         context->branch[ context->depth ] ++;
1144         context->depth ++;
1145         
1146         // Build a tree from nodes within a property value
1147         if ( context->is_value == 1 && context->pass == 1 )
1148         {
1149                 xmlNodePtr node = xmlNewNode( NULL, name );
1150                 
1151                 if ( context->value_doc == NULL )
1152                 {
1153                         // Start a new tree
1154                         context->value_doc = xmlNewDoc( _x("1.0") );
1155                         xmlDocSetRootElement( context->value_doc, node );
1156                 }
1157                 else
1158                 {
1159                         // Append child to tree
1160                         xmlAddChild( context->stack_node[ context->stack_node_size - 1 ], node );
1161                 }
1162                 context_push_node( context, node );
1163                 
1164                 // Set the attributes
1165                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1166                         xmlSetProp( node, atts[ 0 ], atts[ 1 ] );
1167         }
1168         else if ( xmlStrcmp( name, _x("tractor") ) == 0 )
1169                 on_start_tractor( context, name, atts );
1170         else if ( xmlStrcmp( name, _x("multitrack") ) == 0 )
1171                 on_start_multitrack( context, name, atts );
1172         else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 )
1173                 on_start_playlist( context, name, atts );
1174         else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 )
1175                 on_start_producer( context, name, atts );
1176         else if ( xmlStrcmp( name, _x("blank") ) == 0 )
1177                 on_start_blank( context, name, atts );
1178         else if ( xmlStrcmp( name, _x("entry") ) == 0 )
1179                 on_start_entry( context, name, atts );
1180         else if ( xmlStrcmp( name, _x("track") ) == 0 )
1181                 on_start_track( context, name, atts );
1182         else if ( xmlStrcmp( name, _x("filter") ) == 0 )
1183                 on_start_filter( context, name, atts );
1184         else if ( xmlStrcmp( name, _x("transition") ) == 0 )
1185                 on_start_transition( context, name, atts );
1186         else if ( xmlStrcmp( name, _x("property") ) == 0 )
1187                 on_start_property( context, name, atts );
1188         else if ( xmlStrcmp( name, _x("westley") ) == 0 || xmlStrcmp( name, _x("mlt") ) == 0 )
1189         {
1190                 for ( ; atts != NULL && *atts != NULL; atts += 2 )
1191                 {
1192                         if ( xmlStrcmp( atts[0], _x("LC_NUMERIC") ) )
1193                                 mlt_properties_set( context->producer_map, _s( atts[0] ), _s(atts[1] ) );
1194                         else if ( !context->lc_numeric )
1195                                 context->lc_numeric = strdup( _s( atts[1] ) );
1196                 }
1197         }
1198 }
1199
1200 static void on_end_element( void *ctx, const xmlChar *name )
1201 {
1202         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1203         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1204         
1205 //printf("on_end_element: %s\n", name );
1206         if ( context->is_value == 1 && context->pass == 1 && xmlStrcmp( name, _x("property") ) != 0 )
1207                 context_pop_node( context );
1208         else if ( xmlStrcmp( name, _x("multitrack") ) == 0 )
1209                 on_end_multitrack( context, name );
1210         else if ( xmlStrcmp( name, _x("playlist") ) == 0 || xmlStrcmp( name, _x("seq") ) == 0 || xmlStrcmp( name, _x("smil") ) == 0 )
1211                 on_end_playlist( context, name );
1212         else if ( xmlStrcmp( name, _x("track") ) == 0 )
1213                 on_end_track( context, name );
1214         else if ( xmlStrcmp( name, _x("entry") ) == 0 )
1215                 on_end_entry( context, name );
1216         else if ( xmlStrcmp( name, _x("tractor") ) == 0 )
1217                 on_end_tractor( context, name );
1218         else if ( xmlStrcmp( name, _x("property") ) == 0 )
1219                 on_end_property( context, name );
1220         else if ( xmlStrcmp( name, _x("producer") ) == 0 || xmlStrcmp( name, _x("video") ) == 0 )
1221                 on_end_producer( context, name );
1222         else if ( xmlStrcmp( name, _x("filter") ) == 0 )
1223                 on_end_filter( context, name );
1224         else if ( xmlStrcmp( name, _x("transition") ) == 0 )
1225                 on_end_transition( context, name );
1226
1227         context->branch[ context->depth ] = 0;
1228         context->depth --;
1229 }
1230
1231 static void on_characters( void *ctx, const xmlChar *ch, int len )
1232 {
1233         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1234         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1235         char *value = calloc( len + 1, 1 );
1236         enum service_type type;
1237         mlt_service service = context_pop_service( context, &type );
1238         mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
1239
1240         if ( service != NULL )
1241                 context_push_service( context, service, type );
1242
1243         value[ len ] = 0;
1244         strncpy( value, (const char*) ch, len );
1245
1246         if ( context->stack_node_size > 0 )
1247                 xmlNodeAddContent( context->stack_node[ context->stack_node_size - 1 ], ( xmlChar* )value );
1248
1249         // libxml2 generates an on_characters immediately after a get_entity within
1250         // an element value, and we ignore it because it is called again during
1251         // actual substitution.
1252         else if ( context->property != NULL && context->entity_is_replace == 0 )
1253         {
1254                 char *s = mlt_properties_get( properties, context->property );
1255                 if ( s != NULL )
1256                 {
1257                         // Append new text to existing content
1258                         char *new = calloc( strlen( s ) + len + 1, 1 );
1259                         strcat( new, s );
1260                         strcat( new, value );
1261                         mlt_properties_set( properties, context->property, new );
1262                         free( new );
1263                 }
1264                 else
1265                         mlt_properties_set( properties, context->property, value );
1266         }
1267         context->entity_is_replace = 0;
1268         
1269         free( value);
1270 }
1271
1272 /** Convert parameters parsed from resource into entity declarations.
1273 */
1274 static void params_to_entities( deserialise_context context )
1275 {
1276         if ( context->params != NULL )
1277         {       
1278                 int i;
1279                 
1280                 // Add our params as entitiy declarations
1281                 for ( i = 0; i < mlt_properties_count( context->params ); i++ )
1282                 {
1283                         xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i );
1284                         xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY,
1285                                 context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, _s(name) ) );
1286                 }
1287
1288                 // Flag completion
1289                 mlt_properties_close( context->params );
1290                 context->params = NULL;
1291         }
1292 }
1293
1294 // The following 3 facilitate entity substitution in the SAX parser
1295 static void on_internal_subset( void *ctx, const xmlChar* name,
1296         const xmlChar* publicId, const xmlChar* systemId )
1297 {
1298         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1299         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1300         
1301         context->publicId = publicId;
1302         context->systemId = systemId;
1303         xmlCreateIntSubset( context->entity_doc, name, publicId, systemId );
1304         
1305         // Override default entities with our parameters
1306         params_to_entities( context );
1307 }
1308
1309 // TODO: Check this with Dan... I think this is for parameterisation
1310 // but it's breaking standard escaped entities (like &lt; etc).
1311 static void on_entity_declaration( void *ctx, const xmlChar* name, int type, 
1312         const xmlChar* publicId, const xmlChar* systemId, xmlChar* content)
1313 {
1314         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1315         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1316         
1317         xmlAddDocEntity( context->entity_doc, name, type, publicId, systemId, content );
1318 }
1319
1320 // TODO: Check this functionality (see on_entity_declaration)
1321 static xmlEntityPtr on_get_entity( void *ctx, const xmlChar* name )
1322 {
1323         struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx;
1324         deserialise_context context = ( deserialise_context )( xmlcontext->_private );
1325         xmlEntityPtr e = NULL;
1326
1327         // Setup for entity declarations if not ready
1328         if ( xmlGetIntSubset( context->entity_doc ) == NULL )
1329         {
1330                 xmlCreateIntSubset( context->entity_doc, _x("mlt"), _x(""), _x("") );
1331                 context->publicId = _x("");
1332                 context->systemId = _x("");
1333         }
1334
1335         // Add our parameters if not already
1336         params_to_entities( context );
1337         
1338         e = xmlGetPredefinedEntity( name );
1339         
1340         // Send signal to on_characters that an entity substitutin is pending
1341         if ( e == NULL )
1342         {
1343                 e = xmlGetDocEntity( context->entity_doc, name );
1344                 if ( e != NULL )
1345                         context->entity_is_replace = 1;
1346         }
1347         
1348         return e;
1349 }
1350
1351 /** Convert a hexadecimal character to its value.
1352 */
1353 static int tohex( char p )
1354 {
1355         return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10;
1356 }
1357
1358 /** Decode a url-encoded string containing hexadecimal character sequences.
1359 */
1360 static char *url_decode( char *dest, char *src )
1361 {
1362         char *p = dest;
1363         
1364         while ( *src )
1365         {
1366                 if ( *src == '%' )
1367                 {
1368                         *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) );
1369                         src += 3;
1370                 }
1371                 else
1372                 {
1373                         *p ++ = *src ++;
1374                 }
1375         }
1376
1377         *p = *src;
1378         return dest;
1379 }
1380
1381 /** Extract the filename from a URL attaching parameters to a properties list.
1382 */
1383 static void parse_url( mlt_properties properties, char *url )
1384 {
1385         int i;
1386         int n = strlen( url );
1387         char *name = NULL;
1388         char *value = NULL;
1389         
1390         for ( i = 0; i < n; i++ )
1391         {
1392                 switch ( url[ i ] )
1393                 {
1394                         case '?':
1395                                 url[ i++ ] = '\0';
1396                                 name = &url[ i ];
1397                                 break;
1398                         
1399                         case ':':
1400                         case '=':
1401                                 url[ i++ ] = '\0';
1402                                 value = &url[ i ];
1403                                 break;
1404                         
1405                         case '&':
1406                                 url[ i++ ] = '\0';
1407                                 if ( name != NULL && value != NULL )
1408                                         mlt_properties_set( properties, name, value );
1409                                 name = &url[ i ];
1410                                 value = NULL;
1411                                 break;
1412                 }
1413         }
1414         if ( name != NULL && value != NULL )
1415                 mlt_properties_set( properties, name, value );
1416 }
1417
1418 // Quick workaround to avoid unecessary libxml2 warnings
1419 static int file_exists( char *file )
1420 {
1421         char *name = strdup( file );
1422         int exists = 0;
1423         if ( name != NULL && strchr( name, '?' ) )
1424                 *( strchr( name, '?' ) ) = '\0';
1425         if ( name != NULL )
1426         {
1427                 FILE *f = fopen( name, "r" );
1428                 exists = f != NULL;
1429                 if ( exists ) fclose( f );
1430         }
1431         free( name );
1432         return exists;
1433 }
1434
1435 mlt_producer producer_xml_init( mlt_profile profile, mlt_service_type servtype, const char *id, char *data )
1436 {
1437         xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) );
1438         struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) );
1439         mlt_properties properties = NULL;
1440         int i = 0;
1441         struct _xmlParserCtxt *xmlcontext;
1442         int well_formed = 0;
1443         char *filename = NULL;
1444         int info = strcmp( id, "xml-string" ) ? 0 : 1;
1445
1446         if ( data == NULL || !strcmp( data, "" ) || ( info == 0 && !file_exists( data ) ) )
1447                 return NULL;
1448
1449         context = calloc( 1, sizeof( struct deserialise_context_s ) );
1450         if ( context == NULL )
1451                 return NULL;
1452
1453         context->producer_map = mlt_properties_new();
1454         context->destructors = mlt_properties_new();
1455         context->params = mlt_properties_new();
1456         context->profile = profile;
1457
1458         // Decode URL and parse parameters
1459         mlt_properties_set( context->producer_map, "root", "" );
1460         if ( info == 0 )
1461         {
1462                 filename = strdup( data );
1463                 parse_url( context->params, url_decode( filename, data ) );
1464
1465                 // We need the directory prefix which was used for the xml
1466                 if ( strchr( filename, '/' ) )
1467                 {
1468                         char *root = NULL;
1469                         mlt_properties_set( context->producer_map, "root", filename );
1470                         root = mlt_properties_get( context->producer_map, "root" );
1471                         *( strrchr( root, '/' ) ) = '\0';
1472
1473                         // If we don't have an absolute path here, we're heading for disaster...
1474                         if ( root[ 0 ] != '/' )
1475                         {
1476                                 char *cwd = getcwd( NULL, 0 );
1477                                 char *real = malloc( strlen( cwd ) + strlen( root ) + 2 );
1478                                 sprintf( real, "%s/%s", cwd, root );
1479                                 mlt_properties_set( context->producer_map, "root", real );
1480                                 free( real );
1481                                 free( cwd );
1482                         }
1483                 }
1484         }
1485
1486         // We need to track the number of registered filters
1487         mlt_properties_set_int( context->destructors, "registered", 0 );
1488
1489         // Setup SAX callbacks
1490         sax->startElement = on_start_element;
1491
1492         // Setup libxml2 SAX parsing
1493         xmlInitParser(); 
1494         xmlSubstituteEntitiesDefault( 1 );
1495         // This is used to facilitate entity substitution in the SAX parser
1496         context->entity_doc = xmlNewDoc( _x("1.0") );
1497         if ( info == 0 )
1498                 xmlcontext = xmlCreateFileParserCtxt( filename );
1499         else
1500                 xmlcontext = xmlCreateMemoryParserCtxt( data, strlen( data ) );
1501
1502         // Invalid context - clean up and return NULL
1503         if ( xmlcontext == NULL )
1504         {
1505                 mlt_properties_close( context->producer_map );
1506                 mlt_properties_close( context->destructors );
1507                 mlt_properties_close( context->params );
1508                 free( context );
1509                 free( sax );
1510                 free( filename );
1511                 return NULL;
1512         }
1513
1514         // Parse
1515         xmlcontext->sax = sax;
1516         xmlcontext->_private = ( void* )context;        
1517         xmlParseDocument( xmlcontext );
1518         
1519         // Cleanup after parsing
1520         xmlcontext->sax = NULL;
1521         xmlcontext->_private = NULL;
1522         xmlFreeParserCtxt( xmlcontext );
1523         context->stack_node_size = 0;
1524         context->stack_service_size = 0;
1525
1526         // Setup the second pass
1527         context->pass ++;
1528         if ( info == 0 )
1529                 xmlcontext = xmlCreateFileParserCtxt( filename );
1530         else
1531                 xmlcontext = xmlCreateMemoryParserCtxt( data, strlen( data ) );
1532
1533         // Invalid context - clean up and return NULL
1534         if ( xmlcontext == NULL )
1535         {
1536                 mlt_properties_close( context->producer_map );
1537                 mlt_properties_close( context->destructors );
1538                 mlt_properties_close( context->params );
1539                 xmlFreeDoc( context->entity_doc );
1540                 free( context );
1541                 free( sax );
1542                 free( filename );
1543                 return NULL;
1544         }
1545
1546         // Setup SAX callbacks
1547         sax->endElement = on_end_element;
1548         sax->characters = on_characters;
1549         sax->cdataBlock = on_characters;
1550         sax->internalSubset = on_internal_subset;
1551         sax->entityDecl = on_entity_declaration;
1552         sax->getEntity = on_get_entity;
1553
1554         // Parse
1555         xmlcontext->sax = sax;
1556         xmlcontext->_private = ( void* )context;
1557         xmlParseDocument( xmlcontext );
1558         well_formed = xmlcontext->wellFormed;
1559
1560         // Cleanup after parsing
1561         xmlFreeDoc( context->entity_doc );
1562         free( sax );
1563         xmlMemoryDump( ); // for debugging
1564
1565         // Get the last producer on the stack
1566         enum service_type type;
1567         mlt_service service = context_pop_service( context, &type );
1568         if ( well_formed && service != NULL )
1569         {
1570                 // Verify it is a producer service (mlt_type="mlt_producer")
1571                 // (producer, playlist, multitrack)
1572                 char *type = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "mlt_type" );
1573                 if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 && strcmp( type, "producer" ) != 0 ) )
1574                         service = NULL;
1575         }
1576
1577 #ifdef DEBUG
1578         xmlDocPtr doc = xml_make_doc( service );
1579         xmlDocFormatDump( stdout, doc, 1 );
1580         xmlFreeDoc( doc );
1581         service = NULL;
1582 #endif
1583         
1584         if ( well_formed && service != NULL )
1585         {
1586                 char *title = mlt_properties_get( context->producer_map, "title" );
1587                 
1588                 // Need the complete producer list for various reasons
1589                 properties = context->destructors;
1590
1591                 // Now make sure we don't have a reference to the service in the properties
1592                 for ( i = mlt_properties_count( properties ) - 1; i >= 1; i -- )
1593                 {
1594                         char *name = mlt_properties_get_name( properties, i );
1595                         if ( mlt_properties_get_data_at( properties, i, NULL ) == service )
1596                         {
1597                                 mlt_properties_set_data( properties, name, service, 0, NULL, NULL );
1598                                 break;
1599                         }
1600                 }
1601
1602                 // We are done referencing destructor property list
1603                 // Set this var to service properties for convenience
1604                 properties = MLT_SERVICE_PROPERTIES( service );
1605         
1606                 // Assign the title
1607                 mlt_properties_set( properties, "title", title );
1608
1609                 // Optimise for overlapping producers
1610                 mlt_producer_optimise( MLT_PRODUCER( service ) );
1611
1612                 // Handle deep copies
1613                 if ( getenv( "MLT_XML_DEEP" ) == NULL )
1614                 {
1615                         // Now assign additional properties
1616                         if ( info == 0 )
1617                                 mlt_properties_set( properties, "resource", data );
1618
1619                         // This tells consumer_xml not to deep copy
1620                         mlt_properties_set( properties, "xml", "was here" );
1621                 }
1622                 else
1623                 {
1624                         // Allow the project to be edited
1625                         mlt_properties_set( properties, "_xml", "was here" );
1626                         mlt_properties_set_int( properties, "_mlt_service_hidden", 1 );
1627                 }
1628         }
1629         else
1630         {
1631                 // Return null if not well formed
1632                 service = NULL;
1633         }
1634
1635         // Clean up
1636         mlt_properties_close( context->producer_map );
1637         if ( context->params != NULL )
1638                 mlt_properties_close( context->params );
1639         mlt_properties_close( context->destructors );
1640         if ( context->lc_numeric )
1641                 free( context->lc_numeric );
1642         free( context );
1643         free( filename );
1644
1645         return MLT_PRODUCER( service );
1646 }