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