X-Git-Url: http://research.m1stereo.tv/gitweb?a=blobdiff_plain;f=src%2Fmodules%2Fwestley%2Fproducer_westley.c;h=ff1649dee9a3d5c0c9ef8583e3568c6a1008aeb5;hb=bd208d01a2a792e698a9b4884b43602b2f245a8f;hp=89ca817f04848d1e760763b8d1d9a3ce89b969ef;hpb=1c5fe28f14d004d6d81728dc92bb32fc260990fb;p=melted diff --git a/src/modules/westley/producer_westley.c b/src/modules/westley/producer_westley.c index 89ca817..ff1649d 100644 --- a/src/modules/westley/producer_westley.c +++ b/src/modules/westley/producer_westley.c @@ -20,19 +20,25 @@ // TODO: destroy unreferenced producers (they are currently destroyed // when the returned producer is closed). -// TODO: determine why deserialise_context can not be released. #include "producer_westley.h" #include #include #include #include +#include #include #include // for xmlCreateFileParserCtxt #include #define STACK_SIZE 1000 +#define BRANCH_SIG_LEN 4000 + +#undef DEBUG +#ifdef DEBUG +extern xmlDocPtr westley_make_doc( mlt_service service ); +#endif struct deserialise_context_s { @@ -47,9 +53,29 @@ struct deserialise_context_s xmlNodePtr stack_node[ STACK_SIZE ]; int stack_node_size; xmlDocPtr entity_doc; + int entity_is_replace; + int depth; + int branch[ STACK_SIZE ]; + const xmlChar *publicId; + const xmlChar *systemId; + mlt_properties params; }; typedef struct deserialise_context_s *deserialise_context; +/** Convert the numerical current branch address to a dot-delimited string. +*/ +static char *serialise_branch( deserialise_context this, char *s ) +{ + int i; + + s[0] = 0; + for ( i = 0; i < this->depth; i++ ) + { + int len = strlen( s ); + snprintf( s + len, BRANCH_SIG_LEN - len, "%d.", this->branch[ i ] ); + } + return s; +} /** Push a service. */ @@ -58,7 +84,16 @@ static int context_push_service( deserialise_context this, mlt_service that ) { int ret = this->stack_service_size >= STACK_SIZE - 1; if ( ret == 0 ) - this->stack_service[ this->stack_service_size ++ ] = that; + { + this->stack_service[ this->stack_service_size++ ] = that; + + // Record the tree branch on which this service lives + if ( mlt_properties_get( mlt_service_properties( that ), "_westley_branch" ) == NULL ) + { + char s[ BRANCH_SIG_LEN ]; + mlt_properties_set( mlt_service_properties( that ), "_westley_branch", serialise_branch( this, s ) ); + } + } return ret; } @@ -106,6 +141,33 @@ static void track_service( mlt_properties properties, void *service, mlt_destruc } +// Prepend the property value with the document root +static inline void qualify_property( deserialise_context context, mlt_properties properties, char *name ) +{ + char *resource = mlt_properties_get( properties, name ); + if ( resource != NULL ) + { + // Qualify file name properties + char *root = mlt_properties_get( context->producer_map, "_root" ); + if ( root != NULL ) + { + char *full_resource = malloc( strlen( root ) + strlen( resource ) + 1 ); + if ( resource[ 0 ] != '/' ) + { + strcpy( full_resource, root ); + strcat( full_resource, resource ); + } + else + { + strcpy( full_resource, resource ); + } + mlt_properties_set( properties, name, full_resource ); + free( full_resource ); + } + } +} + + // Forward declarations static void on_end_track( deserialise_context context, const xmlChar *name ); static void on_end_entry( deserialise_context context, const xmlChar *name ); @@ -298,48 +360,58 @@ static int add_producer( deserialise_context context, mlt_service service, mlt_p { // Get the parent producer mlt_service producer = context_pop_service( context ); + if ( producer != NULL ) { - char *resource = mlt_properties_get( mlt_service_properties( producer ), "resource" ); - - // Put the parent producer back - context_push_service( context, producer ); - - // If the parent producer is a multitrack or playlist (not a track or entry) - if ( resource && ( strcmp( resource, "" ) == 0 || - strcmp( resource, "" ) == 0 ) ) + char current_branch[ BRANCH_SIG_LEN ]; + char *service_branch = mlt_properties_get( mlt_service_properties( producer ), "_westley_branch" ); + + // Validate the producer from the stack is an ancestor and not predecessor + serialise_branch( context, current_branch ); + if ( service_branch != NULL && strncmp( service_branch, current_branch, strlen( service_branch ) ) == 0 ) { - if ( strcmp( resource, "" ) == 0 ) - { - // Append this producer to the playlist - mlt_playlist_append_io( MLT_PLAYLIST( producer ), - MLT_PRODUCER( service ), in, out ); - } - else - { - mlt_properties properties = mlt_service_properties( service ); - - // Set this producer on the multitrack - mlt_multitrack_connect( MLT_MULTITRACK( producer ), - MLT_PRODUCER( service ), - mlt_multitrack_count( MLT_MULTITRACK( producer ) ) ); + char *resource = mlt_properties_get( mlt_service_properties( producer ), "resource" ); + + // Put the parent producer back + context_push_service( context, producer ); - // Set the hide state of the track producer - char *hide_s = mlt_properties_get( properties, "hide" ); - if ( hide_s != NULL ) + // If the parent producer is a multitrack or playlist (not a track or entry) + if ( resource && ( strcmp( resource, "" ) == 0 || + strcmp( resource, "" ) == 0 ) ) + { +//printf( "add_producer: current branch %s service branch %s (%d)\n", current_branch, service_branch, strncmp( service_branch, current_branch, strlen( service_branch ) ) ); + if ( strcmp( resource, "" ) == 0 ) { - if ( strcmp( hide_s, "video" ) == 0 ) - mlt_properties_set_int( properties, "hide", 1 ); - else if ( strcmp( hide_s, "audio" ) == 0 ) - mlt_properties_set_int( properties, "hide", 2 ); - else if ( strcmp( hide_s, "both" ) == 0 ) - mlt_properties_set_int( properties, "hide", 3 ); + // Append this producer to the playlist + mlt_playlist_append_io( MLT_PLAYLIST( producer ), + MLT_PRODUCER( service ), in, out ); } - + else + { + mlt_properties properties = mlt_service_properties( service ); + + // Set this producer on the multitrack + mlt_multitrack_connect( MLT_MULTITRACK( producer ), + MLT_PRODUCER( service ), + mlt_multitrack_count( MLT_MULTITRACK( producer ) ) ); + + // Set the hide state of the track producer + char *hide_s = mlt_properties_get( properties, "hide" ); + if ( hide_s != NULL ) + { + if ( strcmp( hide_s, "video" ) == 0 ) + mlt_properties_set_int( properties, "hide", 1 ); + else if ( strcmp( hide_s, "audio" ) == 0 ) + mlt_properties_set_int( properties, "hide", 2 ); + else if ( strcmp( hide_s, "both" ) == 0 ) + mlt_properties_set_int( properties, "hide", 3 ); + } + + } + // Normally, the enclosing entry or track will pop this service off + // In its absence we do not push it on. + return 1; } - // Normally, the enclosing entry or track will pop this service off - // In its absence we do not push it on. - return 1; } } return 0; @@ -351,7 +423,7 @@ static void on_end_multitrack( deserialise_context context, const xmlChar *name mlt_service producer = context_pop_service( context ); if ( producer == NULL ) return; - + // Get the tractor from the stack mlt_service service = context_pop_service( context ); @@ -361,9 +433,14 @@ static void on_end_multitrack( deserialise_context context, const xmlChar *name resource = mlt_properties_get( mlt_service_properties( service ), "resource" ); if ( service == NULL || resource == NULL || strcmp( resource, "" ) ) { +//printf("creating a tractor\n"); + char current_branch[ BRANCH_SIG_LEN ]; + // Put the anonymous service back onto the stack! - context_push_service( context, service ); + if ( service != NULL ) + context_push_service( context, service ); + // Fabricate the tractor service = mlt_tractor_service( mlt_tractor_init() ); track_service( context->destructors, service, (mlt_destructor) mlt_tractor_close ); @@ -372,6 +449,8 @@ static void on_end_multitrack( deserialise_context context, const xmlChar *name mlt_properties_set_position( properties, "length", mlt_producer_get_out( MLT_PRODUCER( producer ) ) + 1 ); mlt_producer_set_in_and_out( MLT_PRODUCER( service ), 0, mlt_producer_get_out( MLT_PRODUCER( producer ) ) ); mlt_properties_set_double( properties, "fps", mlt_producer_get_fps( MLT_PRODUCER( producer ) ) ); + + mlt_properties_set( properties, "_westley_branch", serialise_branch( context, current_branch ) ); } // Connect the tractor to the multitrack @@ -379,7 +458,7 @@ static void on_end_multitrack( deserialise_context context, const xmlChar *name mlt_properties_set_data( mlt_service_properties( service ), "multitrack", MLT_MULTITRACK( producer ), 0, NULL, NULL ); - // See if the producer should be added to a playlist or multitrack + // See if the tractor should be added to a playlist or multitrack add_producer( context, service, 0, mlt_producer_get_out( MLT_PRODUCER( producer ) ) ); // Always push the multitrack back onto the stack for filters and transitions @@ -586,32 +665,32 @@ static void on_end_producer( deserialise_context context, const xmlChar *name ) if ( properties == NULL ) return; + qualify_property( context, properties, "resource" ); char *resource = mlt_properties_get( properties, "resource" ); // Let Kino-SMIL src be a synonym for resource if ( resource == NULL ) + { + qualify_property( context, properties, "src" ); resource = mlt_properties_get( properties, "src" ); - + } + // Instantiate the producer if ( mlt_properties_get( properties, "mlt_service" ) != NULL ) { - service = MLT_SERVICE( mlt_factory_producer( "fezzik", mlt_properties_get( properties, "mlt_service" ) ) ); + char temp[ 1024 ]; + strncpy( temp, mlt_properties_get( properties, "mlt_service" ), 1024 ); + if ( resource != NULL ) + { + strcat( temp, ":" ); + strncat( temp, resource, 1023 - strlen( temp ) ); + } + service = MLT_SERVICE( mlt_factory_producer( "fezzik", temp ) ); } if ( service == NULL && resource != NULL ) { - char *root = mlt_properties_get( context->producer_map, "_root" ); - char *full_resource = malloc( strlen( root ) + strlen( resource ) + 1 ); - if ( resource[ 0 ] != '/' ) - { - strcpy( full_resource, root ); - strcat( full_resource, resource ); - } - else - { - strcpy( full_resource, resource ); - } - service = MLT_SERVICE( mlt_factory_producer( "fezzik", full_resource ) ); - free( full_resource ); + service = MLT_SERVICE( mlt_factory_producer( "fezzik", resource ) ); } + if ( service == NULL ) return; track_service( context->destructors, service, (mlt_destructor) mlt_producer_close ); @@ -734,6 +813,11 @@ static void on_end_filter( deserialise_context context, const xmlChar *name ) mlt_properties_set_position( properties, "out", mlt_producer_get_out( MLT_PRODUCER( producer ) ) ); // Propogate the properties + qualify_property( context, properties, "resource" ); + qualify_property( context, properties, "luma" ); + qualify_property( context, properties, "luma.resource" ); + qualify_property( context, properties, "composite.luma" ); + qualify_property( context, properties, "producer.resource" ); mlt_properties_inherit( mlt_service_properties( service ), properties ); mlt_properties_close( properties ); context->producer_properties = NULL; @@ -802,6 +886,11 @@ static void on_end_transition( deserialise_context context, const xmlChar *name track_service( context->destructors, service, (mlt_destructor) mlt_transition_close ); // Propogate the properties + qualify_property( context, properties, "resource" ); + qualify_property( context, properties, "luma" ); + qualify_property( context, properties, "luma.resource" ); + qualify_property( context, properties, "composite.luma" ); + qualify_property( context, properties, "producer.resource" ); mlt_properties_inherit( mlt_service_properties( service ), properties ); mlt_properties_close( properties ); context->producer_properties = NULL; @@ -835,6 +924,10 @@ static void on_start_element( void *ctx, const xmlChar *name, const xmlChar **at struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; deserialise_context context = ( deserialise_context )( xmlcontext->_private ); +//printf("on_start_element: %s\n", name ); + context->branch[ context->depth ] ++; + context->depth ++; + // Build a tree from nodes within a property value if ( context->is_value == 1 ) { @@ -882,6 +975,7 @@ static void on_end_element( void *ctx, const xmlChar *name ) struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; deserialise_context context = ( deserialise_context )( xmlcontext->_private ); +//printf("on_end_element: %s\n", name ); if ( context->is_value == 1 && strcmp( name, "property" ) != 0 ) context_pop_node( context ); else if ( strcmp( name, "multitrack" ) == 0 ) @@ -902,6 +996,9 @@ static void on_end_element( void *ctx, const xmlChar *name ) on_end_filter( context, name ); else if ( strcmp( name, "transition" ) == 0 ) on_end_transition( context, name ); + + context->branch[ context->depth ] = 0; + context->depth --; } static void on_characters( void *ctx, const xmlChar *ch, int len ) @@ -912,16 +1009,56 @@ static void on_characters( void *ctx, const xmlChar *ch, int len ) value[ len ] = 0; strncpy( value, (const char*) ch, len ); - + if ( context->stack_node_size > 0 ) xmlNodeAddContent( context->stack_node[ context->stack_node_size - 1 ], ( xmlChar* )value ); - else if ( context->property != NULL && context->producer_properties != NULL ) - mlt_properties_set( context->producer_properties, context->property, value ); + // libxml2 generates an on_characters immediately after a get_entity within + // an element value, and we ignore it because it is called again during + // actual substitution. + else if ( context->property != NULL && context->producer_properties != NULL + && context->entity_is_replace == 0 ) + { + char *s = mlt_properties_get( context->producer_properties, context->property ); + if ( s != NULL ) + { + // Append new text to existing content + char *new = calloc( strlen( s ) + len + 1, 1 ); + strcat( new, s ); + strcat( new, value ); + mlt_properties_set( context->producer_properties, context->property, new ); + free( new ); + } + else + mlt_properties_set( context->producer_properties, context->property, value ); + } + context->entity_is_replace = 0; free( value); } +/** Convert parameters parsed from resource into entity declarations. +*/ +static void params_to_entities( deserialise_context context ) +{ + if ( context->params != NULL ) + { + int i; + + // Add our params as entitiy declarations + for ( i = 0; i < mlt_properties_count( context->params ); i++ ) + { + xmlChar *name = ( xmlChar* )mlt_properties_get_name( context->params, i ); + xmlAddDocEntity( context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY, + context->publicId, context->systemId, ( xmlChar* )mlt_properties_get( context->params, name ) ); + } + + // Flag completion + mlt_properties_close( context->params ); + context->params = NULL; + } +} + // The following 3 facilitate entity substitution in the SAX parser static void on_internal_subset( void *ctx, const xmlChar* name, const xmlChar* publicId, const xmlChar* systemId ) @@ -929,7 +1066,12 @@ static void on_internal_subset( void *ctx, const xmlChar* name, struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + context->publicId = publicId; + context->systemId = systemId; xmlCreateIntSubset( context->entity_doc, name, publicId, systemId ); + + // Override default entities with our parameters + params_to_entities( context ); } static void on_entity_declaration( void *ctx, const xmlChar* name, int type, @@ -945,22 +1087,113 @@ xmlEntityPtr on_get_entity( void *ctx, const xmlChar* name ) { struct _xmlParserCtxt *xmlcontext = ( struct _xmlParserCtxt* )ctx; deserialise_context context = ( deserialise_context )( xmlcontext->_private ); + xmlEntityPtr e = NULL; + + // Setup for entity declarations if not ready + if ( xmlGetIntSubset( context->entity_doc ) == NULL ) + { + xmlCreateIntSubset( context->entity_doc, "westley", "", "" ); + context->publicId = ""; + context->systemId = ""; + } + + // Add our parameters if not already + params_to_entities( context ); + + e = xmlGetDocEntity( context->entity_doc, name ); + + // Send signal to on_characters that an entity substitutin is pending + if ( e != NULL ) + context->entity_is_replace = 1; + + return e; +} + +/** Convert a hexadecimal character to its value. +*/ +static int tohex( char p ) +{ + return isdigit( p ) ? p - '0' : tolower( p ) - 'a' + 10; +} + +/** Decode a url-encoded string containing hexadecimal character sequences. +*/ +static char *url_decode( char *dest, char *src ) +{ + char *p = dest; + + while ( *src ) + { + if ( *src == '%' ) + { + *p ++ = ( tohex( *( src + 1 ) ) << 4 ) | tohex( *( src + 2 ) ); + src += 3; + } + else + { + *p ++ = *src ++; + } + } - return xmlGetDocEntity( context->entity_doc, name ); + *p = *src; + return dest; } +/** Extract the filename from a URL attaching parameters to a properties list. +*/ +static void parse_url( mlt_properties properties, char *url ) +{ + int i; + int n = strlen( url ); + char *name = NULL; + char *value = NULL; + + for ( i = 0; i < n; i++ ) + { + switch ( url[ i ] ) + { + case '?': + url[ i++ ] = '\0'; + name = &url[ i ]; + break; + + case ':': + case '=': + url[ i++ ] = '\0'; + value = &url[ i ]; + break; + + case '&': + url[ i++ ] = '\0'; + if ( name != NULL && value != NULL ) + mlt_properties_set( properties, name, value ); + name = &url[ i ]; + value = NULL; + break; + } + } + if ( name != NULL && value != NULL ) + mlt_properties_set( properties, name, value ); +} -mlt_producer producer_westley_init( char *filename ) +mlt_producer producer_westley_init( char *url ) { + if ( url == NULL ) + return NULL; xmlSAXHandler *sax = calloc( 1, sizeof( xmlSAXHandler ) ); struct deserialise_context_s *context = calloc( 1, sizeof( struct deserialise_context_s ) ); mlt_properties properties = NULL; int i = 0; struct _xmlParserCtxt *xmlcontext; int well_formed = 0; + char *filename = strdup( url ); context->producer_map = mlt_properties_new(); context->destructors = mlt_properties_new(); + context->params = mlt_properties_new(); + + // Decode URL and parse parameters + parse_url( context->params, url_decode( filename, url ) ); // We need to track the number of registered filters mlt_properties_set_int( context->destructors, "registered", 0 ); @@ -1012,12 +1245,20 @@ mlt_producer producer_westley_init( char *filename ) // Verify it is a producer service (mlt_type="mlt_producer") // (producer, playlist, multitrack) char *type = mlt_properties_get( mlt_service_properties( service ), "mlt_type" ); - if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 ) ) + if ( type == NULL || ( strcmp( type, "mlt_producer" ) != 0 && strcmp( type, "producer" ) != 0 ) ) service = NULL; } +#ifdef DEBUG + xmlDocPtr doc = westley_make_doc( service ); + xmlDocFormatDump( stdout, doc, 1 ); + xmlFreeDoc( doc ); + service = NULL; +#endif + if ( well_formed && service != NULL ) { + // Need the complete producer list for various reasons properties = context->destructors; @@ -1040,7 +1281,7 @@ mlt_producer producer_westley_init( char *filename ) mlt_properties_set_data( properties, "__destructors__", context->destructors, 0, (mlt_destructor) mlt_properties_close, NULL ); // Now assign additional properties - mlt_properties_set( properties, "resource", filename ); + mlt_properties_set( properties, "resource", url ); // This tells consumer_westley not to deep copy mlt_properties_set( properties, "westley", "was here" ); @@ -1054,9 +1295,12 @@ mlt_producer producer_westley_init( char *filename ) mlt_properties_close( context->destructors ); } - free( context->stack_service ); + // Clean up mlt_properties_close( context->producer_map ); - //free( context ); + if ( context->params != NULL ) + mlt_properties_close( context->params ); + free( context ); + free( filename ); return MLT_PRODUCER( service ); }