1 /*
2 * consumer_xml.c -- a libxml2 serialiser 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 #include "common.h"
21
22 #include <framework/mlt.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <locale.h>
28 #include <libxml/tree.h>
29 #include <pthread.h>
30
31 #define ID_SIZE 128
32 #define TIME_PROPERTY "_consumer_xml"
33
34 #define _x (const xmlChar*)
35 #define _s (const char*)
36
37 // This maintains counters for adding ids to elements
38 struct serialise_context_s
39 {
40 mlt_properties id_map;
41 int producer_count;
42 int multitrack_count;
43 int playlist_count;
44 int tractor_count;
45 int filter_count;
46 int transition_count;
47 int pass;
48 mlt_properties hide_map;
49 char *root;
50 char *store;
51 int no_meta;
52 mlt_profile profile;
53 mlt_time_format time_format;
54 };
55 typedef struct serialise_context_s* serialise_context;
56
57 /** Forward references to static functions.
58 */
59
60 static int consumer_start( mlt_consumer parent );
61 static int consumer_stop( mlt_consumer parent );
62 static int consumer_is_stopped( mlt_consumer consumer );
63 static void consumer_close( mlt_consumer parent );
64 static void *consumer_thread( void *arg );
65 static void serialise_service( serialise_context context, mlt_service service, xmlNode *node );
66
67 typedef enum
68 {
69 xml_existing,
70 xml_producer,
71 xml_multitrack,
72 xml_playlist,
73 xml_tractor,
74 xml_filter,
75 xml_transition
76 }
77 xml_type;
78
79 /** Create or retrieve an id associated to this service.
80 */
81
xml_get_id(serialise_context context,mlt_service service,xml_type type)82 static char *xml_get_id( serialise_context context, mlt_service service, xml_type type )
83 {
84 char *id = NULL;
85 int i = 0;
86 mlt_properties map = context->id_map;
87
88 // Search the map for the service
89 for ( i = 0; i < mlt_properties_count( map ); i ++ )
90 if ( mlt_properties_get_data_at( map, i, NULL ) == service )
91 break;
92
93 // If the service is not in the map, and the type indicates a new id is needed...
94 if ( i >= mlt_properties_count( map ) && type != xml_existing )
95 {
96 // Attempt to reuse existing id
97 id = mlt_properties_get( MLT_SERVICE_PROPERTIES( service ), "id" );
98
99 // If no id, or the id is used in the map (for another service), then
100 // create a new one.
101 if ( id == NULL || mlt_properties_get_data( map, id, NULL ) != NULL )
102 {
103 char temp[ ID_SIZE ];
104 do
105 {
106 switch( type )
107 {
108 case xml_producer:
109 sprintf( temp, "producer%d", context->producer_count ++ );
110 break;
111 case xml_multitrack:
112 sprintf( temp, "multitrack%d", context->multitrack_count ++ );
113 break;
114 case xml_playlist:
115 sprintf( temp, "playlist%d", context->playlist_count ++ );
116 break;
117 case xml_tractor:
118 sprintf( temp, "tractor%d", context->tractor_count ++ );
119 break;
120 case xml_filter:
121 sprintf( temp, "filter%d", context->filter_count ++ );
122 break;
123 case xml_transition:
124 sprintf( temp, "transition%d", context->transition_count ++ );
125 break;
126 case xml_existing:
127 // Never gets here
128 break;
129 }
130 }
131 while( mlt_properties_get_data( map, temp, NULL ) != NULL );
132
133 // Set the data at the generated name
134 mlt_properties_set_data( map, temp, service, 0, NULL, NULL );
135
136 // Get the pointer to the name (i is the end of the list)
137 id = mlt_properties_get_name( map, i );
138 }
139 else
140 {
141 // Store the existing id in the map
142 mlt_properties_set_data( map, id, service, 0, NULL, NULL );
143 }
144 }
145 else if ( type == xml_existing )
146 {
147 id = mlt_properties_get_name( map, i );
148 }
149
150 return id;
151 }
152
153 /** This is what will be called by the factory - anything can be passed in
154 via the argument, but keep it simple.
155 */
156
consumer_xml_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)157 mlt_consumer consumer_xml_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
158 {
159 // Create the consumer object
160 mlt_consumer consumer = calloc( 1, sizeof( struct mlt_consumer_s ) );
161
162 // If no malloc'd and consumer init ok
163 if ( consumer != NULL && mlt_consumer_init( consumer, NULL, profile ) == 0 )
164 {
165 // Allow thread to be started/stopped
166 consumer->start = consumer_start;
167 consumer->stop = consumer_stop;
168 consumer->is_stopped = consumer_is_stopped;
169
170 // Assign close callback
171 consumer->close = consumer_close;
172
173 mlt_properties_set( MLT_CONSUMER_PROPERTIES( consumer ), "resource", arg );
174 mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( consumer ), "real_time", 0 );
175 mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( consumer ), "prefill", 1 );
176 mlt_properties_set_int( MLT_CONSUMER_PROPERTIES( consumer ), "terminate_on_pause", 1 );
177
178 // Return the consumer produced
179 return consumer;
180 }
181
182 // malloc or consumer init failed
183 free( consumer );
184
185 // Indicate failure
186 return NULL;
187 }
188
serialise_properties(serialise_context context,mlt_properties properties,xmlNode * node)189 static void serialise_properties( serialise_context context, mlt_properties properties, xmlNode *node )
190 {
191 int i;
192 xmlNode *p;
193
194 // Enumerate the properties
195 for ( i = 0; i < mlt_properties_count( properties ); i++ )
196 {
197 char *name = mlt_properties_get_name( properties, i );
198 if ( name != NULL &&
199 name[ 0 ] != '_' &&
200 mlt_properties_get_value( properties, i ) != NULL &&
201 ( !context->no_meta || strncmp( name, "meta.", 5 ) ) &&
202 strcmp( name, "mlt" ) &&
203 strcmp( name, "mlt_type" ) &&
204 strcmp( name, "in" ) &&
205 strcmp( name, "out" ) &&
206 strcmp( name, "id" ) &&
207 strcmp( name, "title" ) &&
208 strcmp( name, "root" ) &&
209 strcmp( name, "width" ) &&
210 strcmp( name, "height" ) )
211 {
212 char *value = mlt_properties_get_value_tf( properties, i, context->time_format );
213 if ( value )
214 {
215 int rootlen = strlen( context->root );
216 const char *value_orig = value;
217 size_t prefix_size = mlt_xml_prefix_size( properties, name, value );
218
219 // Strip off prefix.
220 if ( prefix_size )
221 value += prefix_size;
222
223 // Ignore trailing slash on root.
224 if ( rootlen && ( context->root[rootlen - 1] == '/' || context->root[rootlen - 1] == '\\') )
225 --rootlen;
226
227 // convert absolute path to relative
228 if ( rootlen && !strncmp( value, context->root, rootlen ) &&
229 ( value[rootlen] == '/' || value[rootlen] == '\\' ) )
230 {
231 if ( prefix_size )
232 {
233 char *s = calloc( 1, strlen( value_orig ) - rootlen + 1 );
234 strncat( s, value_orig, prefix_size );
235 strcat( s, value + rootlen + 1 );
236 p = xmlNewTextChild( node, NULL, _x("property"), _x(s) );
237 free( s );
238 } else {
239 p = xmlNewTextChild( node, NULL, _x("property"), _x(value_orig + rootlen + 1) );
240 }
241 }
242 else
243 p = xmlNewTextChild( node, NULL, _x("property"), _x(value_orig) );
244 xmlNewProp( p, _x("name"), _x(name) );
245 }
246 }
247 }
248 }
249
serialise_store_properties(serialise_context context,mlt_properties properties,xmlNode * node,const char * store)250 static void serialise_store_properties( serialise_context context, mlt_properties properties, xmlNode *node, const char *store )
251 {
252 int i;
253 xmlNode *p;
254
255 // Enumerate the properties
256 for ( i = 0; store != NULL && i < mlt_properties_count( properties ); i++ )
257 {
258 char *name = mlt_properties_get_name( properties, i );
259 if ( !strncmp( name, store, strlen( store ) ) )
260 {
261 char *value = mlt_properties_get_value_tf( properties, i, context->time_format );
262 if ( value )
263 {
264 int rootlen = strlen( context->root );
265 // convert absolute path to relative
266 if ( rootlen && !strncmp( value, context->root, rootlen ) && value[ rootlen ] == '/' )
267 p = xmlNewTextChild( node, NULL, _x("property"), _x(value + rootlen + 1) );
268 else
269 p = xmlNewTextChild( node, NULL, _x("property"), _x(value) );
270 xmlNewProp( p, _x("name"), _x(name) );
271 }
272 }
273 }
274 }
275
serialise_service_filters(serialise_context context,mlt_service service,xmlNode * node)276 static inline void serialise_service_filters( serialise_context context, mlt_service service, xmlNode *node )
277 {
278 int i;
279 xmlNode *p;
280 mlt_filter filter = NULL;
281
282 // Enumerate the filters
283 for ( i = 0; ( filter = mlt_producer_filter( MLT_PRODUCER( service ), i ) ) != NULL; i ++ )
284 {
285 mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
286 if ( mlt_properties_get_int( properties, "_loader" ) == 0 )
287 {
288 // Get a new id - if already allocated, do nothing
289 char *id = xml_get_id( context, MLT_FILTER_SERVICE( filter ), xml_filter );
290 if ( id != NULL )
291 {
292 p = xmlNewChild( node, NULL, _x("filter"), NULL );
293 xmlNewProp( p, _x("id"), _x(id) );
294 if ( mlt_properties_get( properties, "title" ) )
295 xmlNewProp( p, _x("title"), _x(mlt_properties_get( properties, "title" )) );
296 if ( mlt_properties_get_position( properties, "in" ) )
297 xmlNewProp( p, _x("in"), _x( mlt_properties_get_time( properties, "in", context->time_format ) ) );
298 if ( mlt_properties_get_position( properties, "out" ) )
299 xmlNewProp( p, _x("out"), _x( mlt_properties_get_time( properties, "out", context->time_format ) ) );
300 serialise_properties( context, properties, p );
301 serialise_service_filters( context, MLT_FILTER_SERVICE( filter ), p );
302 }
303 }
304 }
305 }
306
serialise_producer(serialise_context context,mlt_service service,xmlNode * node)307 static void serialise_producer( serialise_context context, mlt_service service, xmlNode *node )
308 {
309 xmlNode *child = node;
310 mlt_service parent = MLT_SERVICE( mlt_producer_cut_parent( MLT_PRODUCER( service ) ) );
311
312 if ( context->pass == 0 )
313 {
314 mlt_properties properties = MLT_SERVICE_PROPERTIES( parent );
315 // Get a new id - if already allocated, do nothing
316 char *id = xml_get_id( context, parent, xml_producer );
317 if ( id == NULL )
318 return;
319
320 child = xmlNewChild( node, NULL, _x("producer"), NULL );
321
322 // Set the id
323 xmlNewProp( child, _x("id"), _x(id) );
324 if ( mlt_properties_get( properties, "title" ) )
325 xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) );
326 xmlNewProp( child, _x("in"), _x(mlt_properties_get_time( properties, "in", context->time_format )) );
327 xmlNewProp( child, _x("out"), _x(mlt_properties_get_time( properties, "out", context->time_format )) );
328
329 // If the xml producer fails to load a producer, it creates a text producer that says INVALID
330 // and sets the xml_mlt_service property to the original service.
331 const char *xml_mlt_service = mlt_properties_get(properties, "_xml_mlt_service");
332 if (xml_mlt_service) {
333 // We should not serialize this as a text producer but using the original mlt_service.
334 mlt_properties_set(properties, "mlt_service", xml_mlt_service);
335 }
336
337 serialise_properties( context, properties, child );
338 serialise_service_filters( context, service, child );
339
340 // Add producer to the map
341 mlt_properties_set_int( context->hide_map, id, mlt_properties_get_int( properties, "hide" ) );
342 }
343 else
344 {
345 char *id = xml_get_id( context, parent, xml_existing );
346 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
347 xmlNewProp( node, _x("parent"), _x(id) );
348 xmlNewProp( node, _x("in"), _x(mlt_properties_get_time( properties, "in", context->time_format )) );
349 xmlNewProp( node, _x("out"), _x(mlt_properties_get_time( properties, "out", context->time_format )) );
350 }
351 }
352
353 static void serialise_tractor( serialise_context context, mlt_service service, xmlNode *node );
354
serialise_multitrack(serialise_context context,mlt_service service,xmlNode * node)355 static void serialise_multitrack( serialise_context context, mlt_service service, xmlNode *node )
356 {
357 int i;
358
359 if ( context->pass == 0 )
360 {
361 // Iterate over the tracks to collect the producers
362 for ( i = 0; i < mlt_multitrack_count( MLT_MULTITRACK( service ) ); i++ )
363 {
364 mlt_producer producer = mlt_producer_cut_parent( mlt_multitrack_track( MLT_MULTITRACK( service ), i ) );
365 serialise_service( context, MLT_SERVICE( producer ), node );
366 }
367 }
368 else
369 {
370 // Get a new id - if already allocated, do nothing
371 char *id = xml_get_id( context, service, xml_multitrack );
372 if ( id == NULL )
373 return;
374
375 // Serialise the tracks
376 for ( i = 0; i < mlt_multitrack_count( MLT_MULTITRACK( service ) ); i++ )
377 {
378 xmlNode *track = xmlNewChild( node, NULL, _x("track"), NULL );
379 int hide = 0;
380 mlt_producer producer = mlt_multitrack_track( MLT_MULTITRACK( service ), i );
381 mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
382
383 mlt_service parent = MLT_SERVICE( mlt_producer_cut_parent( producer ) );
384
385 char *id = xml_get_id( context, MLT_SERVICE( parent ), xml_existing );
386 xmlNewProp( track, _x("producer"), _x(id) );
387 if ( mlt_producer_is_cut( producer ) )
388 {
389 xmlNewProp( track, _x("in"), _x( mlt_properties_get_time( properties, "in", context->time_format ) ) );
390 xmlNewProp( track, _x("out"), _x( mlt_properties_get_time( properties, "out", context->time_format ) ) );
391 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( producer ), track, context->store );
392 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( producer ), track, "xml_" );
393 if ( !context->no_meta )
394 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( producer ), track, "meta." );
395 serialise_service_filters( context, MLT_PRODUCER_SERVICE( producer ), track );
396 }
397
398 hide = mlt_properties_get_int( context->hide_map, id );
399 if ( hide )
400 xmlNewProp( track, _x("hide"), _x( hide == 1 ? "video" : ( hide == 2 ? "audio" : "both" ) ) );
401 }
402 serialise_service_filters( context, service, node );
403 }
404 }
405
serialise_playlist(serialise_context context,mlt_service service,xmlNode * node)406 static void serialise_playlist( serialise_context context, mlt_service service, xmlNode *node )
407 {
408 int i;
409 xmlNode *child = node;
410 mlt_playlist_clip_info info;
411 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
412
413 if ( context->pass == 0 )
414 {
415 // Get a new id - if already allocated, do nothing
416 char *id = xml_get_id( context, service, xml_playlist );
417 if ( id == NULL )
418 return;
419
420 // Iterate over the playlist entries to collect the producers
421 for ( i = 0; i < mlt_playlist_count( MLT_PLAYLIST( service ) ); i++ )
422 {
423 if ( ! mlt_playlist_get_clip_info( MLT_PLAYLIST( service ), &info, i ) )
424 {
425 if ( info.producer != NULL )
426 {
427 mlt_producer producer = mlt_producer_cut_parent( info.producer );
428 char *service_s = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "mlt_service" );
429 char *resource_s = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "resource" );
430 if ( resource_s != NULL && !strcmp( resource_s, "<playlist>" ) )
431 serialise_playlist( context, MLT_SERVICE( producer ), node );
432 else if ( service_s != NULL && strcmp( service_s, "blank" ) != 0 )
433 serialise_service( context, MLT_SERVICE( producer ), node );
434 }
435 }
436 }
437
438 child = xmlNewChild( node, NULL, _x("playlist"), NULL );
439
440 // Set the id
441 xmlNewProp( child, _x("id"), _x(id) );
442 if ( mlt_properties_get( properties, "title" ) )
443 xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) );
444
445 // Store application specific properties
446 serialise_store_properties( context, properties, child, context->store );
447 serialise_store_properties( context, properties, child, "xml_" );
448 if ( !context->no_meta )
449 serialise_store_properties( context, properties, child, "meta." );
450
451 // Add producer to the map
452 mlt_properties_set_int( context->hide_map, id, mlt_properties_get_int( properties, "hide" ) );
453
454 // Iterate over the playlist entries
455 for ( i = 0; i < mlt_playlist_count( MLT_PLAYLIST( service ) ); i++ )
456 {
457 if ( ! mlt_playlist_get_clip_info( MLT_PLAYLIST( service ), &info, i ) )
458 {
459 mlt_producer producer = mlt_producer_cut_parent( info.producer );
460 mlt_properties producer_props = MLT_PRODUCER_PROPERTIES( producer );
461 char *service_s = mlt_properties_get( producer_props, "mlt_service" );
462 if ( service_s != NULL && strcmp( service_s, "blank" ) == 0 )
463 {
464 xmlNode *entry = xmlNewChild( child, NULL, _x("blank"), NULL );
465 mlt_properties_set_data( producer_props, "_profile", context->profile, 0, NULL, NULL );
466 mlt_properties_set_position( producer_props, TIME_PROPERTY, info.frame_count );
467 xmlNewProp( entry, _x("length"), _x( mlt_properties_get_time( producer_props, TIME_PROPERTY, context->time_format ) ) );
468 }
469 else
470 {
471 char temp[ 20 ];
472 xmlNode *entry = xmlNewChild( child, NULL, _x("entry"), NULL );
473 id = xml_get_id( context, MLT_SERVICE( producer ), xml_existing );
474 xmlNewProp( entry, _x("producer"), _x(id) );
475 mlt_properties_set_position( producer_props, TIME_PROPERTY, info.frame_in );
476 xmlNewProp( entry, _x("in"), _x( mlt_properties_get_time( producer_props, TIME_PROPERTY, context->time_format ) ) );
477 mlt_properties_set_position( producer_props, TIME_PROPERTY, info.frame_out );
478 xmlNewProp( entry, _x("out"), _x( mlt_properties_get_time( producer_props, TIME_PROPERTY, context->time_format ) ) );
479 if ( info.repeat > 1 )
480 {
481 sprintf( temp, "%d", info.repeat );
482 xmlNewProp( entry, _x("repeat"), _x(temp) );
483 }
484 if ( mlt_producer_is_cut( info.cut ) )
485 {
486 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( info.cut ), entry, context->store );
487 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( info.cut ), entry, "xml_" );
488 if ( !context->no_meta )
489 serialise_store_properties( context, MLT_PRODUCER_PROPERTIES( info.cut ), entry, "meta." );
490 serialise_service_filters( context, MLT_PRODUCER_SERVICE( info.cut ), entry );
491 }
492 }
493 }
494 }
495
496 serialise_service_filters( context, service, child );
497 }
498 else if ( xmlStrcmp( node->name, _x("tractor") ) != 0 )
499 {
500 char *id = xml_get_id( context, service, xml_existing );
501 xmlNewProp( node, _x("producer"), _x(id) );
502 }
503 }
504
serialise_tractor(serialise_context context,mlt_service service,xmlNode * node)505 static void serialise_tractor( serialise_context context, mlt_service service, xmlNode *node )
506 {
507 xmlNode *child = node;
508 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
509
510 if ( context->pass == 0 )
511 {
512 // Recurse on connected producer
513 serialise_service( context, mlt_service_producer( service ), node );
514 }
515 else
516 {
517 // Get a new id - if already allocated, do nothing
518 char *id = xml_get_id( context, service, xml_tractor );
519 if ( id == NULL )
520 return;
521
522 child = xmlNewChild( node, NULL, _x("tractor"), NULL );
523
524 // Set the id
525 xmlNewProp( child, _x("id"), _x(id) );
526 if ( mlt_properties_get( properties, "title" ) )
527 xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) );
528 if ( mlt_properties_get( properties, "global_feed" ) )
529 xmlNewProp( child, _x("global_feed"), _x(mlt_properties_get( properties, "global_feed" )) );
530 if ( mlt_properties_get_position( properties, "in" ) >= 0 )
531 xmlNewProp( child, _x("in"), _x(mlt_properties_get_time( properties, "in", context->time_format )) );
532 if ( mlt_properties_get_position( properties, "out" ) >= 0 )
533 xmlNewProp( child, _x("out"), _x(mlt_properties_get_time( properties, "out", context->time_format )) );
534
535 // Store application specific properties
536 serialise_store_properties( context, MLT_SERVICE_PROPERTIES( service ), child, context->store );
537 serialise_store_properties( context, MLT_SERVICE_PROPERTIES( service ), child, "xml_" );
538 if ( !context->no_meta )
539 serialise_store_properties( context, MLT_SERVICE_PROPERTIES( service ), child, "meta." );
540
541 // Recurse on connected producer
542 serialise_service( context, mlt_service_producer( service ), child );
543 serialise_service_filters( context, service, child );
544 }
545 }
546
serialise_filter(serialise_context context,mlt_service service,xmlNode * node)547 static void serialise_filter( serialise_context context, mlt_service service, xmlNode *node )
548 {
549 xmlNode *child = node;
550 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
551
552 // Recurse on connected producer
553 serialise_service( context, mlt_service_producer( service ), node );
554
555 if ( context->pass == 1 )
556 {
557 // Get a new id - if already allocated, do nothing
558 char *id = xml_get_id( context, service, xml_filter );
559 if ( id == NULL )
560 return;
561
562 child = xmlNewChild( node, NULL, _x("filter"), NULL );
563
564 // Set the id
565 xmlNewProp( child, _x("id"), _x(id) );
566 if ( mlt_properties_get( properties, "title" ) )
567 xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) );
568 if ( mlt_properties_get_position( properties, "in" ) )
569 xmlNewProp( child, _x("in"), _x( mlt_properties_get_time( properties, "in", context->time_format ) ) );
570 if ( mlt_properties_get_position( properties, "out" ) )
571 xmlNewProp( child, _x("out"), _x( mlt_properties_get_time( properties, "out", context->time_format ) ) );
572
573 serialise_properties( context, properties, child );
574 serialise_service_filters( context, service, child );
575 }
576 }
577
serialise_transition(serialise_context context,mlt_service service,xmlNode * node)578 static void serialise_transition( serialise_context context, mlt_service service, xmlNode *node )
579 {
580 xmlNode *child = node;
581 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
582
583 // Recurse on connected producer
584 serialise_service( context, MLT_SERVICE( MLT_TRANSITION( service )->producer ), node );
585
586 if ( context->pass == 1 )
587 {
588 // Get a new id - if already allocated, do nothing
589 char *id = xml_get_id( context, service, xml_transition );
590 if ( id == NULL )
591 return;
592
593 child = xmlNewChild( node, NULL, _x("transition"), NULL );
594
595 // Set the id
596 xmlNewProp( child, _x("id"), _x(id) );
597 if ( mlt_properties_get( properties, "title" ) )
598 xmlNewProp( child, _x("title"), _x(mlt_properties_get( properties, "title" )) );
599 if ( mlt_properties_get_position( properties, "in" ) )
600 xmlNewProp( child, _x("in"), _x( mlt_properties_get_time( properties, "in", context->time_format ) ) );
601 if ( mlt_properties_get_position( properties, "out" ) )
602 xmlNewProp( child, _x("out"), _x( mlt_properties_get_time( properties, "out", context->time_format ) ) );
603
604 serialise_properties( context, properties, child );
605 serialise_service_filters( context, service, child );
606 }
607 }
608
serialise_service(serialise_context context,mlt_service service,xmlNode * node)609 static void serialise_service( serialise_context context, mlt_service service, xmlNode *node )
610 {
611 // Iterate over consumer/producer connections
612 while ( service != NULL )
613 {
614 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
615 char *mlt_type = mlt_properties_get( properties, "mlt_type" );
616
617 // Tell about the producer
618 if ( strcmp( mlt_type, "producer" ) == 0 )
619 {
620 char *mlt_service = mlt_properties_get( properties, "mlt_service" );
621 if ( mlt_properties_get( properties, "xml" ) == NULL && ( mlt_service != NULL && !strcmp( mlt_service, "tractor" ) ) )
622 {
623 context->pass = 0;
624 serialise_tractor( context, service, node );
625 context->pass = 1;
626 serialise_tractor( context, service, node );
627 context->pass = 0;
628 break;
629 }
630 else
631 {
632 serialise_producer( context, service, node );
633 }
634 if ( mlt_properties_get( properties, "xml" ) != NULL )
635 break;
636 }
637
638 // Tell about the framework container producers
639 else if ( strcmp( mlt_type, "mlt_producer" ) == 0 )
640 {
641 char *resource = mlt_properties_get( properties, "resource" );
642
643 // Recurse on multitrack's tracks
644 if ( resource && strcmp( resource, "<multitrack>" ) == 0 )
645 {
646 serialise_multitrack( context, service, node );
647 break;
648 }
649
650 // Recurse on playlist's clips
651 else if ( resource && strcmp( resource, "<playlist>" ) == 0 )
652 {
653 serialise_playlist( context, service, node );
654 }
655
656 // Recurse on tractor's producer
657 else if ( resource && strcmp( resource, "<tractor>" ) == 0 )
658 {
659 context->pass = 0;
660 serialise_tractor( context, service, node );
661 context->pass = 1;
662 serialise_tractor( context, service, node );
663 context->pass = 0;
664 break;
665 }
666
667 // Treat it as a normal producer
668 else
669 {
670 serialise_producer( context, service, node );
671 if ( mlt_properties_get( properties, "xml" ) != NULL )
672 break;
673 }
674 }
675
676 // Tell about a filter
677 else if ( strcmp( mlt_type, "filter" ) == 0 )
678 {
679 serialise_filter( context, service, node );
680 break;
681 }
682
683 // Tell about a transition
684 else if ( strcmp( mlt_type, "transition" ) == 0 )
685 {
686 serialise_transition( context, service, node );
687 break;
688 }
689
690 // Get the next connected service
691 service = mlt_service_producer( service );
692 }
693 }
694
serialise_other(mlt_properties properties,struct serialise_context_s * context,xmlNodePtr root)695 static void serialise_other( mlt_properties properties, struct serialise_context_s *context, xmlNodePtr root )
696 {
697 int i;
698 for ( i = 0; i < mlt_properties_count( properties ); i++ )
699 {
700 const char* name = mlt_properties_get_name( properties, i );
701 if ( strlen(name) > 10 && !strncmp( name, "xml_retain", 10 ) )
702 {
703 mlt_service service = mlt_properties_get_data_at( properties, i, NULL );
704 if ( service )
705 {
706 mlt_properties_set_int( MLT_SERVICE_PROPERTIES( service ), "xml_retain", 1 );
707 serialise_service( context, service, root );
708 }
709 }
710 }
711 }
712
xml_make_doc(mlt_consumer consumer,mlt_service service)713 xmlDocPtr xml_make_doc( mlt_consumer consumer, mlt_service service )
714 {
715 mlt_properties properties = MLT_SERVICE_PROPERTIES( service );
716 xmlDocPtr doc = xmlNewDoc( _x("1.0") );
717 xmlNodePtr root = xmlNewNode( NULL, _x("mlt") );
718 struct serialise_context_s *context = calloc( 1, sizeof( struct serialise_context_s ) );
719 mlt_profile profile = mlt_service_profile( MLT_CONSUMER_SERVICE( consumer ) );
720 char tmpstr[ 32 ];
721
722 xmlDocSetRootElement( doc, root );
723
724 // Indicate the numeric locale
725 if ( mlt_properties_get_lcnumeric( properties ) )
726 xmlNewProp( root, _x("LC_NUMERIC"), _x( mlt_properties_get_lcnumeric( properties ) ) );
727 else
728 #ifdef _WIN32
729 {
730 char* lcnumeric = getlocale();
731 mlt_properties_set( properties, "_xml_lcnumeric_in", lcnumeric );
732 free( lcnumeric );
733 mlt_properties_to_utf8( properties, "_xml_lcnumeric_in", "_xml_lcnumeric_out" );
734 lcnumeric = mlt_properties_get( properties, "_xml_lcnumeric_out" );
735 xmlNewProp( root, _x("LC_NUMERIC"), _x( lcnumeric ) );
736 }
737 #else
738 xmlNewProp( root, _x("LC_NUMERIC"), _x( setlocale( LC_NUMERIC, NULL ) ) );
739 #endif
740
741 // Indicate the version
742 xmlNewProp( root, _x("version"), _x( mlt_version_get_string() ) );
743
744 // If we have root, then deal with it now
745 if ( mlt_properties_get( properties, "root" ) != NULL )
746 {
747 if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "no_root" ) )
748 xmlNewProp( root, _x("root"), _x(mlt_properties_get( properties, "root" )) );
749 context->root = strdup( mlt_properties_get( properties, "root" ) );
750 }
751 else
752 {
753 context->root = strdup( "" );
754 }
755
756 // Assign the additional 'storage' pattern for properties
757 context->store = mlt_properties_get( MLT_CONSUMER_PROPERTIES( consumer ), "store" );
758 context->no_meta = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "no_meta" );
759 const char *time_format = mlt_properties_get( MLT_CONSUMER_PROPERTIES( consumer ), "time_format" );
760 if ( time_format && ( !strcmp( time_format, "smpte" ) || !strcmp( time_format, "SMPTE" )
761 || !strcmp( time_format, "timecode" ) || !strcmp( time_format, "smpte_df" ) ) )
762 context->time_format = mlt_time_smpte_df;
763 else if ( time_format && ( !strcmp( time_format, "smpte_ndf" ) ) )
764 context->time_format = mlt_time_smpte_ndf;
765 else if ( time_format && ( !strcmp( time_format, "clock" ) || !strcmp( time_format, "CLOCK" ) ) )
766 context->time_format = mlt_time_clock;
767
768 // Assign a title property
769 if ( mlt_properties_get( properties, "title" ) != NULL )
770 xmlNewProp( root, _x("title"), _x(mlt_properties_get( properties, "title" )) );
771 mlt_properties_set_int( properties, "global_feed", 1 );
772
773 // Add a profile child element
774 if ( profile )
775 {
776 if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "no_profile" ) )
777 {
778 xmlNodePtr profile_node = xmlNewChild( root, NULL, _x("profile"), NULL );
779 if ( profile->description )
780 xmlNewProp( profile_node, _x("description"), _x(profile->description) );
781 sprintf( tmpstr, "%d", profile->width );
782 xmlNewProp( profile_node, _x("width"), _x(tmpstr) );
783 sprintf( tmpstr, "%d", profile->height );
784 xmlNewProp( profile_node, _x("height"), _x(tmpstr) );
785 sprintf( tmpstr, "%d", profile->progressive );
786 xmlNewProp( profile_node, _x("progressive"), _x(tmpstr) );
787 sprintf( tmpstr, "%d", profile->sample_aspect_num );
788 xmlNewProp( profile_node, _x("sample_aspect_num"), _x(tmpstr) );
789 sprintf( tmpstr, "%d", profile->sample_aspect_den );
790 xmlNewProp( profile_node, _x("sample_aspect_den"), _x(tmpstr) );
791 sprintf( tmpstr, "%d", profile->display_aspect_num );
792 xmlNewProp( profile_node, _x("display_aspect_num"), _x(tmpstr) );
793 sprintf( tmpstr, "%d", profile->display_aspect_den );
794 xmlNewProp( profile_node, _x("display_aspect_den"), _x(tmpstr) );
795 sprintf( tmpstr, "%d", profile->frame_rate_num );
796 xmlNewProp( profile_node, _x("frame_rate_num"), _x(tmpstr) );
797 sprintf( tmpstr, "%d", profile->frame_rate_den );
798 xmlNewProp( profile_node, _x("frame_rate_den"), _x(tmpstr) );
799 sprintf( tmpstr, "%d", profile->colorspace );
800 xmlNewProp( profile_node, _x("colorspace"), _x(tmpstr) );
801 }
802 context->profile = profile;
803 }
804
805 // Construct the context maps
806 context->id_map = mlt_properties_new();
807 context->hide_map = mlt_properties_new();
808
809 // Ensure producer is a framework producer
810 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "mlt_type", "mlt_producer" );
811
812 // In pass one, we serialise the end producers and playlists,
813 // adding them to a map keyed by address.
814 serialise_other( MLT_SERVICE_PROPERTIES( service ), context, root );
815 serialise_service( context, service, root );
816
817 // In pass two, we serialise the tractor and reference the
818 // producers and playlists
819 context->pass++;
820 serialise_other( MLT_SERVICE_PROPERTIES( service ), context, root );
821 serialise_service( context, service, root );
822
823 // Cleanup resource
824 mlt_properties_close( context->id_map );
825 mlt_properties_close( context->hide_map );
826 free( context->root );
827 free( context );
828
829 return doc;
830 }
831
832
output_xml(mlt_consumer consumer)833 static void output_xml( mlt_consumer consumer )
834 {
835 // Get the producer service
836 mlt_service service = mlt_service_producer( MLT_CONSUMER_SERVICE( consumer ) );
837 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
838 char *resource = mlt_properties_get( properties, "resource" );
839 xmlDocPtr doc = NULL;
840
841 if ( !service ) return;
842
843 // Set the title if provided
844 if ( mlt_properties_get( properties, "title" ) )
845 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "title", mlt_properties_get( properties, "title" ) );
846
847 // Check for a root on the consumer properties and pass to service
848 if ( mlt_properties_get( properties, "root" ) )
849 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "root", mlt_properties_get( properties, "root" ) );
850
851 // Specify roots in other cases...
852 if ( resource != NULL && mlt_properties_get( properties, "root" ) == NULL )
853 {
854 // Get the current working directory
855 char *cwd = getcwd( NULL, 0 );
856 mlt_properties_set( MLT_SERVICE_PROPERTIES( service ), "root", cwd );
857 free( cwd );
858 }
859
860 // Make the document
861 doc = xml_make_doc( consumer, service );
862
863 // Handle the output
864 if ( resource == NULL || !strcmp( resource, "" ) )
865 {
866 xmlDocFormatDump( stdout, doc, 1 );
867 }
868 else if ( strchr( resource, '.' ) == NULL )
869 {
870 xmlChar *buffer = NULL;
871 int length = 0;
872 xmlDocDumpMemoryEnc( doc, &buffer, &length, "utf-8" );
873 mlt_properties_set( properties, resource, _s(buffer) );
874 #ifdef _WIN32
875 xmlFreeFunc xmlFree = NULL;
876 xmlMemGet( &xmlFree, NULL, NULL, NULL);
877 #endif
878 xmlFree( buffer );
879 }
880 else
881 {
882 xmlSaveFormatFileEnc( resource, doc, "utf-8", 1 );
883 }
884
885 // Close the document
886 xmlFreeDoc( doc );
887 }
consumer_start(mlt_consumer consumer)888 static int consumer_start( mlt_consumer consumer )
889 {
890 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
891
892 if ( mlt_properties_get_int( properties, "all" ) )
893 {
894 // Check that we're not already running
895 if ( !mlt_properties_get_int( properties, "running" ) )
896 {
897 // Allocate a thread
898 pthread_t *thread = calloc( 1, sizeof( pthread_t ) );
899
900 // Assign the thread to properties
901 mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL );
902
903 // Set the running state
904 mlt_properties_set_int( properties, "running", 1 );
905 mlt_properties_set_int( properties, "joined", 0 );
906
907 // Create the thread
908 pthread_create( thread, NULL, consumer_thread, consumer );
909 }
910 }
911 else
912 {
913 output_xml( consumer );
914 mlt_consumer_stop( consumer );
915 mlt_consumer_stopped( consumer );
916 }
917 return 0;
918 }
919
consumer_is_stopped(mlt_consumer consumer)920 static int consumer_is_stopped( mlt_consumer consumer )
921 {
922 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
923 return !mlt_properties_get_int( properties, "running" );
924 }
925
consumer_stop(mlt_consumer consumer)926 static int consumer_stop( mlt_consumer consumer )
927 {
928 // Get the properties
929 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
930
931 // Check that we're running
932 if ( !mlt_properties_get_int( properties, "joined" ) )
933 {
934 // Get the thread
935 pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL );
936
937 // Stop the thread
938 mlt_properties_set_int( properties, "running", 0 );
939 mlt_properties_set_int( properties, "joined", 1 );
940
941 // Wait for termination
942 if ( thread )
943 pthread_join( *thread, NULL );
944 }
945
946 return 0;
947 }
948
consumer_thread(void * arg)949 static void *consumer_thread( void *arg )
950 {
951 // Map the argument to the object
952 mlt_consumer consumer = arg;
953
954 // Get the properties
955 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
956
957 // Convenience functionality
958 int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" );
959 int terminated = 0;
960
961 // Frame and size
962 mlt_frame frame = NULL;
963
964 int video_off = mlt_properties_get_int( properties, "video_off" );
965 int audio_off = mlt_properties_get_int( properties, "audio_off" );
966
967 // Loop while running
968 while( !terminated && mlt_properties_get_int( properties, "running" ) )
969 {
970 // Get the frame
971 frame = mlt_consumer_rt_frame( consumer );
972
973 // Check for termination
974 if ( terminate_on_pause && frame != NULL )
975 terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0;
976
977 // Check that we have a frame to work with
978 if ( frame )
979 {
980 int width = 0, height = 0;
981 int frequency = mlt_properties_get_int( properties, "frequency" );
982 int channels = mlt_properties_get_int( properties, "channels" );
983 float fps = mlt_profile_fps( mlt_service_profile( MLT_CONSUMER_SERVICE( consumer ) ) );
984 int samples = mlt_audio_calculate_frame_samples( fps, frequency, mlt_frame_get_position( frame ) );
985 mlt_image_format iformat = mlt_image_yuv422;
986 mlt_audio_format aformat = mlt_audio_s16;
987 uint8_t *buffer;
988
989 if ( !video_off )
990 mlt_frame_get_image( frame, &buffer, &iformat, &width, &height, 0 );
991 if ( !audio_off )
992 mlt_frame_get_audio( frame, (void**) &buffer, &aformat, &frequency, &channels, &samples );
993
994 // Close the frame
995 mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
996 mlt_frame_close( frame );
997 }
998 }
999 output_xml( consumer );
1000
1001 // Indicate that the consumer is stopped
1002 mlt_properties_set_int( properties, "running", 0 );
1003 mlt_consumer_stopped( consumer );
1004
1005 return NULL;
1006 }
1007
consumer_close(mlt_consumer consumer)1008 static void consumer_close( mlt_consumer consumer )
1009 {
1010 // Stop the consumer
1011 mlt_consumer_stop( consumer );
1012
1013 // Close the parent
1014 mlt_consumer_close( consumer );
1015
1016 // Free the memory
1017 free( consumer );
1018 }
1019