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