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