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