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